Re: SLOPE - Planner optimizations on monotonic expressions.

From: Alexandre Felipe <o(dot)alexandre(dot)felipe(at)gmail(dot)com>
To: Zsolt Parragi <zsolt(dot)parragi(at)percona(dot)com>
Cc: pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: SLOPE - Planner optimizations on monotonic expressions.
Date: 2026-07-27 07:00:34
Message-ID: CAE8JnxOpFLb_=OLwG5ROgENJGZ_YD2rG2Kjv63d9zgjRjBsC4g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Thank you for your thorough review Zsolt

On Tue, Jul 21, 2026 at 11:27 PM Zsolt Parragi <zsolt(dot)parragi(at)percona(dot)com>
wrote:

> Hello
>
> > Removed prosupport for timestamptz <-> timestamp.
>
> This is not fixed, same testcase still fails. Maybe because casts are
> skipped unconditionally?
>
> + if ((fexpr->funcformat == COERCE_IMPLICIT_CAST ||
> + fexpr->funcformat ==
> COERCE_EXPLICIT_CAST) &&
> + list_length(fexpr->args) == 1)
> + {
> + expr = (Expr *) linitial(fexpr->args);
> + continue;
> + }
>
> And it seems like we have more issues with casts, there are some more
> cases: timestamptz -> date (I mentioned this in my previous email),
> timestamp -> time, int4 -> bool (-1 -> true, 0 -> false, 1 -> true)
>

Well explicit casts should not be there. I thought implicit casts would
be fine. But checking pg_cast with
SELECT
oid,
castsource::regtype, casttarget::regtype,
castfunc::regproc,
castcontext, castmethod
FROM pg_cast

I see character varying => regclass, I didn't see any other such conversion.
Making it explicit now would break a few, if not many, DBA scripts.

*timestamptz -> date
I was optimistic here but you are right again. I checked tzinfo (using
python
and tzdump). From 596 time zones there are 7 for which the date moved
backward at some point.

1 America/Goose_Bay 00:00:59 -> 22:01:00, 1988
23 America/Goose_Bay 00:00:59 -> 23:01:00, 1987-2010
1 Canada/Newfoundland 00:00:59 -> 22:01:00, 1988
23 Canada/Newfoundland 00:00:59 -> 23:01:00, 1987-2010
1 America/St_Johns 00:00:59 -> 22:01:00, 1988
23 America/St_Johns 00:00:59 -> 23:01:00, 1987-2010
14 America/Moncton 00:00:59 -> 23:01:00, 1993-2006
1 Antarctica/Casey 01:59:59 -> 23:00:00, 2010-2010
1 Pacific/Guam 00:00:59 -> 23:01:00, 1969
1 Pacific/Saipan 00:00:59 -> 23:01:00, 1969

I rechecked another old issue, and realized that this was dropped
> somewhere around v7, was that intentional?
> > v5 aims to prevent the elimination of the sort node if the index has a
> > custom sort operator family.
>

OK, that got lost in some refactoring, apparently I lost it squeezing
some commits on v7.2 and v7.3, the v7 and then the dropping the code
on v7.14 wasn't detected. What I submitted was v7.20, now I added that part
just after get_slope_wrt

I found one more problem with binary coercible types, the skip there
> is too generic, it should be more restrictive (btree opfamilies
> maybe?):
>
> + /* Skip RelabelType (no-op coercion) */
> + if (IsA(expr, RelabelType))
> + {
> + expr = (Expr *) ((RelabelType *) expr)->arg;
> + continue;
> + }

I think my assumptions (guesses) about RelabelType were completely
incorrect. From your feedback I thought of writing something like this

+ argtype = getBaseType(exprType((Node *) ((RelabelType *) expr)->arg));
+ restype = getBaseType(((RelabelType *) expr)->resulttype);
+ expr = (Expr *) ((RelabelType *) expr)->arg;
+ if (argtype == restype)
+ continue;
+
+ arg_opclass = GetDefaultOpClass(argtype, BTREE_AM_OID);
+ res_opclass = GetDefaultOpClass(restype, BTREE_AM_OID);
+ if (arg_opclass == res_opclass)
+ continue;
+
+ if(OidIsValid(arg_opclass))
+ arg_opfamily = get_opclass_family(arg_opclass);
+
+ if(OidIsValid(res_opclass))
+ res_opfamily = get_opclass_family(res_opclass);
+
+ if (arg_opfamily == res_opfamily)
+ continue;
+
+ return MONOTONICFUNC_NONE;

But then, checking the catalog to see what could match

SELECT opfname, array_agg(oc.opcintype::regtype)
FROM pg_opclass oc
JOIN pg_am am ON am.oid = oc.opcmethod
JOIN pg_opfamily of ON of.oid = oc.opcfamily
WHERE oc.opcdefault
AND am.amname = 'btree'
GROUP BY 1
HAVING count(distinct oc.opcintype) > 1

+------------+----------------------------+
|opfname |array_agg |
+------------+----------------------------+
|datetime_ops|{date,timestamp,timestamptz}|
|float_ops |{real,"double precision"} |
|integer_ops |{bigint,smallint,integer} |
|text_ops |{name,text} |
+------------+----------------------------+

Since we are scoping to default opfamilies I am stopping at
RelabelTypes, declaring it non-monotonic.

+ /*
> + * Case 2: f(x) after x —
> ascending chain. x is already
> + * in retval, so within each group
> of equal x values, f(x)
> + * is constant (for any
> deterministic f). The pathkey is
> + * redundant as a tiebreaker
> regardless of monotonicity.
> + */
> + if (!pathkey_is_redundant(qpk,
> retval))
> + retval = lappend(retval,
> qpk);
> + continue;
>
> "within each group of equal x values, f(x) is constant (for any
> deterministic f)" -- doesn't this also require equalimage?
>

Yes it is required, thank you for educating me about that, I wasn't aware
of this feature. x, f(x) pattern is very broad, and matches things beyond
intended.

There are also a few typos:

disables
>
Fixed.

> takes
>
Fixed

> inferred
>
Fixed

> x1 and x2
>
Fixed

> + * 'nslopes' points to a MonotonicFunction array (one per argument up
> to
> + * nslopes). Arguments beyond nslopes are treated as
> MONOTONICFUNC_NONE.
>
> slopes points to
>
Fixed

> +
> +typedef enum NUMERIC_SIGN
> +{
>
> Also nitpick, but numeric.c has a define with the same name. There's
> no conflict as it's in a different file and that's a macro, but could
> make searching more difficult.

Renamed to SLOPE_NUMERIC.

I also merged the 'redundancy checks' patch in the 'planner support' patch
and I changed that to use chasing pointers instead of nested loops.
doing (max(index columns, query pathkeys)) iterations instead
of ((index columns) * (query pathkeys).

I have applying and cleaning my work tree so many times that is better
to submit this before I lose something important :)

Regards,
Alexandre

Attachment Content-Type Size
v13-0002-Optimized-reverse-pathkeys.patch application/octet-stream 6.3 KB
v13-0001-benchmark.patch application/octet-stream 4.8 KB
v13-0003-SLOPE-catalog-changes.patch application/octet-stream 93.5 KB
v13-0004-SLOPE-Planner-support.patch application/octet-stream 77.5 KB
v13-0005-SLOPE-documentation.patch application/octet-stream 8.3 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Pavel Stehule 2026-07-27 07:02:34 Re: POC: PLpgSQL FOREACH IN JSON ARRAY
Previous Message Andrey Borodin 2026-07-27 06:55:51 Re: Restore vacuum_delay_point() in GIN posting-tree leaf vacuum