Add initial support for common table expressions #486

This commit is contained in:
Timo Westkämper 2013-09-14 17:46:59 +03:00
parent 5cdd255077
commit 2befe61df1
7 changed files with 221 additions and 70 deletions

View File

@ -1,6 +1,6 @@
/*
* 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
@ -21,94 +21,99 @@ import com.mysema.query.types.TemplateExpressionImpl;
/**
* Defines a positioned flag in a Query for customization of query serialization
*
*
* @author tiwe
*
*/
public class QueryFlag implements Serializable{
private static final long serialVersionUID = -7131081607441961628L;
public enum Position {
public enum Position {
/**
* Start of the query
*
*/
WITH,
/**
* Start of the query
*/
START,
/**
* Override for the first element (e.g SELECT, INSERT)
*/
START_OVERRIDE,
/**
* After the first element (after select)
*/
AFTER_SELECT,
/**
* After the projection (after select ...)
*/
AFTER_PROJECTION,
/**
* Before the filter conditions (where)
*/
BEFORE_FILTERS,
/**
* After the filter conditions (where)
*/
AFTER_FILTERS,
/**
* Before group by
*/
BEFORE_GROUP_BY,
/**
* After group by
* After group by
*/
AFTER_GROUP_BY,
/**
* Before having
*/
BEFORE_HAVING,
/**
* After having
*/
AFTER_HAVING,
/**
* Before order (by)
*/
BEFORE_ORDER,
/**
* After order (by)
*/
AFTER_ORDER,
/**
* After all other tokens
* After all other tokens
*/
END
END
}
private final Position position;
private final Expression<?> flag;
public QueryFlag(Position position, String flag) {
this(position, TemplateExpressionImpl.create(Object.class, flag));
}
public QueryFlag(Position position, Expression<?> flag) {
this.position = position;
this.flag = flag;
this.flag = flag;
}
public Position getPosition() {

View File

@ -44,6 +44,8 @@ import com.mysema.query.support.QueryMixin;
import com.mysema.query.types.Expression;
import com.mysema.query.types.ExpressionUtils;
import com.mysema.query.types.FactoryExpression;
import com.mysema.query.types.OperationImpl;
import com.mysema.query.types.Ops;
import com.mysema.query.types.ParamExpression;
import com.mysema.query.types.ParamNotSetException;
import com.mysema.query.types.Path;
@ -724,6 +726,18 @@ public abstract class AbstractSQLQuery<Q extends AbstractSQLQuery<Q> & Query<Q>>
return uniqueResult(iterator);
}
@Override
public Q withRecursive(Path<?> alias, SubQueryExpression<?> query) {
queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, SQLTemplates.RECURSIVE));
return with(alias, query);
}
@Override
public Q with(Path<?> alias, SubQueryExpression<?> query) {
Expression<?> expr = OperationImpl.create(alias.getType(), Ops.ALIAS, alias, query);
return queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, expr));
}
private long unsafeCount() throws SQLException {
final String queryString = buildQueryString(true);
if (logger.isDebugEnabled()) {

View File

@ -1,6 +1,6 @@
/*
* 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
@ -22,6 +22,8 @@ import com.mysema.query.support.DetachableQuery;
import com.mysema.query.support.QueryMixin;
import com.mysema.query.types.Expression;
import com.mysema.query.types.ExpressionUtils;
import com.mysema.query.types.OperationImpl;
import com.mysema.query.types.Ops;
import com.mysema.query.types.Path;
import com.mysema.query.types.Predicate;
import com.mysema.query.types.SubQueryExpression;
@ -36,7 +38,7 @@ import com.mysema.query.types.TemplateExpressionImpl;
public class AbstractSQLSubQuery<Q extends AbstractSQLSubQuery<Q>> extends DetachableQuery<Q> implements SQLCommonQuery<Q> {
protected final QueryMixin<Q> queryMixin;
public AbstractSQLSubQuery() {
this(new DefaultQueryMetadata().noValidate());
}
@ -44,52 +46,56 @@ public class AbstractSQLSubQuery<Q extends AbstractSQLSubQuery<Q>> extends Detac
@SuppressWarnings("unchecked")
public AbstractSQLSubQuery(QueryMetadata metadata) {
super(new QueryMixin<Q>(metadata));
this.queryMixin = (QueryMixin<Q>)super.queryMixin;
this.queryMixin = super.queryMixin;
this.queryMixin.setSelf((Q)this);
}
/**
* Add the given prefix and expression as a general query flag
*
*
* @param position position of the flag
* @param prefix prefix for the flag
* @param expr expression of the flag
* @return
*/
@Override
public Q addFlag(Position position, String prefix, Expression<?> expr) {
Expression<?> flag = TemplateExpressionImpl.create(expr.getType(), prefix + "{0}", expr);
return queryMixin.addFlag(new QueryFlag(position, flag));
}
/**
* Add the given String literal as a query flag
*
*
* @param position
* @param flag
* @return
*/
@Override
public Q addFlag(Position position, String flag) {
return queryMixin.addFlag(new QueryFlag(position, flag));
}
/**
* Add the given Expression as a query flag
*
*
* @param position
* @param flag
* @return
*/
@Override
public Q addFlag(Position position, Expression<?> flag) {
return queryMixin.addFlag(new QueryFlag(position, flag));
}
/**
* Add the given String literal as a join flag to the last added join with the
* Add the given String literal as a join flag to the last added join with the
* position BEFORE_TARGET
*
* @param flag
* @return
*/
@Override
public Q addJoinFlag(String flag) {
return addJoinFlag(flag, JoinFlag.Position.BEFORE_TARGET);
}
@ -101,113 +107,149 @@ public class AbstractSQLSubQuery<Q extends AbstractSQLSubQuery<Q>> extends Detac
* @param position
* @return
*/
@Override
@SuppressWarnings("unchecked")
public Q addJoinFlag(String flag, JoinFlag.Position position) {
queryMixin.addJoinFlag(new JoinFlag(flag, position));
return (Q)this;
}
public Q from(Expression<?> arg) {
return queryMixin.from(arg);
}
@Override
public Q from(Expression<?>... args) {
return queryMixin.from(args);
}
@Override
@SuppressWarnings("unchecked")
public Q from(SubQueryExpression<?> subQuery, Path<?> alias) {
return queryMixin.from(ExpressionUtils.as((Expression)subQuery, alias));
}
@Override
public Q fullJoin(RelationalPath<?> target) {
return queryMixin.fullJoin(target);
}
@Override
public <E> Q fullJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.fullJoin(target, alias);
}
@Override
public <E> Q fullJoin(ForeignKey<E> key, RelationalPath<E> entity) {
return queryMixin.fullJoin(entity).on(key.on(entity));
}
@Override
public Q fullJoin(SubQueryExpression<?> target, Path<?> alias) {
return queryMixin.fullJoin(target, alias);
}
@Override
public Q innerJoin(RelationalPath<?> target) {
return queryMixin.innerJoin(target);
}
@Override
public <E> Q innerJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.innerJoin(target, alias);
}
@Override
public <E> Q innerJoin(ForeignKey<E> key, RelationalPath<E> entity) {
return queryMixin.innerJoin(entity).on(key.on(entity));
}
@Override
public Q innerJoin(SubQueryExpression<?> target, Path<?> alias) {
return queryMixin.innerJoin(target, alias);
}
@Override
public Q join(RelationalPath<?> target) {
return queryMixin.join(target);
}
@Override
public <E> Q join(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.join(target, alias);
}
@Override
public <E> Q join(ForeignKey<E> key, RelationalPath<E> entity) {
return queryMixin.join(entity).on(key.on(entity));
}
@Override
public Q join(SubQueryExpression<?> target, Path<?> alias) {
return queryMixin.join(target, alias);
}
@Override
public Q leftJoin(RelationalPath<?> target) {
return queryMixin.leftJoin(target);
}
}
@Override
public <E> Q leftJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.leftJoin(target, alias);
}
@Override
public <E> Q leftJoin(ForeignKey<E> key, RelationalPath<E> entity) {
return queryMixin.leftJoin(entity).on(key.on(entity));
}
@Override
public Q leftJoin(SubQueryExpression<?> target, Path<?> alias) {
return queryMixin.leftJoin(target, alias);
}
public Q on(Predicate condition) {
return queryMixin.on(condition);
}
@Override
public Q on(Predicate... conditions) {
return queryMixin.on(conditions);
}
@Override
public Q rightJoin(RelationalPath<?> target) {
return queryMixin.rightJoin(target);
}
@Override
public <E> Q rightJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.fullJoin(target, alias);
}
@Override
public <E> Q rightJoin(ForeignKey<E> key, RelationalPath<E> entity) {
return queryMixin.rightJoin(entity).on(key.on(entity));
}
@Override
public Q rightJoin(SubQueryExpression<?> target, Path<?> alias) {
return queryMixin.rightJoin(target, alias);
}
@Override
public Q withRecursive(Path<?> alias, SubQueryExpression<?> query) {
queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, SQLTemplates.RECURSIVE));
return with(alias, query);
}
@Override
public Q with(Path<?> alias, SubQueryExpression<?> target) {
Expression<?> expr = OperationImpl.create(alias.getType(), Ops.ALIAS, alias, target);
return queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, expr));
}
@Override
public String toString() {
if (!queryMixin.getMetadata().getJoins().isEmpty()) {

View File

@ -1,6 +1,6 @@
/*
* 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
@ -23,13 +23,13 @@ import com.mysema.query.types.SubQueryExpression;
/**
* SQLCommonQuery is a common interface for SQLQuery and SQLSubQuery
*
*
* @author tiwe
*
* @param <Q> concrete type
*/
public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
/**
* Add the given Expression as a query flag
*
@ -38,7 +38,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q addFlag(Position position, Expression<?> flag);
/**
* Add the given String literal as query flag
*
@ -59,7 +59,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
Q addFlag(Position position, String prefix, Expression<?> expr);
/**
* Add the given String literal as a join flag to the last added join with the
* Add the given String literal as a join flag to the last added join with the
* position BEFORE_TARGET
*
* @param flag
@ -86,13 +86,13 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
/**
* Adds a sub query source
*
*
* @param subQuery
* @param alias
* @return
*/
Q from(SubQueryExpression<?> subQuery, Path<?> alias);
/**
* Adds a full join to the given target
*
@ -100,7 +100,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q fullJoin(RelationalPath<?> o);
/**
* Adds a full join to the given target
*
@ -108,7 +108,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
<E> Q fullJoin(RelationalFunctionCall<E> o, Path<E> alias);
/**
* Adds a full join to the given target
*
@ -124,7 +124,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q fullJoin(SubQueryExpression<?> o, Path<?> alias);
/**
* Adds an inner join to the given target
*
@ -132,7 +132,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q innerJoin(RelationalPath<?> o);
/**
* Adds a full join to the given target
*
@ -164,7 +164,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q join(RelationalPath<?> o);
/**
* Adds a full join to the given target
*
@ -172,7 +172,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
<E> Q join(RelationalFunctionCall<E> o, Path<E> alias);
/**
* Adds a join to the given target
*
@ -188,7 +188,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q join(SubQueryExpression<?> o, Path<?> alias);
/**
* Adds a left join to the given target
*
@ -196,7 +196,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q leftJoin(RelationalPath<?> o);
/**
* Adds a full join to the given target
*
@ -212,7 +212,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
<E> Q leftJoin(ForeignKey<E> foreign, RelationalPath<E> entity);
/**
* Adds a left join to the given target
*
@ -228,7 +228,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
Q on(Predicate... conditions);
/**
* Adds a right join to the given target
*
@ -244,7 +244,7 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
* @return
*/
<E> Q rightJoin(RelationalFunctionCall<E> o, Path<E> alias);
/**
* Adds a right join to the given target
*
@ -261,5 +261,19 @@ public interface SQLCommonQuery<Q extends SQLCommonQuery<Q>> extends Query<Q> {
*/
Q rightJoin(SubQueryExpression<?> o, Path<?> alias);
/**
* Adds a common table expression
*
* @return
*/
Q with(Path<?> alias, SubQueryExpression<?> o);
/**
* Adds a common table expression
*
* @return
*/
Q withRecursive(Path<?> alias, SubQueryExpression<?> o);
}

View File

@ -187,6 +187,33 @@ public class SQLSerializer extends SerializerBase<SQLSerializer> {
}
}
// with
if (hasFlags){
boolean handled = false;
boolean recursive = false;
for (QueryFlag flag : flags) {
if (flag.getPosition() == Position.WITH) {
if (flag.getFlag() == SQLTemplates.RECURSIVE) {
recursive = true;
continue;
}
if (handled) {
append(", ");
}
handle(flag.getFlag());
handled = true;
}
}
if (handled) {
if (recursive) {
prepend(templates.getWithRecursive());
} else {
prepend(templates.getWith());
}
append("\n");
}
}
// start
if (hasFlags) {
serialize(Position.START, flags);

View File

@ -25,9 +25,11 @@ import com.mysema.query.QueryException;
import com.mysema.query.QueryFlag.Position;
import com.mysema.query.QueryMetadata;
import com.mysema.query.QueryModifiers;
import com.mysema.query.types.Expression;
import com.mysema.query.types.Operator;
import com.mysema.query.types.OperatorImpl;
import com.mysema.query.types.Ops;
import com.mysema.query.types.TemplateExpressionImpl;
import com.mysema.query.types.Templates;
/**
@ -38,6 +40,8 @@ import com.mysema.query.types.Templates;
*/
public class SQLTemplates extends Templates {
public static final Expression<?> RECURSIVE = TemplateExpressionImpl.create(Object.class, "");
public static final Operator<Object> CAST = new OperatorImpl<Object>("SQL_CAST");
public static final Operator<Object> UNION = new OperatorImpl<Object>("SQL_UNION");
@ -187,6 +191,8 @@ public class SQLTemplates extends Templates {
private String with = "with ";
private String withRecursive = "with recursive ";
private String createIndex = "create index ";
private String createUniqueIndex = "create unique index ";
@ -502,6 +508,10 @@ public class SQLTemplates extends Templates {
return with;
}
public final String getWithRecursive() {
return withRecursive;
}
public final boolean isParameterMetadataAvailable() {
return parameterMetadataAvailable;
}
@ -738,6 +748,10 @@ public class SQLTemplates extends Templates {
this.with = with;
}
protected void setWithRecursive(String withRecursive) {
this.withRecursive = withRecursive;
}
protected void setCreateIndex(String createIndex) {
this.createIndex = createIndex;
}

View File

@ -1443,6 +1443,41 @@ public class SelectBase extends AbstractBaseTest{
query().from(employee).where(sq1.exists().not()).count();
}
@Test
@IncludeIn({HSQLDB, ORACLE, POSTGRES})
public void With() {
query().with(employee2, sq().from(employee)
.where(employee.firstname.eq("Tom"))
.list(Wildcard.all))
.from(employee, employee2)
.list(employee.id, employee2.id);
}
@Test
@IncludeIn({HSQLDB, ORACLE, POSTGRES})
public void With2() {
QEmployee employee3 = new QEmployee("e3");
query().with(employee2, sq().from(employee)
.where(employee.firstname.eq("Tom"))
.list(Wildcard.all))
.with(employee2, sq().from(employee)
.where(employee.firstname.eq("Tom"))
.list(Wildcard.all))
.from(employee, employee2, employee3)
.list(employee.id, employee2.id, employee3.id);
}
@Test
@IncludeIn({ORACLE, POSTGRES})
public void With_Recursive() {
query().withRecursive(employee2, sq().from(employee)
.where(employee.firstname.eq("Tom"))
.list(Wildcard.all))
.from(employee, employee2)
.list(employee.id, employee2.id);
}
@Test
public void Wildcard() {
// wildcard