diff --git a/querydsl-core/src/main/java/com/mysema/query/QueryFlag.java b/querydsl-core/src/main/java/com/mysema/query/QueryFlag.java index 43b5237d3..ec0f53c97 100644 --- a/querydsl-core/src/main/java/com/mysema/query/QueryFlag.java +++ b/querydsl-core/src/main/java/com/mysema/query/QueryFlag.java @@ -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() { diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLQuery.java b/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLQuery.java index 1e4c29ea4..91b1a6034 100644 --- a/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLQuery.java +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLQuery.java @@ -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 & Query> 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()) { diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLSubQuery.java b/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLSubQuery.java index 46f534f02..c018a38f1 100644 --- a/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLSubQuery.java +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/AbstractSQLSubQuery.java @@ -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> extends DetachableQuery implements SQLCommonQuery { protected final QueryMixin queryMixin; - + public AbstractSQLSubQuery() { this(new DefaultQueryMetadata().noValidate()); } @@ -44,52 +46,56 @@ public class AbstractSQLSubQuery> extends Detac @SuppressWarnings("unchecked") public AbstractSQLSubQuery(QueryMetadata metadata) { super(new QueryMixin(metadata)); - this.queryMixin = (QueryMixin)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> 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 Q fullJoin(RelationalFunctionCall target, Path alias) { return queryMixin.fullJoin(target, alias); } - + + @Override public Q fullJoin(ForeignKey key, RelationalPath 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 Q innerJoin(RelationalFunctionCall target, Path alias) { return queryMixin.innerJoin(target, alias); } + @Override public Q innerJoin(ForeignKey key, RelationalPath 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 Q join(RelationalFunctionCall target, Path alias) { return queryMixin.join(target, alias); } - + + @Override public Q join(ForeignKey key, RelationalPath 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 Q leftJoin(RelationalFunctionCall target, Path alias) { return queryMixin.leftJoin(target, alias); } - + + @Override public Q leftJoin(ForeignKey key, RelationalPath 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 Q rightJoin(RelationalFunctionCall target, Path alias) { return queryMixin.fullJoin(target, alias); } - + + @Override public Q rightJoin(ForeignKey key, RelationalPath 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()) { diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLCommonQuery.java b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLCommonQuery.java index c85a68453..ca377d954 100644 --- a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLCommonQuery.java +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLCommonQuery.java @@ -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 concrete type */ public interface SQLCommonQuery> extends Query { - + /** * Add the given Expression as a query flag * @@ -38,7 +38,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q addFlag(Position position, Expression flag); - + /** * Add the given String literal as query flag * @@ -59,7 +59,7 @@ public interface SQLCommonQuery> extends Query { 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> extends Query { /** * 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> extends Query { * @return */ Q fullJoin(RelationalPath o); - + /** * Adds a full join to the given target * @@ -108,7 +108,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q fullJoin(RelationalFunctionCall o, Path alias); - + /** * Adds a full join to the given target * @@ -124,7 +124,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q fullJoin(SubQueryExpression o, Path alias); - + /** * Adds an inner join to the given target * @@ -132,7 +132,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q innerJoin(RelationalPath o); - + /** * Adds a full join to the given target * @@ -164,7 +164,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q join(RelationalPath o); - + /** * Adds a full join to the given target * @@ -172,7 +172,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q join(RelationalFunctionCall o, Path alias); - + /** * Adds a join to the given target * @@ -188,7 +188,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q join(SubQueryExpression o, Path alias); - + /** * Adds a left join to the given target * @@ -196,7 +196,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q leftJoin(RelationalPath o); - + /** * Adds a full join to the given target * @@ -212,7 +212,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q leftJoin(ForeignKey foreign, RelationalPath entity); - + /** * Adds a left join to the given target * @@ -228,7 +228,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q on(Predicate... conditions); - + /** * Adds a right join to the given target * @@ -244,7 +244,7 @@ public interface SQLCommonQuery> extends Query { * @return */ Q rightJoin(RelationalFunctionCall o, Path alias); - + /** * Adds a right join to the given target * @@ -261,5 +261,19 @@ public interface SQLCommonQuery> extends Query { */ 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); + } diff --git a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLSerializer.java b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLSerializer.java index 38defbfd2..eee87c65a 100644 --- a/querydsl-sql/src/main/java/com/mysema/query/sql/SQLSerializer.java +++ b/querydsl-sql/src/main/java/com/mysema/query/sql/SQLSerializer.java @@ -187,6 +187,33 @@ public class SQLSerializer extends SerializerBase { } } + // 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); 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 ebd400522..60826a7ba 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 @@ -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 CAST = new OperatorImpl("SQL_CAST"); public static final Operator UNION = new OperatorImpl("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; } diff --git a/querydsl-sql/src/test/java/com/mysema/query/SelectBase.java b/querydsl-sql/src/test/java/com/mysema/query/SelectBase.java index a065f584e..f93a2ff20 100644 --- a/querydsl-sql/src/test/java/com/mysema/query/SelectBase.java +++ b/querydsl-sql/src/test/java/com/mysema/query/SelectBase.java @@ -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