Skip to content

Commit 0f9fbc4

Browse files
peterehackorum
authored andcommitted
Allow subfield references without parentheses
This allows subfield references in column references without parentheses, subject to certain condition. This implements the rules from the SQL standard (since SQL99). Specifically, identifier chains of three or more items now have an additional possible interpretation. Before: A.B.C: schema A, table B, column or function C A.B.C.D: database A, schema B, table C, column or function D Now additionally: A.B.C: correlation A, column B, field C; like (A.B).C A.B.C.D: correlation A, column B, field C, field D; like (A.B).C.D Also, identifier chains longer than four items now have an analogous interpretation. They had no possible interpretation before. The "correlation A" above must be an explicit alias, not just a table name. If both possible interpretations apply, then an error is raised. (A workaround is to change the alias used in the query.) Such errors should be very rare in practice.
1 parent b597835 commit 0f9fbc4

5 files changed

Lines changed: 218 additions & 28 deletions

File tree

doc/src/sgml/syntax.sgml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,6 +1406,8 @@ $1[10:42]
14061406
<primary>field selection</primary>
14071407
</indexterm>
14081408

1409+
<!-- TODO -->
1410+
14091411
<para>
14101412
If an expression yields a value of a composite type (row type), then a
14111413
specific field of the row can be extracted by writing

src/backend/executor/functions.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,9 @@ sql_fn_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
378378
* (the first possibility takes precedence)
379379
* A.B.C A = function name, B = record-typed parameter name,
380380
* C = field name
381+
* A.B.C.D...
382+
* A = function name, B = record-typed parameter name,
383+
* C, D, etc. = field names TODO
381384
* A.* Whole-row reference to composite parameter A.
382385
* A.B.* Same, with A = function name, B = parameter name
383386
*

src/backend/parser/parse_expr.c

Lines changed: 188 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,12 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
502502
return result;
503503
}
504504

505+
static bool
506+
type_is_subfieldable(Oid typeid)
507+
{
508+
return ISCOMPLEX(typeid) || typeid == RECORDOID;
509+
}
510+
505511
/*
506512
* Transform a ColumnRef.
507513
*
@@ -510,18 +516,19 @@ transformIndirection(ParseState *pstate, A_Indirection *ind)
510516
static Node *
511517
transformColumnRef(ParseState *pstate, ColumnRef *cref)
512518
{
513-
Node *node = NULL;
519+
Node *node = NULL, *node2 = NULL;
514520
char *nspname = NULL;
515521
char *relname = NULL;
516522
char *colname = NULL;
517-
ParseNamespaceItem *nsitem;
518-
int levels_up;
523+
ParseNamespaceItem *nsitem, *nsitem2;
524+
int levels_up, levels_up2;
525+
ColumnRef *indcref = NULL;
526+
List *indirection = NULL;
519527
enum
520528
{
521529
CRERR_NO_COLUMN,
522530
CRERR_NO_RTE,
523-
CRERR_WRONG_DB,
524-
CRERR_TOO_MANY
531+
CRERR_AMBIGUOUS,
525532
} crerr = CRERR_NO_COLUMN;
526533
const char *err;
527534

@@ -634,8 +641,12 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
634641
* if no luck, try to resolve as unqualified table name (A.*).
635642
* A.B A is an unqualified table name; B is either a
636643
* column or function name (trying column name first).
637-
* A.B.C schema A, table B, col or func name C.
638-
* A.B.C.D catalog A, schema B, table C, col or func D.
644+
* A.B.C schema A, table B, col or func name C; or
645+
* correlation A, column B, field C.
646+
* A.B.C.D catalog A, schema B, table C, col or func D; or
647+
* correlation A, column B, fields C, D.
648+
* A.B.C.D.E...
649+
* correlation A, column B, fields C, D, E, etc.
639650
* A.* A is an unqualified table name; means whole-row value.
640651
* A.B.* whole-row value of table B in schema A.
641652
* A.B.C.* whole-row value of table C in schema B in catalog A.
@@ -741,7 +752,45 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
741752
nsitem = refnameNamespaceItem(pstate, nspname, relname,
742753
cref->location,
743754
&levels_up);
744-
if (nsitem == NULL)
755+
756+
/*
757+
* Also look it up as a subfieldable column reference, but
758+
* then it can't end with a star
759+
*/
760+
if (!IsA(field3, A_Star))
761+
nsitem2 = refnameNamespaceItem(pstate, NULL, strVal(field1),
762+
cref->location,
763+
&levels_up2);
764+
else
765+
nsitem2 = NULL;
766+
767+
/* must be an explicit alias */
768+
if (nsitem2 && nsitem2->p_rte->alias == NULL)
769+
nsitem2 = NULL;
770+
771+
if (nsitem2)
772+
node2 = scanNSItemForColumn(pstate, nsitem2, levels_up2, strVal(field2), cref->location);
773+
774+
/*
775+
* If we found a potential subfield reference, check that the
776+
* type is subfieldable, else forget it.
777+
*/
778+
if (node2)
779+
{
780+
if (type_is_subfieldable(castNode(Var, node2)->vartype))
781+
{
782+
indcref = copyObject(cref);
783+
indcref->fields = list_truncate(indcref->fields, 2);
784+
indirection = list_copy_tail(cref->fields, 2);
785+
}
786+
else
787+
{
788+
nsitem2 = NULL;
789+
node2 = NULL;
790+
}
791+
}
792+
793+
if (nsitem == NULL && nsitem2 == NULL)
745794
{
746795
crerr = CRERR_NO_RTE;
747796
break;
@@ -757,9 +806,12 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
757806

758807
colname = strVal(field3);
759808

809+
if (nsitem)
810+
{
760811
/* Try to identify as a column of the nsitem */
761812
node = scanNSItemForColumn(pstate, nsitem, levels_up, colname,
762813
cref->location);
814+
763815
if (node == NULL)
764816
{
765817
/* Try it as a function call on the whole row */
@@ -773,6 +825,13 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
773825
false,
774826
cref->location);
775827
}
828+
}
829+
830+
if (node != NULL && node2 != NULL)
831+
{
832+
crerr = CRERR_AMBIGUOUS;
833+
break;
834+
}
776835
break;
777836
}
778837
case 4:
@@ -787,20 +846,52 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
787846
nspname = strVal(field2);
788847
relname = strVal(field3);
789848

