diff --git a/src/main/java/net/sf/jsqlparser/statement/select/InterpolateElement.java b/src/main/java/net/sf/jsqlparser/statement/select/InterpolateElement.java
new file mode 100644
index 000000000..a571c4cf5
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/select/InterpolateElement.java
@@ -0,0 +1,73 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.select;
+
+import java.io.Serializable;
+
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.schema.Column;
+
+/**
+ * Models one column entry of the ClickHouse {@code INTERPOLATE} clause, which computes the values
+ * of non-sorted columns for the rows generated by {@code ORDER BY ... WITH FILL} (see
+ * ClickHouse:
+ * ORDER BY).
+ *
+ * {@code col AS expr} evaluates {@code expr} for the generated rows, while a bare {@code col} (a
+ * {@code null} expression) repeats the previous value of the column.
+ */
+public class InterpolateElement implements Serializable {
+
+ private Column column;
+ private Expression expression;
+
+ public InterpolateElement() {
+ super();
+ }
+
+ public InterpolateElement(Column column, Expression expression) {
+ this.column = column;
+ this.expression = expression;
+ }
+
+ public Column getColumn() {
+ return column;
+ }
+
+ public void setColumn(Column column) {
+ this.column = column;
+ }
+
+ public InterpolateElement withColumn(Column column) {
+ setColumn(column);
+ return this;
+ }
+
+ public Expression getExpression() {
+ return expression;
+ }
+
+ public void setExpression(Expression expression) {
+ this.expression = expression;
+ }
+
+ public InterpolateElement withExpression(Expression expression) {
+ setExpression(expression);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ if (expression != null) {
+ return column + " AS " + expression;
+ }
+ return column.toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/OrderByElement.java b/src/main/java/net/sf/jsqlparser/statement/select/OrderByElement.java
index 38b78384d..4662150fa 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/OrderByElement.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/OrderByElement.java
@@ -22,6 +22,7 @@ public class OrderByElement implements Serializable {
private boolean asc = true;
private boolean ascDescPresent = false;
private NullOrdering nullOrdering;
+ private WithFill withFill;
public boolean isAsc() {
return asc;
@@ -39,6 +40,20 @@ public void setNullOrdering(NullOrdering nullOrdering) {
this.nullOrdering = nullOrdering;
}
+ public WithFill getWithFill() {
+ return withFill;
+ }
+
+ public OrderByElement setWithFill(WithFill withFill) {
+ this.withFill = withFill;
+ return this;
+ }
+
+ public OrderByElement withWithFill(WithFill withFill) {
+ setWithFill(withFill);
+ return this;
+ }
+
public boolean isAscDescPresent() {
return ascDescPresent;
}
@@ -74,6 +89,9 @@ public String toString() {
b.append(' ');
b.append(nullOrdering == NullOrdering.NULLS_FIRST ? "NULLS FIRST" : "NULLS LAST");
}
+ if (withFill != null) {
+ b.append(' ').append(withFill);
+ }
if (isMysqlWithRollup()) {
b.append(" WITH ROLLUP");
}
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/Select.java b/src/main/java/net/sf/jsqlparser/statement/select/Select.java
index 737608020..0dc57e32a 100644
--- a/src/main/java/net/sf/jsqlparser/statement/select/Select.java
+++ b/src/main/java/net/sf/jsqlparser/statement/select/Select.java
@@ -36,6 +36,7 @@ public abstract class Select extends ASTNodeAccessImpl implements Statement, Exp
ForClause forClause = null;
List orderByElements;
+ List interpolate;
ForMode forMode = null;
private boolean skipLocked;
private Wait wait;
@@ -54,6 +55,16 @@ public static String orderByToString(boolean oracleSiblings,
return getFormattedList(orderByElements, oracleSiblings ? "ORDER SIBLINGS BY" : "ORDER BY");
}
+ public static String interpolateToString(List interpolate) {
+ if (interpolate == null) {
+ return "";
+ }
+ if (interpolate.isEmpty()) {
+ return " INTERPOLATE";
+ }
+ return getFormattedList(interpolate, "INTERPOLATE", true, true);
+ }
+
public static String getFormattedList(List> list, String expression) {
return getFormattedList(list, expression, true, false);
}
@@ -192,6 +203,19 @@ public void setOrderByElements(List orderByElements) {
this.orderByElements = orderByElements;
}
+ public List getInterpolate() {
+ return interpolate;
+ }
+
+ public void setInterpolate(List interpolate) {
+ this.interpolate = interpolate;
+ }
+
+ public Select withInterpolate(List interpolate) {
+ setInterpolate(interpolate);
+ return this;
+ }
+
public Select withOrderByElements(List orderByElements) {
this.setOrderByElements(orderByElements);
return this;
@@ -463,6 +487,7 @@ public StringBuilder appendTo(StringBuilder builder) {
if (!forUpdateBeforeOrderBy) {
builder.append(orderByToString(oracleSiblings, orderByElements));
+ builder.append(interpolateToString(interpolate));
}
if (forClause != null) {
@@ -512,6 +537,7 @@ public StringBuilder appendTo(StringBuilder builder) {
if (forUpdateBeforeOrderBy) {
builder.append(orderByToString(oracleSiblings, orderByElements));
+ builder.append(interpolateToString(interpolate));
}
return builder;
diff --git a/src/main/java/net/sf/jsqlparser/statement/select/WithFill.java b/src/main/java/net/sf/jsqlparser/statement/select/WithFill.java
new file mode 100644
index 000000000..9da567251
--- /dev/null
+++ b/src/main/java/net/sf/jsqlparser/statement/select/WithFill.java
@@ -0,0 +1,102 @@
+/*-
+ * #%L
+ * JSQLParser library
+ * %%
+ * Copyright (C) 2004 - 2019 JSQLParser
+ * %%
+ * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
+ * #L%
+ */
+package net.sf.jsqlparser.statement.select;
+
+import java.io.Serializable;
+
+import net.sf.jsqlparser.expression.Expression;
+
+/**
+ * Models the ClickHouse {@code WITH FILL} modifier of an {@link OrderByElement}, which fills gaps
+ * in the sorted sequence of the order by expression (see
+ * ClickHouse:
+ * ORDER BY).
+ *
+ * The modifier takes optional {@code FROM}, {@code TO}, {@code STEP} and {@code STALENESS} bounds,
+ * where the step may be an {@code INTERVAL} literal. Its presence on an {@link OrderByElement}
+ * marks the element as filled.
+ */
+public class WithFill implements Serializable {
+
+ private Expression from;
+ private Expression to;
+ private Expression step;
+ private Expression staleness;
+
+ public Expression getFrom() {
+ return from;
+ }
+
+ public void setFrom(Expression from) {
+ this.from = from;
+ }
+
+ public WithFill withFrom(Expression from) {
+ setFrom(from);
+ return this;
+ }
+
+ public Expression getTo() {
+ return to;
+ }
+
+ public void setTo(Expression to) {
+ this.to = to;
+ }
+
+ public WithFill withTo(Expression to) {
+ setTo(to);
+ return this;
+ }
+
+ public Expression getStep() {
+ return step;
+ }
+
+ public void setStep(Expression step) {
+ this.step = step;
+ }
+
+ public WithFill withStep(Expression step) {
+ setStep(step);
+ return this;
+ }
+
+ public Expression getStaleness() {
+ return staleness;
+ }
+
+ public void setStaleness(Expression staleness) {
+ this.staleness = staleness;
+ }
+
+ public WithFill withStaleness(Expression staleness) {
+ setStaleness(staleness);
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder("WITH FILL");
+ if (from != null) {
+ b.append(" FROM ").append(from);
+ }
+ if (to != null) {
+ b.append(" TO ").append(to);
+ }
+ if (step != null) {
+ b.append(" STEP ").append(step);
+ }
+ if (staleness != null) {
+ b.append(" STALENESS ").append(staleness);
+ }
+ return b.toString();
+ }
+}
diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java
index 605c54dc2..66e20bebb 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/OrderByDeParser.java
@@ -14,6 +14,7 @@
import net.sf.jsqlparser.expression.ExpressionVisitor;
import net.sf.jsqlparser.statement.select.OrderByElement;
+import net.sf.jsqlparser.statement.select.WithFill;
public class OrderByDeParser extends AbstractDeParser> {
@@ -64,11 +65,35 @@ public void deParseElement(OrderByElement orderBy) {
? "NULLS FIRST"
: "NULLS LAST");
}
+ if (orderBy.getWithFill() != null) {
+ builder.append(' ');
+ deParseWithFill(orderBy.getWithFill());
+ }
if (orderBy.isMysqlWithRollup()) {
builder.append(" WITH ROLLUP");
}
}
+ private void deParseWithFill(WithFill withFill) {
+ builder.append("WITH FILL");
+ if (withFill.getFrom() != null) {
+ builder.append(" FROM ");
+ withFill.getFrom().accept(expressionVisitor, null);
+ }
+ if (withFill.getTo() != null) {
+ builder.append(" TO ");
+ withFill.getTo().accept(expressionVisitor, null);
+ }
+ if (withFill.getStep() != null) {
+ builder.append(" STEP ");
+ withFill.getStep().accept(expressionVisitor, null);
+ }
+ if (withFill.getStaleness() != null) {
+ builder.append(" STALENESS ");
+ withFill.getStaleness().accept(expressionVisitor, null);
+ }
+ }
+
void setExpressionVisitor(ExpressionVisitor expressionVisitor) {
this.expressionVisitor = expressionVisitor;
}
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 50edf3b70..46421c1c8 100644
--- a/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
+++ b/src/main/java/net/sf/jsqlparser/util/deparser/SelectDeParser.java
@@ -51,6 +51,7 @@
import net.sf.jsqlparser.statement.select.First;
import net.sf.jsqlparser.statement.select.FromItem;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
+import net.sf.jsqlparser.statement.select.InterpolateElement;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
import net.sf.jsqlparser.statement.select.LateralView;
@@ -136,6 +137,7 @@ public StringBuilder visit(ParenthesedSelect select, S context) {
if (select.getOrderByElements() != null) {
new OrderByDeParser(expressionVisitor, builder).deParse(select.isOracleSiblings(),
select.getOrderByElements());
+ deParseInterpolate(select.getInterpolate());
}
Alias alias = select.getAlias();
@@ -471,9 +473,33 @@ protected void deparseOrderByElementsClause(PlainSelect plainSelect,
if (orderByElements != null) {
new OrderByDeParser(expressionVisitor, builder).deParse(plainSelect.isOracleSiblings(),
orderByElements);
+ deParseInterpolate(plainSelect.getInterpolate());
}
}
+ private void deParseInterpolate(List interpolate) {
+ if (interpolate == null) {
+ return;
+ }
+ if (interpolate.isEmpty()) {
+ builder.append(" INTERPOLATE");
+ return;
+ }
+ builder.append(" INTERPOLATE (");
+ for (Iterator iter = interpolate.iterator(); iter.hasNext();) {
+ InterpolateElement element = iter.next();
+ element.getColumn().accept(expressionVisitor, null);
+ if (element.getExpression() != null) {
+ builder.append(" AS ");
+ element.getExpression().accept(expressionVisitor, null);
+ }
+ if (iter.hasNext()) {
+ builder.append(", ");
+ }
+ }
+ builder.append(")");
+ }
+
@Override
public StringBuilder visit(SelectItem> selectItem, S context) {
selectItem.getExpression().accept(expressionVisitor, context);
@@ -729,6 +755,7 @@ public StringBuilder visit(SetOperationList list, S context) {
}
if (list.getOrderByElements() != null) {
new OrderByDeParser(expressionVisitor, builder).deParse(list.getOrderByElements());
+ deParseInterpolate(list.getInterpolate());
}
if (list.getLimit() != null) {
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java
index 858702dda..8e04d7df9 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/OrderByValidator.java
@@ -32,6 +32,12 @@ public Void visit(OrderByElement orderBy, S context) {
validateOptionalFeature(c, orderBy.getNullOrdering(), Feature.orderByNullOrdering);
}
getValidator(ExpressionValidator.class).validate(orderBy.getExpression());
+ if (orderBy.getWithFill() != null) {
+ validateOptionalExpression(orderBy.getWithFill().getFrom());
+ validateOptionalExpression(orderBy.getWithFill().getTo());
+ validateOptionalExpression(orderBy.getWithFill().getStep());
+ validateOptionalExpression(orderBy.getWithFill().getStaleness());
+ }
return null;
}
diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
index 5cab04030..92334db52 100644
--- a/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
+++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/SelectValidator.java
@@ -21,6 +21,7 @@
import net.sf.jsqlparser.statement.select.Fetch;
import net.sf.jsqlparser.statement.select.ForMode;
import net.sf.jsqlparser.statement.select.FromItemVisitor;
+import net.sf.jsqlparser.statement.select.InterpolateElement;
import net.sf.jsqlparser.statement.select.IntersectOp;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.LateralSubSelect;
@@ -136,6 +137,7 @@ public Void visit(PlainSelect plainSelect, S context) {
validateOptionalExpression(plainSelect.getHaving());
validateOptionalOrderByElements(plainSelect.getOrderByElements());
+ validateOptionalInterpolate(plainSelect.getInterpolate());
if (plainSelect.getLimit() != null) {
getValidator(LimitValidator.class).validate(plainSelect.getLimit());
@@ -163,6 +165,15 @@ private void validateMySqlSelectIntoClause(MySqlSelectIntoClause mySqlSelectInto
validateOptionalExpression(mySqlSelectIntoClause.getLinesTerminatedBy());
}
+ private void validateOptionalInterpolate(List interpolate) {
+ if (interpolate != null) {
+ for (InterpolateElement element : interpolate) {
+ validateOptionalExpression(element.getColumn());
+ validateOptionalExpression(element.getExpression());
+ }
+ }
+ }
+
@Override
public Void visit(SelectItem> selectExpressionItem, S context) {
selectExpressionItem.getExpression().accept(getValidator(ExpressionValidator.class),
@@ -308,6 +319,7 @@ public Void visit(SetOperationList setOperation, S context) {
}
validateOptionalOrderByElements(setOperation.getOrderByElements());
+ validateOptionalInterpolate(setOperation.getInterpolate());
if (setOperation.getLimit() != null) {
getValidator(LimitValidator.class).validate(setOperation.getLimit());
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 99e62720f..5f8e91d11 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -1279,6 +1279,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -1303,6 +1304,7 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -1453,7 +1455,9 @@ String NonReservedWord() :
| tk=
| tk=
| tk=
+ | tk=
| tk=
+ | tk=
| tk=
| tk=
| tk=
@@ -4686,6 +4690,7 @@ Select Select() #Select:
Select select = null;
List> with = null;
List orderByElements = null;
+ List interpolateElements = null;
Limit limit = null;
Offset offset = null;
Fetch fetch = null;
@@ -4693,7 +4698,6 @@ Select Select() #Select:
Alias alias = null;
}
{
-
[ with=WithList() ]
(
LOOKAHEAD(3) select = FromQuery()
@@ -4710,6 +4714,7 @@ Select Select() #Select:
[ LOOKAHEAD(2) select = SetOperationList(select) ]
[ LOOKAHEAD( ) orderByElements = OrderByElements() { select.setOrderByElements(orderByElements); } ]
+ [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { select.setInterpolate(interpolateElements); } ]
[ LOOKAHEAD() limit=LimitWithOffset() {select.setLimit(limit);} ]
[ LOOKAHEAD() offset = Offset() { select.setOffset(offset);} ]
@@ -5334,6 +5339,7 @@ PlainSelect PlainSelect() #PlainSelect:
Expression where = null;
ForClause forClause = null;
List orderByElements;
+ List interpolateElements = null;
GroupByElement groupBy = null;
Expression having = null;
Expression qualify;
@@ -5462,6 +5468,7 @@ PlainSelect PlainSelect() #PlainSelect:
{ plainSelect.setWindowDefinitions(winDefs); }
]
[ LOOKAHEAD( ) orderByElements = OrderByElements() { plainSelect.setOrderByElements(orderByElements); } ]
+ [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ]
[ LOOKAHEAD(2) forClause = ForClause() {plainSelect.setForClause(forClause);} ]
[ LOOKAHEAD(2) { plainSelect.setEmitChanges(true); } ]
// Parse the LIMIT row count once (this accepts a parenthesized subquery too), then
@@ -5502,6 +5509,7 @@ PlainSelect PlainSelect() #PlainSelect:
[ LOOKAHEAD( ) orderByElements = OrderByElements()
{ plainSelect.setOrderByElements(orderByElements); plainSelect.setForUpdateBeforeOrderBy(true); }
]
+ [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { plainSelect.setInterpolate(interpolateElements); } ]
]
[ LOOKAHEAD( ( | ))
mySqlSelectIntoClause = MySqlSelectIntoClause(MySqlSelectIntoClause.Position.TRAILING)
@@ -5528,6 +5536,7 @@ PlainSelect PlainSelect() #PlainSelect:
Select SetOperationList(Select select) #SetOperationList: {
SetOperationList list = new SetOperationList();
List orderByElements = null;
+ List interpolateElements = null;
Limit limit = null;
Offset offset = null;
Fetch fetch = null;
@@ -5575,6 +5584,7 @@ Select SetOperationList(Select select) #SetOperationList: {
)+
[ LOOKAHEAD(2) orderByElements=OrderByElements() {list.setOrderByElements(orderByElements);} ]
+ [ LOOKAHEAD(2) interpolateElements = InterpolateClause() { list.setInterpolate(interpolateElements); } ]
[ LOOKAHEAD() limit = LimitWithOffset() { list.setLimit(limit); } ]
[ LOOKAHEAD() offset = Offset() { list.setOffset(offset); } ]
[ LOOKAHEAD(, { limit==null }) limit = LimitWithOffset() { list.setLimit(limit); } ]
@@ -5592,6 +5602,10 @@ Select SetOperationList(Select select) #SetOperationList: {
ps.setLimit(null);
ps.setOffset(null);
}
+ if (ps.getInterpolate() != null) {
+ list.setInterpolate(ps.getInterpolate());
+ ps.setInterpolate(null);
+ }
if (ps.getFetch() != null) {
list.setFetch(ps.getFetch());
ps.setFetch(null);
@@ -6778,6 +6792,7 @@ OrderByElement OrderByElement():
OrderByElement orderByElement = new OrderByElement();
Expression columnReference = null;
Token collateToken = null;
+ WithFill withFill = null;
}
{
columnReference = Expression()
@@ -6791,6 +6806,7 @@ OrderByElement OrderByElement():
)
]
]
+ [ LOOKAHEAD(2) withFill = WithFill() { orderByElement.setWithFill(withFill); } ]
[ LOOKAHEAD(2) { orderByElement.setMysqlWithRollup(true); } ]
{
orderByElement.setExpression(columnReference);
@@ -6798,6 +6814,47 @@ OrderByElement OrderByElement():
}
}
+WithFill WithFill():
+{
+ WithFill withFill = new WithFill();
+ Expression fillExpression = null;
+}
+{
+
+ [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setFrom(fillExpression); } ]
+ [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setTo(fillExpression); } ]
+ [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setStep(fillExpression); } ]
+ [ LOOKAHEAD(2) fillExpression = Expression() { withFill.setStaleness(fillExpression); } ]
+ { return withFill; }
+}
+
+List InterpolateClause():
+{
+ List interpolateElements = new ArrayList();
+ InterpolateElement interpolateElement = null;
+}
+{
+
+ [ "("
+ [ interpolateElement = InterpolateElement() { interpolateElements.add(interpolateElement); }
+ ( "," interpolateElement = InterpolateElement() { interpolateElements.add(interpolateElement); } )*
+ ]
+ ")" ]
+ { return interpolateElements; }
+}
+
+InterpolateElement InterpolateElement():
+{
+ InterpolateElement interpolateElement = new InterpolateElement();
+ Column column = null;
+ Expression expression = null;
+}
+{
+ column = Column() { interpolateElement.setColumn(column); }
+ [ expression = Expression() { interpolateElement.setExpression(expression); } ]
+ { return interpolateElement; }
+}
+
JdbcParameter JdbcParameter() : {
Token tk;
JdbcParameter retval;
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 90f69ffd4..cfdca4e77 100644
--- a/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java
+++ b/src/test/java/net/sf/jsqlparser/statement/select/ClickHouseTest.java
@@ -147,6 +147,105 @@ public void testMultipleSettingsClauseIssue2362() throws JSQLParserException {
Assertions.assertEquals(2, select.getSettings().size());
}
+ @Test
+ public void testOrderByWithFillIssue2467() throws JSQLParserException {
+ String sql =
+ "SELECT d, v FROM t ORDER BY d WITH FILL FROM toDate('2024-01-01') TO toDate('2024-02-01') STEP INTERVAL 1 DAY INTERPOLATE (v AS v + 1)";
+ PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+
+ WithFill withFill = select.getOrderByElements().get(0).getWithFill();
+ Assertions.assertNotNull(withFill);
+ Assertions.assertEquals("toDate('2024-01-01')", withFill.getFrom().toString());
+ Assertions.assertEquals("toDate('2024-02-01')", withFill.getTo().toString());
+ Assertions.assertEquals("INTERVAL 1 DAY", withFill.getStep().toString());
+ Assertions.assertNull(withFill.getStaleness());
+
+ Assertions.assertEquals(1, select.getInterpolate().size());
+ Assertions.assertEquals("v", select.getInterpolate().get(0).getColumn().getColumnName());
+ Assertions.assertEquals("v + 1", select.getInterpolate().get(0).getExpression().toString());
+ }
+
+ @Test
+ public void testOrderByWithFillOnly() throws JSQLParserException {
+ String sql = "SELECT key, value FROM t ORDER BY key WITH FILL";
+ PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+ Assertions.assertNotNull(select.getOrderByElements().get(0).getWithFill());
+ Assertions.assertNull(select.getOrderByElements().get(0).getWithFill().getFrom());
+ }
+
+ @Test
+ public void testOrderByWithFillFromToStep() throws JSQLParserException {
+ String sql = "SELECT n FROM t ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5";
+ PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+ WithFill withFill = select.getOrderByElements().get(0).getWithFill();
+ Assertions.assertEquals("0", withFill.getFrom().toString());
+ Assertions.assertEquals("5.51", withFill.getTo().toString());
+ Assertions.assertEquals("0.5", withFill.getStep().toString());
+ }
+
+ @Test
+ public void testOrderByWithFillStepIntervalAndStaleness() throws JSQLParserException {
+ String sql =
+ "SELECT d1, d2 FROM t ORDER BY d1 WITH FILL STEP INTERVAL 1 DAY STALENESS 3, d2 WITH FILL";
+ PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+ Assertions.assertEquals(2, select.getOrderByElements().size());
+ WithFill first = select.getOrderByElements().get(0).getWithFill();
+ Assertions.assertEquals("INTERVAL 1 DAY", first.getStep().toString());
+ Assertions.assertEquals("3", first.getStaleness().toString());
+ Assertions.assertNotNull(select.getOrderByElements().get(1).getWithFill());
+ }
+
+ @Test
+ public void testOrderByWithFillDescNullsLast() throws JSQLParserException {
+ String sql = "SELECT d FROM t ORDER BY d DESC NULLS LAST WITH FILL TO 10";
+ PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+ OrderByElement element = select.getOrderByElements().get(0);
+ Assertions.assertFalse(element.isAsc());
+ Assertions.assertEquals(OrderByElement.NullOrdering.NULLS_LAST, element.getNullOrdering());
+ Assertions.assertEquals("10", element.getWithFill().getTo().toString());
+ }
+
+ @Test
+ public void testOrderByWithFillInterpolateVariants() throws JSQLParserException {
+ // multiple items, with and without AS
+ String sql = "SELECT a, b, c FROM t ORDER BY a WITH FILL INTERPOLATE (b, c AS c + 1)";
+ PlainSelect select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+ Assertions.assertEquals(2, select.getInterpolate().size());
+ Assertions.assertNull(select.getInterpolate().get(0).getExpression());
+ Assertions.assertEquals("c + 1", select.getInterpolate().get(1).getExpression().toString());
+
+ // bare INTERPOLATE fills all allowed columns
+ sql = "SELECT a, b FROM t ORDER BY a WITH FILL INTERPOLATE";
+ select = (PlainSelect) assertSqlCanBeParsedAndDeparsed(sql, true);
+ Assertions.assertNotNull(select.getInterpolate());
+ Assertions.assertTrue(select.getInterpolate().isEmpty());
+ }
+
+ @Test
+ public void testOrderByWithFillUnionInterpolate() throws JSQLParserException {
+ String sql =
+ "SELECT a FROM t1 UNION SELECT a FROM t2 ORDER BY a WITH FILL INTERPOLATE (a AS a + 1) LIMIT 10";
+ Select select = (Select) assertSqlCanBeParsedAndDeparsed(sql, true);
+ SetOperationList setOperationList = (SetOperationList) select.getSelectBody();
+ Assertions.assertNotNull(setOperationList.getOrderByElements().get(0).getWithFill());
+ Assertions.assertEquals(1, setOperationList.getInterpolate().size());
+ }
+
+ @Test
+ public void testOrderByWithFillSubquery() throws JSQLParserException {
+ String sql = "SELECT * FROM (SELECT d, v FROM t ORDER BY d WITH FILL FROM 0 TO 9) AS sub";
+ assertSqlCanBeParsedAndDeparsed(sql, true);
+ }
+
+ @Test
+ public void testFillStepInterpolateStalenessAsColumnNames() throws JSQLParserException {
+ // the new keywords stay non-reserved and must remain usable as plain column names
+ assertSqlCanBeParsedAndDeparsed("SELECT fill, step, staleness FROM t ORDER BY interpolate",
+ true);
+ assertSqlCanBeParsedAndDeparsed("SELECT interpolate FROM t WHERE fill = 1 AND step = 2",
+ true);
+ }
+
@Test
public void testCastToNestedParametricTypeIssue2441() throws JSQLParserException {
// ClickHouse allows parametric (constructor-style) data types as a CAST target,