/*
* Copyright 2015, The Querydsl Team (http://www.querydsl.com/team)
*
* 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.querydsl.sql;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.mysema.commons.lang.CloseableIterator;
import com.querydsl.core.FetchableQuery;
import com.querydsl.core.JoinFlag;
import com.querydsl.core.Query;
import com.querydsl.core.QueryFlag;
import com.querydsl.core.QueryFlag.Position;
import com.querydsl.core.support.FetchableSubQueryBase;
import com.querydsl.core.support.QueryMixin;
import com.querydsl.core.types.*;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.Wildcard;
/**
* {@code ProjectableSQLQuery} is the base type for SQL query implementations
*
* @param <T> result type
* @param <Q> concrete subtype
*/
public abstract class ProjectableSQLQuery<T, Q extends ProjectableSQLQuery<T, Q> & Query<Q>> extends FetchableSubQueryBase<T, Q>
implements SQLCommonQuery<Q>, FetchableQuery<T, Q> {
private static final Path<?> defaultQueryAlias = ExpressionUtils.path(Object.class, "query");
protected final Configuration configuration;
@Nullable
protected Expression<?> union;
protected SubQueryExpression<?> firstUnionSubQuery;
protected boolean unionAll;
@SuppressWarnings("unchecked")
public ProjectableSQLQuery(QueryMixin<Q> queryMixin, Configuration configuration) {
super(queryMixin);
this.queryMixin.setSelf((Q) this);
this.configuration = configuration;
}
@Override
public <R,C> R accept(Visitor<R,C> v, @Nullable C context) {
if (union != null) {
return union.accept(v, context);
} else {
return super.accept(v, context);
}
}
/**
* Add the given String literal as a join flag to the last added join with the position
* BEFORE_TARGET
*
* @param flag join flag
* @return the current object
*/
@Override
public Q addJoinFlag(String flag) {
return addJoinFlag(flag, JoinFlag.Position.BEFORE_TARGET);
}
/**
* Add the given String literal as a join flag to the last added join
*
* @param flag join flag
* @param position position
* @return the current object
*/
@Override
@SuppressWarnings("unchecked")
public Q addJoinFlag(String flag, JoinFlag.Position position) {
queryMixin.addJoinFlag(new JoinFlag(flag, position));
return (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 the current object
*/
@Override
public Q addFlag(Position position, String prefix, Expression<?> expr) {
Expression<?> flag = Expressions.template(expr.getType(), prefix + "{0}", expr);
return queryMixin.addFlag(new QueryFlag(position, flag));
}
/**
* Add the given query flag
*
* @param flag query flag
* @return the current object
*/
public Q addFlag(QueryFlag flag) {
return queryMixin.addFlag(flag);
}
/**
* Add the given String literal as query flag
*
* @param position position
* @param flag query flag
* @return the current object
*/
@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 position
* @param flag query flag
* @return the current object
*/
@Override
public Q addFlag(Position position, Expression<?> flag) {
return queryMixin.addFlag(new QueryFlag(position, flag));
}
@Override
public long fetchCount() {
queryMixin.setProjection(Wildcard.countAsInt);
return ((Number) fetchOne()).longValue();
}
public Q from(Expression<?> arg) {
return queryMixin.from(arg);
}
@Override
public Q from(Expression<?>... args) {
return queryMixin.from(args);
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Q from(SubQueryExpression<?> subQuery, Path<?> alias) {
return queryMixin.from(ExpressionUtils.as((Expression) subQuery, alias));
}
@Override
public Q fullJoin(EntityPath<?> target) {
return queryMixin.fullJoin(target);
}
@Override
public <E> Q fullJoin(EntityPath<E> target, Path<E> alias) {
return queryMixin.fullJoin(target, alias);
}
@Override
public <E> Q fullJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.fullJoin(target, alias);
}
@Override
public Q fullJoin(SubQueryExpression<?> target, Path<?> 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 innerJoin(EntityPath<?> target) {
return queryMixin.innerJoin(target);
}
@Override
public <E> Q innerJoin(EntityPath<E> target, Path<E> alias) {
return queryMixin.innerJoin(target, alias);
}
@Override
public <E> Q innerJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.innerJoin(target, alias);
}
@Override
public Q innerJoin(SubQueryExpression<?> target, Path<?> 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 join(EntityPath<?> target) {
return queryMixin.join(target);
}
@Override
public <E> Q join(EntityPath<E> target, Path<E> alias) {
return queryMixin.join(target, alias);
}
@Override
public <E> Q join(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.join(target, alias);
}
@Override
public Q join(SubQueryExpression<?> target, Path<?> 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 leftJoin(EntityPath<?> target) {
return queryMixin.leftJoin(target);
}
@Override
public <E> Q leftJoin(EntityPath<E> target, Path<E> alias) {
return queryMixin.leftJoin(target, alias);
}
@Override
public <E> Q leftJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.leftJoin(target, alias);
}
@Override
public Q leftJoin(SubQueryExpression<?> target, Path<?> 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 rightJoin(EntityPath<?> target) {
return queryMixin.rightJoin(target);
}
@Override
public <E> Q rightJoin(EntityPath<E> target, Path<E> alias) {
return queryMixin.rightJoin(target, alias);
}
@Override
public <E> Q rightJoin(RelationalFunctionCall<E> target, Path<E> alias) {
return queryMixin.rightJoin(target, alias);
}
@Override
public Q rightJoin(SubQueryExpression<?> target, Path<?> alias) {
return queryMixin.rightJoin(target, alias);
}
@Override
public <E> Q rightJoin(ForeignKey<E> key, RelationalPath<E> entity) {
return queryMixin.rightJoin(entity).on(key.on(entity));
}
@SuppressWarnings("unchecked")
private <RT> Union<RT> innerUnion(SubQueryExpression<?>... sq) {
return innerUnion((List) ImmutableList.copyOf(sq));
}
@SuppressWarnings("unchecked")
private <RT> Union<RT> innerUnion(List<SubQueryExpression<RT>> sq) {
queryMixin.setProjection(sq.get(0).getMetadata().getProjection());
if (!queryMixin.getMetadata().getJoins().isEmpty()) {
throw new IllegalArgumentException("Don't mix union and from");
}
this.union = UnionUtils.union(sq, unionAll);
this.firstUnionSubQuery = sq.get(0);
return new UnionImpl(this);
}
public Q on(Predicate condition) {
return queryMixin.on(condition);
}
@Override
public Q on(Predicate... conditions) {
return queryMixin.on(conditions);
}
/**
* Creates an union expression for the given subqueries
*
* @param <RT>
* @param sq subqueries
* @return union
*/
public <RT> Union<RT> union(SubQueryExpression<RT>... sq) {
return innerUnion(sq);
}
/**
* Creates an union expression for the given subqueries
*
* @param <RT>
* @param sq subqueries
* @return union
*/
public <RT> Union<RT> union(List<SubQueryExpression<RT>> sq) {
return innerUnion(sq);
}
/**
* Creates an union expression for the given subqueries
*
* @param <RT>
* @param alias alias for union
* @param sq subqueries
* @return the current object
*/
@SuppressWarnings("unchecked")
public <RT> Q union(Path<?> alias, SubQueryExpression<RT>... sq) {
return from(UnionUtils.union(ImmutableList.copyOf(sq), (Path) alias, false));
}
/**
* Creates an union expression for the given subqueries
*
* @param <RT>
* @param sq subqueries
* @return union
*/
public <RT> Union<RT> unionAll(SubQueryExpression<RT>... sq) {
unionAll = true;
return innerUnion(sq);
}
/**
* Creates an union expression for the given subqueries
*
* @param <RT>
* @param sq subqueries
* @return union
*/
public <RT> Union<RT> unionAll(List<SubQueryExpression<RT>> sq) {
unionAll = true;
return innerUnion(sq);
}
/**
* Creates an union expression for the given subqueries
*
* @param <RT>
* @param alias alias for union
* @param sq subqueries
* @return the current object
*/
@SuppressWarnings("unchecked")
public <RT> Q unionAll(Path<?> alias, SubQueryExpression<RT>... sq) {
return from(UnionUtils.union(ImmutableList.copyOf(sq), (Path) alias, true));
}
@Override
public T fetchOne() {
if (getMetadata().getModifiers().getLimit() == null
&& !queryMixin.getMetadata().getProjection().toString().contains("count(")) {
limit(2);
}
CloseableIterator<T> iterator = iterate();
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 withRecursive(Path<?> alias, Expression<?> query) {
queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, SQLTemplates.RECURSIVE));
return with(alias, query);
}
@Override
public WithBuilder<Q> withRecursive(Path<?> alias, Path<?>... columns) {
queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, SQLTemplates.RECURSIVE));
return with(alias, columns);
}
@Override
public Q with(Path<?> alias, SubQueryExpression<?> query) {
Expression<?> expr = ExpressionUtils.operation(alias.getType(), SQLOps.WITH_ALIAS, alias, query);
return queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, expr));
}
@Override
public Q with(Path<?> alias, Expression<?> query) {
Expression<?> expr = ExpressionUtils.operation(alias.getType(), SQLOps.WITH_ALIAS, alias, query);
return queryMixin.addFlag(new QueryFlag(QueryFlag.Position.WITH, expr));
}
@Override
public WithBuilder<Q> with(Path<?> alias, Path<?>... columns) {
Expression<?> columnsCombined = ExpressionUtils.list(Object.class, columns);
Expression<?> aliasCombined = Expressions.operation(alias.getType(), SQLOps.WITH_COLUMNS, alias, columnsCombined);
return new WithBuilder<Q>(queryMixin, aliasCombined);
}
protected void clone(Q query) {
this.union = query.union;
this.unionAll = query.unionAll;
this.firstUnionSubQuery = query.firstUnionSubQuery;
}
@Override
public abstract Q clone();
protected abstract SQLSerializer createSerializer();
private Set<Path<?>> getRootPaths(Collection<? extends Expression<?>> exprs) {
Set<Path<?>> paths = Sets.newHashSet();
for (Expression<?> e : exprs) {
Path<?> path = e.accept(PathExtractor.DEFAULT, null);
if (path != null && !path.getMetadata().isRoot()) {
paths.add(path.getMetadata().getRootPath());
}
}
return paths;
}
@SuppressWarnings("unchecked")
private Collection<? extends Expression<?>> expandProjection(Expression<?> expr) {
if (expr instanceof FactoryExpression) {
return ((FactoryExpression) expr).getArgs();
} else {
return ImmutableList.of(expr);
}
}
@SuppressWarnings("unchecked")
protected SQLSerializer serialize(boolean forCountRow) {
SQLSerializer serializer = createSerializer();
if (union != null) {
if (queryMixin.getMetadata().getProjection() == null ||
expandProjection(queryMixin.getMetadata().getProjection())
.equals(expandProjection(firstUnionSubQuery.getMetadata().getProjection()))) {
serializer.serializeUnion(union, queryMixin.getMetadata(), unionAll);
} else {
QueryMixin<Q> mixin2 = new QueryMixin<Q>(queryMixin.getMetadata().clone());
Set<Path<?>> paths = getRootPaths(expandProjection(mixin2.getMetadata().getProjection()));
if (paths.isEmpty()) {
mixin2.from(ExpressionUtils.as((Expression) union, defaultQueryAlias));
} else if (paths.size() == 1) {
mixin2.from(ExpressionUtils.as((Expression) union, paths.iterator().next()));
} else {
throw new IllegalStateException("Unable to create serialize union");
}
serializer.serialize(mixin2.getMetadata(), forCountRow);
}
} else {
serializer.serialize(queryMixin.getMetadata(), forCountRow);
}
return serializer;
}
/**
* Get the query as an SQL query string and bindings
*
* @return SQL string and bindings
*/
public SQLBindings getSQL() {
SQLSerializer serializer = serialize(false);
ImmutableList.Builder<Object> args = ImmutableList.builder();
Map<ParamExpression<?>, Object> params = getMetadata().getParams();
for (Object o : serializer.getConstants()) {
if (o instanceof ParamExpression) {
if (!params.containsKey(o)) {
throw new ParamNotSetException((ParamExpression<?>) o);
}
o = queryMixin.getMetadata().getParams().get(o);
}
args.add(o);
}
return new SQLBindings(serializer.toString(), args.build());
}
@Override
public String toString() {
SQLSerializer serializer = serialize(false);
return serializer.toString().trim();
}
}