849+
/* Locate the referenced nsitem (only eligible if database name matches) */
850+
if (strcmp(catname, get_database_name(MyDatabaseId)) == 0)
851+
nsitem = refnameNamespaceItem(pstate, nspname, relname,
852+
cref->location,
853+
&levels_up);
854+
else
855+
nsitem = NULL;
856+
857+
/*
858+
* Also look it up as a subfieldable column reference, but
859+
* then it can't end with a star
860+
*/
861+
if (!IsA(field4, A_Star))
862+
nsitem2 = refnameNamespaceItem(pstate, NULL, strVal(field1),
863+
cref->location,
864+
&levels_up2);
865+
else
866+
nsitem2 = NULL;
867+
868+
/* must be an explicit alias */
869+
if (nsitem2 && nsitem2->p_rte->alias == NULL)
870+
nsitem2 = NULL;
871+
872+
if (nsitem2)
873+
node2 = scanNSItemForColumn(pstate, nsitem2, levels_up2, strVal(field2), cref->location);
874+
790875
/*
791-
* We check the catalog name and then ignore it.
876+
* If we found a potential subfield reference, check that the
877+
* type is subfieldable, else forget it.
792878
*/
793-
if (strcmp(catname, get_database_name(MyDatabaseId)) != 0)
879+
if (node2)
794880
{
795-
crerr = CRERR_WRONG_DB;
796-
break;
881+
if (type_is_subfieldable(castNode(Var, node2)->vartype))
882+
{
883+
indcref = copyObject(cref);
884+
indcref->fields = list_truncate(indcref->fields, 2);
885+
indirection = list_copy_tail(cref->fields, 2);
886+
}
887+
else
888+
{
889+
nsitem2 = NULL;
890+
node2 = NULL;
891+
}
797892
}
798893

799-
/* Locate the referenced nsitem */
800-
nsitem = refnameNamespaceItem(pstate, nspname, relname,
801-
cref->location,
802-
&levels_up);
803-
if (nsitem == NULL)
894+
if (nsitem == NULL && nsitem2 == NULL)
804895
{
805896
crerr = CRERR_NO_RTE;
806897
break;
@@ -816,6 +907,8 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
816907

817908
colname = strVal(field4);
818909

910+
if (nsitem)
911+
{
819912
/* Try to identify as a column of the nsitem */
820913
node = scanNSItemForColumn(pstate, nsitem, levels_up, colname,
821914
cref->location);
@@ -832,11 +925,85 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
832925
false,
833926
cref->location);
834927
}
928+
}
929+
930+
if (node != NULL && node2 != NULL)
931+
{
932+
crerr = CRERR_AMBIGUOUS;
933+
break;
934+
}
835935
break;
836936
}
837937
default:
838-
crerr = CRERR_TOO_MANY; /* too many dotted names */
839-
break;
938+
{
939+
Node *field1 = (Node *) linitial(cref->fields);
940+
Node *field2 = (Node *) lsecond(cref->fields);
941+
Node *fieldl = (Node *) llast(cref->fields);
942+
943+
/* XXX for error reporting below */
944+
relname = strVal(field1);
945+
946+
/*
947+
* Look it up as a subfieldable column reference, but then it
948+
* can't end with a star
949+
*/
950+
if (!IsA(fieldl, A_Star))
951+
nsitem2 = refnameNamespaceItem(pstate, NULL, strVal(field1),
952+
cref->location,
953+
&levels_up2);
954+
else
955+
nsitem2 = NULL;
956+
957+
/* must be an explicit alias */
958+
if (nsitem2 && nsitem2->p_rte->alias == NULL)
959+
nsitem2 = NULL;
960+
961+
if (nsitem2)
962+
node2 = scanNSItemForColumn(pstate, nsitem2, levels_up2, strVal(field2), cref->location);
963+
964+
/*
965+
* If we found a potential subfield reference, check that the
966+
* type is subfieldable, else forget it.
967+
*/
968+
if (node2)
969+
{
970+
if (type_is_subfieldable(castNode(Var, node2)->vartype))
971+
{
972+
indcref = copyObject(cref);
973+
indcref->fields = list_truncate(indcref->fields, 2);
974+
indirection = list_copy_tail(cref->fields, 2);
975+
}
976+
else
977+
{
978+
nsitem2 = NULL;
979+
node2 = NULL;
980+
}
981+
}
982+
983+
if (nsitem2 == NULL)
984+
{
985+
crerr = CRERR_NO_RTE;
986+
break;
987+
}
988+
break;
989+
}
990+
}
991+
992+
/*
993+
* If we decided it's a subfield reference, convert it to an indirection
994+
* (as if you had written "(A.B).C.D" instead of "A.B.C.D"). (Note that
995+
* the subfield references detected above always come from an identifier
996+
* chain of length >= 3, but the indirections we are building here have a
997+
* column reference of length 2, and so there won't be any endless
998+
* recursion.)
999+
*/
1000+
if (node2)
1001+
{
1002+
A_Indirection *a = makeNode(A_Indirection);
1003+
1004+
a->arg = (Node *) indcref;
1005+
a->indirection = indirection;
1006+
node = transformIndirection(pstate, a);
8401007
}
8411008

