From c57382e1bd61c8a98a856c81dd9131da366b0750 Mon Sep 17 00:00:00 2001 From: dengliming Date: Mon, 23 Feb 2026 19:58:44 +0800 Subject: [PATCH] Add support clickhouse GLOBAL ANY/ALL JOIN syntax variants --- .../sf/jsqlparser/statement/select/Join.java | 50 +++++++++++++++++++ .../util/deparser/SelectDeParser.java | 6 +++ .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 5 +- .../statement/select/ClickHouseTest.java | 40 +++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Join.java b/src/main/java/net/sf/jsqlparser/statement/select/Join.java index 6b774e201..191465e27 100644 --- a/src/main/java/net/sf/jsqlparser/statement/select/Join.java +++ b/src/main/java/net/sf/jsqlparser/statement/select/Join.java @@ -35,6 +35,8 @@ public class Join extends ASTNodeAccessImpl { private boolean simple = false; private boolean cross = false; private boolean semi = false; + private boolean any = false; + private boolean all = false; private boolean straight = false; private boolean apply = false; private boolean fetch = false; @@ -186,6 +188,48 @@ public Join withSemi(boolean b) { return this; } + /** + * Whether is an "ANY" join + * + * @return true if is an "ANY" join + */ + public boolean isAny() { + return any; + } + + public void setAny(boolean b) { + if (b) { + all = false; + } + any = b; + } + + public Join withAny(boolean b) { + this.setAny(b); + return this; + } + + /** + * Whether is an "ALL" join + * + * @return true if is an "ALL" join + */ + public boolean isAll() { + return all; + } + + public void setAll(boolean b) { + if (b) { + any = false; + } + all = b; + } + + public Join withAll(boolean b) { + this.setAll(b); + return this; + } + /** * Whether is a "LEFT" join * @@ -421,6 +465,12 @@ public String toString() { builder.append("NATURAL "); } + if (isAny()) { + builder.append("ANY "); + } else if (isAll()) { + builder.append("ALL "); + } + if (isRight()) { builder.append("RIGHT "); } else if (isFull()) { diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java index 7b03e66a0..bb6335d90 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java @@ -597,6 +597,12 @@ public void deparseJoin(Join join) { builder.append(" NATURAL"); } + if (join.isAny()) { + builder.append(" ANY"); + } else if (join.isAll()) { + builder.append(" ALL"); + } + if (join.isRight()) { builder.append(" RIGHT"); } else if (join.isFull()) { diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index a23217b19..c681a03f6 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -4964,17 +4964,18 @@ Join JoinerExpression() #JoinerExpression: } { [ { join.setGlobal(true); } ] + [ { join.setAny(true); } | { join.setAll(true); } ] [ { join.setNatural(true); } ] [ ( - { join.setLeft(true); } [ { join.setSemi(true); } | { join.setOuter(true); } ] + { join.setLeft(true); } [ { join.setSemi(true); } | { join.setOuter(true); } | { join.setAny(true); } | { join.setAll(true); } ] | ( { join.setRight(true); } | { join.setFull(true); } - ) [ { join.setOuter(true); } ] + ) [ { join.setOuter(true); } | { join.setAny(true); } | { join.setAll(true); } ] | { join.setInner(true); } ) diff --git a/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java b/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java index 3c9e99f97..39f7d8799 100644 --- a/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java @@ -26,6 +26,46 @@ public void testGlobalJoin() throws JSQLParserException { assertSqlCanBeParsedAndDeparsed(sql, true); } + @Test + public void testGlobalAnyLeftJoin() throws JSQLParserException { + String sql = "SELECT * FROM events e GLOBAL ANY LEFT JOIN users u ON e.user_id = u.id"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Join join = select.getJoins().get(0); + Assertions.assertTrue(join.isGlobal()); + Assertions.assertTrue(join.isAny()); + Assertions.assertTrue(join.isLeft()); + } + + @Test + public void testGlobalAllRightJoin() throws JSQLParserException { + String sql = "SELECT * FROM events e GLOBAL ALL RIGHT JOIN users u ON e.user_id = u.id"; + PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true); + Join join = select.getJoins().get(0); + Assertions.assertTrue(join.isGlobal()); + Assertions.assertTrue(join.isAll()); + Assertions.assertTrue(join.isRight()); + } + + @Test + public void testLeftAnyJoinOrderVariant() throws JSQLParserException { + String sql = "SELECT * FROM events e LEFT ANY JOIN users u ON e.user_id = u.id"; + Select statement = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect select = (PlainSelect) statement.getSelectBody(); + Join join = select.getJoins().get(0); + Assertions.assertTrue(join.isAny()); + Assertions.assertTrue(join.isLeft()); + } + + @Test + public void testRightAllJoinOrderVariant() throws JSQLParserException { + String sql = "SELECT * FROM events e RIGHT ALL JOIN users u ON e.user_id = u.id"; + Select statement = (Select) CCJSqlParserUtil.parse(sql); + PlainSelect select = (PlainSelect) statement.getSelectBody(); + Join join = select.getJoins().get(0); + Assertions.assertTrue(join.isAll()); + Assertions.assertTrue(join.isRight()); + } + @Test public void testFunctionWithAttributesIssue1742() throws JSQLParserException { String sql = "SELECT f1(arguments).f2.f3 from dual";