Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit a7cd853

Browse files
committed
Change post-rewriter representation of dropped columns in joinaliasvars.
It's possible to drop a column from an input table of a JOIN clause in a view, if that column is nowhere actually referenced in the view. But it will still be there in the JOIN clause's joinaliasvars list. We used to replace such entries with NULL Const nodes, which is handy for generation of RowExpr expansion of a whole-row reference to the view. The trouble with that is that it can't be distinguished from the situation after subquery pull-up of a constant subquery output expression below the JOIN. Instead, replace such joinaliasvars with null pointers (empty expression trees), which can't be confused with pulled-up expressions. expandRTE() still emits the old convention, though, for convenience of RowExpr generation and to reduce the risk of breaking extension code. In HEAD and 9.3, this patch also fixes a problem with some new code in ruleutils.c that was failing to cope with implicitly-casted joinaliasvars entries, as per recent report from Feike Steenbergen. That oversight was because of an inadequate description of the data structure in parsenodes.h, which I've now corrected. There were some pre-existing oversights of the same ilk elsewhere, which I believe are now all fixed.
1 parent c359a1b commit a7cd853

File tree

8 files changed

+118
-51
lines changed

8 files changed

+118
-51
lines changed

src/backend/optimizer/util/var.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ flatten_join_alias_vars_mutator(Node *node,
654654
newvar = (Node *) lfirst(lv);
655655
attnum++;
656656
/* Ignore dropped columns */
657-
if (IsA(newvar, Const))
657+
if (newvar == NULL)
658658
continue;
659659
newvar = copyObject(newvar);
660660

@@ -687,6 +687,7 @@ flatten_join_alias_vars_mutator(Node *node,
687687
/* Expand join alias reference */
688688
Assert(var->varattno > 0);
689689
newvar = (Node *) list_nth(rte->joinaliasvars, var->varattno - 1);
690+
Assert(newvar != NULL);
690691
newvar = copyObject(newvar);
691692

692693
/*

src/backend/parser/parse_relation.c

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "funcapi.h"
2525
#include "nodes/makefuncs.h"
2626
#include "nodes/nodeFuncs.h"
27+
#include "optimizer/clauses.h"
2728
#include "parser/parsetree.h"
2829
#include "parser/parse_relation.h"
2930
#include "parser/parse_type.h"
@@ -749,14 +750,15 @@ markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
749750
* The aliasvar could be either a Var or a COALESCE expression,
750751
* but in the latter case we should already have marked the two
751752
* referent variables as being selected, due to their use in the
752-
* JOIN clause. So we need only be concerned with the simple Var
753-
* case.
753+
* JOIN clause. So we need only be concerned with the Var case.
754+
* But we do need to drill down through implicit coercions.
754755
*/
755756
Var *aliasvar;
756757

757758
Assert(col > 0 && col <= list_length(rte->joinaliasvars));
758759
aliasvar = (Var *) list_nth(rte->joinaliasvars, col - 1);
759-
if (IsA(aliasvar, Var))
760+
aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
761+
if (aliasvar && IsA(aliasvar, Var))
760762
markVarForSelectPriv(pstate, aliasvar, NULL);
761763
}
762764
}
@@ -1841,19 +1843,27 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
18411843
* deleted columns in the join; but we have to check since
18421844
* this routine is also used by the rewriter, and joins
18431845
* found in stored rules might have join columns for
1844-
* since-deleted columns. This will be signaled by a NULL
1845-
* Const in the alias-vars list.
1846+
* since-deleted columns. This will be signaled by a null
1847+
* pointer in the alias-vars list.
18461848
*/
1847-
if (IsA(avar, Const))
1849+
if (avar == NULL)
18481850
{
18491851
if (include_dropped)
18501852
{
18511853
if (colnames)
18521854
*colnames = lappend(*colnames,
18531855
makeString(pstrdup("")));
18541856
if (colvars)
1857+
{
1858+
/*
1859+
* Can't use join's column type here (it might
1860+
* be dropped!); but it doesn't really matter
1861+
* what type the Const claims to be.
1862+
*/
18551863
*colvars = lappend(*colvars,
1856-
copyObject(avar));
1864+
makeNullConst(INT4OID, -1,
1865+
InvalidOid));
1866+
}
18571867
}
18581868
continue;
18591869
}
@@ -2242,6 +2252,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
22422252

