From 820862795fff46158c9931f2f64cc3fab8a4e004 Mon Sep 17 00:00:00 2001 From: Alexandra Wang Date: Fri, 5 Sep 2025 14:17:37 -0700 Subject: [PATCH v18 2/7] Allow Generic Type Subscripting to Accept Dot Notation (.) as Input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change extends generic type subscripting to recognize dot notation (.) in addition to bracket notation ([]). While this does not yet provide full support for dot notation, it enables subscripting containers to process it in the future. For now, container-specific transform functions only handle subscripting indices and stop processing when encountering dot notation. It is up to individual containers to decide how to transform dot notation in subsequent updates. This change also updates the SubscriptTransform() API to remove the "bool isSlice" argument. Each container’s transform function now determines slice-ness itself, since it may only process a prefix of the indirection list. For example, array_subscript_transform() stops at dot notation, so "isSlice" should not depend on slice specifiers that appear afterward. Authored-by: Nikita Glukhov Authored-by: Alexandra Wang Reviewed-by: Andrew Dunstan Reviewed-by: Matheus Alcantara Reviewed-by: Jian He Reviewed-by: Chao Li --- contrib/hstore/hstore_subs.c | 11 +++-- src/backend/parser/parse_expr.c | 68 ++++++++++++++++++---------- src/backend/parser/parse_node.c | 57 ++++++++++++++--------- src/backend/parser/parse_target.c | 3 +- src/backend/utils/adt/arraysubs.c | 40 ++++++++++++++-- src/backend/utils/adt/jsonbsubs.c | 16 +++++-- src/include/nodes/subscripting.h | 3 +- src/include/parser/parse_node.h | 3 +- src/test/regress/expected/arrays.out | 26 +++++++++-- src/test/regress/sql/arrays.sql | 7 ++- 10 files changed, 163 insertions(+), 71 deletions(-) diff --git a/contrib/hstore/hstore_subs.c b/contrib/hstore/hstore_subs.c index 1b29543ab67..f008e5faf03 100644 --- a/contrib/hstore/hstore_subs.c +++ b/contrib/hstore/hstore_subs.c @@ -42,14 +42,16 @@ static void hstore_subscript_transform(SubscriptingRef *sbsref, List **indirection, ParseState *pstate, - bool isSlice, bool isAssignment) { A_Indices *ai; Node *subexpr; + Assert(*indirection != NIL); + ai = linitial_node(A_Indices, *indirection); + /* We support only single-subscript, non-slice cases */ - if (isSlice || list_length(*indirection) != 1) + if (list_length(*indirection) != 1 || ai->is_slice) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("hstore allows only one subscript"), @@ -57,8 +59,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref, exprLocation((Node *) *indirection)))); /* Transform the subscript expression to type text */ - ai = linitial_node(A_Indices, *indirection); - Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice); + Assert(ai->uidx != NULL && ai->lidx == NULL); subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); /* If it's not text already, try to coerce */ @@ -82,7 +83,7 @@ hstore_subscript_transform(SubscriptingRef *sbsref, sbsref->refrestype = TEXTOID; sbsref->reftypmod = -1; - *indirection = NIL; + *indirection = list_delete_first_n(*indirection, 1); } /* diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 6e8fd42c612..f8a0617f823 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -441,38 +441,59 @@ transformIndirection(ParseState *pstate, A_Indirection *ind) ListCell *i; /* - * We have to split any field-selection operations apart from - * subscripting. Adjacent A_Indices nodes have to be treated as a single + * Combine field names and subscripts into a single indirection list, as + * some subscripting containers, such as jsonb, support field access using + * dot notation. Adjacent A_Indices nodes have to be treated as a single * multidimensional subscript operation. */ foreach(i, ind->indirection) { Node *n = lfirst(i); - if (IsA(n, A_Indices)) + if (IsA(n, A_Indices) || IsA(n, String)) subscripts = lappend(subscripts, n); - else if (IsA(n, A_Star)) + else { + Assert(IsA(n, A_Star)); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("row expansion via \"*\" is not supported here"), parser_errposition(pstate, location))); } - else + } + + while (subscripts) + { + /* try processing container subscripts first */ + Node *newresult = (Node *) + transformContainerSubscripts(pstate, + result, + exprType(result), + exprTypmod(result), + &subscripts, + false, + true); + + if (!newresult) { - Node *newresult; + /* + * generic subscripting failed; falling back to field selection + * for a composite type. + */ + Node *n; + + Assert(subscripts); - Assert(IsA(n, String)); + n = linitial(subscripts); - /* process subscripts before this field selection */ - while (subscripts) - result = (Node *) transformContainerSubscripts(pstate, - result, - exprType(result), - exprTypmod(result), - &subscripts, - false); + if (!IsA(n, String)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("cannot subscript type %s because it does not support subscripting", + format_type_be(exprType(result))), + parser_errposition(pstate, exprLocation(result)))); + /* try to find function for field selection */ newresult = ParseFuncOrColumn(pstate, list_make1(n), list_make1(result), @@ -480,19 +501,16 @@ transformIndirection(ParseState *pstate, A_Indirection *ind) NULL, false, location); - if (newresult == NULL) + + if (!newresult) unknown_attribute(pstate, result, strVal(n), location); - result = newresult; + + /* consume field select */ + subscripts = list_delete_first(subscripts); } + + result = newresult; } - /* process trailing subscripts, if any */ - while (subscripts) - result = (Node *) transformContainerSubscripts(pstate, - result, - exprType(result), - exprTypmod(result), - &subscripts, - false); return result; } diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c index f05baa50a15..d7b23688b9b 100644 --- a/src/backend/parser/parse_node.c +++ b/src/backend/parser/parse_node.c @@ -238,6 +238,8 @@ transformContainerType(Oid *containerType, int32 *containerTypmod) * containerTypMod typmod for the container * indirection Untransformed list of subscripts (must not be NIL) * isAssignment True if this will become a container assignment. + * noError True for return NULL with no error, if the container type + * is not subscriptable. */ SubscriptingRef * transformContainerSubscripts(ParseState *pstate, @@ -245,13 +247,13 @@ transformContainerSubscripts(ParseState *pstate, Oid containerType, int32 containerTypMod, List **indirection, - bool isAssignment) + bool isAssignment, + bool noError) { SubscriptingRef *sbsref; const SubscriptRoutines *sbsroutines; Oid elementType; - bool isSlice = false; - ListCell *idx; + int indirection_length = list_length(*indirection); /* * Determine the actual container type, smashing any domain. In the @@ -267,28 +269,15 @@ transformContainerSubscripts(ParseState *pstate, */ sbsroutines = getSubscriptingRoutines(containerType, &elementType); if (!sbsroutines) + { + if (noError) + return NULL; + ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot subscript type %s because it does not support subscripting", format_type_be(containerType)), parser_errposition(pstate, exprLocation(containerBase)))); - - /* - * Detect whether any of the indirection items are slice specifiers. - * - * A list containing only simple subscripts refers to a single container - * element. If any of the items are slice specifiers (lower:upper), then - * the subscript expression means a container slice operation. - */ - foreach(idx, *indirection) - { - A_Indices *ai = lfirst_node(A_Indices, idx); - - if (ai->is_slice) - { - isSlice = true; - break; - } } /* @@ -310,7 +299,33 @@ transformContainerSubscripts(ParseState *pstate, * determine the subscripting result type. */ sbsroutines->transform(sbsref, indirection, pstate, - isSlice, isAssignment); + isAssignment); + + /* + * Error out, if datatype failed to consume any indirection elements. + */ + if (list_length(*indirection) == indirection_length) + { + Node *ind = linitial(*indirection); + + if (noError) + return NULL; + + if (IsA(ind, String)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %s does not support dot notation", + format_type_be(containerType)), + parser_errposition(pstate, exprLocation(containerBase)))); + else if (IsA(ind, A_Indices)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("type %s does not support array subscripting", + format_type_be(containerType)), + parser_errposition(pstate, exprLocation(containerBase)))); + else + elog(ERROR, "invalid indirection operation: %d", nodeTag(ind)); + } /* * Verify we got a valid type (this defends, for example, against someone diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index a097736229a..b89736ff1ea 100644 --- a/src/backend/parser/parse_target.c +++ b/src/backend/parser/parse_target.c @@ -936,7 +936,8 @@ transformAssignmentSubscripts(ParseState *pstate, containerType, containerTypMod, &subscripts, - true); + true, + false); typeNeeded = sbsref->refrestype; typmodNeeded = sbsref->reftypmod; diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c index 234c2c278c1..378b6bf16cb 100644 --- a/src/backend/utils/adt/arraysubs.c +++ b/src/backend/utils/adt/arraysubs.c @@ -56,12 +56,38 @@ static void array_subscript_transform(SubscriptingRef *sbsref, List **indirection, ParseState *pstate, - bool isSlice, bool isAssignment) { List *upperIndexpr = NIL; List *lowerIndexpr = NIL; ListCell *idx; + int ndim; + bool isSlice = false; + + /* + * Detect whether any of the indirection items are slice specifiers. + * + * A list containing only simple subscripts refers to a single container + * element. If any of the items are slice specifiers (lower:upper), then + * the subscript expression means a container slice operation. + */ + foreach(idx, *indirection) + { + Node *ai = lfirst(idx); + + /* + * We should not inspect slice specifiers beyond an indirection node + * type that we don't support. + */ + if (!IsA(ai, A_Indices)) + break; + + if (castNode(A_Indices, ai)->is_slice) + { + isSlice = true; + break; + } + } /* * Transform the subscript expressions, and separate upper and lower @@ -73,9 +99,14 @@ array_subscript_transform(SubscriptingRef *sbsref, */ foreach(idx, *indirection) { - A_Indices *ai = lfirst_node(A_Indices, idx); + A_Indices *ai; Node *subexpr; + if (!IsA(lfirst(idx), A_Indices)) + break; + + ai = lfirst_node(A_Indices, idx); + if (isSlice) { if (ai->lidx) @@ -145,14 +176,15 @@ array_subscript_transform(SubscriptingRef *sbsref, sbsref->reflowerindexpr = lowerIndexpr; /* Verify subscript list lengths are within implementation limit */ - if (list_length(upperIndexpr) > MAXDIM) + ndim = list_length(upperIndexpr); + if (ndim > MAXDIM) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)", list_length(upperIndexpr), MAXDIM))); /* We need not check lowerIndexpr separately */ - *indirection = NIL; + *indirection = list_delete_first_n(*indirection, ndim); /* * Determine the result type of the subscripting operation. It's the same diff --git a/src/backend/utils/adt/jsonbsubs.c b/src/backend/utils/adt/jsonbsubs.c index 5ead693a3b2..2aa410f605b 100644 --- a/src/backend/utils/adt/jsonbsubs.c +++ b/src/backend/utils/adt/jsonbsubs.c @@ -43,7 +43,6 @@ static void jsonb_subscript_transform(SubscriptingRef *sbsref, List **indirection, ParseState *pstate, - bool isSlice, bool isAssignment) { List *upperIndexpr = NIL; @@ -55,10 +54,15 @@ jsonb_subscript_transform(SubscriptingRef *sbsref, */ foreach(idx, *indirection) { - A_Indices *ai = lfirst_node(A_Indices, idx); + A_Indices *ai; Node *subExpr; - if (isSlice) + if (!IsA(lfirst(idx), A_Indices)) + break; + + ai = lfirst_node(A_Indices, idx); + + if (ai->is_slice) { Node *expr = ai->uidx ? ai->uidx : ai->lidx; @@ -142,7 +146,7 @@ jsonb_subscript_transform(SubscriptingRef *sbsref, * Slice with omitted upper bound. Should not happen as we already * errored out on slice earlier, but handle this just in case. */ - Assert(isSlice && ai->is_slice); + Assert(ai->is_slice); ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("jsonb subscript does not support slices"), @@ -160,7 +164,9 @@ jsonb_subscript_transform(SubscriptingRef *sbsref, sbsref->refrestype = JSONBOID; sbsref->reftypmod = -1; - *indirection = NIL; + /* Remove processed elements */ + if (upperIndexpr) + *indirection = list_delete_first_n(*indirection, list_length(upperIndexpr)); } /* diff --git a/src/include/nodes/subscripting.h b/src/include/nodes/subscripting.h index df988a85fad..14ef414a180 100644 --- a/src/include/nodes/subscripting.h +++ b/src/include/nodes/subscripting.h @@ -68,7 +68,7 @@ struct SubscriptExecSteps; * same length as refupperindexpr for a slice operation. Insert NULLs * (that is, an empty parse tree, not a null Const node) for any omitted * subscripts in a slice operation. (Of course, if the transform method - * does not care to support slicing, it can just throw an error if isSlice.) + * does not care to support slicing, it can just throw an error.) * See array_subscript_transform() for sample code. * * The transform method receives a pointer to a list of raw indirections. @@ -100,7 +100,6 @@ struct SubscriptExecSteps; typedef void (*SubscriptTransform) (SubscriptingRef *sbsref, List **indirection, struct ParseState *pstate, - bool isSlice, bool isAssignment); /* diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h index 58a4b9df157..5cc3ce58c30 100644 --- a/src/include/parser/parse_node.h +++ b/src/include/parser/parse_node.h @@ -362,7 +362,8 @@ extern SubscriptingRef *transformContainerSubscripts(ParseState *pstate, Oid containerType, int32 containerTypMod, List **indirection, - bool isAssignment); + bool isAssignment, + bool noError); extern Const *make_const(ParseState *pstate, A_Const *aconst); #endif /* PARSE_NODE_H */ diff --git a/src/test/regress/expected/arrays.out b/src/test/regress/expected/arrays.out index b815473f414..78dd9e71bf3 100644 --- a/src/test/regress/expected/arrays.out +++ b/src/test/regress/expected/arrays.out @@ -1782,17 +1782,17 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; (1 row) -- A few simple tests for arrays of composite types -create type comptype as (f1 int, f2 text); +create type comptype as (f1 int, f2 text, f3 int[]); create table comptable (c1 comptype, c2 comptype[]); -- XXX would like to not have to specify row() construct types here ... insert into comptable - values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]); + values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]); -- check that implicitly named array type _comptype isn't a problem create type _comptype as enum('fooey'); select * from comptable; - c1 | c2 ----------+----------------------- - (1,foo) | {"(2,bar)","(3,baz)"} + c1 | c2 +-------------------+----------------------------------------------- + (1,foo,"{10,20}") | {"(2,bar,\"{30,40}\")","(3,baz,\"{50,60}\")"} (1 row) select c2[2].f2 from comptable; @@ -1801,6 +1801,22 @@ select c2[2].f2 from comptable; baz (1 row) +select c2[2].f3 from comptable; + f3 +--------- + {50,60} +(1 row) + +select c2[2].f3[1:2] from comptable; + f3 +--------- + {50,60} +(1 row) + +select c2[1:2].f3[1:2] from comptable; +ERROR: column notation .f3 applied to type comptype[], which is not a composite type +LINE 1: select c2[1:2].f3[1:2] from comptable; + ^ drop type _comptype; drop table comptable; drop type comptype; diff --git a/src/test/regress/sql/arrays.sql b/src/test/regress/sql/arrays.sql index 47d62c1d38d..450389831a0 100644 --- a/src/test/regress/sql/arrays.sql +++ b/src/test/regress/sql/arrays.sql @@ -555,19 +555,22 @@ SELECT max(f1), min(f1), max(f2), min(f2), max(f3), min(f3) FROM arraggtest; -- A few simple tests for arrays of composite types -create type comptype as (f1 int, f2 text); +create type comptype as (f1 int, f2 text, f3 int[]); create table comptable (c1 comptype, c2 comptype[]); -- XXX would like to not have to specify row() construct types here ... insert into comptable - values (row(1,'foo'), array[row(2,'bar')::comptype, row(3,'baz')::comptype]); + values (row(1,'foo',array[10,20]), array[row(2,'bar',array[30,40])::comptype, row(3,'baz',array[50,60])::comptype]); -- check that implicitly named array type _comptype isn't a problem create type _comptype as enum('fooey'); select * from comptable; select c2[2].f2 from comptable; +select c2[2].f3 from comptable; +select c2[2].f3[1:2] from comptable; +select c2[1:2].f3[1:2] from comptable; drop type _comptype; drop table comptable; -- 2.39.5 (Apple Git-154)