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-08-05 18:09:31
Message-ID: CAE8JnxNYXzQvPkGRRRDY6-YBW3eBWQRCWk3Nq3iNOJK-i29hYg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Sorry for the basic bug in v13.

The slope logic was incorrectly breaking the outer loop
in build_index_pathkeys. v14 fixes that and renders the if block
by removing the
+ if (slope_match)
+ {
+ if (query_pk_cell == NULL)
+ break;

query_pk_cell is initialised with null making the inner loop
stop condition sufficient.

When an incompatible pathkey is detected skipping the inner loop
+ /*
+ * Index can't satisfy query pathkeys any further
+ */
+ query_pk_cell = NULL;
+ break;

Excluding the inner loop
we get

- if (cpathkey)
+ if(cpathkey && !pathkey_emitted)

with pathkey_emitted = false

- else
+ if(!column_pinned)

with column_pinned set to true inside the if clause block.

On Mon, Jul 27, 2026 at 8:00 AM Alexandre Felipe <
o(dot)alexandre(dot)felipe(at)gmail(dot)com> wrote:

>
> 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
v14-0005-SLOPE-documentation.patch application/octet-stream 8.3 KB
v14-0002-Optimized-reverse-pathkeys.patch application/octet-stream 6.3 KB
v14-0004-SLOPE-Planner-support.patch application/octet-stream 78.7 KB
v14-0003-SLOPE-catalog-changes.patch application/octet-stream 93.9 KB
v14-0001-benchmark.patch application/octet-stream 4.8 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Peter Eisentraut 2026-08-05 18:10:59 Re: Update our timezone code to IANA tzcode2026b
Previous Message surya poondla 2026-08-05 17:53:05 Re: Fix races conditions in DropRole() and GrantRole()