8421009
/*
@@ -877,17 +1044,10 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
8771044
errorMissingRTE(pstate, makeRangeVar(nspname, relname,
8781045
cref->location));
8791046
break;
880-
case CRERR_WRONG_DB:
881-
ereport(ERROR,
882-
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
883-
errmsg("cross-database references are not implemented: %s",
884-
NameListToString(cref->fields)),
885-
parser_errposition(pstate, cref->location)));
886-
break;
887-
case CRERR_TOO_MANY:
1047+
case CRERR_AMBIGUOUS:
8881048
ereport(ERROR,
8891049
(errcode(ERRCODE_SYNTAX_ERROR),
890-
errmsg("improper qualified name (too many dotted names): %s",
1050+
errmsg("ambiguous identifier chain: %s",
8911051
NameListToString(cref->fields)),
8921052
parser_errposition(pstate, cref->location)));
8931053
break;

src/test/regress/expected/rowtypes.out

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ SELECT * FROM pg_input_error_info('(1,1e400)', 'complex');
100100
"1e400" is out of range for type double precision | | | 22003
101101
(1 row)
102102

103+
-- test identifier chain syntax for column and field references
103104
create temp table quadtable(f1 int, q quad);
104105
insert into quadtable values (1, ((3.3,4.4),(5.5,6.6)));
105106
insert into quadtable values (2, ((null,4.4),(5.5,6.6)));
@@ -121,6 +122,24 @@ select f1, (q).c1, (qq.q).c1.i from quadtable qq;
121122
2 | (,4.4) | 4.4
122123
(2 rows)
123124

125+
select f1, qq.q.c1 from quadtable qq;
126+
f1 | c1
127+
----+-----------
128+
1 | (3.3,4.4)
129+
2 | (,4.4)
130+
(2 rows)
131+
132+
select f1, qq.q.c1.i from quadtable qq;
133+
f1 | i
134+
----+-----
135+
1 | 4.4
136+
2 | 4.4
137+
(2 rows)
138+
139+
select f1, quadtable.q.c1.i from quadtable; -- fails, works only with explicit alias
140+
ERROR: missing FROM-clause entry for table "c1"
141+
LINE 1: select f1, quadtable.q.c1.i from quadtable;
142+
^
124143
create temp table people (fn fullname, bd date);
125144
insert into people values ('(Joe,Blow)', '1984-01-10');
126145
select * from people;

src/test/regress/sql/rowtypes.sql

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ SELECT pg_input_is_valid('(1,zed)', 'complex');
3838
SELECT * FROM pg_input_error_info('(1,zed)', 'complex');
3939
SELECT * FROM pg_input_error_info('(1,1e400)', 'complex');
4040

41+
-- test identifier chain syntax for column and field references
4142
create temp table quadtable(f1 int, q quad);
4243

4344
insert into quadtable values (1, ((3.3,4.4),(5.5,6.6)));
@@ -49,6 +50,11 @@ select f1, q.c1 from quadtable; -- fails, q is a table reference
4950

5051
select f1, (q).c1, (qq.q).c1.i from quadtable qq;
5152

53+
select f1, qq.q.c1 from quadtable qq;
54+
select f1, qq.q.c1.i from quadtable qq;
55+
56+
select f1, quadtable.q.c1.i from quadtable; -- fails, works only with explicit alias
57+
5258
create temp table people (fn fullname, bd date);
5359

5460
insert into people values ('(Joe,Blow)', '1984-01-10');

0 commit comments

Comments
 (0)