From d4a2627d310955c3639cccdd4eed0fac43c28851 Mon Sep 17 00:00:00 2001 From: minleejae <55116920+minleejae@users.noreply.github.com> Date: Tue, 25 Aug 2026 10:18:14 +0900 Subject: [PATCH] fix(parser): support CAST to an array type for MySQL multi-valued indexes MySQL defines a multi-valued index key part by casting to an array, e.g. CAST(data->'$.zips' AS UNSIGNED ARRAY). The trailing ARRAY keyword was not reachable, so both the CREATE INDEX and the ALTER TABLE form failed to parse. ARRAY is accepted as a further word of the cast target type, the way INT UNSIGNED already is, so no model change is needed and the deparsed statement round trips. It is added to the CAST production only, where MySQL allows it, which leaves the ARRAY constructor form untouched. Closes the last open group of #2490. --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 8 +++++++- .../jsqlparser/expression/CastExpressionTest.java | 15 +++++++++++++++ .../sf/jsqlparser/statement/alter/AlterTest.java | 6 ++++++ .../statement/create/CreateIndexTest.java | 6 ++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index de05f830d..75a9feddb 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -9946,7 +9946,13 @@ CastExpression CastExpression(): ")" ) | - type=ColDataType() { retval.setColDataType(type); } + ( + type=ColDataType() { retval.setColDataType(type); } + // MySQL casts to an array of the given type when a multi-valued index key part is + // defined, e.g. CAST(data->'$.zips' AS UNSIGNED ARRAY). ARRAY reads as a further + // word of the type, the way INT UNSIGNED already does. + [ { type.setDataType(type.getDataType() + " ARRAY"); } ] + ) ) // BigQuery FORMAT clause diff --git a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java index d0365054e..38827001c 100644 --- a/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/CastExpressionTest.java @@ -13,6 +13,7 @@ import net.sf.jsqlparser.JSQLParserException; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.select.PlainSelect; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Assertions; @@ -155,4 +156,18 @@ void testNestedCompositeTypeCastIssue2341() throws JSQLParserException { Assertions.assertEquals(1, parenthesedCast.size()); Assertions.assertInstanceOf(CastExpression.class, parenthesedCast.get(0)); } + + @Test + public void testCastToArrayIssue2490() throws JSQLParserException { + // MySQL casts to an array of the given type when a multi-valued index key part is + // defined, and accepts the same cast anywhere else an expression is allowed. + assertSqlCanBeParsedAndDeparsed("SELECT CAST(data -> '$.zips' AS UNSIGNED ARRAY) FROM t"); + assertSqlCanBeParsedAndDeparsed("SELECT CAST(x AS CHAR (10) ARRAY) FROM t"); + + PlainSelect select = (PlainSelect) CCJSqlParserUtil + .parse("SELECT CAST(data -> '$.zips' AS UNSIGNED ARRAY) FROM t"); + CastExpression cast = Assertions.assertInstanceOf(CastExpression.class, + select.getSelectItem(0).getExpression()); + Assertions.assertEquals("UNSIGNED ARRAY", cast.getColDataType().getDataType()); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index d9344e1b4..a18215b79 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -2414,4 +2414,10 @@ public void testAlterTableAddIndexKeyPartWithPrefixLengthAndDirectionIssue2490() assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD INDEX i33 (c1 (20) ASC)"); assertSqlCanBeParsedAndDeparsed("ALTER TABLE t ADD UNIQUE INDEX i34 (c1 (10) DESC)"); } + + @Test + public void testAlterTableAddMultiValuedIndexIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "ALTER TABLE t ADD INDEX i31 ((CAST(data -> '$.zips' AS UNSIGNED ARRAY)))"); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java index dea34d192..a12456591 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java @@ -234,4 +234,10 @@ public void testCreateFullTextAndSpatialIndexIssue2490() throws JSQLParserExcept assertSqlCanBeParsedAndDeparsed("CREATE FULLTEXT INDEX i18 ON t (body) WITH PARSER ngram"); assertSqlCanBeParsedAndDeparsed("CREATE SPATIAL INDEX i19 ON t (g)"); } + + @Test + public void testCreateMultiValuedIndexIssue2490() throws JSQLParserException { + assertSqlCanBeParsedAndDeparsed( + "CREATE INDEX i20 ON t ((CAST(data -> '$.zips' AS UNSIGNED ARRAY)))"); + } }