From 4e8eee9a183595aeb8961d267cd0c4ef6509f463 Mon Sep 17 00:00:00 2001 From: dengliming Date: Fri, 13 Mar 2026 23:51:58 +0800 Subject: [PATCH 1/2] fix(parser): support KEY-prefixed expressions like KEY chain.entity --- .../expression/ExpressionVisitor.java | 9 +++- .../expression/ExpressionVisitorAdapter.java | 14 +++--- .../jsqlparser/expression/KeyExpression.java | 44 +++++++++++++++++++ .../sf/jsqlparser/util/TablesNamesFinder.java | 6 +++ .../util/deparser/ExpressionDeParser.java | 25 ++++++++--- .../validator/ExpressionValidator.java | 13 +++++- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 14 +++++- .../jsqlparser/expression/FunctionTest.java | 31 +++++++++++++ 8 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 src/main/java/net/sf/jsqlparser/expression/KeyExpression.java diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java index f70021f83..998c60ef5 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java @@ -9,6 +9,7 @@ */ package net.sf.jsqlparser.expression; +import java.util.List; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -68,8 +69,6 @@ import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.update.UpdateSet; -import java.util.List; - public interface ExpressionVisitor { default T visitExpressions(ExpressionList expressions, S context) { @@ -790,6 +789,12 @@ default void visit(Inverse inverse) { T visit(DateUnitExpression dateUnitExpression, S context); + T visit(KeyExpression keyExpression, S context); + + default void visit(KeyExpression keyExpression) { + this.visit(keyExpression, null); + } + T visit(PostgresNamedFunctionParameter postgresNamedFunctionParameter, S context); default void visit(PostgresNamedFunctionParameter postgresNamedFunctionParameter) { diff --git a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java index b6e96a83a..c546a4c2c 100644 --- a/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java +++ b/src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java @@ -9,6 +9,10 @@ */ package net.sf.jsqlparser.expression; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; import net.sf.jsqlparser.expression.operators.arithmetic.Addition; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseAnd; import net.sf.jsqlparser.expression.operators.arithmetic.BitwiseLeftShift; @@ -73,11 +77,6 @@ import net.sf.jsqlparser.statement.select.UnPivot; import net.sf.jsqlparser.statement.select.WithItem; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Optional; - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.UncommentedEmptyMethodBody"}) public class ExpressionVisitorAdapter implements ExpressionVisitor, PivotVisitor, SelectItemVisitor { @@ -769,6 +768,11 @@ public T visit(ConnectByPriorOperator connectByPriorOperator, S context) { return connectByPriorOperator.getColumn().accept(this, context); } + @Override + public T visit(KeyExpression keyExpression, S context) { + return keyExpression.getExpression().accept(this, context); + } + @Override public T visit(OracleNamedFunctionParameter oracleNamedFunctionParameter, S context) { return oracleNamedFunctionParameter.getExpression().accept(this, context); diff --git a/src/main/java/net/sf/jsqlparser/expression/KeyExpression.java b/src/main/java/net/sf/jsqlparser/expression/KeyExpression.java new file mode 100644 index 000000000..2bb063875 --- /dev/null +++ b/src/main/java/net/sf/jsqlparser/expression/KeyExpression.java @@ -0,0 +1,44 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2026 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.expression; + +import java.util.Objects; +import net.sf.jsqlparser.parser.ASTNodeAccessImpl; + +/** + * Dialect specific expression for constructs such as {@code KEY chain.entity}. + */ +public class KeyExpression extends ASTNodeAccessImpl implements Expression { + private final Expression expression; + + public KeyExpression(Expression expression) { + this.expression = Objects.requireNonNull(expression, + "The EXPRESSION of the KEY expression must not be null"); + } + + public Expression getExpression() { + return expression; + } + + @Override + public T accept(ExpressionVisitor expressionVisitor, S context) { + return expressionVisitor.visit(this, context); + } + + public StringBuilder appendTo(StringBuilder builder) { + builder.append("KEY ").append(expression); + return builder; + } + + @Override + public String toString() { + return appendTo(new StringBuilder()).toString(); + } +} diff --git a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java index 94ebc6330..09ca9faba 100644 --- a/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java +++ b/src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java @@ -1795,6 +1795,12 @@ public Void visit(ConnectByPriorOperator connectByPriorOperator, S context) return null; } + @Override + public Void visit(KeyExpression keyExpression, S context) { + keyExpression.getExpression().accept(this, context); + return null; + } + @Override public Void visit(IfElseStatement ifElseStatement, S context) { ifElseStatement.getIfStatement().accept(this, context); diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java index 58da9deb5..0e57937fa 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java @@ -9,6 +9,11 @@ */ package net.sf.jsqlparser.util.deparser; +import static java.util.stream.Collectors.joining; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; import net.sf.jsqlparser.expression.AllValue; import net.sf.jsqlparser.expression.AnalyticExpression; import net.sf.jsqlparser.expression.AnalyticType; @@ -20,8 +25,8 @@ import net.sf.jsqlparser.expression.CaseExpression; import net.sf.jsqlparser.expression.CastExpression; import net.sf.jsqlparser.expression.CollateExpression; -import net.sf.jsqlparser.expression.ConnectByRootOperator; import net.sf.jsqlparser.expression.ConnectByPriorOperator; +import net.sf.jsqlparser.expression.ConnectByRootOperator; import net.sf.jsqlparser.expression.DateTimeLiteralExpression; import net.sf.jsqlparser.expression.DateUnitExpression; import net.sf.jsqlparser.expression.DateValue; @@ -41,6 +46,7 @@ import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.JsonTableFunction; import net.sf.jsqlparser.expression.KeepExpression; +import net.sf.jsqlparser.expression.KeyExpression; import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.LowExpression; @@ -134,12 +140,6 @@ import net.sf.jsqlparser.statement.select.SelectVisitor; import net.sf.jsqlparser.statement.select.WithItem; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import static java.util.stream.Collectors.joining; - @SuppressWarnings({"PMD.CyclomaticComplexity"}) public class ExpressionDeParser extends AbstractDeParser // FIXME maybe we should implement an ItemsListDeparser too? @@ -1540,6 +1540,10 @@ public void visit(SimilarToExpression expr) { visit(expr, null); } + public void visit(KeyExpression keyExpression) { + visit(keyExpression, null); + } + @Override public StringBuilder visit(ArrayExpression array, S context) { @@ -1660,6 +1664,13 @@ public StringBuilder visit(ConnectByPriorOperator connectByPriorOperator, S return builder; } + @Override + public StringBuilder visit(KeyExpression keyExpression, S context) { + builder.append("KEY "); + keyExpression.getExpression().accept(this, context); + return builder; + } + @Override public StringBuilder visit(OracleNamedFunctionParameter oracleNamedFunctionParameter, S context) { diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java index f489869b0..1ad32ec91 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java @@ -19,8 +19,8 @@ import net.sf.jsqlparser.expression.CaseExpression; import net.sf.jsqlparser.expression.CastExpression; import net.sf.jsqlparser.expression.CollateExpression; -import net.sf.jsqlparser.expression.ConnectByRootOperator; import net.sf.jsqlparser.expression.ConnectByPriorOperator; +import net.sf.jsqlparser.expression.ConnectByRootOperator; import net.sf.jsqlparser.expression.DateTimeLiteralExpression; import net.sf.jsqlparser.expression.DateUnitExpression; import net.sf.jsqlparser.expression.DateValue; @@ -40,6 +40,7 @@ import net.sf.jsqlparser.expression.JsonFunction; import net.sf.jsqlparser.expression.JsonTableFunction; import net.sf.jsqlparser.expression.KeepExpression; +import net.sf.jsqlparser.expression.KeyExpression; import net.sf.jsqlparser.expression.LambdaExpression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.LowExpression; @@ -1061,6 +1062,12 @@ public Void visit(ConnectByPriorOperator connectByPriorOperator, S context) return null; } + @Override + public Void visit(KeyExpression keyExpression, S context) { + keyExpression.getExpression().accept(this, context); + return null; + } + @Override public Void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter, S context) { oracleNamedFunctionParameter.getExpression().accept(this, context); @@ -1249,6 +1256,10 @@ public void visit(ConnectByRootOperator connectByRootOperator) { visit(connectByRootOperator, null); } + public void visit(KeyExpression keyExpression) { + visit(keyExpression, null); + } + public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) { visit(oracleNamedFunctionParameter, null); } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index e89688565..374ceebe7 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -7165,6 +7165,8 @@ Expression PrimaryExpression() #PrimaryExpression: | retval=ConnectByPriorOperator() + | LOOKAHEAD(2, { !interrupted && getToken(1).kind == K_KEY && getToken(2).kind != OPENING_BRACKET }) retval=KeyExpression() + | LOOKAHEAD(2, {!interrupted}) { retval = new AllValue(); } | LOOKAHEAD(2, {!interrupted}) retval=Column() @@ -7307,7 +7309,17 @@ ConnectByPriorOperator ConnectByPriorOperator() #ConnectByPriorOperator: { { expression = Expression() { - return new ConnectByPriorOperator(expression); + return new ConnectByPriorOperator(expression); + } +} + +KeyExpression KeyExpression() #KeyExpression: { + Expression expression; +} +{ + expression = Expression() + { + return new KeyExpression(expression); } } diff --git a/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java b/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java index f977d558d..deb508c19 100644 --- a/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java +++ b/src/test/java/net/sf/jsqlparser/expression/FunctionTest.java @@ -9,7 +9,11 @@ */ package net.sf.jsqlparser.expression; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.test.TestUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -126,4 +130,31 @@ void TestIntervalParameterIssue2272() throws JSQLParserException { "SELECT DATE_SUB('2025-06-19', INTERVAL QUARTER(STR_TO_DATE('20250619', '%Y%m%d')) - 1 QUARTER) from dual"; TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + void testAesDecryptWithKeyExpressionParameter() throws JSQLParserException { + String expression = "aes_decrypt(from_base64(entity), KEY chain.entity)"; + TestUtils.assertExpressionCanBeParsedAndDeparsed(expression, true); + + Function function = (Function) CCJSqlParserUtil.parseExpression(expression); + KeyExpression keyExpression = + assertInstanceOf(KeyExpression.class, function.getParameters().get(1)); + assertEquals("chain.entity", keyExpression.getExpression().toString()); + + function.accept(new ExpressionVisitorAdapter<>(), null); + } + + @Test + void testAesDecryptWithKeyExpressionInSelect() throws JSQLParserException { + String sqlStr = "SELECT t1.entity, SUM(t2.balance) AS total_balance\n" + + "FROM (\n" + + " SELECT DISTINCT address, aes_decrypt(from_base64(entity), KEY chain.entity) AS entity\n" + + " FROM bch_entity\n" + + ") t1\n" + + "JOIN bch_address_token_statis t2\n" + + "ON t1.address = t2.address\n" + + "GROUP BY t1.entity"; + + TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true); + } } From fa52d0f5463a35ad3d6886fdc63147a324601838 Mon Sep 17 00:00:00 2001 From: dengliming Date: Fri, 13 Mar 2026 23:59:24 +0800 Subject: [PATCH 2/2] suppress PMD excessive method length in AlterExpression --- .../net/sf/jsqlparser/statement/alter/AlterExpression.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java index afb1c2d05..43615abdb 100644 --- a/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java +++ b/src/main/java/net/sf/jsqlparser/statement/alter/AlterExpression.java @@ -1072,7 +1072,8 @@ protected void toStringPartition(StringBuilder b) { * Handles the general case for ADD, MODIFY, CHANGE, DROP (column), COMMENT, row-level security, * and all field-based dispatch (columns, constraints, FK, UK, PK, index). */ - @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"}) + @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity", + "PMD.ExcessiveMethodLength"}) protected void toStringGeneral(StringBuilder b) { if (operation == AlterOperation.COMMENT_WITH_EQUAL_SIGN) { b.append("COMMENT =").append(" ");