Skip to content

Commit b601407

Browse files
Implements Hierarchical CONNECT_BY_ROOT Operator (#1282)
* Implements Hierarchical CONNECT_BY_ROOT Operator Fixes Issue #1269 Resolves some Special Oracle Tests * Improve Test Coverage Co-authored-by: Tobias <t.warneke@gmx.net>
1 parent 750c30a commit b601407

12 files changed

Lines changed: 161 additions & 3 deletions

File tree

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ jacocoTestCoverageVerification {
6868
violationRules {
6969
rule {
7070
limit {
71-
minimum = 0.837
71+
minimum = 0.840
7272
}
7373
}
7474
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2021 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
/*
11+
* Copyright (C) 2021 JSQLParser.
12+
*
13+
* This library is free software; you can redistribute it and/or modify it under the terms of the
14+
* GNU Lesser General Public License as published by the Free Software Foundation; either version
15+
* 2.1 of the License, or (at your option) any later version.
16+
*
17+
* This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
18+
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19+
* Lesser General Public License for more details.
20+
*
21+
* You should have received a copy of the GNU Lesser General Public License along with this library;
22+
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
23+
* 02110-1301 USA
24+
*/
25+
26+
package net.sf.jsqlparser.expression;
27+
28+
import java.util.Objects;
29+
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
30+
import net.sf.jsqlparser.schema.Column;
31+
32+
/**
33+
*
34+
* @author are
35+
*/
36+
public class ConnectByRootOperator extends ASTNodeAccessImpl implements Expression {
37+
private final Column column;
38+
39+
public ConnectByRootOperator(Column column) {
40+
this.column = Objects.requireNonNull(column, "The COLUMN of the ConnectByRoot Operator must not be null");
41+
}
42+
43+
public Column getColumn() {
44+
return column;
45+
}
46+
47+
@Override
48+
public void accept(ExpressionVisitor expressionVisitor) {
49+
expressionVisitor.visit(this);
50+
}
51+
52+
public StringBuilder appendTo(StringBuilder builder) {
53+
builder.append("CONNECT_BY_ROOT ").append(column);
54+
return builder;
55+
}
56+
57+
@Override
58+
public String toString() {
59+
return appendTo(new StringBuilder()).toString();
60+
}
61+
62+
}

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitor.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,5 +173,6 @@ public interface ExpressionVisitor {
173173

174174
void visit(JsonFunction aThis);
175175

176+
void visit(ConnectByRootOperator aThis);
176177
void visit(OracleNamedFunctionParameter aThis);
177178
}

src/main/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapter.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,10 +613,16 @@ public void visit(JsonFunction expression) {
613613
}
614614
}
615615

616+
@Override
617+
public void visit(ConnectByRootOperator connectByRootOperator) {
618+
connectByRootOperator.getColumn().accept(this);
619+
}
620+
616621
@Override
617622
public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) {
618623
oracleNamedFunctionParameter.getExpression().accept(this);
619624
}
625+
620626
public void visit(ColumnDefinition columnDefinition) {
621627
columnDefinition.accept(this);
622628
}

src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import net.sf.jsqlparser.expression.CaseExpression;
2222
import net.sf.jsqlparser.expression.CastExpression;
2323
import net.sf.jsqlparser.expression.CollateExpression;
24+
import net.sf.jsqlparser.expression.ConnectByRootOperator;
2425
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
2526
import net.sf.jsqlparser.expression.DateValue;
2627
import net.sf.jsqlparser.expression.DoubleValue;
@@ -1030,6 +1031,10 @@ public void visit(JsonFunction expression) {
10301031
}
10311032

10321033
@Override
1034+
public void visit(ConnectByRootOperator connectByRootOperator) {
1035+
connectByRootOperator.getColumn().accept(this);
1036+
}
1037+
10331038
public void visit(IfElseStatement ifElseStatement) {
10341039
ifElseStatement.getIfStatement().accept(this);
10351040
if (ifElseStatement.getElseStatement()!=null) {

src/main/java/net/sf/jsqlparser/util/deparser/ExpressionDeParser.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import net.sf.jsqlparser.expression.CaseExpression;
2323
import net.sf.jsqlparser.expression.CastExpression;
2424
import net.sf.jsqlparser.expression.CollateExpression;
25+
import net.sf.jsqlparser.expression.ConnectByRootOperator;
2526
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
2627
import net.sf.jsqlparser.expression.DateValue;
2728
import net.sf.jsqlparser.expression.DoubleValue;
@@ -1018,6 +1019,12 @@ public void visit(JsonAggregateFunction expression) {
10181019
public void visit(JsonFunction expression) {
10191020
expression.append(buffer);
10201021
}
1022+
1023+
@Override
1024+
public void visit(ConnectByRootOperator connectByRootOperator) {
1025+
buffer.append("CONNECT_BY_ROOT ");
1026+
connectByRootOperator.getColumn().accept(this);
1027+
}
10211028

10221029
@Override
10231030
public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) {

src/main/java/net/sf/jsqlparser/util/validation/validator/ExpressionValidator.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import net.sf.jsqlparser.expression.CaseExpression;
1818
import net.sf.jsqlparser.expression.CastExpression;
1919
import net.sf.jsqlparser.expression.CollateExpression;
20+
import net.sf.jsqlparser.expression.ConnectByRootOperator;
2021
import net.sf.jsqlparser.expression.DateTimeLiteralExpression;
2122
import net.sf.jsqlparser.expression.DateValue;
2223
import net.sf.jsqlparser.expression.DoubleValue;
@@ -599,6 +600,11 @@ public void visit(JsonFunction expression) {
599600
// no idea what this is good for
600601
}
601602

603+
@Override
604+
public void visit(ConnectByRootOperator connectByRootOperator) {
605+
connectByRootOperator.getColumn().accept(this);
606+
}
607+
602608
@Override
603609
public void visit(OracleNamedFunctionParameter oracleNamedFunctionParameter) {
604610
oracleNamedFunctionParameter.getExpression().accept(this);

src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ TOKEN: /* SQL Keywords. prefixed with K_ to avoid name clashes */
167167
| <K_COMMIT:"COMMIT">
168168
| <K_COMMENT:"COMMENT">
169169
| <K_CONNECT:"CONNECT">
170+
| <K_CONNECT_BY_ROOT: "CONNECT_BY_ROOT">
170171
| <K_CONSTRAINT:"CONSTRAINT">
171172
| <K_COSTS: "COSTS">
172173
| <K_CREATE:"CREATE">
@@ -3665,6 +3666,8 @@ Expression PrimaryExpression() #PrimaryExpression:
36653666

36663667
| LOOKAHEAD(2) retval = NextValExpression()
36673668

3669+
| retval=ConnectByRootOperator()
3670+
36683671
| retval=Column()
36693672

36703673
| token=<S_CHAR_LITERAL> { retval = new StringValue(token.image); linkAST(retval,jjtThis); }
@@ -3745,6 +3748,16 @@ Expression PrimaryExpression() #PrimaryExpression:
37453748
}
37463749
}
37473750

3751+
ConnectByRootOperator ConnectByRootOperator() #ConnectByRootOperator: {
3752+
Column column;
3753+
}
3754+
{
3755+
<K_CONNECT_BY_ROOT> column = Column()
3756+
{
3757+
return new ConnectByRootOperator(column);
3758+
}
3759+
}
3760+
37483761
NextValExpression NextValExpression() : {
37493762
List<String> data = new ArrayList<String>();
37503763
Token token;

src/test/java/net/sf/jsqlparser/expression/ExpressionVisitorAdapterTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,15 @@ public void testJsonAggregateFunction() throws JSQLParserException {
257257
.accept(adapter);
258258
}
259259

260-
@Test
260+
@Test
261+
public void testConnectedByRootExpression() throws JSQLParserException {
262+
ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter();
263+
CCJSqlParserUtil
264+
.parseExpression("CONNECT_BY_ROOT last_name as name")
265+
.accept(adapter);
266+
}
267+
268+
@Test
261269
public void testRowConstructor() throws JSQLParserException {
262270
ExpressionVisitorAdapter adapter = new ExpressionVisitorAdapter();
263271
CCJSqlParserUtil

src/test/java/net/sf/jsqlparser/statement/select/SelectTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4666,6 +4666,30 @@ public void testKeywordFilterIssue1255() throws JSQLParserException {
46664666
}
46674667

46684668
@Test
4669+
public void testConnectByRootIssue1255() throws JSQLParserException {
4670+
assertSqlCanBeParsedAndDeparsed(
4671+
"SELECT last_name \"Employee\", CONNECT_BY_ROOT last_name \"Manager\",\n" +
4672+
" LEVEL-1 \"Pathlen\", SYS_CONNECT_BY_PATH(last_name, '/') \"Path\"\n" +
4673+
" FROM employees\n" +
4674+
" WHERE LEVEL > 1 and department_id = 110\n" +
4675+
" CONNECT BY PRIOR employee_id = manager_id", true);
4676+
4677+
assertSqlCanBeParsedAndDeparsed(
4678+
"SELECT name, SUM(salary) \"Total_Salary\" FROM (\n" +
4679+
" SELECT CONNECT_BY_ROOT last_name as name, Salary\n" +
4680+
" FROM employees\n" +
4681+
" WHERE department_id = 110\n" +
4682+
" CONNECT BY PRIOR employee_id = manager_id)\n" +
4683+
" GROUP BY name", true);
4684+
4685+
assertSqlCanBeParsedAndDeparsed(
4686+
"SELECT CONNECT_BY_ROOT last_name as name"
4687+
+ ", salary "
4688+
+ "FROM employees "
4689+
+ "WHERE department_id = 110 "
4690+
+ "CONNECT BY PRIOR employee_id = manager_id", true);
4691+
}
4692+
46694693
public void testUnionLimitOrderByIssue1268() throws JSQLParserException {
46704694
String sqlStr = "(SELECT __time FROM traffic_protocol_stat_log LIMIT 1) UNION ALL (SELECT __time FROM traffic_protocol_stat_log ORDER BY __time LIMIT 1)";
46714695
assertSqlCanBeParsedAndDeparsed(sqlStr, true);

0 commit comments

Comments
 (0)