From 732b52575fc6a86ae4017272dd04c99ee7f1993a Mon Sep 17 00:00:00 2001 From: Alexandre Felipe Date: Sun, 17 May 2026 17:12:31 +0100 Subject: [PATCH v8 7/7] FIX: NaN special cases --- src/backend/optimizer/path/pathkeys.c | 93 ++++-- src/backend/utils/adt/misc.c | 399 ++++++++++++++------------ src/include/catalog/pg_proc.dat | 6 +- src/include/nodes/pathnodes.h | 5 +- src/include/nodes/plannodes.h | 6 + src/test/regress/expected/slope.out | 116 +++++++- src/test/regress/sql/slope.sql | 116 +++++++- 7 files changed, 537 insertions(+), 204 deletions(-) diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c index d5f6439f7f8..cddea99bf78 100644 --- a/src/backend/optimizer/path/pathkeys.c +++ b/src/backend/optimizer/path/pathkeys.c @@ -19,6 +19,7 @@ #include "access/stratnum.h" #include "catalog/pg_opfamily.h" +#include "catalog/pg_type.h" #include "fmgr.h" #include "nodes/nodeFuncs.h" #include "nodes/supportnodes.h" @@ -41,7 +42,8 @@ static bool matches_boolean_partition_clause(RestrictInfo *rinfo, int partkeycol); static Var *find_var_for_subquery_tle(RelOptInfo *rel, TargetEntry *tle); static bool right_merge_direction(PlannerInfo *root, PathKey *pathkey); -static MonotonicFunction get_expr_slope_wrt(Expr *expr, Expr *target); +static MonotonicFunction get_expr_slope_wrt(Expr *expr, Expr *target, + bool target_cannan); static PathKey *slope_emit_pathkey(PlannerInfo *root, PathKey *pk, Expr *indexkey, bool reverse_sort, bool nulls_first); @@ -861,6 +863,44 @@ get_variation_source(Expr *expr, Expr **inner_out, Index *relid_out) } } +/* + * var_type_can_nan + * True if a variation-source column can hold NaN values. + */ +static bool +var_type_can_nan(Var *var) +{ + switch (var->vartype) + { + case FLOAT4OID: + case FLOAT8OID: + case NUMERICOID: + return true; + default: + return false; + } +} + +/* + * finalize_slope + * Apply NaN-aware restrictions to a composed monotonicity slope. + * + * Decreasing functions do not preserve sort order when the variation + * source can produce NaNs. Flat functions are treated similarly, since + * NaNs and infinities in the source can still change the result. An + * overall decreasing (or flat) result is only rejected once composition + * reaches the source. For example, (1 - (1 - x)) is increasing in x even + * though (1 - x) is decreasing. + */ +static MonotonicFunction +finalize_slope(MonotonicFunction slope, bool target_cannan) +{ + if (target_cannan && + (slope == MONOTONICFUNC_DECREASING || slope == MONOTONICFUNC_BOTH)) + return MONOTONICFUNC_NONE; + return slope; +} + /* * get_expr_slope_wrt * Determine the monotonicity slope of an expression with respect to @@ -869,10 +909,11 @@ get_variation_source(Expr *expr, Expr **inner_out, Index *relid_out) * Returns the slope of 'expr' with respect to 'target' * MONOTONICFUNC_INCREASING: monotonically increasing * MONOTONICFUNC_DECREASING: monotonically decreasing + * MONOTONICFUNC_BOTH: independent of target * MONOTONICFUNC_NONE: cannot determine monotonicity */ static MonotonicFunction -get_expr_slope_wrt(Expr *expr, Expr *target) +get_expr_slope_wrt(Expr *expr, Expr *target, bool target_cannan) { MonotonicFunction slope = MONOTONICFUNC_INCREASING; @@ -889,7 +930,7 @@ get_expr_slope_wrt(Expr *expr, Expr *target) /* Check if we've reached the target */ if (equal(expr, target)) - return slope; + return finalize_slope(slope, target_cannan); /* Skip RelabelType (no-op coercion) */ if (IsA(expr, RelabelType)) @@ -944,7 +985,7 @@ get_expr_slope_wrt(Expr *expr, Expr *target) if (req.slopes == NULL || req.nslopes <= 0) return MONOTONICFUNC_NONE; - /* Find the single non-constant argument */ + /* Find the single non-constant argument that can affect the result */ i = 0; foreach(lc, args) { @@ -952,27 +993,36 @@ get_expr_slope_wrt(Expr *expr, Expr *target) if (!IsA(arg, Const)) { + MonotonicFunction arg_slope; + + if (unlikely(i >= req.nslopes)) + return MONOTONICFUNC_NONE; + + arg_slope = req.slopes[i]; + if (arg_slope == MONOTONICFUNC_BOTH) + { + i++; + continue; + } + if (arg_slope == MONOTONICFUNC_DECREASING) + func_arg_slope = MONOTONICFUNC_DECREASING; + else if (arg_slope != MONOTONICFUNC_INCREASING) + return MONOTONICFUNC_NONE; + if (next_expr != NULL) { /* Multivariate - check if this is the target */ - return equal(expr, target) ? slope : MONOTONICFUNC_NONE; + if (equal(expr, target)) + return finalize_slope(slope, target_cannan); + return MONOTONICFUNC_NONE; } next_expr = arg; - if (likely(i < req.nslopes)) - { - if (req.slopes[i] == MONOTONICFUNC_DECREASING) - func_arg_slope = MONOTONICFUNC_DECREASING; - else if (req.slopes[i] != MONOTONICFUNC_INCREASING) - return MONOTONICFUNC_NONE; - } - else - return MONOTONICFUNC_NONE; } i++; } if (next_expr == NULL) - return MONOTONICFUNC_NONE; /* all constant */ + return finalize_slope(MONOTONICFUNC_BOTH, target_cannan); /* Compose slopes */ if (func_arg_slope == MONOTONICFUNC_DECREASING) @@ -1007,7 +1057,8 @@ precompute_slope_pathkeys(PlannerInfo *root) pk->pk_var = NULL; pk->pk_varrelid = 0; - pk->pk_slope = MONOTONICFUNC_BOTH; /* not yet computed */ + pk->pk_slope = MONOTONICFUNC_UNKNOWN; + pk->pk_var_cannan = false; if (pk->pk_eclass->ec_has_volatile || pk->pk_eclass->ec_members == NIL) @@ -1024,6 +1075,8 @@ precompute_slope_pathkeys(PlannerInfo *root) if (pk->pk_var == NULL || pk->pk_varrelid == 0 || pk->pk_var == em->em_expr) pk->pk_var = NULL; + else if (IsA(pk->pk_var, Var)) + pk->pk_var_cannan = var_type_can_nan((Var *) pk->pk_var); } } @@ -1188,19 +1241,21 @@ build_index_pathkeys(PlannerInfo *root, { PathKey *spk; - if (qpk->pk_slope == MONOTONICFUNC_BOTH) + if (qpk->pk_slope == MONOTONICFUNC_UNKNOWN) { EquivalenceMember *em; em = linitial(qpk->pk_eclass->ec_members); qpk->pk_slope = get_expr_slope_wrt(em->em_expr, - qpk->pk_var); + qpk->pk_var, + qpk->pk_var_cannan); } spk = slope_emit_pathkey(root, qpk, indexkey, reverse_sort, nulls_first); if (spk && !pathkey_is_redundant(spk, retval)) retval = lappend(retval, spk); - slope_matched = true; + if (spk) + slope_matched = true; continue; } diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index 19b7b48e80a..5d907a46941 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -33,6 +33,7 @@ #include "miscadmin.h" #include "nodes/miscnodes.h" #include "nodes/supportnodes.h" +#include "nodes/nodeFuncs.h" #include "parser/parse_type.h" #include "parser/scansup.h" #include "pgstat.h" @@ -1121,84 +1122,46 @@ monotonic_slope_support(Node *rawreq, int nslopes, return PointerGetDatum(NULL); } -/* - * arg0_asc_slope_support - * Prosupport: f(x, ...) is monotonically increasing in x. - */ -Datum -arg0_asc_slope_support(PG_FUNCTION_ARGS) -{ - static const MonotonicFunction pattern[1] = {MONOTONICFUNC_INCREASING}; - - return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), - lengthof(pattern), pattern); -} - -/* - * arg0_desc_slope_support - * Prosupport: f(x, ...) is monotonically decreasing in x. - */ -Datum -arg0_desc_slope_support(PG_FUNCTION_ARGS) -{ - static const MonotonicFunction pattern[1] = {MONOTONICFUNC_DECREASING}; - - return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), - lengthof(pattern), pattern); -} -/* - * arg1_asc_slope_support - * Prosupport: f(a, x, ...) is monotonically increasing in x. - */ -Datum -arg1_asc_slope_support(PG_FUNCTION_ARGS) -{ - static const MonotonicFunction pattern[2] = {MONOTONICFUNC_NONE, - MONOTONICFUNC_INCREASING}; - - return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), - lengthof(pattern), pattern); -} - -/* - * diff_slope_support - * Prosupport: f(x, y) = x - y is increasing in x, decreasing in y. - */ -Datum -diff_slope_support(PG_FUNCTION_ARGS) -{ - static const MonotonicFunction pattern[2] = {MONOTONICFUNC_INCREASING, - MONOTONICFUNC_DECREASING}; - - return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), - lengthof(pattern), pattern); -} - -/* - * addition_slope_support - * Prosupport: f(x, y) = x + y is increasing in both x and y. - */ -Datum -addition_slope_support(PG_FUNCTION_ARGS) -{ - static const MonotonicFunction pattern[2] = {MONOTONICFUNC_INCREASING, - MONOTONICFUNC_INCREASING}; - - return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), - lengthof(pattern), pattern); +static bool +get_2slope_args(Node *rawreq, Node **arg0, Node **arg1){ + if(!IsA(rawreq, SupportRequestMonotonic)) + return false; + SupportRequestMonotonic *req = (SupportRequestMonotonic *) rawreq; + List *args; + + if (IsA(req->expr, FuncExpr)) + args = ((FuncExpr *) req->expr)->args; + else if (IsA(req->expr, OpExpr)) + args = ((OpExpr *) req->expr)->args; + else + return false; + if(list_length(args) < 2) + return false; + *arg0 = (Node *) linitial(args); + *arg1 = (Node *) lsecond(args); + return true; } +enum NUMERIC_SIGN { + NUMERIC_SIGN_NINF=-2, + NUMERIC_SIGN_NEG=-1, + NUMERIC_SIGN_ZERO=0, + NUMERIC_SIGN_POS=1, + NUMERIC_SIGN_PINF=3, + NUMERIC_SIGN_NAN=4, + NUMERIC_SIGN_NULL=5, +}; /* * get_const_sign * Helper to determine the sign of a numeric constant. * Returns 1 for positive, -1 for negative, 0 for zero or unknown. */ -static int +static inline enum NUMERIC_SIGN get_const_sign(Const *constval) { if (constval->constisnull) - return 0; + return NUMERIC_SIGN_NULL; switch (constval->consttype) { @@ -1223,39 +1186,170 @@ get_const_sign(Const *constval) case FLOAT4OID: { float4 val = DatumGetFloat4(constval->constvalue); - - if (isnan(val) || val == 0.0f) - return 0; - return (val > 0.0f) ? 1 : -1; + if (isnan(val)) + return NUMERIC_SIGN_NAN; + if(isinf(val)) + return val > 0 ? NUMERIC_SIGN_PINF : NUMERIC_SIGN_NINF; + else if(val == 0) + return NUMERIC_SIGN_ZERO; + else + return val > 0 ? NUMERIC_SIGN_POS : NUMERIC_SIGN_NEG; } case FLOAT8OID: { float8 val = DatumGetFloat8(constval->constvalue); - - if (isnan(val) || val == 0.0) - return 0; - return (val > 0.0) ? 1 : -1; + if (isnan(val)) + return NUMERIC_SIGN_NAN; + if(isinf(val)) + return val > 0 ? NUMERIC_SIGN_PINF : NUMERIC_SIGN_NINF; + else if(val == 0) + return NUMERIC_SIGN_ZERO; + else + return val > 0 ? NUMERIC_SIGN_POS : NUMERIC_SIGN_NEG; } case NUMERICOID: { Numeric num = DatumGetNumeric(constval->constvalue); Datum result; Numeric sign_num; - int sign; + int val_sign; - if (numeric_is_nan(num) || numeric_is_inf(num)) - return 0; + if (numeric_is_nan(num)) + return NUMERIC_SIGN_NAN; result = DirectFunctionCall1(numeric_sign, NumericGetDatum(num)); sign_num = DatumGetNumeric(result); - sign = numeric_int4_safe(sign_num, NULL); - return sign; + val_sign = numeric_int4_safe(sign_num, NULL); + if(numeric_is_inf(num)) + return val_sign == 1 ? NUMERIC_SIGN_PINF : NUMERIC_SIGN_NINF; + else + return (enum NUMERIC_SIGN)val_sign; } default: return 0; } } +static const MonotonicFunction asc_slope[2] = {MONOTONICFUNC_INCREASING, MONOTONICFUNC_INCREASING}; +static const MonotonicFunction desc_slope[2] = {MONOTONICFUNC_DECREASING, MONOTONICFUNC_DECREASING}; +static const MonotonicFunction flat_slope[2] = {MONOTONICFUNC_BOTH, MONOTONICFUNC_BOTH}; +static const MonotonicFunction diff_slope[2] = {MONOTONICFUNC_INCREASING,MONOTONICFUNC_DECREASING}; + + + +/* + * arg0_asc_slope_support + * Prosupport: f(x, ...) is monotonically increasing in x. + */ + Datum + arg0_asc_slope_support(PG_FUNCTION_ARGS) + { + static const MonotonicFunction pattern[1] = {MONOTONICFUNC_INCREASING}; + + return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), + lengthof(pattern), pattern); + } + + /* + * arg0_desc_slope_support + * Prosupport: f(x, ...) is monotonically decreasing in x. + */ + Datum + arg0_desc_slope_support(PG_FUNCTION_ARGS) + { + static const MonotonicFunction pattern[1] = {MONOTONICFUNC_DECREASING}; + + return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), + lengthof(pattern), pattern); + } + + /* + * arg1_asc_slope_support + * Prosupport: f(a, x, ...) is monotonically increasing in x. + */ + Datum + arg1_asc_slope_support(PG_FUNCTION_ARGS) + { + static const MonotonicFunction pattern[2] = {MONOTONICFUNC_NONE, + MONOTONICFUNC_INCREASING}; + + return monotonic_slope_support((Node *) PG_GETARG_POINTER(0), + lengthof(pattern), pattern); + } +/* +* diff_slope_support +* Prosupport: f(x, y) = x - y is increasing in x, decreasing in y. +*/ +Datum +diff_slope_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *arg0; + Node *arg1; + + if(!get_2slope_args(rawreq, &arg0, &arg1)) + PG_RETURN_POINTER(NULL); + + if(IsA(arg0, Const)){ + switch (get_const_sign((Const *) arg0)) { + case NUMERIC_SIGN_NINF: + case NUMERIC_SIGN_PINF: + case NUMERIC_SIGN_NAN: + return monotonic_slope_support(rawreq, 2, flat_slope); + default: + break; + } + }else if(IsA(arg1, Const)) { + switch (get_const_sign((Const *) arg1)) { + case NUMERIC_SIGN_NINF: + case NUMERIC_SIGN_PINF: + case NUMERIC_SIGN_NAN: + return monotonic_slope_support(rawreq, 2, flat_slope); + default: + break; + } + } + + return monotonic_slope_support(rawreq, 2, diff_slope); +} + +/* +* addition_slope_support +* Prosupport: f(x, y) = x + y is increasing in both x and y. +*/ +Datum +addition_slope_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *arg0; + Node *arg1; + + if(!get_2slope_args(rawreq, &arg0, &arg1)) + PG_RETURN_POINTER(NULL); + + if(IsA(arg0, Const)){ + switch (get_const_sign((Const *) arg0)) { + case NUMERIC_SIGN_PINF: + case NUMERIC_SIGN_NINF: + case NUMERIC_SIGN_NAN: + return monotonic_slope_support(rawreq, 2, flat_slope); + default: + break; + } + }else if(IsA(arg1, Const)) { + switch (get_const_sign((Const *) arg1)) { + case NUMERIC_SIGN_PINF: + case NUMERIC_SIGN_NINF: + case NUMERIC_SIGN_NAN: + return monotonic_slope_support(rawreq, 2, flat_slope); + default: + break; + } + } + + return monotonic_slope_support(rawreq, 2, asc_slope); +} + /* * multiply_slope_support * Prosupport: x * c is increasing if c > 0, decreasing if c < 0. @@ -1270,67 +1364,33 @@ Datum multiply_slope_support(PG_FUNCTION_ARGS) { Node *rawreq = (Node *) PG_GETARG_POINTER(0); - - if (IsA(rawreq, SupportRequestMonotonic)) - { - SupportRequestMonotonic *req = (SupportRequestMonotonic *) rawreq; - List *args; - Node *arg0; - Node *arg1; - Const *constval = NULL; - int var_argno = -1; - int sign; - - if (IsA(req->expr, FuncExpr)) - args = ((FuncExpr *) req->expr)->args; - else if (IsA(req->expr, OpExpr)) - args = ((OpExpr *) req->expr)->args; - else - PG_RETURN_POINTER(NULL); - - if (list_length(args) != 2) - PG_RETURN_POINTER(NULL); - - arg0 = (Node *) linitial(args); - arg1 = (Node *) lsecond(args); - - if (IsA(arg0, Const)) - { - constval = (Const *) arg0; - var_argno = 1; - } - else if (IsA(arg1, Const)) - { - constval = (Const *) arg1; - var_argno = 0; - } - else - PG_RETURN_POINTER(NULL); - - sign = get_const_sign(constval); - if (sign == 0) + Const *constval; + Node *arg0; + Node *arg1; + + if(!get_2slope_args(rawreq, &arg0, &arg1)) + PG_RETURN_POINTER(NULL); + + if (IsA(arg0, Const)) + constval = (Const *) arg0; + else if (IsA(arg1, Const)) + constval = (Const *) arg1; + else + PG_RETURN_POINTER(NULL); + + switch (get_const_sign(constval)) { + case NUMERIC_SIGN_POS: + return monotonic_slope_support(rawreq, 2, asc_slope); + case NUMERIC_SIGN_NEG: + return monotonic_slope_support(rawreq, 2, desc_slope); + case NUMERIC_SIGN_NAN: + return monotonic_slope_support(rawreq, 2, flat_slope); + case NUMERIC_SIGN_ZERO: + return monotonic_slope_support(rawreq, 2, flat_slope); + default: PG_RETURN_POINTER(NULL); - - { - static const MonotonicFunction asc_first[2] = {MONOTONICFUNC_INCREASING, - MONOTONICFUNC_NONE}; - static const MonotonicFunction asc_second[2] = {MONOTONICFUNC_NONE, - MONOTONICFUNC_INCREASING}; - static const MonotonicFunction desc_first[2] = {MONOTONICFUNC_DECREASING, - MONOTONICFUNC_NONE}; - static const MonotonicFunction desc_second[2] = {MONOTONICFUNC_NONE, - MONOTONICFUNC_DECREASING}; - - if (var_argno == 0) - req->slopes = (sign > 0) ? asc_first : desc_first; - else - req->slopes = (sign > 0) ? asc_second : desc_second; - req->nslopes = 2; - PG_RETURN_POINTER(req); - } } - - PG_RETURN_POINTER(NULL); + } /* @@ -1346,46 +1406,29 @@ Datum divide_slope_support(PG_FUNCTION_ARGS) { Node *rawreq = (Node *) PG_GETARG_POINTER(0); - - if (IsA(rawreq, SupportRequestMonotonic)) - { - SupportRequestMonotonic *req = (SupportRequestMonotonic *) rawreq; - List *args; - Node *arg1; - Const *constval; - int sign; - - if (IsA(req->expr, FuncExpr)) - args = ((FuncExpr *) req->expr)->args; - else if (IsA(req->expr, OpExpr)) - args = ((OpExpr *) req->expr)->args; - else - PG_RETURN_POINTER(NULL); - - if (list_length(args) != 2) - PG_RETURN_POINTER(NULL); - - arg1 = (Node *) lsecond(args); - - if (!IsA(arg1, Const)) - PG_RETURN_POINTER(NULL); - - constval = (Const *) arg1; - sign = get_const_sign(constval); - if (sign == 0) + Const *constval; + Node *arg0; + Node *arg1; + + if(!get_2slope_args(rawreq, &arg0, &arg1)) + PG_RETURN_POINTER(NULL); + + if (!IsA(arg1, Const)) + PG_RETURN_POINTER(NULL); + + constval = (Const *) arg1; + switch (get_const_sign(constval)) { + case NUMERIC_SIGN_POS: + return monotonic_slope_support(rawreq, 2, asc_slope); + case NUMERIC_SIGN_NEG: + return monotonic_slope_support(rawreq, 2, desc_slope); + case NUMERIC_SIGN_NAN: + return monotonic_slope_support(rawreq, 2, flat_slope); + case NUMERIC_SIGN_PINF: + case NUMERIC_SIGN_NINF: + return monotonic_slope_support(rawreq, 2, flat_slope); + default: PG_RETURN_POINTER(NULL); - - { - static const MonotonicFunction asc_pattern[2] = {MONOTONICFUNC_INCREASING, - MONOTONICFUNC_NONE}; - static const MonotonicFunction desc_pattern[2] = {MONOTONICFUNC_DECREASING, - MONOTONICFUNC_NONE}; - - req->slopes = (sign > 0) ? asc_pattern : desc_pattern; - req->nslopes = 2; - PG_RETURN_POINTER(req); - } } - PG_RETURN_POINTER(NULL); } diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6a3b611528c..7de5169ac2b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -607,7 +607,8 @@ prorettype => 'float4', proargtypes => 'float4 float4', prosrc => 'float4mi' }, { oid => '206', proname => 'float4um', prosupport => 'arg0_desc_slope_support', - prorettype => 'float4', proargtypes => 'float4', prosrc => 'float4um' }, + prorettype => 'float4', proargtypes => 'float4', + prosrc => 'float4um' }, { oid => '207', proname => 'float4abs', prorettype => 'float4', proargtypes => 'float4', prosrc => 'float4abs' }, @@ -648,7 +649,8 @@ prorettype => 'float8', proargtypes => 'float8 float8', prosrc => 'float8mi' }, { oid => '220', proname => 'float8um', prosupport => 'arg0_desc_slope_support', - prorettype => 'float8', proargtypes => 'float8', prosrc => 'float8um' }, + prorettype => 'float8', proargtypes => 'float8', + prosrc => 'float8um' }, { oid => '221', proname => 'float8abs', prorettype => 'float8', proargtypes => 'float8', prosrc => 'float8abs' }, diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 66b5cceebaa..6b345e41171 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -1819,11 +1819,14 @@ typedef struct PathKey * SLOPE: innermost source of variation, filled by * precompute_slope_pathkeys(). NULL if this pathkey is a plain Var * or cannot benefit from SLOPE. pk_slope stores a MonotonicFunction - * value (from plannodes.h) as int to avoid a header dependency. + * value (from plannodes.h), including MONOTONICFUNC_UNKNOWN until + * computed, as int to avoid a header dependency. pk_var_cannan is + * true when pk_var has a type that can hold NaN (float or numeric). */ Expr *pk_var pg_node_attr(read_write_ignore, equal_ignore); Index pk_varrelid pg_node_attr(read_write_ignore, equal_ignore); int pk_slope pg_node_attr(read_write_ignore, equal_ignore); + bool pk_var_cannan pg_node_attr(read_write_ignore, equal_ignore); } PathKey; /* diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h index c9e374f92bf..9c29a6ebc4b 100644 --- a/src/include/nodes/plannodes.h +++ b/src/include/nodes/plannodes.h @@ -1845,6 +1845,12 @@ typedef enum MonotonicFunction MONOTONICFUNC_INCREASING = (1 << 0), MONOTONICFUNC_DECREASING = (1 << 1), MONOTONICFUNC_BOTH = MONOTONICFUNC_INCREASING | MONOTONICFUNC_DECREASING, + + /* + * Planner-internal sentinel: pk_slope has not yet been computed. + * Not returned by prosupport functions. + */ + MONOTONICFUNC_UNKNOWN = (1 << 2), } MonotonicFunction; /* diff --git a/src/test/regress/expected/slope.out b/src/test/regress/expected/slope.out index 74d7f82b94a..f9283493995 100644 --- a/src/test/regress/expected/slope.out +++ b/src/test/regress/expected/slope.out @@ -301,7 +301,7 @@ select floor(v_float8 + 1), count(*) from slope_src group by 1; -- direction and nulls agree (Forward) or both are flipped (Backward). -- When only one differs, a Sort is required. -- -CREATE TABLE slope_nulls_tmp (v float8); +CREATE TABLE slope_nulls_tmp (v int4); INSERT INTO slope_nulls_tmp VALUES (1), (NULL), (2); ANALYZE slope_nulls_tmp; CREATE TEMPORARY TABLE slope_nulls_results ( @@ -346,7 +346,9 @@ BEGIN raise exception 'r1 <> r2'; end if; node_type := plan_json->0->'Plan'->>'Node Type'; - INSERT INTO slope_nulls_results (sign, index_order, query_order, scan_method, example) VALUES ( + INSERT INTO slope_nulls_results + (sign, index_order, query_order, scan_method, example) + VALUES ( r.sign, r.idx_dir || ' NULLS ' || r.idx_nf, r.qry_dir || ' NULLS ' || r.qry_nf, @@ -522,3 +524,113 @@ from slope_src; -- Cleanup RESET enable_hashagg; +-- +-- Test special numeric values +-- +CREATE UNLOGGED TABLE slope_numeric_corners (i serial, x float8); +INSERT INTO slope_numeric_corners (x) +SELECT x::float8 as x FROM + unnest(ARRAY['-inf', '-1', '0', '3', 'inf', 'nan', NULL]) x(x) +ORDER BY 1; +CREATE INDEX slope_numeric_corners_x_idx_nulllast ON slope_numeric_corners (x ASC NULLS LAST); +CREATE INDEX slope_numeric_corners_x_idx_nullfirst ON slope_numeric_corners (x ASC NULLS FIRST); +CREATE TEMPORARY TABLE slope_numeric_corners_results ( + seq serial, + expr text COLLATE "C", + sort_order text COLLATE "C", + nulls_order text COLLATE "C", + result float8[], + expected float8[], + expected_seq int4[], + nan_values float8[], + plan1_json json, + plan2_json json +); +DO $$ +DECLARE + r record; + result float8[]; + expected float8[]; + expected_seq int4[]; + nan_values float8[]; + query text; + agg_query text; + plan_query text; + nan_query text; + expected_seq_query text; + plan1_json json; + plan2_json json; +BEGIN + + SET enable_bitmapscan = off; + SET enable_indexscan = off; + SET enable_indexonlyscan = off; + SET enable_seqscan = off; + SET enable_sort = off; + FOR r IN + SELECT replace(s, 'a', a) as expr, sort_order, nulls_order + FROM + unnest(ARRAY['ASC', 'DESC']) WITH ORDINALITY AS so(sort_order, so_i), + unnest(ARRAY['FIRST', 'LAST']) WITH ORDINALITY AS nf(nulls_order, no_i), + unnest(ARRAY[ + 'x+a', 'x-a', 'x*a', 'x/a', + 'a+x', 'a-x', 'a*x', '-x' + ]) WITH ORDINALITY AS s(s, s_i), + unnest(ARRAY['''inf''::float8', '''-inf''::float8', '1::float8']) WITH ORDINALITY AS a(a, a_i) + ORDER BY s_i, so_i, no_i + LOOP + query := 'SELECT *, ' || r.expr || ' as f ' + || 'FROM slope_numeric_corners ' + || 'ORDER BY f ' || r.sort_order || ' NULLS ' || r.nulls_order; + nan_query := 'SELECT array_agg(x) FROM slope_numeric_corners + WHERE ' || r.expr || ' = ''nan''::float8 AND x != ''nan''::float8'; + agg_query := 'SELECT array_agg(f::float8) FROM (' || query || ') tmp'; + expected_seq_query := 'SELECT array_agg(i::int4) FROM (' || query || ') tmp'; + plan_query := 'EXPLAIN (FORMAT JSON) ' || query; + -- slope optimization disabled + SET enable_seqscan = on; + SET enable_indexscan = off; + SET enable_sort = on; + EXECUTE agg_query into expected; + EXECUTE expected_seq_query into expected_seq; + EXECUTE nan_query into nan_values; + EXECUTE plan_query into plan1_json; + -- slope optimization enabled + SET enable_seqscan = off; + SET enable_indexscan = on; + SET enable_sort = off; + EXECUTE agg_query into result; + EXECUTE plan_query into plan2_json; + INSERT INTO slope_numeric_corners_results + (expr, sort_order, nulls_order, result, expected, expected_seq, nan_values, plan1_json, plan2_json) + VALUES (r.expr, r.sort_order, r.nulls_order, result, expected, expected_seq, nan_values, plan1_json, plan2_json); + END LOOP; +END; +$$; +-- display failing test cases +SELECT seq, expr +, sort_order, nulls_order +, expected, expected_seq, result, nan_values +, plan2_json->0->'Plan'->>'Node Type' as plan2 +FROM slope_numeric_corners_results +WHERE expected != result; + seq | expr | sort_order | nulls_order | expected | expected_seq | result | nan_values | plan2 +-----+------+------------+-------------+----------+--------------+--------+------------+------- +(0 rows) + +-- check the number of test cases +SELECT expected = result as passed, count(1) +FROM slope_numeric_corners_results +GROUP BY 1; + passed | count +--------+------- + t | 96 +(1 row) + +DROP TABLE slope_numeric_corners; +DROP TABLE slope_numeric_corners_results; +RESET enable_bitmapscan; +RESET enable_indexscan; +RESET enable_indexonlyscan; +RESET enable_seqscan; +RESET enable_sort; diff --git a/src/test/regress/sql/slope.sql b/src/test/regress/sql/slope.sql index 31eba90695f..89a7a710981 100644 --- a/src/test/regress/sql/slope.sql +++ b/src/test/regress/sql/slope.sql @@ -145,7 +145,7 @@ select floor(v_float8 + 1), count(*) from slope_src group by 1; -- direction and nulls agree (Forward) or both are flipped (Backward). -- When only one differs, a Sort is required. -- -CREATE TABLE slope_nulls_tmp (v float8); +CREATE TABLE slope_nulls_tmp (v int4); INSERT INTO slope_nulls_tmp VALUES (1), (NULL), (2); ANALYZE slope_nulls_tmp; @@ -193,7 +193,9 @@ BEGIN raise exception 'r1 <> r2'; end if; node_type := plan_json->0->'Plan'->>'Node Type'; - INSERT INTO slope_nulls_results (sign, index_order, query_order, scan_method, example) VALUES ( + INSERT INTO slope_nulls_results + (sign, index_order, query_order, scan_method, example) + VALUES ( r.sign, r.idx_dir || ' NULLS ' || r.idx_nf, r.qry_dir || ' NULLS ' || r.qry_nf, @@ -276,3 +278,113 @@ from slope_src; -- Cleanup RESET enable_hashagg; + + +-- +-- Test special numeric values +-- + +CREATE UNLOGGED TABLE slope_numeric_corners (i serial, x float8); +INSERT INTO slope_numeric_corners (x) +SELECT x::float8 as x FROM + unnest(ARRAY['-inf', '-1', '0', '3', 'inf', 'nan', NULL]) x(x) +ORDER BY 1; + +CREATE INDEX slope_numeric_corners_x_idx_nulllast ON slope_numeric_corners (x ASC NULLS LAST); +CREATE INDEX slope_numeric_corners_x_idx_nullfirst ON slope_numeric_corners (x ASC NULLS FIRST); + +CREATE TEMPORARY TABLE slope_numeric_corners_results ( + seq serial, + expr text COLLATE "C", + sort_order text COLLATE "C", + nulls_order text COLLATE "C", + result float8[], + expected float8[], + expected_seq int4[], + nan_values float8[], + plan1_json json, + plan2_json json +); + +DO $$ +DECLARE + r record; + result float8[]; + expected float8[]; + expected_seq int4[]; + nan_values float8[]; + query text; + agg_query text; + plan_query text; + nan_query text; + expected_seq_query text; + plan1_json json; + plan2_json json; +BEGIN + + SET enable_bitmapscan = off; + SET enable_indexscan = off; + SET enable_indexonlyscan = off; + SET enable_seqscan = off; + SET enable_sort = off; + FOR r IN + SELECT replace(s, 'a', a) as expr, sort_order, nulls_order + FROM + unnest(ARRAY['ASC', 'DESC']) WITH ORDINALITY AS so(sort_order, so_i), + unnest(ARRAY['FIRST', 'LAST']) WITH ORDINALITY AS nf(nulls_order, no_i), + unnest(ARRAY[ + 'x+a', 'x-a', 'x*a', 'x/a', + 'a+x', 'a-x', 'a*x', '-x' + ]) WITH ORDINALITY AS s(s, s_i), + unnest(ARRAY['''inf''::float8', '''-inf''::float8', '1::float8']) WITH ORDINALITY AS a(a, a_i) + ORDER BY s_i, so_i, no_i + LOOP + query := 'SELECT *, ' || r.expr || ' as f ' + || 'FROM slope_numeric_corners ' + || 'ORDER BY f ' || r.sort_order || ' NULLS ' || r.nulls_order; + nan_query := 'SELECT array_agg(x) FROM slope_numeric_corners + WHERE ' || r.expr || ' = ''nan''::float8 AND x != ''nan''::float8'; + agg_query := 'SELECT array_agg(f::float8) FROM (' || query || ') tmp'; + expected_seq_query := 'SELECT array_agg(i::int4) FROM (' || query || ') tmp'; + plan_query := 'EXPLAIN (FORMAT JSON) ' || query; + -- slope optimization disabled + SET enable_seqscan = on; + SET enable_indexscan = off; + SET enable_sort = on; + EXECUTE agg_query into expected; + EXECUTE expected_seq_query into expected_seq; + EXECUTE nan_query into nan_values; + EXECUTE plan_query into plan1_json; + -- slope optimization enabled + SET enable_seqscan = off; + SET enable_indexscan = on; + SET enable_sort = off; + EXECUTE agg_query into result; + EXECUTE plan_query into plan2_json; + INSERT INTO slope_numeric_corners_results + (expr, sort_order, nulls_order, result, expected, expected_seq, nan_values, plan1_json, plan2_json) + VALUES (r.expr, r.sort_order, r.nulls_order, result, expected, expected_seq, nan_values, plan1_json, plan2_json); + END LOOP; +END; +$$; + +-- display failing test cases +SELECT seq, expr +, sort_order, nulls_order +, expected, expected_seq, result, nan_values +, plan2_json->0->'Plan'->>'Node Type' as plan2 +FROM slope_numeric_corners_results +WHERE expected != result; + +-- check the number of test cases +SELECT expected = result as passed, count(1) +FROM slope_numeric_corners_results +GROUP BY 1; + +DROP TABLE slope_numeric_corners; +DROP TABLE slope_numeric_corners_results; +RESET enable_bitmapscan; +RESET enable_indexscan; +RESET enable_indexonlyscan; +RESET enable_seqscan; +RESET enable_sort; \ No newline at end of file -- 2.53.0