Skip to content

Commit

Permalink
feat: Support BigQuery specific Aggregate clauses
Browse files Browse the repository at this point in the history
- HAVING { MIN | MAX }
- { IGNORE | RESPECT } NULLS
- LIMIT m

- fixes #1856

Signed-off-by: Andreas Reichel <[email protected]>
  • Loading branch information
manticore-projects committed Apr 4, 2024
1 parent ffddeef commit 0179cc0
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 41 deletions.
108 changes: 85 additions & 23 deletions src/main/java/net/sf/jsqlparser/expression/AnalyticExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.statement.select.Limit;
import net.sf.jsqlparser.statement.select.OrderByElement;

import java.util.List;
Expand All @@ -26,6 +27,7 @@
*/
public class AnalyticExpression extends ASTNodeAccessImpl implements Expression {


private String name;
private Expression expression;
private Expression offset;
Expand All @@ -35,42 +37,51 @@ public class AnalyticExpression extends ASTNodeAccessImpl implements Expression
private AnalyticType type = AnalyticType.OVER;
private boolean distinct = false;
private boolean unique = false;
private boolean ignoreNulls = false; // IGNORE NULLS inside function parameters
private boolean ignoreNullsOutside = false; // IGNORE NULLS outside function parameters
private Expression filterExpression = null;
private List<OrderByElement> funcOrderBy = null;
private String windowName = null; // refers to an external window definition (paritionBy,
// orderBy, windowElement)
private WindowDefinition windowDef = new WindowDefinition();

private Function.HavingClause havingClause;

private Function.NullHandling nullHandling = null;

private Limit limit = null;

public AnalyticExpression() {}

public AnalyticExpression(Function function) {
name = function.getName();
allColumns = function.isAllColumns();
distinct = function.isDistinct();
unique = function.isUnique();
funcOrderBy = function.getOrderByElements();
this.name = function.getName();
this.allColumns = function.isAllColumns();
this.distinct = function.isDistinct();
this.unique = function.isUnique();


ExpressionList<? extends Expression> list = function.getParameters();
if (list != null) {
if (list.getExpressions().size() > 3) {
if (list.size() > 3) {
throw new IllegalArgumentException(
"function object not valid to initialize analytic expression");
}

expression = list.get(0);
if (list.getExpressions().size() > 1) {
if (list.size() > 1) {
offset = list.get(1);
}
if (list.getExpressions().size() > 2) {
if (list.size() > 2) {
defaultValue = list.get(2);
}
}
ignoreNulls = function.isIgnoreNulls();
keep = function.getKeep();
this.havingClause = function.getHavingClause();
this.nullHandling = function.getNullHandling();
this.funcOrderBy = function.getOrderByElements();
this.limit = function.getLimit();
this.keep = function.getKeep();
}


@Override
public void accept(ExpressionVisitor expressionVisitor) {
expressionVisitor.visit(this);
Expand All @@ -92,15 +103,15 @@ public void setKeep(KeepExpression keep) {
this.keep = keep;
}

public ExpressionList getPartitionExpressionList() {
public ExpressionList<?> getPartitionExpressionList() {
return windowDef.partitionBy.getPartitionExpressionList();
}

public void setPartitionExpressionList(ExpressionList partitionExpressionList) {
public void setPartitionExpressionList(ExpressionList<?> partitionExpressionList) {
setPartitionExpressionList(partitionExpressionList, false);
}

public void setPartitionExpressionList(ExpressionList partitionExpressionList,
public void setPartitionExpressionList(ExpressionList<?> partitionExpressionList,
boolean brackets) {
windowDef.partitionBy.setPartitionExpressionList(partitionExpressionList, brackets);
}
Expand Down Expand Up @@ -174,11 +185,11 @@ public void setUnique(boolean unique) {
}

public boolean isIgnoreNulls() {
return ignoreNulls;
return this.nullHandling == Function.NullHandling.IGNORE_NULLS;
}

public void setIgnoreNulls(boolean ignoreNulls) {
this.ignoreNulls = ignoreNulls;
this.nullHandling = ignoreNulls ? Function.NullHandling.IGNORE_NULLS : null;
}

public boolean isIgnoreNullsOutside() {
Expand All @@ -205,6 +216,41 @@ public void setWindowDefinition(WindowDefinition windowDef) {
this.windowDef = windowDef;
}


public Function.HavingClause getHavingClause() {
return havingClause;
}

public AnalyticExpression setHavingClause(Function.HavingClause havingClause) {
this.havingClause = havingClause;
return this;
}

public AnalyticExpression setHavingClause(String havingType, Expression expression) {
this.havingClause = new Function.HavingClause(
Function.HavingClause.HavingType.valueOf(havingType.trim().toUpperCase()),
expression);
return this;
}

public Function.NullHandling getNullHandling() {
return nullHandling;
}

public AnalyticExpression setNullHandling(Function.NullHandling nullHandling) {
this.nullHandling = nullHandling;
return this;
}

public Limit getLimit() {
return limit;
}

public AnalyticExpression setLimit(Limit limit) {
this.limit = limit;
return this;
}

@Override
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity",
"PMD.MissingBreakInSwitch"})
Expand All @@ -216,32 +262,48 @@ public String toString() {
b.append("DISTINCT ");
}
if (expression != null) {
b.append(expression.toString());
b.append(expression);
if (offset != null) {
b.append(", ").append(offset.toString());
b.append(", ").append(offset);
if (defaultValue != null) {
b.append(", ").append(defaultValue.toString());
b.append(", ").append(defaultValue);
}
}
} else if (isAllColumns()) {
b.append("*");
}
if (isIgnoreNulls()) {
b.append(" IGNORE NULLS");

if (havingClause != null) {
havingClause.appendTo(b);
}

if (nullHandling != null) {
switch (nullHandling) {
case IGNORE_NULLS:
b.append(" IGNORE NULLS");
break;
case RESPECT_NULLS:
b.append(" RESPECT NULLS");
break;
}
}
if (funcOrderBy != null) {
b.append(" ORDER BY ");
b.append(funcOrderBy.stream().map(OrderByElement::toString).collect(joining(", ")));
}

if (limit != null) {
b.append(limit);
}

b.append(") ");
if (keep != null) {
b.append(keep.toString()).append(" ");
b.append(keep).append(" ");
}

if (filterExpression != null) {
b.append("FILTER (WHERE ");
b.append(filterExpression.toString());
b.append(filterExpression);
b.append(")");
if (type != AnalyticType.FILTER_ONLY) {
b.append(" ");
Expand Down
112 changes: 109 additions & 3 deletions src/main/java/net/sf/jsqlparser/expression/Function.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import net.sf.jsqlparser.expression.operators.relational.NamedExpressionList;
import net.sf.jsqlparser.parser.ASTNodeAccessImpl;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.Limit;
import net.sf.jsqlparser.statement.select.OrderByElement;

import java.util.Arrays;
Expand All @@ -22,6 +23,56 @@
* A function as MAX,COUNT...
*/
public class Function extends ASTNodeAccessImpl implements Expression {
public enum NullHandling {
IGNORE_NULLS, RESPECT_NULLS;
}

public static class HavingClause extends ASTNodeAccessImpl implements Expression {
enum HavingType {
MAX, MIN;
}

HavingType havingType;
Expression expression;

public HavingClause(HavingType havingType, Expression expression) {
this.havingType = havingType;
this.expression = expression;
}

public HavingType getHavingType() {
return havingType;
}

public HavingClause setHavingType(HavingType havingType) {
this.havingType = havingType;
return this;
}

public Expression getExpression() {
return expression;
}

public HavingClause setExpression(Expression expression) {
this.expression = expression;
return this;
}

@Override
public void accept(ExpressionVisitor expressionVisitor) {
expression.accept(expressionVisitor);
}

public StringBuilder appendTo(StringBuilder builder) {
builder.append(" HAVING ").append(havingType.name()).append(" ").append(expression);
return builder;
}

@Override
public String toString() {
return appendTo(new StringBuilder()).toString();
}
}

private List<String> nameparts;
private ExpressionList<?> parameters;
Expand All @@ -31,10 +82,14 @@ public class Function extends ASTNodeAccessImpl implements Expression {
private boolean unique = false;
private boolean isEscaped = false;
private Expression attributeExpression;
private HavingClause havingClause;
private Column attributeColumn = null;
private List<OrderByElement> orderByElements;
private NullHandling nullHandling = null;
private Limit limit = null;

private KeepExpression keep = null;
private boolean ignoreNulls = false;


public Function() {}

Expand Down Expand Up @@ -82,8 +137,26 @@ public void setAllColumns(boolean b) {
allColumns = b;
}

public NullHandling getNullHandling() {
return nullHandling;
}

public Function setNullHandling(NullHandling nullHandling) {
this.nullHandling = nullHandling;
return this;
}

public Limit getLimit() {
return limit;
}

public Function setLimit(Limit limit) {
this.limit = limit;
return this;
}

public boolean isIgnoreNulls() {
return ignoreNulls;
return nullHandling != null && nullHandling == NullHandling.IGNORE_NULLS;
}

/**
Expand All @@ -92,7 +165,22 @@ public boolean isIgnoreNulls() {
*
*/
public void setIgnoreNulls(boolean ignoreNulls) {
this.ignoreNulls = ignoreNulls;
this.nullHandling = ignoreNulls ? NullHandling.IGNORE_NULLS : null;
}

public HavingClause getHavingClause() {
return havingClause;
}

public Function setHavingClause(HavingClause havingClause) {
this.havingClause = havingClause;
return this;
}

public Function setHavingClause(String havingType, Expression expression) {
this.havingClause = new HavingClause(
HavingClause.HavingType.valueOf(havingType.trim().toUpperCase()), expression);
return this;
}

/**
Expand Down Expand Up @@ -226,6 +314,21 @@ public String toString() {
b.append("ALL ");
}
b.append(parameters);

if (havingClause != null) {
havingClause.appendTo(b);
}

if (nullHandling != null) {
switch (nullHandling) {
case IGNORE_NULLS:
b.append(" IGNORE NULLS");
break;
case RESPECT_NULLS:
b.append(" RESPECT NULLS");
break;
}
}
if (orderByElements != null) {
b.append(" ORDER BY ");
boolean comma = false;
Expand All @@ -238,6 +341,9 @@ public String toString() {
b.append(orderByElement);
}
}
if (limit != null) {
b.append(limit);
}
b.append(")");
params = b.toString();
} else {
Expand Down
Loading

0 comments on commit 0179cc0

Please sign in to comment.