diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLExpressions.java b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLExpressions.java index 1fe77e267..266496884 100644 --- a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLExpressions.java +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLExpressions.java @@ -333,6 +333,113 @@ public final class SQLExpressions { return DateOperation.create((Class)date.getType(), Ops.DateTimeOps.ADD_DAYS, date, ConstantImpl.create(days)); } + /** + * @param expr + * @return + */ + public static WindowOver sum(Expression expr) { + return new WindowOver((Class)expr.getType(), Ops.AggOps.SUM_AGG, expr); + } + + /** + * @param expr + * @return + */ + public static WindowOver count(Expression expr) { + return new WindowOver(Long.class, Ops.AggOps.COUNT_AGG, expr); + } + + /** + * @param expr + * @return + */ + public static WindowOver avg(Expression expr) { + return new WindowOver((Class)expr.getType(), Ops.AggOps.AVG_AGG, expr); + } + + /** + * @param expr + * @return + */ + public static WindowOver min(Expression expr) { + return new WindowOver((Class)expr.getType(), Ops.AggOps.MIN_AGG, expr); + } + + /** + * @param expr + * @return + */ + public static WindowOver max(Expression expr) { + return new WindowOver((Class)expr.getType(), Ops.AggOps.MAX_AGG, expr); + } + + /** + * expr evaluated at the row that is one row after the current row within the partition; + * + * @param expr + * @return + */ + public static WindowOver lead(Expression expr) { + return new WindowOver((Class)expr.getType(), SQLTemplates.LEAD, expr); + } + + /** + * expr evaluated at the row that is one row before the current row within the partition + * + * @param expr + * @return + */ + public static WindowOver lag(Expression expr) { + return new WindowOver((Class)expr.getType(), SQLTemplates.LAG, expr); + } + + /** + * rank of the current row with gaps; same as row_number of its first peer + * + * @return + */ + public static WindowOver rank() { + return new WindowOver(Long.class, SQLTemplates.RANK); + } + + /** + * rank of the current row without gaps; this function counts peer groups + * + * @return + */ + public static WindowOver denseRank() { + return new WindowOver(Long.class, SQLTemplates.DENSERANK); + } + + /** + * number of the current row within its partition, counting from 1 + * + * @return + */ + public static WindowOver rowNumber() { + return new WindowOver(Long.class, SQLTemplates.ROWNUMBER); + } + + /** + * returns value evaluated at the row that is the first row of the window frame + * + * @param expr + * @return + */ + public static WindowOver first(Expression expr) { + return new WindowOver((Class)expr.getType(), SQLTemplates.FIRST, expr); + } + + /** + * returns value evaluated at the row that is the last row of the window frame + * + * @param expr + * @return + */ + public static WindowOver last(Expression expr) { + return new WindowOver((Class)expr.getType(), SQLTemplates.LAST, expr); + } + private SQLExpressions() {} } diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLTemplates.java b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLTemplates.java index e92a39be5..07471e137 100644 --- a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLTemplates.java +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLTemplates.java @@ -46,6 +46,20 @@ public class SQLTemplates extends Templates { public static final Operator NEXTVAL = new OperatorImpl("SQL_NEXTVAL"); + public static final Operator ROWNUMBER = new OperatorImpl("ROWNUMBER"); + + public static final Operator RANK = new OperatorImpl("RANK"); + + public static final Operator DENSERANK = new OperatorImpl("DENSERANK"); + + public static final Operator FIRST = new OperatorImpl("FIRST"); + + public static final Operator LAST = new OperatorImpl("LAST"); + + public static final Operator LEAD = new OperatorImpl("LAST"); + + public static final Operator LAG = new OperatorImpl("LAST"); + public static final SQLTemplates DEFAULT = new SQLTemplates("\"",'\\',false); public static abstract class Builder { @@ -260,6 +274,14 @@ public class SQLTemplates extends Templates { add(UNION_ALL, "{0}\nunion all\n{1}", 1); add(NEXTVAL, "nextval('{0s}')"); + add(ROWNUMBER, "row_number()"); + add(RANK, "rank()"); + add(DENSERANK, "dense_rank()"); + add(FIRST, "first({0})"); + add(LAST, "last({0})"); + add(LEAD, "lead({0})"); + add(LAG, "lag({0})"); + add(Ops.AggOps.BOOLEAN_ANY, "some({0})"); add(Ops.AggOps.BOOLEAN_ALL, "every({0})"); diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/WindowFunction.java b/querydsl-sql/src/main/java/com/mysema/query/sql/WindowFunction.java new file mode 100644 index 000000000..2cc8dc100 --- /dev/null +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/WindowFunction.java @@ -0,0 +1,115 @@ +/* + * Copyright 2011, Mysema Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mysema.query.sql; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Nullable; + +import com.mysema.query.types.Expression; +import com.mysema.query.types.MutableExpressionBase; +import com.mysema.query.types.Visitor; +import com.mysema.query.types.expr.SimpleExpression; +import com.mysema.query.types.template.SimpleTemplate; + +/** + * @author tiwe + */ +public class WindowFunction extends MutableExpressionBase { + + private static final long serialVersionUID = -4130672293308756779L; + + // TODO : change this to List> + private List> orderBy = new ArrayList>(); + + @Nullable + private Expression partitionBy; + + private final Expression target; + + private volatile SimpleExpression value; + + public WindowFunction(Expression expr) { + super(expr.getType()); + this.target = expr; + } + + public SimpleExpression getValue() { + if (value == null) { + List> args = new ArrayList>(); + StringBuilder builder = new StringBuilder(); + builder.append("{0} over ("); + args.add(target); + if (partitionBy != null) { + builder.append("partition by {1}"); + args.add(partitionBy); + } + if (!orderBy.isEmpty()) { + if (partitionBy != null) { + builder.append(" "); + } + builder.append("order by "); + boolean first = true; + for (Expression expr : orderBy) { + if (!first) { + builder.append(", "); + } + builder.append("{" + args.size()+"}"); + args.add(expr); + first = false; + } + } + builder.append(")"); + value = SimpleTemplate.create( + (Class)target.getType(), + builder.toString(), + args.toArray(new Expression[args.size()])); + } + return value; + } + + @Override + public R accept(Visitor v, C context) { + return getValue().accept(v, context); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof WindowFunction) { + WindowFunction so = (WindowFunction)o; + return so.target.equals(target) + && so.partitionBy.equals(partitionBy) + && so.orderBy.equals(orderBy); + } else { + return false; + } + } + + public WindowFunction orderBy(Expression... orderBy) { + value = null; + this.orderBy.addAll(Arrays.asList(orderBy)); + return this; + } + + public WindowFunction partition(Expression partitionBy) { + value = null; + this.partitionBy = partitionBy; + return this; + } + +} diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/WindowOver.java b/querydsl-sql/src/main/java/com/mysema/query/sql/WindowOver.java new file mode 100644 index 000000000..4fac2d6f8 --- /dev/null +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/WindowOver.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011, Mysema Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.mysema.query.sql; + +import com.google.common.collect.ImmutableList; +import com.mysema.query.types.Expression; +import com.mysema.query.types.Operator; +import com.mysema.query.types.expr.SimpleOperation; + +/** + * WindowOver is the first part of a WindowFunction construction + * + * @author tiwe + * + * @param + */ +public class WindowOver extends SimpleOperation { + + private static final long serialVersionUID = 464583892898579544L; + + public WindowOver(Class type, Operator op) { + super(type, op, ImmutableList.>of()); + } + + public WindowOver(Class type, Operator op, Expression arg) { + super(type, op, ImmutableList.>of(arg)); + } + + public WindowFunction over() { + return new WindowFunction(this); + } + +} diff --git a/querydsl-sql/src/test/java/com/mysema/query/sql/WindowFunctionTest.java b/querydsl-sql/src/test/java/com/mysema/query/sql/WindowFunctionTest.java new file mode 100644 index 000000000..67f3406e5 --- /dev/null +++ b/querydsl-sql/src/test/java/com/mysema/query/sql/WindowFunctionTest.java @@ -0,0 +1,43 @@ +package com.mysema.query.sql; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.mysema.query.support.Expressions; +import com.mysema.query.types.Expression; +import com.mysema.query.types.path.NumberPath; + +public class WindowFunctionTest { + + private static String toString(Expression e) { + return new SQLSerializer(SQLTemplates.DEFAULT).handle(e).toString(); + } + + @Test + public void Complex() { + NumberPath path = Expressions.numberPath(Long.class, "path"); + NumberPath path2 = Expressions.numberPath(Long.class, "path2"); + Expression wf = SQLExpressions.sum(path).over().partition(path2).orderBy(path); + assertEquals("sum(path) over (partition by path2 order by path)", toString(wf)); + } + + @Test + public void All() { + NumberPath path = Expressions.numberPath(Long.class, "path"); + assertEquals("sum(path)", toString(SQLExpressions.sum(path))); + assertEquals("count(path)", toString(SQLExpressions.count(path))); + assertEquals("avg(path)", toString(SQLExpressions.avg(path))); + assertEquals("min(path)", toString(SQLExpressions.min(path))); + assertEquals("max(path)", toString(SQLExpressions.max(path))); + assertEquals("lead(path)", toString(SQLExpressions.lead(path))); + assertEquals("lag(path)", toString(SQLExpressions.lag(path))); + assertEquals("rank()", toString(SQLExpressions.rank())); + assertEquals("dense_rank()", toString(SQLExpressions.denseRank())); + assertEquals("row_number()", toString(SQLExpressions.rowNumber())); + assertEquals("first(path)", toString(SQLExpressions.first(path))); + assertEquals("last(path)", toString(SQLExpressions.last(path))); + + } + +}