@@ -64,13 +64,14 @@ static void set_worktable_pathlist(PlannerInfo *root, RelOptInfo *rel,
64
64
RangeTblEntry * rte );
65
65
static RelOptInfo * make_rel_from_joinlist (PlannerInfo * root , List * joinlist );
66
66
static bool subquery_is_pushdown_safe (Query * subquery , Query * topquery ,
67
- bool * differentTypes );
67
+ bool * unsafeColumns );
68
68
static bool recurse_pushdown_safe (Node * setOp , Query * topquery ,
69
- bool * differentTypes );
69
+ bool * unsafeColumns );
70
+ static void check_output_expressions (Query * subquery , bool * unsafeColumns );
70
71
static void compare_tlist_datatypes (List * tlist , List * colTypes ,
71
- bool * differentTypes );
72
+ bool * unsafeColumns );
72
73
static bool qual_is_pushdown_safe (Query * subquery , Index rti , Node * qual ,
73
- bool * differentTypes );
74
+ bool * unsafeColumns );
74
75
static void subquery_push_qual (Query * subquery ,
75
76
RangeTblEntry * rte , Index rti , Node * qual );
76
77
static void recurse_push_qual (Node * setOp , Query * topquery ,
@@ -545,7 +546,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
545
546
{
546
547
Query * parse = root -> parse ;
547
548
Query * subquery = rte -> subquery ;
548
- bool * differentTypes ;
549
+ bool * unsafeColumns ;
549
550
double tuple_fraction ;
550
551
PlannerInfo * subroot ;
551
552
List * pathkeys ;
@@ -557,8 +558,12 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
557
558
*/
558
559
subquery = copyObject (subquery );
559
560
560
- /* We need a workspace for keeping track of set-op type coercions */
561
- differentTypes = (bool * )
561
+ /*
562
+ * We need a workspace for keeping track of unsafe-to-reference columns.
563
+ * unsafeColumns[i] is set TRUE if we've found that output column i of the
564
+ * subquery is unsafe to use in a pushed-down qual.
565
+ */
566
+ unsafeColumns = (bool * )
562
567
palloc0 ((list_length (subquery -> targetList ) + 1 ) * sizeof (bool ));
563
568
564
569
/*
@@ -582,7 +587,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
582
587
* push down a pushable qual, because it'd result in a worse plan?
583
588
*/
584
589
if (rel -> baserestrictinfo != NIL &&
585
- subquery_is_pushdown_safe (subquery , subquery , differentTypes ))
590
+ subquery_is_pushdown_safe (subquery , subquery , unsafeColumns ))
586
591
{
587
592
/* OK to consider pushing down individual quals */
588
593
List * upperrestrictlist = NIL ;
@@ -594,7 +599,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
594
599
Node * clause = (Node * ) rinfo -> clause ;
595
600
596
601
if (!rinfo -> pseudoconstant &&
597
- qual_is_pushdown_safe (subquery , rti , clause , differentTypes ))
602
+ qual_is_pushdown_safe (subquery , rti , clause , unsafeColumns ))
598
603
{
599
604
/* Push it down */
600
605
subquery_push_qual (subquery , rte , rti , clause );
@@ -608,7 +613,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
608
613
rel -> baserestrictinfo = upperrestrictlist ;
609
614
}
610
615
611
- pfree (differentTypes );
616
+ pfree (unsafeColumns );
612
617
613
618
/*
614
619
* We can safely pass the outer tuple_fraction down to the subquery if the
@@ -986,17 +991,19 @@ standard_join_search(PlannerInfo *root, int levels_needed, List *initial_rels)
986
991
* 3. If the subquery contains EXCEPT or EXCEPT ALL set ops we cannot push
987
992
* quals into it, because that would change the results.
988
993
*
989
- * 4. For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
990
- * push quals into each component query, but the quals can only reference
991
- * subquery columns that suffer no type coercions in the set operation.
992
- * Otherwise there are possible semantic gotchas. So, we check the
993
- * component queries to see if any of them have different output types;
994
- * differentTypes[k] is set true if column k has different type in any
995
- * component.
994
+ * In addition, we make several checks on the subquery's output columns
995
+ * to see if it is safe to reference them in pushed-down quals. If output
996
+ * column k is found to be unsafe to reference, we set unsafeColumns[k] to
997
+ * TRUE, but we don't reject the subquery overall since column k might
998
+ * not be referenced by some/all quals. The unsafeColumns[] array will be
999
+ * consulted later by qual_is_pushdown_safe(). It's better to do it this
1000
+ * way than to make the checks directly in qual_is_pushdown_safe(), because
1001
+ * when the subquery involves set operations we have to check the output
1002
+ * expressions in each arm of the set op.
996
1003
*/
997
1004
static bool
998
1005
subquery_is_pushdown_safe (Query * subquery , Query * topquery ,
999
- bool * differentTypes )
1006
+ bool * unsafeColumns )
1000
1007
{
1001
1008
SetOperationStmt * topop ;
1002
1009
@@ -1008,13 +1015,22 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1008
1015
if (subquery -> hasWindowFuncs )
1009
1016
return false;
1010
1017
1018
+ /*
1019
+ * If we're at a leaf query, check for unsafe expressions in its target
1020
+ * list, and mark any unsafe ones in unsafeColumns[]. (Non-leaf nodes in
1021
+ * setop trees have only simple Vars in their tlists, so no need to check
1022
+ * them.)
1023
+ */
1024
+ if (subquery -> setOperations == NULL )
1025
+ check_output_expressions (subquery , unsafeColumns );
1026
+
1011
1027
/* Are we at top level, or looking at a setop component? */
1012
1028
if (subquery == topquery )
1013
1029
{
1014
1030
/* Top level, so check any component queries */
1015
1031
if (subquery -> setOperations != NULL )
1016
1032
if (!recurse_pushdown_safe (subquery -> setOperations , topquery ,
1017
- differentTypes ))
1033
+ unsafeColumns ))
1018
1034
return false;
1019
1035
}
1020
1036
else
@@ -1027,7 +1043,7 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1027
1043
Assert (topop && IsA (topop , SetOperationStmt ));
1028
1044
compare_tlist_datatypes (subquery -> targetList ,
1029
1045
topop -> colTypes ,
1030
- differentTypes );
1046
+ unsafeColumns );
1031
1047
}
1032
1048
return true;
1033
1049
}
@@ -1037,7 +1053,7 @@ subquery_is_pushdown_safe(Query *subquery, Query *topquery,
1037
1053
*/
1038
1054
static bool
1039
1055
recurse_pushdown_safe (Node * setOp , Query * topquery ,
1040
- bool * differentTypes )
1056
+ bool * unsafeColumns )
1041
1057
{
1042
1058
if (IsA (setOp , RangeTblRef ))
1043
1059
{
@@ -1046,19 +1062,19 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
1046
1062
Query * subquery = rte -> subquery ;
1047
1063
1048
1064
Assert (subquery != NULL );
1049
- return subquery_is_pushdown_safe (subquery , topquery , differentTypes );
1065
+ return subquery_is_pushdown_safe (subquery , topquery , unsafeColumns );
1050
1066
}
1051
1067
else if (IsA (setOp , SetOperationStmt ))
1052
1068
{
1053
1069
SetOperationStmt * op = (SetOperationStmt * ) setOp ;
1054
1070
1055
- /* EXCEPT is no good */
1071
+ /* EXCEPT is no good (point 3 for subquery_is_pushdown_safe) */
1056
1072
if (op -> op == SETOP_EXCEPT )
1057
1073
return false;
1058
1074
/* Else recurse */
1059
- if (!recurse_pushdown_safe (op -> larg , topquery , differentTypes ))
1075
+ if (!recurse_pushdown_safe (op -> larg , topquery , unsafeColumns ))
1060
1076
return false;
1061
- if (!recurse_pushdown_safe (op -> rarg , topquery , differentTypes ))
1077
+ if (!recurse_pushdown_safe (op -> rarg , topquery , unsafeColumns ))
1062
1078
return false;
1063
1079
}
1064
1080
else
@@ -1070,17 +1086,92 @@ recurse_pushdown_safe(Node *setOp, Query *topquery,
1070
1086
}
1071
1087
1072
1088
/*
1073
- * Compare tlist's datatypes against the list of set-operation result types.
1074
- * For any items that are different, mark the appropriate element of
1075
- * differentTypes[] to show that this column will have type conversions.
1089
+ * check_output_expressions - check subquery's output expressions for safety
1090
+ *
1091
+ * There are several cases in which it's unsafe to push down an upper-level
1092
+ * qual if it references a particular output column of a subquery. We check
1093
+ * each output column of the subquery and set unsafeColumns[k] to TRUE if
1094
+ * that column is unsafe for a pushed-down qual to reference. The conditions
1095
+ * checked here are:
1096
+ *
1097
+ * 1. We must not push down any quals that refer to subselect outputs that
1098
+ * return sets, else we'd introduce functions-returning-sets into the
1099
+ * subquery's WHERE/HAVING quals.
1100
+ *
1101
+ * 2. We must not push down any quals that refer to subselect outputs that
1102
+ * contain volatile functions, for fear of introducing strange results due
1103
+ * to multiple evaluation of a volatile function.
1104
+ *
1105
+ * 3. If the subquery uses DISTINCT ON, we must not push down any quals that
1106
+ * refer to non-DISTINCT output columns, because that could change the set
1107
+ * of rows returned. (This condition is vacuous for DISTINCT, because then
1108
+ * there are no non-DISTINCT output columns, so we needn't check. But note
1109
+ * we are assuming that the qual can't distinguish values that the DISTINCT
1110
+ * operator sees as equal. This is a bit shaky but we have no way to test
1111
+ * for the case, and it's unlikely enough that we shouldn't refuse the
1112
+ * optimization just because it could theoretically happen.)
1113
+ */
1114
+ static void
1115
+ check_output_expressions (Query * subquery , bool * unsafeColumns )
1116
+ {
1117
+ ListCell * lc ;
1118
+
1119
+ foreach (lc , subquery -> targetList )
1120
+ {
1121
+ TargetEntry * tle = (TargetEntry * ) lfirst (lc );
1122
+
1123
+ if (tle -> resjunk )
1124
+ continue ; /* ignore resjunk columns */
1125
+
1126
+ /* We need not check further if output col is already known unsafe */
1127
+ if (unsafeColumns [tle -> resno ])
1128
+ continue ;
1129
+
1130
+ /* Functions returning sets are unsafe (point 1) */
1131
+ if (expression_returns_set ((Node * ) tle -> expr ))
1132
+ {
1133
+ unsafeColumns [tle -> resno ] = true;
1134
+ continue ;
1135
+ }
1136
+
1137
+ /* Volatile functions are unsafe (point 2) */
1138
+ if (contain_volatile_functions ((Node * ) tle -> expr ))
1139
+ {
1140
+ unsafeColumns [tle -> resno ] = true;
1141
+ continue ;
1142
+ }
1143
+
1144
+ /* If subquery uses DISTINCT ON, check point 3 */
1145
+ if (subquery -> hasDistinctOn &&
1146
+ !targetIsInSortList (tle , InvalidOid , subquery -> distinctClause ))
1147
+ {
1148
+ /* non-DISTINCT column, so mark it unsafe */
1149
+ unsafeColumns [tle -> resno ] = true;
1150
+ continue ;
1151
+ }
1152
+ }
1153
+ }
1154
+
1155
+ /*
1156
+ * For subqueries using UNION/UNION ALL/INTERSECT/INTERSECT ALL, we can
1157
+ * push quals into each component query, but the quals can only reference
1158
+ * subquery columns that suffer no type coercions in the set operation.
1159
+ * Otherwise there are possible semantic gotchas. So, we check the
1160
+ * component queries to see if any of them have output types different from
1161
+ * the top-level setop outputs. unsafeColumns[k] is set true if column k
1162
+ * has different type in any component.
1076
1163
*
1077
1164
* We don't have to care about typmods here: the only allowed difference
1078
1165
* between set-op input and output typmods is input is a specific typmod
1079
1166
* and output is -1, and that does not require a coercion.
1167
+ *
1168
+ * tlist is a subquery tlist.
1169
+ * colTypes is an OID list of the top-level setop's output column types.
1170
+ * unsafeColumns[] is the result array.
1080
1171
*/
1081
1172
static void
1082
1173
compare_tlist_datatypes (List * tlist , List * colTypes ,
1083
- bool * differentTypes )
1174
+ bool * unsafeColumns )
1084
1175
{
1085
1176
ListCell * l ;
1086
1177
ListCell * colType = list_head (colTypes );
@@ -1094,7 +1185,7 @@ compare_tlist_datatypes(List *tlist, List *colTypes,
1094
1185
if (colType == NULL )
1095
1186
elog (ERROR , "wrong number of tlist entries" );
1096
1187
if (exprType ((Node * ) tle -> expr ) != lfirst_oid (colType ))
1097
- differentTypes [tle -> resno ] = true;
1188
+ unsafeColumns [tle -> resno ] = true;
1098
1189
colType = lnext (colType );
1099
1190
}
1100
1191
if (colType != NULL )
@@ -1117,34 +1208,15 @@ compare_tlist_datatypes(List *tlist, List *colTypes,
1117
1208
* (since there is no easy way to name that within the subquery itself).
1118
1209
*
1119
1210
* 3. The qual must not refer to any subquery output columns that were
1120
- * found to have inconsistent types across a set operation tree by
1121
- * subquery_is_pushdown_safe().
1122
- *
1123
- * 4. If the subquery uses DISTINCT ON, we must not push down any quals that
1124
- * refer to non-DISTINCT output columns, because that could change the set
1125
- * of rows returned. (This condition is vacuous for DISTINCT, because then
1126
- * there are no non-DISTINCT output columns, so we needn't check. But note
1127
- * we are assuming that the qual can't distinguish values that the DISTINCT
1128
- * operator sees as equal. This is a bit shaky but we have no way to test
1129
- * for the case, and it's unlikely enough that we shouldn't refuse the
1130
- * optimization just because it could theoretically happen.)
1131
- *
1132
- * 5. We must not push down any quals that refer to subselect outputs that
1133
- * return sets, else we'd introduce functions-returning-sets into the
1134
- * subquery's WHERE/HAVING quals.
1135
- *
1136
- * 6. We must not push down any quals that refer to subselect outputs that
1137
- * contain volatile functions, for fear of introducing strange results due
1138
- * to multiple evaluation of a volatile function.
1211
+ * found to be unsafe to reference by subquery_is_pushdown_safe().
1139
1212
*/
1140
1213
static bool
1141
1214
qual_is_pushdown_safe (Query * subquery , Index rti , Node * qual ,
1142
- bool * differentTypes )
1215
+ bool * unsafeColumns )
1143
1216
{
1144
1217
bool safe = true;
1145
1218
List * vars ;
1146
1219
ListCell * vl ;
1147
- Bitmapset * tested = NULL ;
1148
1220
1149
1221
/* Refuse subselects (point 1) */
1150
1222
if (contain_subplans (qual ))
@@ -1164,7 +1236,6 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
1164
1236
foreach (vl , vars )
1165
1237
{
1166
1238
Var * var = (Var * ) lfirst (vl );
1167
- TargetEntry * tle ;
1168
1239
1169
1240
/*
1170
1241
* XXX Punt if we find any PlaceHolderVars in the restriction clause.
@@ -1180,6 +1251,7 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
1180
1251
}
1181
1252
1182
1253
Assert (var -> varno == rti );
1254
+ Assert (var -> varattno >= 0 );
1183
1255
1184
1256
/* Check point 2 */
1185
1257
if (var -> varattno == 0 )
@@ -1188,53 +1260,15 @@ qual_is_pushdown_safe(Query *subquery, Index rti, Node *qual,
1188
1260
break ;
1189
1261
}
1190
1262
1191
- /*
1192
- * We use a bitmapset to avoid testing the same attno more than once.
1193
- * (NB: this only works because subquery outputs can't have negative
1194
- * attnos.)
1195
- */
1196
- if (bms_is_member (var -> varattno , tested ))
1197
- continue ;
1198
- tested = bms_add_member (tested , var -> varattno );
1199
-
1200
1263
/* Check point 3 */
1201
- if (differentTypes [var -> varattno ])
1202
- {
1203
- safe = false;
1204
- break ;
1205
- }
1206
-
1207
- /* Must find the tlist element referenced by the Var */
1208
- tle = get_tle_by_resno (subquery -> targetList , var -> varattno );
1209
- Assert (tle != NULL );
1210
- Assert (!tle -> resjunk );
1211
-
1212
- /* If subquery uses DISTINCT ON, check point 4 */
1213
- if (subquery -> hasDistinctOn &&
1214
- !targetIsInSortList (tle , InvalidOid , subquery -> distinctClause ))
1215
- {
1216
- /* non-DISTINCT column, so fail */
1217
- safe = false;
1218
- break ;
1219
- }
1220
-
1221
- /* Refuse functions returning sets (point 5) */
1222
- if (expression_returns_set ((Node * ) tle -> expr ))
1223
- {
1224
- safe = false;
1225
- break ;
1226
- }
1227
-
1228
- /* Refuse volatile functions (point 6) */
1229
- if (contain_volatile_functions ((Node * ) tle -> expr ))
1264
+ if (unsafeColumns [var -> varattno ])
1230
1265
{
1231
1266
safe = false;
1232
1267
break ;
1233
1268
}
1234
1269
}
1235
1270
1236
1271
list_free (vars );
1237
- bms_free (tested );
1238
1272
1239
1273
return safe ;
1240
1274
}
0 commit comments