22432253
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
22442254
aliasvar = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
2255+
Assert(aliasvar != NULL);
22452256
*vartype = exprType(aliasvar);
22462257
*vartypmod = exprTypmod(aliasvar);
22472258
*varcollid = exprCollation(aliasvar);
@@ -2304,7 +2315,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
23042315
* but one in a stored rule might contain columns that were
23052316
* dropped from the underlying tables, if said columns are
23062317
* nowhere explicitly referenced in the rule. This will be
2307-
* signaled to us by a NULL Const in the joinaliasvars list.
2318+
* signaled to us by a null pointer in the joinaliasvars list.
23082319
*/
23092320
Var *aliasvar;
23102321

@@ -2313,7 +2324,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
23132324
elog(ERROR, "invalid varattno %d", attnum);
23142325
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
23152326

2316-
result = IsA(aliasvar, Const);
2327+
result = (aliasvar == NULL);
23172328
}
23182329
break;
23192330
case RTE_FUNCTION:

src/backend/parser/parse_target.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ markTargetListOrigin(ParseState *pstate, TargetEntry *tle,
311311

312312
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
313313
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
314+
/* We intentionally don't strip implicit coercions here */
314315
markTargetListOrigin(pstate, tle, aliasvar, netlevelsup);
315316
}
316317
break;
@@ -1461,6 +1462,8 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
14611462
/* Join RTE --- recursively inspect the alias variable */
14621463
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
14631464
expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
1465+
Assert(expr != NULL);
1466+
/* We intentionally don't strip implicit coercions here */
14641467
if (IsA(expr, Var))
14651468
return expandRecordVariable(pstate, (Var *) expr, netlevelsup);
14661469
/* else fall through to inspect the expression */

src/backend/rewrite/rewriteHandler.c

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,9 +91,8 @@ static Query *fireRIRrules(Query *parsetree, List *activeRIRs,
9191
* such a list in a stored rule to include references to dropped columns.
9292
* (If the column is not explicitly referenced anywhere else in the query,
9393
* the dependency mechanism won't consider it used by the rule and so won't
94-
* prevent the column drop.) To support get_rte_attribute_is_dropped(),
95-
* we replace join alias vars that reference dropped columns with NULL Const
96-
* nodes.
94+
* prevent the column drop.) To support get_rte_attribute_is_dropped(), we
95+
* replace join alias vars that reference dropped columns with null pointers.
9796
*
9897
* (In PostgreSQL 8.0, we did not do this processing but instead had
9998
* get_rte_attribute_is_dropped() recurse to detect dropped columns in joins.
@@ -160,8 +159,8 @@ AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
160159

161160
/*
162161
* Scan the join's alias var list to see if any columns have
163-
* been dropped, and if so replace those Vars with NULL
164-
* Consts.
162+
* been dropped, and if so replace those Vars with null
163+
* pointers.
165164
*
166165
* Since a join has only two inputs, we can expect to see
167166
* multiple references to the same input RTE; optimize away
@@ -172,16 +171,20 @@ AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
172171
curinputrte = NULL;
173172
foreach(ll, rte->joinaliasvars)
174173
{
175-
Var *aliasvar = (Var *) lfirst(ll);
174+
Var *aliasitem = (Var *) lfirst(ll);
175+
Var *aliasvar = aliasitem;
176+
177+
/* Look through any implicit coercion */
178+
aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
176179

177180
/*
178181
* If the list item isn't a simple Var, then it must
179182
* represent a merged column, ie a USING column, and so it
180183
* couldn't possibly be dropped, since it's referenced in
181-
* the join clause. (Conceivably it could also be a NULL
182-
* constant already? But that's OK too.)
184+
* the join clause. (Conceivably it could also be a null
185+
* pointer already? But that's OK too.)
183186
*/
184-
if (IsA(aliasvar, Var))
187+
if (aliasvar && IsA(aliasvar, Var))
185188
{
186189
/*
187190
* The elements of an alias list have to refer to
@@ -205,15 +208,11 @@ AcquireRewriteLocks(Query *parsetree, bool forUpdatePushedDown)
205208
if (get_rte_attribute_is_dropped(curinputrte,
206209
aliasvar->varattno))
207210
{
208-
/*
209-
* can't use vartype here, since that might be a
210-
* now-dropped type OID, but it doesn't really
211-
* matter what type the Const claims to be.
212-
*/
213-
aliasvar = (Var *) makeNullConst(INT4OID, -1, InvalidOid);
211+
/* Replace the join alias item with a NULL */
212+
aliasitem = NULL;
214213
}
215214
}
216-
newaliasvars = lappend(newaliasvars, aliasvar);
215+
newaliasvars = lappend(newaliasvars, aliasitem);
217216
}
218217
rte->joinaliasvars = newaliasvars;
219218
break;

