Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/main/java/net/sf/jsqlparser/statement/select/Join.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class Join extends ASTNodeAccessImpl {
private boolean straight = false;
private boolean apply = false;
private boolean fetch = false;
private boolean array = false;
private FromItem fromItem;
private KSQLJoinWindow joinWindow;

Expand Down Expand Up @@ -73,7 +74,10 @@ public boolean isInnerJoin() {
|| cross

/* Natural Join */
|| natural);
|| natural

/* Array Join */
|| array);
}

/**
Expand Down Expand Up @@ -337,6 +341,19 @@ public Join withCross(boolean cross) {
return this;
}

public boolean isArray() {
return array;
}

public void setArray(boolean array) {
this.array = array;
}

public Join withArray(boolean array) {
this.setArray(array);
return this;
}

/**
* Returns the right item of the join
*/
Expand Down Expand Up @@ -489,6 +506,10 @@ public String toString() {
builder.append("SEMI ");
}

if (isArray()) {
builder.append("ARRAY ");
}

if (isStraight()) {
builder.append("STRAIGHT_JOIN ");
} else if (isApply()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,10 @@ public void deparseJoin(Join join) {
builder.append(" SEMI");
}

if (join.isArray()) {
builder.append(" ARRAY");
}

if (join.isStraight()) {
builder.append(" STRAIGHT_JOIN ");
} else if (join.isApply()) {
Expand Down
38 changes: 22 additions & 16 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -829,9 +829,11 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
case K_CONNECT: return nextKind != K_BY;
case K_START: return nextKind != K_WITH;
case K_LEFT: return nextKind != K_JOIN && nextKind != K_OUTER
&& nextKind != K_SEMI;
&& nextKind != K_SEMI && nextKind != K_ARRAY_LITERAL;
case K_RIGHT: return nextKind != K_JOIN && nextKind != K_OUTER
&& nextKind != K_SEMI;
&& nextKind != K_SEMI && nextKind != K_ARRAY_LITERAL;
case K_ARRAY_LITERAL:
return nextKind != K_JOIN;
case K_ALL: return nextKind != K_JOIN;
case K_ANY: return nextKind != OPENING_BRACKET;
case K_SOME: return nextKind != OPENING_BRACKET;
Expand Down Expand Up @@ -993,29 +995,17 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
// String-literal alias: SELECT col 'myAlias'
if (kind == S_CHAR_LITERAL) return true;

// Base identifier tokens
Comment thread
manticore-projects marked this conversation as resolved.
if (kind == S_IDENTIFIER || kind == S_QUOTED_IDENTIFIER
|| kind == DATA_TYPE || kind == K_DATETIMELITERAL
|| kind == K_DATE_LITERAL) {
return true;
}

// OPTION (...) introduces a query hint clause, not an alias
if (kind == K_OPTION && getToken(2).kind == OPENING_BRACKET) {
return false;
}

// Non-reserved keywords
if (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD) {
return true;
}

// For reserved keywords in alias position, skip the structural-
// keyword whitelist and go directly to follower disambiguation.
// Only check keywords that are actually in RelObjectName's token
// alternatives — don't fire for brackets, operators, literals, etc.
switch (kind) {
case K_ALL: case K_ANY: case K_CASEWHEN: case K_CONNECT:
case K_ALL: case K_ARRAY_LITERAL: case K_ANY: case K_CASEWHEN: case K_CONNECT:
case K_CREATE: case K_DEFAULT:
case K_GLOBAL: case K_GROUP: case K_GROUPING: case K_IF:
case K_IIF: case K_IGNORE: case K_IN: case K_INTERVAL:
Expand All @@ -1026,8 +1016,22 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
case K_TOP: case K_VALUE: case K_VALUES:
return isReservedKeywordSafeByFollower();
default:
return false;
break;
}

// Base identifier tokens
if (kind == S_IDENTIFIER || kind == S_QUOTED_IDENTIFIER
|| kind == DATA_TYPE || kind == K_DATETIMELITERAL
|| kind == K_DATE_LITERAL) {
return true;
}

// Non-reserved keywords
if (kind >= MIN_NON_RESERVED_WORD && kind <= MAX_NON_RESERVED_WORD) {
return true;
}

return false;
}

/**
Expand Down Expand Up @@ -6492,6 +6496,8 @@ Join JoinerExpression() #JoinerExpression:
<K_OUTER> { join.setOuter(true); }
]

[ <K_ARRAY_LITERAL> { join.setArray(true); } ]

(
(
[ joinHint=JoinHint() {join.setJoinHint(joinHint); } ]
Expand Down
29 changes: 29 additions & 0 deletions src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -6527,4 +6527,33 @@ void testIssue2445FunctionWithBracketParameters() throws JSQLParserException {
" , cast(ex.value_date - f.appraisal_date AS DECIMAL) / 365 )";
TestUtils.assertSqlCanBeParsedAndDeparsed(sqlStr, true);
}

@Test
void testClickHouseArrayJoin() throws Exception {
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, x FROM t LEFT ARRAY JOIN arr AS x",
true);
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, x FROM t ARRAY JOIN arr AS x", true);
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, arr FROM t ARRAY JOIN arr", true);
TestUtils.assertSqlCanBeParsedAndDeparsed("SELECT s, arr FROM t LEFT ARRAY JOIN arr", true);

TestUtils.assertSqlCanBeParsedAndDeparsed(
"SELECT s, x, o.name FROM t LEFT ARRAY JOIN arr AS x INNER JOIN other_table o ON t.id = o.t_id",
true);
TestUtils.assertSqlCanBeParsedAndDeparsed(
"WITH exploded AS (SELECT s, x FROM t LEFT ARRAY JOIN arr AS x) SELECT * FROM exploded",
true);

PlainSelect selectLeft =
(PlainSelect) CCJSqlParserUtil.parse("SELECT s, x FROM t LEFT ARRAY JOIN arr AS x");
Join joinLeft = selectLeft.getJoins().get(0);
Assertions.assertTrue(joinLeft.isArray(), "Should be an array join");
Assertions.assertTrue(joinLeft.isLeft(), "Should be a left join");

PlainSelect selectInner =
(PlainSelect) CCJSqlParserUtil.parse("SELECT s, x FROM t ARRAY JOIN arr AS x");
Join joinInner = selectInner.getJoins().get(0);
Assertions.assertTrue(joinInner.isArray(), "Should be an array join");
Assertions.assertFalse(joinInner.isLeft(), "Should not be a left join");
}

}
Loading