All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.greenrobot.dao.query.QueryBuilder Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2011-2013 Markus Junginger, greenrobot (http://greenrobot.de)
 *
 * 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 de.greenrobot.dao.query;

import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import de.greenrobot.dao.AbstractDao;
import de.greenrobot.dao.AbstractDaoSession;
import de.greenrobot.dao.DaoException;
import de.greenrobot.dao.DaoLog;
import de.greenrobot.dao.InternalQueryDaoAccess;
import de.greenrobot.dao.Property;
import de.greenrobot.dao.internal.SqlUtils;
import de.greenrobot.dao.query.WhereCondition.PropertyCondition;

/**
 * Builds custom entity queries using constraints and parameters and without SQL (QueryBuilder creates SQL for you). To
 * acquire an QueryBuilder, use {@link AbstractDao#queryBuilder()} or {@link AbstractDaoSession#queryBuilder(Class)}.
 * Entity properties are referenced by Fields in the "Properties" inner class of the generated DAOs. This approach
 * allows compile time checks and prevents typo errors occuring at build time.
*
* Example: Query for all users with the first name "Joe" ordered by their last name. (The class Properties is an inner * class of UserDao and should be imported before.)
* * List joes = dao.queryBuilder().where(Properties.FirstName.eq("Joe")).orderAsc(Properties.LastName).list(); * * * @author Markus * * @param * Entity class to create an query for. */ public class QueryBuilder { /** Set to true to debug the SQL. */ public static boolean LOG_SQL; /** Set to see the given values. */ public static boolean LOG_VALUES; private StringBuilder orderBuilder; private StringBuilder joinBuilder; private final List whereConditions; private final List values; private final AbstractDao dao; private final String tablePrefix; private Integer limit; private Integer offset; /** For internal use by greenDAO only. */ public static QueryBuilder internalCreate(AbstractDao dao) { return new QueryBuilder(dao); } protected QueryBuilder(AbstractDao dao) { this(dao, "T"); } protected QueryBuilder(AbstractDao dao, String tablePrefix) { this.dao = dao; this.tablePrefix = tablePrefix; values = new ArrayList(); whereConditions = new ArrayList(); } private void checkOrderBuilder() { if (orderBuilder == null) { orderBuilder = new StringBuilder(); } else if (orderBuilder.length() > 0) { orderBuilder.append(","); } } /** * Adds the given conditions to the where clause using an logical AND. To create new conditions, use the properties * given in the generated dao classes. */ public QueryBuilder where(WhereCondition cond, WhereCondition... condMore) { whereConditions.add(cond); for (WhereCondition whereCondition : condMore) { checkCondition(whereCondition); whereConditions.add(whereCondition); } return this; } /** * Adds the given conditions to the where clause using an logical OR. To create new conditions, use the properties * given in the generated dao classes. */ public QueryBuilder whereOr(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) { whereConditions.add(or(cond1, cond2, condMore)); return this; } /** * Creates a WhereCondition by combining the given conditions using OR. The returned WhereCondition must be used * inside {@link #where(WhereCondition, WhereCondition...)} or * {@link #whereOr(WhereCondition, WhereCondition, WhereCondition...)}. */ public WhereCondition or(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) { return combineWhereConditions(" OR ", cond1, cond2, condMore); } /** * Creates a WhereCondition by combining the given conditions using AND. The returned WhereCondition must be used * inside {@link #where(WhereCondition, WhereCondition...)} or * {@link #whereOr(WhereCondition, WhereCondition, WhereCondition...)}. */ public WhereCondition and(WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) { return combineWhereConditions(" AND ", cond1, cond2, condMore); } protected WhereCondition combineWhereConditions(String combineOp, WhereCondition cond1, WhereCondition cond2, WhereCondition... condMore) { StringBuilder builder = new StringBuilder("("); List combinedValues = new ArrayList(); addCondition(builder, combinedValues, cond1); builder.append(combineOp); addCondition(builder, combinedValues, cond2); for (WhereCondition cond : condMore) { builder.append(combineOp); addCondition(builder, combinedValues, cond); } builder.append(')'); return new WhereCondition.StringCondition(builder.toString(), combinedValues.toArray()); } protected void addCondition(StringBuilder builder, List values, WhereCondition condition) { checkCondition(condition); condition.appendTo(builder, tablePrefix); condition.appendValuesTo(values); } protected void checkCondition(WhereCondition whereCondition) { if (whereCondition instanceof PropertyCondition) { checkProperty(((PropertyCondition) whereCondition).property); } } /** Not supported yet. */ public QueryBuilder join(Class entityClass, Property toOneProperty) { throw new UnsupportedOperationException(); // return new QueryBuilder(); } /** Not supported yet. */ public QueryBuilder joinToMany(Class entityClass, Property toManyProperty) { throw new UnsupportedOperationException(); // @SuppressWarnings("unchecked") // AbstractDao joinDao = (AbstractDao) dao.getSession().getDao(entityClass); // return new QueryBuilder(joinDao, "TX"); } /** Adds the given properties to the ORDER BY section using ascending order. */ public QueryBuilder orderAsc(Property... properties) { orderAscOrDesc(" ASC", properties); return this; } /** Adds the given properties to the ORDER BY section using descending order. */ public QueryBuilder orderDesc(Property... properties) { orderAscOrDesc(" DESC", properties); return this; } private void orderAscOrDesc(String ascOrDescWithLeadingSpace, Property... properties) { for (Property property : properties) { checkOrderBuilder(); append(orderBuilder, property); if (String.class.equals(property.type)) { orderBuilder.append(" COLLATE LOCALIZED"); } orderBuilder.append(ascOrDescWithLeadingSpace); } } /** Adds the given properties to the ORDER BY section using the given custom order. */ public QueryBuilder orderCustom(Property property, String customOrderForProperty) { checkOrderBuilder(); append(orderBuilder, property).append(' '); orderBuilder.append(customOrderForProperty); return this; } /** * Adds the given raw SQL string to the ORDER BY section. Do not use this for standard properties: ordedAsc and * orderDesc are prefered. */ public QueryBuilder orderRaw(String rawOrder) { checkOrderBuilder(); orderBuilder.append(rawOrder); return this; } protected StringBuilder append(StringBuilder builder, Property property) { checkProperty(property); builder.append(tablePrefix).append('.').append('\'').append(property.columnName).append('\''); return builder; } protected void checkProperty(Property property) { if (dao != null) { Property[] properties = dao.getProperties(); boolean found = false; for (Property property2 : properties) { if (property == property2) { found = true; break; } } if (!found) { throw new DaoException("Property '" + property.name + "' is not part of " + dao); } } } /** Limits the number of results returned by queries. */ public QueryBuilder limit(int limit) { this.limit = limit; return this; } /** * Sets the offset for query results in combination with {@link #limit(int)}. The first {@code limit} results are * skipped and the total number of results will be limited by {@code limit}. You cannot use offset without limit. */ public QueryBuilder offset(int offset) { this.offset = offset; return this; } /** * Builds a reusable query object (Query objects can be executed more efficiently than creating a QueryBuilder for * each execution. */ public Query build() { String select; if (joinBuilder == null || joinBuilder.length() == 0) { select = InternalQueryDaoAccess.getStatements(dao).getSelectAll(); } else { select = SqlUtils.createSqlSelect(dao.getTablename(), tablePrefix, dao.getAllColumns()); } StringBuilder builder = new StringBuilder(select); appendWhereClause(builder, tablePrefix); if (orderBuilder != null && orderBuilder.length() > 0) { builder.append(" ORDER BY ").append(orderBuilder); } int limitPosition = -1; if (limit != null) { builder.append(" LIMIT ?"); values.add(limit); limitPosition = values.size() - 1; } int offsetPosition = -1; if (offset != null) { if (limit == null) { throw new IllegalStateException("Offset cannot be set without limit"); } builder.append(" OFFSET ?"); values.add(offset); offsetPosition = values.size() - 1; } String sql = builder.toString(); if (LOG_SQL) { DaoLog.d("Built SQL for query: " + sql); } if (LOG_VALUES) { DaoLog.d("Values for query: " + values); } return Query.create(dao, sql, values.toArray(), limitPosition, offsetPosition); } /** * Builds a reusable query object for deletion (Query objects can be executed more efficiently than creating a * QueryBuilder for each execution. */ public DeleteQuery buildDelete() { String tablename = dao.getTablename(); String baseSql = SqlUtils.createSqlDelete(tablename, null); StringBuilder builder = new StringBuilder(baseSql); appendWhereClause(builder, tablename); String sql = builder.toString(); if (LOG_SQL) { DaoLog.d("Built SQL for delete query: " + sql); } if (LOG_VALUES) { DaoLog.d("Values for delete query: " + values); } return DeleteQuery.create(dao, sql, values.toArray()); } /** * Builds a reusable query object for counting rows (Query objects can be executed more efficiently than creating a * QueryBuilder for each execution. */ public CountQuery buildCount() { String tablename = dao.getTablename(); String baseSql = SqlUtils.createSqlSelectCountStar(tablename); StringBuilder builder = new StringBuilder(baseSql); appendWhereClause(builder, tablename); String sql = builder.toString(); if (LOG_SQL) { DaoLog.d("Built SQL for count query: " + sql); } if (LOG_VALUES) { DaoLog.d("Values for count query: " + values); } return CountQuery.create(dao, sql, values.toArray()); } private void appendWhereClause(StringBuilder builder, String tablePrefixOrNull) { values.clear(); if (!whereConditions.isEmpty()) { builder.append(" WHERE "); ListIterator iter = whereConditions.listIterator(); while (iter.hasNext()) { if (iter.hasPrevious()) { builder.append(" AND "); } WhereCondition condition = iter.next(); condition.appendTo(builder, tablePrefixOrNull); condition.appendValuesTo(values); } } } /** * Shorthand for {@link QueryBuilder#build() build()}.{@link Query#list() list()}; see {@link Query#list()} for * details. To execute a query more than once, you should build the query and keep the {@link Query} object for * efficiency reasons. */ public List list() { return build().list(); } /** * Shorthand for {@link QueryBuilder#build() build()}.{@link Query#listLazy() listLazy()}; see * {@link Query#listLazy()} for details. To execute a query more than once, you should build the query and keep the * {@link Query} object for efficiency reasons. */ public LazyList listLazy() { return build().listLazy(); } /** * Shorthand for {@link QueryBuilder#build() build()}.{@link Query#listLazyUncached() listLazyUncached()}; see * {@link Query#listLazyUncached()} for details. To execute a query more than once, you should build the query and * keep the {@link Query} object for efficiency reasons. */ public LazyList listLazyUncached() { return build().listLazyUncached(); } /** * Shorthand for {@link QueryBuilder#build() build()}.{@link Query#listIterator() listIterator()}; see * {@link Query#listIterator()} for details. To execute a query more than once, you should build the query and keep * the {@link Query} object for efficiency reasons. */ public CloseableListIterator listIterator() { return build().listIterator(); } /** * Shorthand for {@link QueryBuilder#build() build()}.{@link Query#unique() unique()}; see {@link Query#unique()} * for details. To execute a query more than once, you should build the query and keep the {@link Query} object for * efficiency reasons. */ public T unique() { return build().unique(); } /** * Shorthand for {@link QueryBuilder#build() build()}.{@link Query#uniqueOrThrow() uniqueOrThrow()}; see * {@link Query#uniqueOrThrow()} for details. To execute a query more than once, you should build the query and keep * the {@link Query} object for efficiency reasons. */ public T uniqueOrThrow() { return build().uniqueOrThrow(); } /** * Shorthand for {@link QueryBuilder#buildCount() buildCount()}.{@link CountQuery#count() count()}; see * {@link CountQuery#count()} for details. To execute a query more than once, you should build the query and keep * the {@link CountQuery} object for efficiency reasons. */ public long count() { return buildCount().count(); } }