src/backend/utils/adt/ruleutils.c

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ typedef struct
235235
* child RTE's attno and rightattnos[i] is zero; and conversely for a
236236
* column of the right child. But for merged columns produced by JOIN
237237
* USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero.
238+
* Also, if the column has been dropped, both are zero.
238239
*
239240
* If it's a JOIN USING, usingNames holds the alias names selected for the
240241
* merged columns (these might be different from the original USING list,
@@ -3053,6 +3054,13 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
30533054
char *colname = colinfo->colnames[i];
30543055
char *real_colname;
30553056

3057+
/* Ignore dropped column (only possible for non-merged column) */
3058+
if (colinfo->leftattnos[i] == 0 && colinfo->rightattnos[i] == 0)
3059+
{
3060+
Assert(colname == NULL);
3061+
continue;
3062+
}
3063+
30563064
/* Get the child column name */
30573065
if (colinfo->leftattnos[i] > 0)
30583066
real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1];
@@ -3061,15 +3069,9 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
30613069
else
30623070
{
30633071
/* We're joining system columns --- use eref name */
3064-
real_colname = (char *) list_nth(rte->eref->colnames, i);
3065-
}
3066-
3067-
/* Ignore dropped columns (only possible for non-merged column) */
3068-
if (real_colname == NULL)
3069-
{
3070-
Assert(colname == NULL);
3071-
continue;
3072+
real_colname = strVal(list_nth(rte->eref->colnames, i));
30723073
}
3074+
Assert(real_colname != NULL);
30733075

30743076
/* In an unnamed join, just report child column names as-is */
30753077
if (rte->alias == NULL)
@@ -3402,7 +3404,14 @@ identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
34023404
{
34033405
Var *aliasvar = (Var *) lfirst(lc);
34043406

3405-
if (IsA(aliasvar, Var))
3407+
/* get rid of any implicit coercion above the Var */
3408+
aliasvar = (Var *) strip_implicit_coercions((Node *) aliasvar);
3409+
3410+
if (aliasvar == NULL)
3411+
{
3412+
/* It's a dropped column; nothing to do here */
3413+
}
3414+
else if (IsA(aliasvar, Var))
34063415
{
34073416
Assert(aliasvar->varlevelsup == 0);
34083417
Assert(aliasvar->varattno != 0);
@@ -3422,15 +3431,8 @@ identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
34223431
*/
34233432
}
34243433
else
3425-
{
3426-
/*
3427-
* Although NULL constants can appear in joinaliasvars lists
3428-
* during planning, we shouldn't see any here, since the Query
3429-
* tree hasn't been through AcquireRewriteLocks().
3430-
*/
34313434
elog(ERROR, "unrecognized node type in join alias vars: %d",
34323435
(int) nodeTag(aliasvar));
3433-
}
34343436

34353437
i++;
34363438
}
@@ -5359,7 +5361,8 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
53595361
Var *aliasvar;
53605362

