Skip to content

Commit 4ac1d1b

Browse files
committed
Parse slices with omitted bounds or a step
`l[2:]`, `l[:]` and `l[-5:]` failed, `l[2:3]` parsed as the JSON path `2 : 3` and `l[:2]` as the numeric bind `:2`. Inside a subscript, a `:` now separates the bounds of a slice rather than starting a JSON path. Slices also take DuckDB's optional step, `l[1:5:2]`, where a `-` stop stands for the end of the list, `l[1:-:2]`. `RangeExpression` and `ArrayExpression` gain a step and accept missing bounds.
1 parent 19f12ea commit 4ac1d1b

9 files changed

Lines changed: 222 additions & 44 deletions

File tree

‎src/main/java/net/sf/jsqlparser/expression/ArrayExpression.java‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class ArrayExpression extends ASTNodeAccessImpl implements Expression {
1717
private Expression indexExpression;
1818
private Expression startIndexExpression;
1919
private Expression stopIndexExpression;
20+
private Expression stepExpression;
2021

2122

2223
public ArrayExpression() {
@@ -72,6 +73,19 @@ public void setStopIndexExpression(Expression stopIndexExpression) {
7273
this.stopIndexExpression = stopIndexExpression;
7374
}
7475

76+
public Expression getStepExpression() {
77+
return stepExpression;
78+
}
79+
80+
public void setStepExpression(Expression stepExpression) {
81+
this.stepExpression = stepExpression;
82+
}
83+
84+
public ArrayExpression withStepExpression(Expression stepExpression) {
85+
this.setStepExpression(stepExpression);
86+
return this;
87+
}
88+
7589
@Override
7690
public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
7791
return expressionVisitor.visit(this, context);
@@ -82,11 +96,8 @@ public String toString() {
8296
if (indexExpression != null) {
8397
return objExpression.toString() + "[" + indexExpression.toString() + "]";
8498
} else {
85-
return objExpression.toString() + "[" +
86-
(startIndexExpression == null ? "" : startIndexExpression.toString()) +
87-
":" +
88-
(stopIndexExpression == null ? "" : stopIndexExpression.toString()) +
89-
"]";
99+
return objExpression.toString() + "[" + RangeExpression.toString(startIndexExpression,
100+
stopIndexExpression, stepExpression) + "]";
90101
}
91102
}
92103

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,6 +715,9 @@ public <S> T visit(ArrayExpression arrayExpression, S context) {
715715
if (arrayExpression.getStopIndexExpression() != null) {
716716
subExpressions.add(arrayExpression.getStopIndexExpression());
717717
}
718+
if (arrayExpression.getStepExpression() != null) {
719+
subExpressions.add(arrayExpression.getStepExpression());
720+
}
718721
return visitExpressions(arrayExpression, context, subExpressions);
719722
}
720723

@@ -874,7 +877,7 @@ public <S> T visit(TrimFunction trimFunction, S context) {
874877
@Override
875878
public <S> T visit(RangeExpression rangeExpression, S context) {
876879
return visitExpressions(rangeExpression, context, rangeExpression.getStartExpression(),
877-
rangeExpression.getEndExpression());
880+
rangeExpression.getEndExpression(), rangeExpression.getStepExpression());
878881
}
879882

880883
@Override

‎src/main/java/net/sf/jsqlparser/expression/RangeExpression.java‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
public class RangeExpression extends ASTNodeAccessImpl implements Expression {
1515
private Expression startExpression;
1616
private Expression endExpression;
17+
private Expression stepExpression;
1718

1819
public RangeExpression(Expression startExpression, Expression endExpression) {
1920
this.startExpression = startExpression;
@@ -38,9 +39,42 @@ public RangeExpression setEndExpression(Expression endExpression) {
3839
return this;
3940
}
4041

42+
public Expression getStepExpression() {
43+
return stepExpression;
44+
}
45+
46+
public RangeExpression setStepExpression(Expression stepExpression) {
47+
this.stepExpression = stepExpression;
48+
return this;
49+
}
50+
51+
public RangeExpression withStepExpression(Expression stepExpression) {
52+
return setStepExpression(stepExpression);
53+
}
54+
55+
/**
56+
* Renders {@code [start]:[end][:step]}, a missing end before a step as DuckDB's {@code -}.
57+
*/
58+
public static String toString(Expression start, Expression end, Expression step) {
59+
StringBuilder builder = new StringBuilder();
60+
if (start != null) {
61+
builder.append(start);
62+
}
63+
builder.append(":");
64+
if (end != null) {
65+
builder.append(end);
66+
} else if (step != null) {
67+
builder.append("-");
68+
}
69+
if (step != null) {
70+
builder.append(":").append(step);
71+
}
72+
return builder.toString();
73+
}
74+
4175
@Override
4276
public String toString() {
43-
return startExpression + ":" + endExpression;
77+
return toString(startExpression, endExpression, stepExpression);
4478
}
4579

4680
@Override

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,12 @@ public <S> Void visit(TrimFunction trimFunction, S context) {
350350

351351
@Override
352352
public <S> Void visit(RangeExpression rangeExpression, S context) {
353-
rangeExpression.getStartExpression().accept(this, context);
354-
rangeExpression.getEndExpression().accept(this, context);
353+
for (Expression bound : new Expression[] {rangeExpression.getStartExpression(),
354+
rangeExpression.getEndExpression(), rangeExpression.getStepExpression()}) {
355+
if (bound != null) {
356+
bound.accept(this, context);
357+
}
358+
}
355359
return null;
356360
}
357361

@@ -2240,6 +2244,9 @@ public <S> Void visit(ArrayExpression array, S context) {
22402244
if (array.getStopIndexExpression() != null) {
22412245
array.getStopIndexExpression().accept(this, context);
22422246
}
2247+
if (array.getStepExpression() != null) {
2248+
array.getStepExpression().accept(this, context);
2249+
}
22432250
return null;
22442251
}
22452252

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -843,12 +843,27 @@ public void visit(TrimFunction trimFunction) {
843843

844844
@Override
845845
public <S> StringBuilder visit(RangeExpression rangeExpression, S context) {
846-
rangeExpression.getStartExpression().accept(this, context);
847-
builder.append(":");
848-
rangeExpression.getEndExpression().accept(this, context);
846+
appendSlice(rangeExpression.getStartExpression(), rangeExpression.getEndExpression(),
847+
rangeExpression.getStepExpression(), context);
849848
return builder;
850849
}
851850

851+
private <S> void appendSlice(Expression start, Expression end, Expression step, S context) {
852+
if (start != null) {
853+
start.accept(this, context);
854+
}
855+
builder.append(":");
856+
if (end != null) {
857+
end.accept(this, context);
858+
} else if (step != null) {
859+
builder.append("-");
860+
}
861+
if (step != null) {
862+
builder.append(":");
863+
step.accept(this, context);
864+
}
865+
}
866+
852867
@Override
853868
public <S> StringBuilder visit(TernaryExpression ternaryExpression, S context) {
854869
ternaryExpression.getCondition().accept(this, context);
@@ -1645,13 +1660,8 @@ public <S> StringBuilder visit(ArrayExpression array, S context) {
16451660
if (array.getIndexExpression() != null) {
16461661
array.getIndexExpression().accept(this, context);
16471662
} else {
1648-
if (array.getStartIndexExpression() != null) {
1649-
array.getStartIndexExpression().accept(this, context);
1650-
}
1651-
builder.append(":");
1652-
if (array.getStopIndexExpression() != null) {
1653-
array.getStopIndexExpression().accept(this, context);
1654-
}
1663+
appendSlice(array.getStartIndexExpression(), array.getStopIndexExpression(),
1664+
array.getStepExpression(), context);
16551665
}
16561666

16571667
builder.append("]");

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,6 +1034,9 @@ public <S> Void visit(ArrayExpression array, S context) {
10341034
if (array.getStopIndexExpression() != null) {
10351035
array.getStopIndexExpression().accept(this, context);
10361036
}
1037+
if (array.getStepExpression() != null) {
1038+
array.getStepExpression().accept(this, context);
1039+
}
10371040
return null;
10381041
}
10391042

@@ -1200,8 +1203,12 @@ public <S> Void visit(TrimFunction trimFunction, S context) {
12001203

12011204
@Override
12021205
public <S> Void visit(RangeExpression rangeExpression, S context) {
1203-
rangeExpression.getStartExpression().accept(this, context);
1204-
rangeExpression.getEndExpression().accept(this, context);
1206+
for (Expression bound : new Expression[] {rangeExpression.getStartExpression(),
1207+
rangeExpression.getEndExpression(), rangeExpression.getStepExpression()}) {
1208+
if (bound != null) {
1209+
bound.accept(this, context);
1210+
}
1211+
}
12051212
return null;
12061213
}
12071214

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

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
203203
// and must not be taken as the JSON path operator
204204
private int mapKeyDepth = 0;
205205

206+
// depth of subscripts: a ":" there separates the bounds of a slice such as l[1:3]
207+
// and must not be taken as the JSON path operator
208+
private int subscriptDepth = 0;
209+
206210
private Expression createOutputArgument(Expression argument) throws ParseException {
207211
Expression value = argument;
208212
if (argument instanceof VariableAssignment) {
@@ -1571,6 +1575,10 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
15711575
|| next.kind >= MIN_NON_RESERVED_WORD && next.kind <= MAX_NON_RESERVED_WORD;
15721576
}
15731577

1578+
private static boolean isSliceDelimiter(Token token) {
1579+
return ":".equals(token.image) || "]".equals(token.image) || ",".equals(token.image);
1580+
}
1581+
15741582
private boolean isAliasAhead() {
15751583
if (isAsOfJoinAhead() || isSemiAntiJoinAhead() || isApproxNearestAhead()) {
15761584
return false;
@@ -6884,7 +6892,7 @@ Column Column() #Column :
68846892
// @todo: we better should return a SEQUENCE instead of a COLUMN
68856893
[ LOOKAHEAD(2) "." <K_NEXTVAL> { data.getNames().add("nextval"); } ]
68866894

6887-
[ LOOKAHEAD(2) arrayConstructor = ArrayConstructor(false) ]
6895+
[ LOOKAHEAD(2) arrayConstructor = Subscript() ]
68886896
{
68896897
Column col = createColumn(data.getNames(), data.getDelimiters());
68906898
if (tk != null) { col.withCommentText(tk.image); }
@@ -11341,29 +11349,83 @@ Expression AddChain():
1134111349

1134211350
Expression ArrayExpression(Expression obj): {
1134311351
Expression expr;
11352+
} {
11353+
expr = ArraySubscript(obj)
11354+
( LOOKAHEAD(2) expr = ArraySubscript(expr) )*
11355+
{ return expr; }
11356+
}
11357+
11358+
ArrayExpression ArraySubscript(Expression obj): {
1134411359
Expression idxExpr = null;
11345-
Expression startExpr = null;
11346-
Expression stopExpr = null;
11360+
RangeExpression slice = null;
11361+
} {
11362+
"["
11363+
try {
11364+
{ subscriptDepth++; }
11365+
[ LOOKAHEAD(3, { !":".equals(getToken(1).image) }) idxExpr = ArrayIndexExpression() ]
11366+
[ slice = SliceTail(idxExpr) ]
11367+
} finally {
11368+
subscriptDepth--;
11369+
}
11370+
"]"
11371+
{
11372+
return slice == null
11373+
? new ArrayExpression(obj, idxExpr, null, null)
11374+
: new ArrayExpression(obj, null, slice.getStartExpression(),
11375+
slice.getEndExpression()).withStepExpression(slice.getStepExpression());
11376+
}
11377+
}
11378+
11379+
/**
11380+
* The rest of a slice after its optional start: {@code :[stop][:[step]]}. As in DuckDB, a
11381+
* {@code -} stop before a step stands for the end of the list, as in {@code l[1:-:2]}.
11382+
*/
11383+
RangeExpression SliceTail(Expression start): {
11384+
Expression stop = null;
11385+
Expression step = null;
11386+
} {
11387+
":"
11388+
[
11389+
LOOKAHEAD("-" ":") "-"
11390+
|
11391+
LOOKAHEAD({ !isSliceDelimiter(getToken(1)) }) stop = Expression()
11392+
]
11393+
[
11394+
":" [ LOOKAHEAD({ !isSliceDelimiter(getToken(1)) }) step = Expression() ]
11395+
]
11396+
{ return new RangeExpression(start, stop).withStepExpression(step); }
11397+
}
11398+
11399+
/** A subscript of a column, such as {@code col[1]}, {@code col[1, 2]} or {@code col[1:3]}. */
11400+
ArrayConstructor Subscript() : {
11401+
ExpressionList<Expression> expList = new ExpressionList<Expression>();
11402+
Expression exp;
1134711403
} {
1134811404
"["
11349-
[LOOKAHEAD(3) idxExpr = ArrayIndexExpression()]
11405+
try {
11406+
{ subscriptDepth++; }
1135011407
[
11351-
(":" { startExpr=idxExpr; idxExpr=null; })
11352-
[stopExpr = SimpleExpression()]
11408+
exp=SubscriptElement() { expList.add(exp); }
11409+
( "," exp=SubscriptElement() { expList.add(exp); } )*
1135311410
]
11411+
} finally {
11412+
subscriptDepth--;
11413+
}
1135411414
"]"
11355-
{ expr = new ArrayExpression(obj, idxExpr, startExpr, stopExpr); }
11415+
{ return new ArrayConstructor(expList, false); }
11416+
}
11417+
11418+
Expression SubscriptElement():
11419+
{
11420+
Expression expression = null;
11421+
}
11422+
{
1135611423
(
11357-
LOOKAHEAD(2) "["
11358-
[LOOKAHEAD(3) idxExpr = ArrayIndexExpression()]
11359-
[
11360-
(":" { startExpr=idxExpr; idxExpr=null; })
11361-
[stopExpr = SimpleExpression()]
11362-
]
11363-
"]"
11364-
{ expr = new ArrayExpression(expr, idxExpr, startExpr, stopExpr); }
11365-
)*
11366-
{ return expr; }
11424+
LOOKAHEAD({ ":".equals(getToken(1).image) }) expression=SliceTail(null)
11425+
|
11426+
expression=Expression() [ expression=SliceTail(expression) ]
11427+
)
11428+
{ return expression; }
1136711429
}
1136811430

1136911431
/**
@@ -11618,7 +11680,7 @@ Expression PrimaryExpression() #PrimaryExpression:
1161811680
// Check for JSON operands
1161911681
[
1162011682
LOOKAHEAD(2, { !isPostgreSqlJsonAccess(getToken(1))
11621-
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0)
11683+
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0 && subscriptDepth == 0)
1162211684
|| !":".equals(getToken(1).image)) }) (
1162311685
LOOKAHEAD(2) (
1162411686
token="->"
@@ -11983,7 +12045,7 @@ JsonExpression JsonExpression(Expression expr, List<Map.Entry<Expression, String
1198312045

1198412046
(
1198512047
LOOKAHEAD(2, { !isPostgreSqlJsonAccess(getToken(1))
11986-
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0)
12048+
&& ((ternaryThenBranchDepth == 0 && mapKeyDepth == 0 && subscriptDepth == 0)
1198712049
|| !":".equals(getToken(1).image)) }) (
1198812050
token="->"
1198912051
|

0 commit comments

Comments
 (0)