Fix non-bulletproof ScalarArrayOpExpr code for extended statistics.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 5 Aug 2022 17:58:37 +0000 (13:58 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 5 Aug 2022 17:58:48 +0000 (13:58 -0400)
statext_is_compatible_clause_internal() checked that the arguments
of a ScalarArrayOpExpr are one Var and one Const, but it would allow
cases where the Const was on the left.  Subsequent uses of the clause
are not expecting that and would suffer assertion failures or core
dumps.  mcv.c also had not bothered to cope with the case of a NULL
array constant, which seems really unacceptably sloppy of somebody.
(Although our tools failed us there too, since AFAIK neither Coverity
nor any compiler warned of the obvious use-of-uninitialized-variable
condition.)  It seems best to handle that by having
statext_is_compatible_clause_internal() reject it.

Noted while fixing bug #17570.  Back-patch to v13 where the
extended stats code grew some awareness of ScalarArrayOpExpr.

src/backend/statistics/extended_stats.c
src/backend/statistics/mcv.c
src/test/regress/expected/stats_ext.out
src/test/regress/sql/stats_ext.sql

index 5922909fe6056dba415e04358d7526fa96f9a4b7..21604f9e9c55a8f8e997bd7e63d9647f043ac932 100644 (file)
@@ -1331,8 +1331,8 @@ choose_best_statistics(List *stats, char requiredkind,
  *
  * (c) combinations using AND/OR/NOT
  *
- * (d) ScalarArrayOpExprs of the form (Var/Expr op ANY (array)) or (Var/Expr
- * op ALL (array))
+ * (d) ScalarArrayOpExprs of the form (Var/Expr op ANY (Const)) or
+ * (Var/Expr op ALL (Const))
  *
  * In the future, the range of supported clauses may be expanded to more
  * complex cases, for example (Var op Var).
@@ -1452,13 +1452,19 @@ statext_is_compatible_clause_internal(PlannerInfo *root, Node *clause,
        RangeTblEntry *rte = root->simple_rte_array[relid];
        ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) clause;
        Node       *clause_expr;
+       Const      *cst;
+       bool        expronleft;
 
        /* Only expressions with two arguments are considered compatible. */
        if (list_length(expr->args) != 2)
            return false;
 
        /* Check if the expression has the right shape (one Var, one Const) */
-       if (!examine_opclause_args(expr->args, &clause_expr, NULL, NULL))
+       if (!examine_opclause_args(expr->args, &clause_expr, &cst, &expronleft))
+           return false;
+
+       /* We only support Var on left and non-null array constants */
+       if (!expronleft || cst->constisnull)
            return false;
 
        /*
index e6a60865282cdfa2af5a494ce028049aeea74852..9cbd093fce7fd1cff926e66a6e8ac0756d7459d2 100644 (file)
@@ -1746,20 +1746,17 @@ mcv_get_match_bitmap(PlannerInfo *root, List *clauses,
            if (!examine_opclause_args(expr->args, &clause_expr, &cst, &expronleft))
                elog(ERROR, "incompatible clause");
 
-           /* ScalarArrayOpExpr has the Var always on the left */
-           Assert(expronleft);
+           /* We expect Var on left and non-null constant on right */
+           if (!expronleft || cst->constisnull)
+               elog(ERROR, "incompatible clause");
 
-           /* XXX what if (cst->constisnull == NULL)? */
-           if (!cst->constisnull)
-           {
-               arrayval = DatumGetArrayTypeP(cst->constvalue);
-               get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
-                                    &elmlen, &elmbyval, &elmalign);
-               deconstruct_array(arrayval,
-                                 ARR_ELEMTYPE(arrayval),
-                                 elmlen, elmbyval, elmalign,
-                                 &elem_values, &elem_nulls, &num_elems);
-           }
+           arrayval = DatumGetArrayTypeP(cst->constvalue);
+           get_typlenbyvalalign(ARR_ELEMTYPE(arrayval),
+                                &elmlen, &elmbyval, &elmalign);
+           deconstruct_array(arrayval,
+                             ARR_ELEMTYPE(arrayval),
+                             elmlen, elmbyval, elmalign,
+                             &elem_values, &elem_nulls, &num_elems);
 
            /* match the attribute/expression to a dimension of the statistic */
            idx = mcv_match_expression(clause_expr, keys, exprs, &collid);
index b494411074499680bc4749e114dc088760e55d2f..34913ecff92434523342b5afd38453dd03db2842 100644 (file)
@@ -1822,7 +1822,8 @@ CREATE TABLE mcv_lists (
     b VARCHAR,
     filler3 DATE,
     c INT,
-    d TEXT
+    d TEXT,
+    ia INT[]
 )
 WITH (autovacuum_enabled = off);
 -- random data (no MCV list)
@@ -1892,8 +1893,9 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 A
 -- 100 distinct combinations, all in the MCV list
 TRUNCATE mcv_lists;
 DROP STATISTICS mcv_lists_stats;
-INSERT INTO mcv_lists (a, b, c, filler1)
-     SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i);
+INSERT INTO mcv_lists (a, b, c, ia, filler1)
+     SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i
+       FROM generate_series(1,5000) s(i);
 ANALYZE mcv_lists;
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1''');
  estimated | actual 
@@ -2033,8 +2035,14 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY
          1 |    100
 (1 row)
 
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)');
+ estimated | actual 
+-----------+--------
+         4 |     50
+(1 row)
+
 -- create statistics
-CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists;
+CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists;
 ANALYZE mcv_lists;
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1''');
  estimated | actual 
@@ -2180,6 +2188,12 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY
        100 |    100
 (1 row)
 
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)');
+ estimated | actual 
+-----------+--------
+         4 |     50
+(1 row)
+
 -- check change of unrelated column type does not reset the MCV statistics
 ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64);
 SELECT d.stxdmcv IS NOT NULL
index 953c7acfc8847562f5421b165d1e265debef20bf..fc844c352afa8233e891db428b64bf7ccb66461d 100644 (file)
@@ -908,7 +908,8 @@ CREATE TABLE mcv_lists (
     b VARCHAR,
     filler3 DATE,
     c INT,
-    d TEXT
+    d TEXT,
+    ia INT[]
 )
 WITH (autovacuum_enabled = off);
 
@@ -957,8 +958,9 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE mod(a,7) = 1 A
 TRUNCATE mcv_lists;
 DROP STATISTICS mcv_lists_stats;
 
-INSERT INTO mcv_lists (a, b, c, filler1)
-     SELECT mod(i,100), mod(i,50), mod(i,25), i FROM generate_series(1,5000) s(i);
+INSERT INTO mcv_lists (a, b, c, ia, filler1)
+     SELECT mod(i,100), mod(i,50), mod(i,25), array[mod(i,25)], i
+       FROM generate_series(1,5000) s(i);
 
 ANALYZE mcv_lists;
 
@@ -1008,8 +1010,10 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])');
 
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)');
+
 -- create statistics
-CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c FROM mcv_lists;
+CREATE STATISTICS mcv_lists_stats (mcv) ON a, b, c, ia FROM mcv_lists;
 
 ANALYZE mcv_lists;
 
@@ -1061,6 +1065,8 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a < ALL (ARRAY[4, 5]) AND b IN (''1'', ''2'', NULL, ''3'') AND c > ANY (ARRAY[1, 2, NULL, 3])');
 
+SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = ANY (ARRAY[4,5]) AND 4 = ANY(ia)');
+
 -- check change of unrelated column type does not reset the MCV statistics
 ALTER TABLE mcv_lists ALTER COLUMN d TYPE VARCHAR(64);