tmater commented on code in PR #4844:
URL: https://github.com/apache/calcite/pull/4844#discussion_r3045060922


##########
core/src/main/codegen/templates/Parser.jj:
##########
@@ -3791,13 +3809,61 @@ void AddExpression2b(List<Object> list, ExprContext 
exprContext) :
     e = Expression3(exprContext) {
         list.add(e);
     }
+    AddRegularPostfixes(list)
+    [
+        LOOKAHEAD(2, <COLON> SimpleIdentifier(),
+            { this.conformance.isColonFieldAccessAllowed() })
+        <COLON>

Review Comment:
   I kept `:` intentionally non-chainable here. After one `:` we are already 
back in the existing postfix world, so the follow-up access can be expressed 
with the normal operators:
   
   - `a:b.c` instead of `a:b:c`
   - `a:['x'].y` instead of `a:['x']:y`
   - `(a:b)['x']` / `a:b['x']` instead of `a:b:['x']`
   
   So a second `:` would mostly be extra surface syntax, not extra 
expressiveness. Keeping it to a single colon also keeps the grammar narrower 
and avoids widening the ambiguous space around other colon uses, especially 
`::` in Babel and JSON constructor `:` handling.
   
   If we decide later that repeated `:` is a real dialect requirement, we can 
extend the current `[...]` to a loop, but I wanted to start with the minimal 
syntax that covers the targeted cases.
   



##########
babel/src/test/java/org/apache/calcite/test/BabelParserTest.java:
##########
@@ -301,6 +306,100 @@ private void checkParseInfixCast(String sqlType) {
     sql(sql).ok(expected);
   }
 
+  @Test void testColonFieldAccessWithInfixCast() {
+    final SqlParserFixture f =
+        fixture().withConformance(new SqlAbstractConformance() {
+          @Override public boolean isColonFieldAccessAllowed() {
+            return true;
+          }
+        });
+
+    // Bracket after :: binds to the type, not as subscript on the cast result
+    sql("select v::varchar[1] from t")
+        .ok("SELECT `V` :: VARCHAR[1]\nFROM `T`");
+    f.sql("select v::varchar[1] from t")
+        .ok("SELECT `V` :: VARCHAR[1]\nFROM `T`");
+
+    // Explicit parentheses make no difference — bracket still binds to the 
type
+    sql("select (v::varchar)[1] from t")
+        .ok("SELECT `V` :: VARCHAR[1]\nFROM `T`");
+    f.sql("select (v::varchar)[1] from t")
+        .ok("SELECT `V` :: VARCHAR[1]\nFROM `T`");
+
+    // Array type with bracket
+    sql("select v::integer array[1] from t")
+        .ok("SELECT `V` :: INTEGER ARRAY[1]\nFROM `T`");
+    f.sql("select v::integer array[1] from t")
+        .ok("SELECT `V` :: INTEGER ARRAY[1]\nFROM `T`");
+
+    // Parenthesized array type with bracket
+    sql("select (v::integer array)[1] from t")
+        .ok("SELECT `V` :: INTEGER ARRAY[1]\nFROM `T`");
+    f.sql("select (v::integer array)[1] from t")
+        .ok("SELECT `V` :: INTEGER ARRAY[1]\nFROM `T`");
+
+    // Multiple brackets bind to the type
+    sql("select v::varchar[1][2] from t")
+        .ok("SELECT `V` :: VARCHAR[1][2]\nFROM `T`");
+    f.sql("select v::varchar[1][2] from t")
+        .ok("SELECT `V` :: VARCHAR[1][2]\nFROM `T`");
+
+    // Expression on the LHS of ::
+    sql("select (v + 1)::integer[1] from t")
+        .ok("SELECT (`V` + 1) :: INTEGER[1]\nFROM `T`");
+    f.sql("select (v + 1)::integer[1] from t")
+        .ok("SELECT (`V` + 1) :: INTEGER[1]\nFROM `T`");
+
+    // Plain dot: [n].field correctly means subscript-then-field
+    sql("select v.field[1].field2 from t")
+        .ok("SELECT (`V`.`FIELD`[1].`FIELD2`)\nFROM `T`");
+    f.sql("select v:field[1].field2 from t")
+        .ok("SELECT ((`V`.`FIELD`)[1].`FIELD2`)\nFROM `T`");
+
+    // With ::, [n].field is consumed as part of the type
+    sql("select v::varchar[1].field from t")
+        .ok("SELECT `V` :: (VARCHAR[1].`FIELD`)\nFROM `T`");
+    f.sql("select v:field::varchar[1].field2 from t")
+        .ok("SELECT (`V`.`FIELD`) :: (VARCHAR[1].`FIELD2`)\nFROM `T`");
+
+    // Explicit parentheses around :: let [n].field apply to the cast result
+    sql("select (v::varchar)[1].field from t")
+        .ok("SELECT (`V` :: VARCHAR[1].`FIELD`)\nFROM `T`");
+    f.sql("select (v:field::varchar)[1].field2 from t")
+        .ok("SELECT ((`V`.`FIELD`) :: VARCHAR[1].`FIELD2`)\nFROM `T`");
+
+    // Bracket on the result of :: cast
+    sql("select v::integer[1] from t")
+        .ok("SELECT `V` :: INTEGER[1]\nFROM `T`");
+    f.sql("select v:field::integer[1] from t")
+        .ok("SELECT (`V`.`FIELD`) :: INTEGER[1]\nFROM `T`");
+
+    // Dot/bracket access combined with infix cast
+    sql("select v.field::integer,\n"
+            + "  arr[1].field::varchar,\n"
+            + "  v.field.field2::integer,\n"
+            + "  v.field[2]::integer\n"
+            + "from t")
+        .ok("SELECT `V`.`FIELD` :: INTEGER,"
+            + " (`ARR`[1].`FIELD`) :: VARCHAR,"
+            + " `V`.`FIELD`.`FIELD2` :: INTEGER,"
+            + " `V`.`FIELD`[2] :: INTEGER\n"
+            + "FROM `T`");
+    f.sql("select v:field::integer,\n"
+            + "  arr[1]:field::varchar,\n"
+            + "  v:field.field2::integer,\n"
+            + "  v:field[2]::integer\n"
+            + "from t")
+        .ok("SELECT (`V`.`FIELD`) :: INTEGER,"
+            + " (`ARR`[1].`FIELD`) :: VARCHAR,"
+            + " ((`V`.`FIELD`).`FIELD2`) :: INTEGER,"
+            + " (`V`.`FIELD`)[2] :: INTEGER\n"
+            + "FROM `T`");
+
+    // Double-colon followed by colon field access is not allowed
+    f.sql("select v::variant^:^field from t")
+        .fails("(?s).*Encountered \":.*\".*");
+  }

Review Comment:
   Done



##########
babel/src/main/codegen/includes/parserImpls.ftl:
##########
@@ -208,6 +208,7 @@ void InfixCast(List<Object> list, ExprContext exprContext, 
Span s) :
                 s.pos()));
         list.add(dt);
     }
+    AddRegularPostfixes(list)

Review Comment:
   Before the refactor, `InfixCast` could stop after `DataType()` and rely on 
the outer `Expression2()` loop to notice any following `.` or `[...]`. After 
the refactor, that outer postfix branch was removed and the shared postfix 
handling moved earlier into `AddExpression2b()`. But Babel `InfixCast` is not 
part of that earlier path; it is an `extraBinaryExpressions` hook that runs 
later.
   
   So if `InfixCast` does not invoke `AddRegularPostfixes()` itself, nobody 
else will. That is why it has to “own” it now: not because the semantics 
changed, but because the control flow changed. The shared postfix parser still 
exists, but this is now the only place on the Babel `::` path where it can be 
called.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to