Support Common Table Expressions in HQL parser.

See #2981.
This commit is contained in:
Greg L. Turnquist
2023-05-26 13:34:22 -05:00
parent bec9809f95
commit ad7b28063a
4 changed files with 203 additions and 8 deletions

View File

@@ -51,9 +51,37 @@ selectStatement
;
queryExpression
: orderedQuery (setOperator orderedQuery)*
: withClause? orderedQuery (setOperator orderedQuery)*
;
withClause
: WITH cte (',' cte)*
;
cte
: identifier AS (NOT? MATERIALIZED)? '(' queryExpression ')' searchClause? cycleClause?
;
searchClause
: SEARCH (BREADTH | DEPTH) FIRST BY searchSpecifications SET identifier
;
searchSpecifications
: searchSpecification (',' searchSpecification)*
;
searchSpecification
: identifier sortDirection? nullsPrecedence?
;
cycleClause
: CYCLE cteAttributes SET identifier (TO literal DEFAULT literal)? (USING identifier)?
;
cteAttributes
: identifier (',' identifier)*
;
orderedQuery
: (query | '(' queryExpression ')') queryOrder?
;

View File

@@ -59,6 +59,10 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
if (ctx.withClause() != null) {
tokens.addAll(visit(ctx.withClause()));
}
tokens.addAll(visit(ctx.orderedQuery(0)));
for (int i = 1; i < ctx.orderedQuery().size(); i++) {
@@ -70,6 +74,150 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitWithClause(HqlParser.WithClauseContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.add(TOKEN_WITH);
ctx.cte().forEach(cteContext -> {
tokens.addAll(visit(cteContext));
tokens.add(TOKEN_COMMA);
});
CLIP(tokens);
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitCte(HqlParser.CteContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.addAll(visit(ctx.identifier()));
tokens.add(TOKEN_AS);
NOSPACE(tokens);
if (ctx.NOT() != null) {
tokens.add(TOKEN_NOT);
}
if (ctx.MATERIALIZED() != null) {
tokens.add(TOKEN_MATERIALIZED);
}
tokens.add(TOKEN_OPEN_PAREN);
tokens.addAll(visit(ctx.queryExpression()));
tokens.add(TOKEN_CLOSE_PAREN);
if (ctx.searchClause() != null) {
tokens.addAll(visit(ctx.searchClause()));
}
if (ctx.cycleClause() != null) {
tokens.addAll(visit(ctx.cycleClause()));
}
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitSearchClause(HqlParser.SearchClauseContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.add(new JpaQueryParsingToken(ctx.SEARCH().getText()));
if (ctx.BREADTH() != null) {
tokens.add(new JpaQueryParsingToken(ctx.BREADTH().getText()));
} else if (ctx.DEPTH() != null) {
tokens.add(new JpaQueryParsingToken(ctx.DEPTH().getText()));
}
tokens.add(new JpaQueryParsingToken(ctx.FIRST().getText()));
tokens.add(new JpaQueryParsingToken(ctx.BY().getText()));
tokens.addAll(visit(ctx.searchSpecifications()));
tokens.add(new JpaQueryParsingToken(ctx.SET().getText()));
tokens.addAll(visit(ctx.identifier()));
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitSearchSpecifications(HqlParser.SearchSpecificationsContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
ctx.searchSpecification().forEach(searchSpecificationContext -> {
tokens.addAll(visit(searchSpecificationContext));
tokens.add(TOKEN_COMMA);
});
CLIP(tokens);
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitSearchSpecification(HqlParser.SearchSpecificationContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.addAll(visit(ctx.identifier()));
if (ctx.sortDirection() != null) {
tokens.addAll(visit(ctx.sortDirection()));
}
if (ctx.nullsPrecedence() != null) {
tokens.addAll(visit(ctx.nullsPrecedence()));
}
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitCycleClause(HqlParser.CycleClauseContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.add(new JpaQueryParsingToken(ctx.CYCLE().getText()));
tokens.addAll(visit(ctx.cteAttributes()));
tokens.add(new JpaQueryParsingToken(ctx.SET().getText()));
tokens.addAll(visit(ctx.identifier(0)));
if (ctx.TO() != null) {
tokens.add(new JpaQueryParsingToken(ctx.TO().getText()));
tokens.addAll(visit(ctx.literal(0)));
tokens.add(new JpaQueryParsingToken(ctx.DEFAULT().getText()));
tokens.addAll(visit(ctx.literal(1)));
}
if (ctx.USING() != null) {
tokens.add(new JpaQueryParsingToken(ctx.USING().getText()));
tokens.addAll(visit(ctx.identifier(1)));
}
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitCteAttributes(HqlParser.CteAttributesContext ctx) {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
ctx.identifier().forEach(identifierContext -> {
tokens.addAll(visit(identifierContext));
tokens.add(TOKEN_COMMA);
});
CLIP(tokens);
return tokens;
}
@Override
public List<JpaQueryParsingToken> visitOrderedQuery(HqlParser.OrderedQueryContext ctx) {
@@ -1876,7 +2024,7 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
List<JpaQueryParsingToken> tokens = new ArrayList<>();
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
tokens.addAll(visit(ctx.predicate()));
return tokens;
@@ -1919,7 +2067,7 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
tokens.addAll(visit(ctx.expression(0)));
if (ctx.NOT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
}
tokens.add(new JpaQueryParsingToken(ctx.BETWEEN()));
@@ -1939,7 +2087,7 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
tokens.add(new JpaQueryParsingToken(ctx.IS()));
if (ctx.NOT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
}
if (ctx.NULL() != null) {
@@ -1962,7 +2110,7 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
tokens.addAll(visit(ctx.expression(0)));
if (ctx.NOT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
}
if (ctx.LIKE() != null) {
@@ -1990,7 +2138,7 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
tokens.addAll(visit(ctx.expression()));
if (ctx.NOT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
}
tokens.add(new JpaQueryParsingToken(ctx.IN()));
@@ -2081,14 +2229,14 @@ class HqlQueryRenderer extends HqlBaseVisitor<List<JpaQueryParsingToken>> {
tokens.add(new JpaQueryParsingToken(ctx.IS()));
if (ctx.NOT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
}
tokens.add(new JpaQueryParsingToken(ctx.EMPTY()));
} else if (ctx.MEMBER() != null) {
if (ctx.NOT() != null) {
tokens.add(new JpaQueryParsingToken(ctx.NOT()));
tokens.add(TOKEN_NOT);
}
tokens.add(new JpaQueryParsingToken(ctx.MEMBER()));

View File

@@ -59,6 +59,13 @@ class JpaQueryParsingToken {
public static final JpaQueryParsingToken TOKEN_DESC = new JpaQueryParsingToken("desc", false);
public static final JpaQueryParsingToken TOKEN_ASC = new JpaQueryParsingToken("asc", false);
public static final JpaQueryParsingToken TOKEN_WITH = new JpaQueryParsingToken("WITH");
public static final JpaQueryParsingToken TOKEN_NOT = new JpaQueryParsingToken("NOT");
public static final JpaQueryParsingToken TOKEN_MATERIALIZED = new JpaQueryParsingToken("materialized");
/**
* The text value of the token.
*/

View File

@@ -1487,4 +1487,16 @@ class HqlQueryRendererTests {
""");
});
}
@Test // GH-2981
void cteWithClauseShouldWork() {
assertQuery("""
WITH maxId AS(select max(sr.snapshot.id) snapshotId from SnapshotReference sr
where sr.id.selectionId = ?1 and sr.enabled
group by sr.userId
)
select sr from maxId m join SnapshotReference sr on sr.snapshot.id = m.snapshotId
""");
}
}