package com.getbase.android.db.fluentsqlite;
import com.getbase.android.db.cursors.FluentCursor;
import com.getbase.android.db.fluentsqlite.Expressions.CollatingSequence;
import com.getbase.android.db.fluentsqlite.Expressions.Expression;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import android.database.sqlite.SQLiteDatabase;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
public final class Query {
private final QueryBuilderImpl mQueryBuilder;
private Query(QueryBuilderImpl queryBuilder) {
mQueryBuilder = queryBuilder;
}
public FluentCursor perform(SQLiteDatabase db) {
return mQueryBuilder.perform(db);
}
public RawQuery toRawQuery() {
return mQueryBuilder.toRawQuery();
}
public QueryBuilder buildUpon() {
return mQueryBuilder.copy();
}
public Set<String> getTables() {
return mQueryBuilder.getTables();
}
private void getTables(ImmutableSet.Builder<String> builder) {
builder.addAll(mQueryBuilder.getTables());
}
private static final Function<String, String> SURROUND_WITH_PARENS = new Function<String, String>() {
@Override
public String apply(String input) {
return "(" + input + ")";
}
};
public static QueryBuilder select() {
return new QueryBuilderImpl();
}
public static CompoundQueryBuilder select(Query query) {
return new CompoundQueryBuilderImpl(query);
}
public static CompoundQueryBuilder select(QueryBuilder queryBuilder) {
return select(queryBuilder.build());
}
public static Query union(Query... queries) {
Preconditions.checkNotNull(queries);
Preconditions.checkArgument(queries.length > 0);
if (queries.length == 1) {
return queries[0];
}
CompoundQueryBuilder builder = select(queries[0]);
for (int i = 1; i < queries.length; i++) {
builder = builder.union(queries[i]);
}
return builder.build();
}
public static Query unionAll(Query... queries) {
Preconditions.checkNotNull(queries);
Preconditions.checkArgument(queries.length > 0);
if (queries.length == 1) {
return queries[0];
}
CompoundQueryBuilder builder = select(queries[0]);
for (int i = 1; i < queries.length; i++) {
builder = builder.unionAll(queries[i]);
}
return builder.build();
}
public static Query intersect(Query... queries) {
Preconditions.checkNotNull(queries);
Preconditions.checkArgument(queries.length > 0);
if (queries.length == 1) {
return queries[0];
}
CompoundQueryBuilder builder = select(queries[0]);
for (int i = 1; i < queries.length; i++) {
builder = builder.intersect(queries[i]);
}
return builder.build();
}
public interface CompoundQueryBuilder extends CompoundOrderByBuilder, CompoundLimitBuilder {
CompoundQueryBuilder union(Query query);
CompoundQueryBuilder union(QueryBuilder queryBuilder);
CompoundQueryBuilder unionAll(Query query);
CompoundQueryBuilder unionAll(QueryBuilder queryBuilder);
CompoundQueryBuilder intersect(Query query);
CompoundQueryBuilder intersect(QueryBuilder queryBuilder);
CompoundQueryBuilder except(Query query);
CompoundQueryBuilder except(QueryBuilder queryBuilder);
Query build();
}
public interface CompoundOrderByBuilder {
CompoundOrderingTermBuilder orderBy(String expression);
CompoundOrderingTermBuilder orderBy(Expression expression);
}
public interface CompoundOrderingTermBuilder extends CompoundOrderingDirectionSelector {
CompoundOrderingDirectionSelector collate(String collation);
CompoundOrderingDirectionSelector collate(CollatingSequence collation);
}
public interface CompoundOrderingDirectionSelector extends CompoundQueryBuilder {
CompoundQueryBuilder asc();
CompoundQueryBuilder desc();
}
public interface CompoundLimitBuilder {
CompoundLimitOffsetBuilder limit(String expression);
CompoundLimitOffsetBuilder limit(int limit);
}
public interface CompoundLimitOffsetBuilder extends CompoundQueryBuilder {
CompoundQueryBuilder offset(String expression);
CompoundQueryBuilder offset(int limit);
}
private static class CompoundQueryBuilderImpl implements CompoundQueryBuilder, CompoundOrderingTermBuilder, CompoundLimitOffsetBuilder {
private QueryBuilderImpl mQueryBuilder;
private CompoundQueryBuilderImpl(Query query) {
mQueryBuilder = query.mQueryBuilder.copy();
if (mQueryBuilder.isCompound()) {
mQueryBuilder.mCompoundQueryParts = Lists.newLinkedList();
mQueryBuilder.mCompoundQueryParts.add(new QueryOrOperator(query));
}
}
@Override
public CompoundQueryBuilder offset(String expression) {
mQueryBuilder.offset(expression);
return this;
}
@Override
public CompoundQueryBuilder offset(int limit) {
mQueryBuilder.offset(limit);
return this;
}
@Override
public CompoundOrderingDirectionSelector collate(String collation) {
mQueryBuilder.collate(collation);
return this;
}
@Override
public CompoundOrderingDirectionSelector collate(CollatingSequence collation) {
mQueryBuilder.collate(collation);
return this;
}
@Override
public CompoundQueryBuilder asc() {
mQueryBuilder.asc();
return this;
}
@Override
public CompoundQueryBuilder desc() {
mQueryBuilder.desc();
return this;
}
private CompoundQueryBuilder withCompoundQueryPart(Query queryPart, String operation) {
if (!mQueryBuilder.mCurrentQueryPart.isEmpty()) {
mQueryBuilder.mCompoundQueryParts.add(new QueryOrOperator(new QueryBuilderImpl(mQueryBuilder.mCurrentQueryPart).build()));
}
mQueryBuilder.mCompoundQueryParts.add(new QueryOrOperator(operation));
mQueryBuilder.mCompoundQueryParts.add(new QueryOrOperator(queryPart));
mQueryBuilder.mCurrentQueryPart = new QueryBuilderImpl.CompoundQueryPart();
return this;
}
@Override
public CompoundQueryBuilder union(Query query) {
return withCompoundQueryPart(query, "UNION");
}
@Override
public CompoundQueryBuilder union(QueryBuilder queryBuilder) {
return union(queryBuilder.build());
}
@Override
public CompoundQueryBuilder unionAll(Query query) {
return withCompoundQueryPart(query, "UNION ALL");
}
@Override
public CompoundQueryBuilder unionAll(QueryBuilder queryBuilder) {
return unionAll(queryBuilder.build());
}
@Override
public CompoundQueryBuilder intersect(Query query) {
return withCompoundQueryPart(query, "INTERSECT");
}
@Override
public CompoundQueryBuilder intersect(QueryBuilder queryBuilder) {
return intersect(queryBuilder.build());
}
@Override
public CompoundQueryBuilder except(Query query) {
return withCompoundQueryPart(query, "EXCEPT");
}
@Override
public CompoundQueryBuilder except(QueryBuilder queryBuilder) {
return except(queryBuilder.build());
}
@Override
public Query build() {
return new Query(mQueryBuilder.copy());
}
@Override
public CompoundLimitOffsetBuilder limit(String expression) {
mQueryBuilder.limit(expression);
return this;
}
@Override
public CompoundLimitOffsetBuilder limit(int limit) {
mQueryBuilder.limit(limit);
return this;
}
@Override
public CompoundOrderingTermBuilder orderBy(String expression) {
mQueryBuilder.orderBy(expression);
return this;
}
@Override
public CompoundOrderingTermBuilder orderBy(Expression expression) {
mQueryBuilder.orderBy(expression);
return this;
}
}
private static class QueryBuilderImpl implements QueryBuilder, ColumnAliasBuilder, LimitOffsetBuilder, OrderingTermBuilder, ColumnListTableSelector, ColumnsListAliasBuilder {
@Override
public Query build() {
buildPendingOrderByClause();
return new Query(copy());
}
private static class CompoundQueryPart {
private List<String> mProjection = Lists.newArrayList();
private String mColumnWithPotentialAlias;
private List<String> mColumnsWithPotentialTable = Lists.newArrayList();
private String mColumnsListsTableWithPotentialAlias;
private List<String> mGroupByExpressions = Lists.newArrayList();
private List<String> mHaving = Lists.newArrayList();
private List<String> mSelection = Lists.newArrayList();
private LinkedListMultimap<QueryPart, Object> mArgs = LinkedListMultimap.create();
private TableOrSubquery mPendingTable;
private LinkedHashMap<TableOrSubquery, String> mTables = Maps.newLinkedHashMap();
private boolean mIsDistinct = false;
private String mPendingJoinType = "";
private JoinSpec mPendingJoin;
private List<JoinSpec> mJoins = Lists.newArrayList();
private Set<String> mTablesUsedInExpressions = Sets.newHashSet();
private boolean isEmpty() {
return mProjection.isEmpty() &&
mColumnWithPotentialAlias == null &&
mColumnsWithPotentialTable.isEmpty() &&
mColumnsListsTableWithPotentialAlias == null &&
mGroupByExpressions.isEmpty() &&
mHaving.isEmpty() &&
mSelection.isEmpty() &&
mArgs.isEmpty() &&
mPendingTable == null &&
mTables.isEmpty() &&
Strings.isNullOrEmpty(mPendingJoinType) &&
mPendingJoin == null &&
mJoins.isEmpty();
}
CompoundQueryPart() {
}
CompoundQueryPart(CompoundQueryPart other) {
mIsDistinct = other.mIsDistinct;
mProjection.addAll(other.mProjection);
mColumnWithPotentialAlias = other.mColumnWithPotentialAlias;
mColumnsWithPotentialTable.addAll(other.mColumnsWithPotentialTable);
mColumnsListsTableWithPotentialAlias = other.mColumnsListsTableWithPotentialAlias;
mGroupByExpressions.addAll(other.mGroupByExpressions);
mHaving.addAll(other.mHaving);
mSelection.addAll(other.mSelection);
mArgs.putAll(other.mArgs);
mPendingTable = other.mPendingTable;
mTables.putAll(other.mTables);
mPendingJoinType = other.mPendingJoinType;
mPendingJoin = other.mPendingJoin != null ? new JoinSpec(other.mPendingJoin) : null;
mJoins = Lists.newArrayListWithCapacity(other.mJoins.size());
for (JoinSpec join : other.mJoins) {
mJoins.add(new JoinSpec(join));
}
mTablesUsedInExpressions.addAll(other.mTablesUsedInExpressions);
}
private void addPendingColumn() {
if (mColumnWithPotentialAlias != null) {
mProjection.add(mColumnWithPotentialAlias);
mColumnWithPotentialAlias = null;
}
}
private void addPendingColumns() {
if (mColumnsListsTableWithPotentialAlias != null) {
for (String column : mColumnsWithPotentialTable) {
mProjection.add(mColumnsListsTableWithPotentialAlias + "." + column);
}
} else {
mProjection.addAll(mColumnsWithPotentialTable);
}
mColumnsListsTableWithPotentialAlias = null;
mColumnsWithPotentialTable.clear();
}
private void addPendingTable(String alias) {
if (mPendingTable != null) {
mTables.put(mPendingTable, alias);
mPendingTable = null;
}
}
private void addPendingJoin() {
if (mPendingJoin != null) {
mJoins.add(mPendingJoin);
mPendingJoin = null;
}
}
private void processPendingParts() {
addPendingColumn();
addPendingColumns();
addPendingTable(null);
addPendingJoin();
}
RawQuery toRawQuery() {
processPendingParts();
Preconditions.checkState(!(!mHaving.isEmpty() && mGroupByExpressions.isEmpty()), "a GROUP BY clause is required when using HAVING clause");
List<String> args = Lists.newArrayList();
StringBuilder builder = new StringBuilder();
builder.append("SELECT ");
if (mIsDistinct) {
builder.append("DISTINCT ");
}
if (!mProjection.isEmpty()) {
builder.append(Joiner.on(", ").join(mProjection));
} else {
builder.append("*");
}
args.addAll(Collections2.transform(mArgs.get(QueryPart.PROJECTION), Functions.toStringFunction()));
if (!mTables.isEmpty()) {
builder.append(" FROM ");
List<String> tables = Lists.newArrayList();
for (Entry<TableOrSubquery, String> tableEntry : mTables.entrySet()) {
TableOrSubquery tableOrSubquery = tableEntry.getKey();
String alias = tableEntry.getValue();
String tableString;
if (tableOrSubquery.mTable != null) {
tableString = tableOrSubquery.mTable;
} else {
RawQuery rawSubquery = tableOrSubquery.mSubquery.toRawQuery();
tableString = SURROUND_WITH_PARENS.apply(rawSubquery.mRawQuery);
args.addAll(rawSubquery.mRawQueryArgs);
}
if (alias != null) {
tableString += " AS " + alias;
}
tables.add(tableString);
}
builder.append(Joiner.on(", ").join(tables));
}
for (JoinSpec join : mJoins) {
builder.append(" ");
builder.append(join.mJoinType);
builder.append("JOIN ");
if (join.mJoinSource.mTable != null) {
builder.append(join.mJoinSource.mTable);
} else {
final RawQuery rawQuery = join.mJoinSource.mSubquery.toRawQuery();
builder.append(SURROUND_WITH_PARENS.apply(rawQuery.mRawQuery));
args.addAll(rawQuery.mRawQueryArgs);
}
if (join.mAlias != null) {
builder.append(" AS ");
builder.append(join.mAlias);
}
if (join.mUsingColumns != null) {
builder.append(" USING ");
builder.append("(");
builder.append(Joiner.on(", ").join(join.mUsingColumns));
builder.append(")");
} else if (!join.mConstraints.isEmpty()) {
builder.append(" ON ");
builder.append(Joiner.on(" AND ").join(Collections2.transform(join.mConstraints, SURROUND_WITH_PARENS)));
args.addAll(Collections2.transform(join.mConstraintsArgs, Functions.toStringFunction()));
}
}
if (!mSelection.isEmpty()) {
builder.append(" WHERE ");
builder.append(Joiner.on(" AND ").join(Collections2.transform(mSelection, SURROUND_WITH_PARENS)));
args.addAll(Collections2.transform(mArgs.get(QueryPart.SELECTION), Functions.toStringFunction()));
}
if (!mGroupByExpressions.isEmpty()) {
builder.append(" GROUP BY ");
builder.append(Joiner.on(", ").join(mGroupByExpressions));
args.addAll(Collections2.transform(mArgs.get(QueryPart.GROUP_BY), Functions.toStringFunction()));
if (!mHaving.isEmpty()) {
builder.append(" HAVING ");
builder.append(Joiner.on(" AND ").join(Collections2.transform(mHaving, SURROUND_WITH_PARENS)));
args.addAll(Collections2.transform(mArgs.get(QueryPart.HAVING), Functions.toStringFunction()));
}
}
return new RawQuery(builder.toString(), args);
}
public void getTables(ImmutableSet.Builder<String> builder) {
addTableOrSubquery(builder, mPendingTable);
for (TableOrSubquery tableOrSubquery : mTables.keySet()) {
addTableOrSubquery(builder, tableOrSubquery);
}
if (mPendingJoin != null) {
addTableOrSubquery(builder, mPendingJoin.mJoinSource);
}
for (JoinSpec join : mJoins) {
addTableOrSubquery(builder, join.mJoinSource);
}
builder.addAll(mTablesUsedInExpressions);
}
}
private CompoundQueryPart mCurrentQueryPart = new CompoundQueryPart();
private String mLimit;
private String mOffset;
private String mOrderByExpression;
private String mOrderByCollation;
private String mOrderByOrder;
private List<String> mOrderClauses = Lists.newArrayList();
private List<Object> mOrderByArgs = Lists.newArrayList();
private Set<String> mTablesUsedInExpressions = Sets.newHashSet();
private LinkedList<QueryOrOperator> mCompoundQueryParts = Lists.newLinkedList();
public boolean isCompound() {
int queryPartsCount = (mCurrentQueryPart.isEmpty() ? 0 : 1) + mCompoundQueryParts.size();
return queryPartsCount > 1;
}
private QueryBuilderImpl() {
}
private QueryBuilderImpl(QueryBuilderImpl other) {
mLimit = other.mLimit;
mOffset = other.mOffset;
mOrderByExpression = other.mOrderByExpression;
mOrderByCollation = other.mOrderByCollation;
mOrderByOrder = other.mOrderByOrder;
mOrderClauses = Lists.newCopyOnWriteArrayList(other.mOrderClauses);
mOrderByArgs = Lists.newCopyOnWriteArrayList(other.mOrderByArgs);
mTablesUsedInExpressions = Sets.newHashSet(other.mTablesUsedInExpressions);
mCurrentQueryPart = new CompoundQueryPart(other.mCurrentQueryPart);
mCompoundQueryParts = Lists.newLinkedList(other.mCompoundQueryParts);
}
private QueryBuilderImpl(CompoundQueryPart compoundQueryPart) {
mCurrentQueryPart = new CompoundQueryPart(compoundQueryPart);
}
QueryBuilderImpl copy() {
return new QueryBuilderImpl(this);
}
@Override
public Set<String> getTables() {
Builder<String> builder = ImmutableSet.builder();
mCurrentQueryPart.getTables(builder);
for (QueryOrOperator part : mCompoundQueryParts) {
if (part.isQuery()) {
part.mQuery.getTables(builder);
}
}
builder.addAll(mTablesUsedInExpressions);
return builder.build();
}
private static void addTableOrSubquery(ImmutableSet.Builder<String> builder, TableOrSubquery tableOrSubquery) {
if (tableOrSubquery != null) {
if (tableOrSubquery.mSubquery != null) {
builder.addAll(tableOrSubquery.mSubquery.getTables());
} else {
builder.add(tableOrSubquery.mTable);
}
}
}
@Override
public RawQuery toRawQuery() {
boolean currentPartIsNotEmpty = !mCurrentQueryPart.isEmpty();
Preconditions.checkState(currentPartIsNotEmpty || mCompoundQueryParts.size() > 1);
buildPendingOrderByClause();
List<String> args = Lists.newArrayList();
StringBuilder builder = new StringBuilder();
for (QueryOrOperator part : mCompoundQueryParts) {
if (part.isOperator()) {
builder.append(" ");
builder.append(part.mOperator);
builder.append(" ");
} else {
Query query = part.mQuery;
RawQuery partRawQuery = query.toRawQuery();
if (query.mQueryBuilder.isCompound()) {
builder.append("SELECT * FROM (");
}
builder.append(partRawQuery.mRawQuery);
if (query.mQueryBuilder.isCompound()) {
builder.append(")");
}
args.addAll(partRawQuery.mRawQueryArgs);
}
}
if (currentPartIsNotEmpty) {
RawQuery lastQueryPart = mCurrentQueryPart.toRawQuery();
args.addAll(lastQueryPart.mRawQueryArgs);
builder.append(lastQueryPart.mRawQuery);
}
if (!mOrderClauses.isEmpty()) {
builder.append(" ORDER BY ");
builder.append(Joiner.on(", ").join(mOrderClauses));
}
args.addAll(Collections2.transform(mOrderByArgs, Functions.toStringFunction()));
if (mLimit != null) {
builder.append(" LIMIT ");
builder.append(mLimit);
if (mOffset != null) {
builder.append(" OFFSET ");
builder.append(mOffset);
}
}
return new RawQuery(builder.toString(), args);
}
@Override
public FluentCursor perform(SQLiteDatabase db) {
RawQuery rawQuery = toRawQuery();
return new FluentCursor(db.rawQuery(rawQuery.mRawQuery, rawQuery.mRawQueryArgs.toArray(new String[rawQuery.mRawQueryArgs.size()])));
}
@Override
public ColumnAliasBuilder column(String column) {
return expr(Expressions.column(column));
}
@Override
public ColumnAliasBuilder column(String table, String column) {
return expr(Expressions.column(table, column));
}
@Override
public ColumnAliasBuilder literal(Number number) {
return expr(Expressions.literal(number));
}
@Override
public ColumnAliasBuilder literal(Object object) {
return expr(Expressions.literal(object));
}
@Override
public ColumnAliasBuilder nul() {
return expr(Expressions.nul());
}
@Override
public ColumnAliasBuilder expr(Expression expression) {
mCurrentQueryPart.addPendingColumns();
mCurrentQueryPart.addPendingColumn();
mCurrentQueryPart.mColumnWithPotentialAlias = expression.getSql();
mCurrentQueryPart.mTablesUsedInExpressions.addAll(expression.getTables());
if (expression.getArgsCount() > 0) {
mCurrentQueryPart.mArgs.putAll(QueryPart.PROJECTION, Arrays.asList(expression.getMergedArgs()));
}
return this;
}
@Override
public QueryBuilder as(String alias) {
Preconditions.checkState(mCurrentQueryPart.mColumnWithPotentialAlias != null);
mCurrentQueryPart.mProjection.add(mCurrentQueryPart.mColumnWithPotentialAlias + " AS " + alias);
mCurrentQueryPart.mColumnWithPotentialAlias = null;
return this;
}
@Override
public ColumnListTableSelector columns(String... columns) {
mCurrentQueryPart.addPendingColumn();
mCurrentQueryPart.addPendingColumns();
if (columns != null) {
Collections.addAll(mCurrentQueryPart.mColumnsWithPotentialTable, columns);
}
return this;
}
@Override
public ColumnsTableSelector allColumns() {
mCurrentQueryPart.addPendingColumn();
mCurrentQueryPart.addPendingColumns();
mCurrentQueryPart.mColumnsWithPotentialTable.add("*");
return mColumnsTableSelectorHelper;
}
@Override
public ColumnsListAliasBuilder of(String table) {
mCurrentQueryPart.mColumnsListsTableWithPotentialAlias = table;
return this;
}
@Override
public QueryBuilder asColumnNames() {
for (String column : mCurrentQueryPart.mColumnsWithPotentialTable) {
mCurrentQueryPart.mProjection.add(mCurrentQueryPart.mColumnsListsTableWithPotentialAlias + "." + column + " AS " + column);
}
mCurrentQueryPart.mColumnsListsTableWithPotentialAlias = null;
mCurrentQueryPart.mColumnsWithPotentialTable.clear();
return this;
}
@Override
public QueryBuilder distinct() {
mCurrentQueryPart.mIsDistinct = true;
return this;
}
@Override
public QueryBuilder all() {
mCurrentQueryPart.mIsDistinct = false;
return this;
}
private static abstract class ColumnsTableSelectorHelper extends QueryBuilderProxy implements ColumnsTableSelector {
private ColumnsTableSelectorHelper(QueryBuilder delegate) {
super(delegate);
}
}
private ColumnsTableSelectorHelper mColumnsTableSelectorHelper = new ColumnsTableSelectorHelper(this) {
@Override
public QueryBuilder of(String table) {
for (String column : mCurrentQueryPart.mColumnsWithPotentialTable) {
mCurrentQueryPart.mProjection.add(table + "." + column);
}
mCurrentQueryPart.mColumnsWithPotentialTable.clear();
return QueryBuilderImpl.this;
}
};
@Override
public UnionTypeSelector union() {
return mCompoundQueryHelper.withOperation("UNION");
}
@Override
public NextQueryPartStart intersect() {
return mCompoundQueryHelper.withOperation("INTERSECT");
}
@Override
public NextQueryPartStart except() {
return mCompoundQueryHelper.withOperation("EXCEPT");
}
private abstract static class CompoundQueryHelper implements UnionTypeSelector {
protected String mOperation;
public CompoundQueryHelper withOperation(String operation) {
mOperation = operation;
return this;
}
@Override
public NextQueryPartStart all() {
return withOperation("UNION ALL");
}
}
private CompoundQueryHelper mCompoundQueryHelper = new CompoundQueryHelper() {
@Override
public QueryBuilder select() {
mCompoundQueryParts.add(new QueryOrOperator(new QueryBuilderImpl(mCurrentQueryPart).build()));
mCompoundQueryParts.add(new QueryOrOperator(mOperation));
mCurrentQueryPart = new CompoundQueryPart();
return QueryBuilderImpl.this;
}
};
@Override
public QueryBuilder groupBy(String expression) {
mCurrentQueryPart.mGroupByExpressions.add(expression);
return this;
}
@Override
public QueryBuilder groupBy(Expression expression) {
mCurrentQueryPart.mTablesUsedInExpressions.addAll(expression.getTables());
if (expression.getArgsCount() > 0) {
mCurrentQueryPart.mArgs.putAll(QueryPart.GROUP_BY, Arrays.asList(expression.getMergedArgs()));
}
return groupBy(expression.getSql());
}
@Override
public QueryBuilder having(String having, Object... havingArgs) {
mCurrentQueryPart.mHaving.add(having);
if (havingArgs != null) {
mCurrentQueryPart.mArgs.putAll(QueryPart.HAVING, Arrays.asList(havingArgs));
}
return this;
}
@Override
public QueryBuilder having(Expression having, Object... havingArgs) {
mCurrentQueryPart.mTablesUsedInExpressions.addAll(having.getTables());
return having(having.getSql(), having.getMergedArgs(havingArgs));
}
@Override
public JoinTypeBuilder natural() {
mCurrentQueryPart.mPendingJoinType = "NATURAL ";
return this;
}
@Override
public JoinBuilder left() {
mCurrentQueryPart.mPendingJoinType += "LEFT ";
return this;
}
@Override
public JoinBuilder cross() {
mCurrentQueryPart.mPendingJoinType += "CROSS ";
return this;
}
@Override
public JoinAliasBuilder join(String table) {
mCurrentQueryPart.addPendingJoin();
mCurrentQueryPart.mPendingJoin = new JoinSpec(mCurrentQueryPart.mPendingJoinType, new TableOrSubquery(table));
mCurrentQueryPart.mPendingJoinType = "";
return mJoinHelper;
}
@Override
public JoinAliasBuilder join(Query subquery) {
mCurrentQueryPart.addPendingJoin();
mCurrentQueryPart.mPendingJoin = new JoinSpec(mCurrentQueryPart.mPendingJoinType, new TableOrSubquery(subquery));
mCurrentQueryPart.mPendingJoinType = "";
return mJoinHelper;
}
@Override
public JoinAliasBuilder join(QueryBuilder subqueryBuilder) {
return join(subqueryBuilder.build());
}
private static abstract class JoinHelper extends QueryBuilderProxy implements JoinAliasBuilder {
private JoinHelper(QueryBuilder delegate) {
super(delegate);
}
}
private JoinHelper mJoinHelper = new JoinHelper(this) {
@Override
public JoinConstraintBuilder as(String alias) {
mCurrentQueryPart.mPendingJoin.mAlias = alias;
return this;
}
@Override
public QueryBuilder using(String... columns) {
Preconditions.checkArgument(columns != null, "Column list in USING clause cannot be null");
Preconditions.checkArgument(columns.length > 0, "Column list in USING clause cannot be empty");
mCurrentQueryPart.mPendingJoin.mUsingColumns = columns;
mCurrentQueryPart.addPendingJoin();
return QueryBuilderImpl.this;
}
@Override
public JoinOnConstraintBuilder on(String constraint, Object... constraintArgs) {
mCurrentQueryPart.mPendingJoin.mConstraints.add(constraint);
if (constraintArgs != null) {
Collections.addAll(mCurrentQueryPart.mPendingJoin.mConstraintsArgs, constraintArgs);
}
return this;
}
@Override
public JoinOnConstraintBuilder on(Expression constraint, Object... constraintArgs) {
mCurrentQueryPart.mTablesUsedInExpressions.addAll(constraint.getTables());
mCurrentQueryPart.mPendingJoin.mConstraints.add(constraint.getSql());
Collections.addAll(mCurrentQueryPart.mPendingJoin.mConstraintsArgs, constraint.getMergedArgs(constraintArgs));
return this;
}
};
private static class JoinSpec {
final String mJoinType;
final TableOrSubquery mJoinSource;
String mAlias;
String[] mUsingColumns;
List<String> mConstraints = Lists.newArrayList();
List<Object> mConstraintsArgs = Lists.newArrayList();
private JoinSpec(String joinType, TableOrSubquery joinSource) {
mJoinType = joinType;
mJoinSource = joinSource;
}
private JoinSpec(JoinSpec other) {
mJoinType = other.mJoinType;
mJoinSource = other.mJoinSource;
mAlias = other.mAlias;
mUsingColumns = other.mUsingColumns != null ? Arrays.copyOf(other.mUsingColumns, other.mUsingColumns.length) : null;
mConstraints.addAll(other.mConstraints);
mConstraintsArgs.addAll(other.mConstraintsArgs);
}
}
@Override
public LimitOffsetBuilder limit(String expression) {
Preconditions.checkState(mLimit == null, "LIMIT can be set only once");
mLimit = expression;
return this;
}
@Override
public LimitOffsetBuilder limit(int limit) {
return limit(String.valueOf(limit));
}
@Override
public QueryBuilder offset(String expression) {
Preconditions.checkState(mLimit != null);
Preconditions.checkState(mOffset == null);
mOffset = expression;
return this;
}
@Override
public QueryBuilder offset(int limit) {
return offset(String.valueOf(limit));
}
@Override
public OrderingTermBuilder orderBy(String expression) {
buildPendingOrderByClause();
mOrderByExpression = expression;
return this;
}
@Override
public OrderingTermBuilder orderBy(Expression expression) {
mTablesUsedInExpressions.addAll(expression.getTables());
Collections.addAll(mOrderByArgs, expression.getMergedArgs());
return orderBy(expression.getSql());
}
@Override
public OrderingDirectionSelector collate(String collation) {
mOrderByCollation = collation;
return this;
}
@Override
public OrderingDirectionSelector collate(CollatingSequence collation) {
mOrderByCollation = collation.name();
return this;
}
@Override
public QueryBuilder asc() {
mOrderByOrder = " ASC";
return this;
}
@Override
public QueryBuilder desc() {
mOrderByOrder = " DESC";
return this;
}
private void buildPendingOrderByClause() {
if (mOrderByExpression != null) {
String orderByClause = mOrderByExpression;
if (mOrderByCollation != null) {
orderByClause += " COLLATE " + mOrderByCollation;
}
if (mOrderByOrder != null) {
orderByClause += mOrderByOrder;
}
mOrderByExpression = null;
mOrderByCollation = null;
mOrderByOrder = null;
mOrderClauses.add(orderByClause);
}
}
@SafeVarargs
@Override
public final <T> QueryBuilder where(String selection, T... selectionArgs) {
if (!Strings.isNullOrEmpty(selection)) {
mCurrentQueryPart.mSelection.add(selection);
if (selectionArgs != null) {
mCurrentQueryPart.mArgs.putAll(QueryPart.SELECTION, Arrays.asList(selectionArgs));
}
}
return this;
}
@SafeVarargs
@Override
public final <T> QueryBuilder where(Expression selection, T... selectionArgs) {
if (selection != null) {
mCurrentQueryPart.mTablesUsedInExpressions.addAll(selection.getTables());
where(selection.getSql(), selection.getMergedArgs(selectionArgs));
}
return this;
}
private static abstract class TableAliasBuilderImpl extends QueryBuilderProxy implements TableAliasBuilder {
private TableAliasBuilderImpl(QueryBuilder delegate) {
super(delegate);
}
}
private TableAliasBuilderImpl mTableAliasBuilder = new TableAliasBuilderImpl(this) {
@Override
public QueryBuilder as(String alias) {
mCurrentQueryPart.addPendingTable(alias);
return QueryBuilderImpl.this;
}
};
@Override
public TableAliasBuilder from(String table) {
mCurrentQueryPart.addPendingTable(null);
mCurrentQueryPart.mPendingTable = new TableOrSubquery(table);
return mTableAliasBuilder;
}
@Override
public TableAliasBuilder from(Query subquery) {
mCurrentQueryPart.addPendingTable(null);
mCurrentQueryPart.mPendingTable = new TableOrSubquery(subquery);
return mTableAliasBuilder;
}
@Override
public TableAliasBuilder from(QueryBuilder subqueryBuilder) {
return from(subqueryBuilder.build());
}
private static class TableOrSubquery {
final String mTable;
final Query mSubquery;
private TableOrSubquery(String table) {
mTable = table;
mSubquery = null;
}
private TableOrSubquery(Query subquery) {
mTable = null;
mSubquery = subquery;
}
}
}
private static class QueryOrOperator {
final String mOperator;
final Query mQuery;
private QueryOrOperator(String operator) {
mOperator = operator;
mQuery = null;
}
private QueryOrOperator(Query query) {
mOperator = null;
mQuery = query;
}
private boolean isOperator() {
return mOperator != null;
}
private boolean isQuery() {
return mQuery != null;
}
}
public interface QueryBuilder extends DistinctSelector, TableSelector, ColumnSelector, SelectionBuilder, NaturalJoinTypeBuilder, GroupByBuilder, HavingBuilder, OrderByBuilder, LimitBuilder, CompoundOperator {
Query build();
RawQuery toRawQuery();
Set<String> getTables();
FluentCursor perform(SQLiteDatabase db);
}
private static class QueryBuilderProxy implements QueryBuilder {
private final QueryBuilder mDelegate;
private QueryBuilderProxy(QueryBuilder delegate) {
mDelegate = delegate;
}
@Override
public ColumnAliasBuilder column(String column) {
return mDelegate.column(column);
}
@Override
public ColumnAliasBuilder column(String table, String column) {
return mDelegate.column(table, column);
}
@Override
public ColumnAliasBuilder literal(Number number) {
return mDelegate.literal(number);
}
@Override
public ColumnAliasBuilder literal(Object object) {
return mDelegate.literal(object);
}
@Override
public ColumnAliasBuilder nul() {
return mDelegate.nul();
}
@Override
public ColumnListTableSelector columns(String... columns) {
return mDelegate.columns(columns);
}
@Override
public ColumnsTableSelector allColumns() {
return mDelegate.allColumns();
}
@Override
public ColumnAliasBuilder expr(Expression expression) {
return mDelegate.expr(expression);
}
@Override
public UnionTypeSelector union() {
return mDelegate.union();
}
@Override
public NextQueryPartStart intersect() {
return mDelegate.intersect();
}
@Override
public NextQueryPartStart except() {
return mDelegate.except();
}
@Override
public QueryBuilder groupBy(String expression) {
return mDelegate.groupBy(expression);
}
@Override
public QueryBuilder groupBy(Expression expression) {
return mDelegate.groupBy(expression);
}
@Override
public QueryBuilder having(String having, Object... havingArgs) {
return mDelegate.having(having, havingArgs);
}
@Override
public QueryBuilder having(Expression having, Object... havingArgs) {
return mDelegate.having(having, havingArgs);
}
@Override
public JoinBuilder left() {
return mDelegate.left();
}
@Override
public JoinBuilder cross() {
return mDelegate.cross();
}
@Override
public JoinAliasBuilder join(String table) {
return mDelegate.join(table);
}
@Override
public JoinAliasBuilder join(Query subquery) {
return mDelegate.join(subquery);
}
@Override
public JoinAliasBuilder join(QueryBuilder subqueryBuilder) {
return mDelegate.join(subqueryBuilder);
}
@Override
public LimitOffsetBuilder limit(String expression) {
return mDelegate.limit(expression);
}
@Override
public LimitOffsetBuilder limit(int limit) {
return mDelegate.limit(limit);
}
@Override
public JoinTypeBuilder natural() {
return mDelegate.natural();
}
@Override
public OrderingTermBuilder orderBy(String expression) {
return mDelegate.orderBy(expression);
}
@Override
public OrderingTermBuilder orderBy(Expression expression) {
return mDelegate.orderBy(expression);
}
@SafeVarargs
@Override
public final <T> QueryBuilder where(String selection, T... selectionArgs) {
return mDelegate.where(selection, selectionArgs);
}
@SafeVarargs
@Override
public final <T> QueryBuilder where(Expression selection, T... selectionArgs) {
return mDelegate.where(selection, selectionArgs);
}
@Override
public TableAliasBuilder from(String table) {
return mDelegate.from(table);
}
@Override
public TableAliasBuilder from(Query subquery) {
return mDelegate.from(subquery);
}
@Override
public TableAliasBuilder from(QueryBuilder subqueryBuilder) {
return mDelegate.from(subqueryBuilder);
}
@Override
public Query build() {
return mDelegate.build();
}
@Override
public RawQuery toRawQuery() {
return mDelegate.toRawQuery();
}
@Override
public Set<String> getTables() {
return mDelegate.getTables();
}
@Override
public FluentCursor perform(SQLiteDatabase db) {
return mDelegate.perform(db);
}
@Override
public QueryBuilder distinct() {
return mDelegate.distinct();
}
@Override
public QueryBuilder all() {
return mDelegate.all();
}
}
public interface TableSelector {
TableAliasBuilder from(String table);
TableAliasBuilder from(Query subquery);
TableAliasBuilder from(QueryBuilder subqueryBuilder);
}
public interface DistinctSelector {
QueryBuilder distinct();
QueryBuilder all();
}
public interface TableAliasBuilder extends QueryBuilder {
QueryBuilder as(String alias);
}
public interface ColumnSelector {
ColumnAliasBuilder column(String column);
ColumnAliasBuilder column(String table, String column);
ColumnAliasBuilder literal(Number number);
ColumnAliasBuilder literal(Object object);
ColumnAliasBuilder nul();
ColumnListTableSelector columns(String... columns);
ColumnsTableSelector allColumns();
ColumnAliasBuilder expr(Expression expression);
}
public interface ColumnsTableSelector extends QueryBuilder {
QueryBuilder of(String table);
}
public interface ColumnListTableSelector extends QueryBuilder {
ColumnsListAliasBuilder of(String table);
}
public interface ColumnsListAliasBuilder extends QueryBuilder {
QueryBuilder asColumnNames();
}
public interface ColumnAliasBuilder extends QueryBuilder {
QueryBuilder as(String alias);
}
@SuppressWarnings("unchecked")
public interface SelectionBuilder {
<T> QueryBuilder where(String selection, T... selectionArgs);
<T> QueryBuilder where(Expression selection, T... selectionArgs);
}
public interface JoinTypeBuilder extends JoinBuilder {
JoinBuilder left();
JoinBuilder cross();
}
public interface NaturalJoinTypeBuilder extends JoinTypeBuilder {
JoinTypeBuilder natural();
}
public interface JoinBuilder {
JoinAliasBuilder join(String table);
JoinAliasBuilder join(Query subquery);
JoinAliasBuilder join(QueryBuilder subqueryBuilder);
}
public interface JoinAliasBuilder extends JoinConstraintBuilder {
JoinConstraintBuilder as(String alias);
}
public interface JoinConstraintBuilder extends JoinOnConstraintBuilder {
QueryBuilder using(String... columns);
}
public interface JoinOnConstraintBuilder extends QueryBuilder {
JoinOnConstraintBuilder on(String constraint, Object... constraintArgs);
JoinOnConstraintBuilder on(Expression constraint, Object... constraintArgs);
}
public interface GroupByBuilder {
QueryBuilder groupBy(String expression);
QueryBuilder groupBy(Expression expression);
}
public interface HavingBuilder {
QueryBuilder having(String having, Object... havingArgs);
QueryBuilder having(Expression having, Object... havingArgs);
}
public interface OrderByBuilder {
OrderingTermBuilder orderBy(String expression);
OrderingTermBuilder orderBy(Expression expression);
}
public interface OrderingTermBuilder extends OrderingDirectionSelector {
OrderingDirectionSelector collate(String collation);
OrderingDirectionSelector collate(CollatingSequence collation);
}
public interface OrderingDirectionSelector extends QueryBuilder {
QueryBuilder asc();
QueryBuilder desc();
}
public interface LimitBuilder {
LimitOffsetBuilder limit(String expression);
LimitOffsetBuilder limit(int limit);
}
public interface LimitOffsetBuilder extends QueryBuilder {
QueryBuilder offset(String expression);
QueryBuilder offset(int limit);
}
public interface CompoundOperator {
UnionTypeSelector union();
NextQueryPartStart intersect();
NextQueryPartStart except();
}
public interface UnionTypeSelector extends NextQueryPartStart {
NextQueryPartStart all();
}
public interface NextQueryPartStart {
QueryBuilder select();
}
}