53615363
aliasvar = (Var *) list_nth(rte->joinaliasvars, attnum - 1);
5362-
if (IsA(aliasvar, Var))
5364+
/* we intentionally don't strip implicit coercions here */
5365+
if (aliasvar && IsA(aliasvar, Var))
53635366
{
53645367
return get_variable(aliasvar, var->varlevelsup + levelsup,
53655368
istoplevel, context);
@@ -5670,6 +5673,8 @@ get_name_for_var_field(Var *var, int fieldno,
56705673
elog(ERROR, "cannot decompile join alias var in plan tree");
56715674
Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
56725675
expr = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
5676+
Assert(expr != NULL);
5677+
/* we intentionally don't strip implicit coercions here */
56735678
if (IsA(expr, Var))
56745679
return get_name_for_var_field((Var *) expr, fieldno,
56755680
var->varlevelsup + levelsup,

src/include/nodes/parsenodes.h

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ typedef struct XmlSerialize
660660
* a stored rule might contain entries for columns dropped since the rule
661661
* was created. (This is only possible for columns not actually referenced
662662
* in the rule.) When loading a stored rule, we replace the joinaliasvars
663-
* items for any such columns with NULL Consts. (We can't simply delete
663+
* items for any such columns with null pointers. (We can't simply delete
664664
* them from the joinaliasvars list, because that would affect the attnums
665665
* of Vars referencing the rest of the list.)
666666
*
@@ -731,13 +731,19 @@ typedef struct RangeTblEntry
731731
/*
732732
* Fields valid for a join RTE (else NULL/zero):
733733
*
734-
* joinaliasvars is a list of Vars or COALESCE expressions corresponding
735-
* to the columns of the join result. An alias Var referencing column K
736-
* of the join result can be replaced by the K'th element of joinaliasvars
737-
* --- but to simplify the task of reverse-listing aliases correctly, we
738-
* do not do that until planning time. In a Query loaded from a stored
739-
* rule, it is also possible for joinaliasvars items to be NULL Consts,
740-
* denoting columns dropped since the rule was made.
734+
* joinaliasvars is a list of (usually) Vars corresponding to the columns
735+
* of the join result. An alias Var referencing column K of the join
736+
* result can be replaced by the K'th element of joinaliasvars --- but to
737+
* simplify the task of reverse-listing aliases correctly, we do not do
738+
* that until planning time. In detail: an element of joinaliasvars can
739+
* be a Var of one of the join's input relations, or such a Var with an
740+
* implicit coercion to the join's output column type, or a COALESCE
741+
* expression containing the two input column Vars (possibly coerced).
742+
* Within a Query loaded from a stored rule, it is also possible for
743+
* joinaliasvars items to be null pointers, which are placeholders for
744+
* (necessarily unreferenced) columns dropped since the rule was made.
745+
* Also, once planning begins, joinaliasvars items can be almost anything,
746+
* as a result of subquery-flattening substitutions.
741747
*/
742748
JoinType jointype; /* type of join */
743749
List *joinaliasvars; /* list of alias-var expansions */

src/test/regress/expected/create_view.out

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,33 @@ select pg_get_viewdef('vv4', true);
12431243
FULL JOIN tt8 tt8y(x_1, z, z2) USING (x_1);
12441244
(1 row)
12451245

1246+
--
1247+
-- Also check dropping a column that existed when the view was made
1248+
--
1249+
create table tt9 (x int, xx int, y int);
1250+
create table tt10 (x int, z int);
1251+
create view vv5 as select x,y,z from tt9 join tt10 using(x);
1252+
select pg_get_viewdef('vv5', true);
1253+
pg_get_viewdef
1254+
-------------------------
1255+
SELECT tt9.x, +
1256+
tt9.y, +
1257+
tt10.z +
1258+
FROM tt9 +
1259+
JOIN tt10 USING (x);
1260+
(1 row)
1261+
1262+
alter table tt9 drop column xx;
1263+
select pg_get_viewdef('vv5', true);
1264+
pg_get_viewdef
1265+
-------------------------
1266+
SELECT tt9.x, +
1267+
tt9.y, +
1268+
tt10.z +
1269+
FROM tt9 +
1270+
JOIN tt10 USING (x);
1271+
(1 row)
1272+
12461273
-- clean up all the random objects we made above
12471274
set client_min_messages = warning;
12481275
DROP SCHEMA temp_view_test CASCADE;

src/test/regress/sql/create_view.sql

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,21 @@ select pg_get_viewdef('vv2', true);
389389
select pg_get_viewdef('vv3', true);
390390
select pg_get_viewdef('vv4', true);
391391

392+
--
393+
-- Also check dropping a column that existed when the view was made
394+
--
395+
396+
create table tt9 (x int, xx int, y int);
397+
create table tt10 (x int, z int);
398+
399+
create view vv5 as select x,y,z from tt9 join tt10 using(x);
400+
401+
select pg_get_viewdef('vv5', true);
402+
403+
alter table tt9 drop column xx;
404+
405+
select pg_get_viewdef('vv5', true);
406+
392407
-- clean up all the random objects we made above
393408
set client_min_messages = warning;
394409
DROP SCHEMA temp_view_test CASCADE;

0 commit comments

Comments
 (0)