-/*\r
- * Copyright 2004-2011 H2 Group.\r
- * Copyright 2011 James Moger.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.iciql;\r
-\r
-import java.lang.reflect.Field;\r
-import java.sql.ResultSet;\r
-import java.sql.SQLException;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.HashMap;\r
-import java.util.IdentityHashMap;\r
-import java.util.List;\r
-\r
-import com.iciql.Iciql.DataTypeAdapter;\r
-import com.iciql.Iciql.EnumType;\r
-import com.iciql.NestedConditions.And;\r
-import com.iciql.NestedConditions.Or;\r
-import com.iciql.bytecode.ClassReader;\r
-import com.iciql.util.IciqlLogger;\r
-import com.iciql.util.JdbcUtils;\r
-import com.iciql.util.Utils;\r
-\r
-/**\r
- * This class represents a query.\r
- *\r
- * @param <T>\r
- * the return type\r
- */\r
-\r
-public class Query<T> {\r
-\r
- private Db db;\r
- private SelectTable<T> from;\r
- private ArrayList<Token> conditions = Utils.newArrayList();\r
- private ArrayList<UpdateColumn> updateColumnDeclarations = Utils.newArrayList();\r
- private int conditionDepth = 0;\r
- private ArrayList<SelectTable<T>> joins = Utils.newArrayList();\r
- private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();\r
- private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();\r
- private ArrayList<Object> groupByExpressions = Utils.newArrayList();\r
- private long limit;\r
- private long offset;\r
-\r
- private Query(Db db) {\r
- this.db = db;\r
- }\r
-\r
- /**\r
- * from() is a static factory method to build a Query object.\r
- *\r
- * @param db\r
- * @param alias\r
- * @return a query object\r
- */\r
- @SuppressWarnings("unchecked")\r
- static <T> Query<T> from(Db db, T alias) {\r
- Query<T> query = new Query<T>(db);\r
- TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());\r
- query.from = new SelectTable<T>(db, query, alias, false);\r
- def.initSelectObject(query.from, alias, query.aliasMap, false);\r
- return query;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- static <T> Query<T> rebuild(Db db, T alias) {\r
- Query<T> query = new Query<T>(db);\r
- TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());\r
- query.from = new SelectTable<T>(db, query, alias, false);\r
- def.initSelectObject(query.from, alias, query.aliasMap, true);\r
- return query;\r
- }\r
-\r
- public long selectCount() {\r
- SQLStatement stat = getSelectStatement(false);\r
- stat.appendSQL("COUNT(*) ");\r
- appendFromWhere(stat);\r
- ResultSet rs = stat.executeQuery();\r
- try {\r
- rs.next();\r
- long value = rs.getLong(1);\r
- return value;\r
- } catch (SQLException e) {\r
- throw IciqlException.fromSQL(stat.getSQL(), e);\r
- } finally {\r
- JdbcUtils.closeSilently(rs, true);\r
- }\r
- }\r
-\r
- public List<T> select() {\r
- return select(false);\r
- }\r
-\r
- public T selectFirst() {\r
- List<T> list = limit(1).select(false);\r
- return list.isEmpty() ? null : list.get(0);\r
- }\r
-\r
- public List<T> selectDistinct() {\r
- return select(true);\r
- }\r
-\r
- public <X, Z> X selectFirst(Z x) {\r
- List<X> list = limit(1).select(x);\r
- return list.isEmpty() ? null : list.get(0);\r
- }\r
-\r
- public <X> void createView(Class<X> viewClass) {\r
- TableDefinition<X> viewDef = db.define(viewClass);\r
-\r
- SQLStatement fromWhere = new SQLStatement(db);\r
- appendFromWhere(fromWhere, false);\r
-\r
- SQLStatement stat = new SQLStatement(db);\r
- db.getDialect().prepareCreateView(stat, viewDef, fromWhere.toSQL());\r
- IciqlLogger.create(stat.toSQL());\r
- stat.execute();\r
- }\r
-\r
- public <X> void replaceView(Class<X> viewClass) {\r
- db.dropView(viewClass);\r
- createView(viewClass);\r
- }\r
-\r
- public String getSQL() {\r
- SQLStatement stat = getSelectStatement(false);\r
- stat.appendSQL("*");\r
- appendFromWhere(stat);\r
- return stat.getSQL().trim();\r
- }\r
-\r
- /**\r
- * toSQL returns a static string version of the query with runtime variables\r
- * properly encoded. This method is also useful when combined with the where\r
- * clause methods like isParameter() or atLeastParameter() which allows\r
- * iciql to generate re-usable parameterized string statements.\r
- *\r
- * @return the sql query as plain text\r
- */\r
- public String toSQL() {\r
- return toSQL(false);\r
- }\r
-\r
- /**\r
- * toSQL returns a static string version of the query with runtime variables\r
- * properly encoded. This method is also useful when combined with the where\r
- * clause methods like isParameter() or atLeastParameter() which allows\r
- * iciql to generate re-usable parameterized string statements.\r
- *\r
- * @param distinct\r
- * if true SELECT DISTINCT is used for the query\r
- * @return the sql query as plain text\r
- */\r
- public String toSQL(boolean distinct) {\r
- return toSQL(distinct, null);\r
- }\r
-\r
- /**\r
- * toSQL returns a static string version of the query with runtime variables\r
- * properly encoded. This method is also useful when combined with the where\r
- * clause methods like isParameter() or atLeastParameter() which allows\r
- * iciql to generate re-usable parameterized string statements.\r
- *\r
- * @param distinct\r
- * if true SELECT DISTINCT is used for the query\r
- * @param k\r
- * k is used to select only the columns of the specified alias\r
- * for an inner join statement. An example of a generated\r
- * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER\r
- * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true\r
- * without the alias parameter the statement would start with\r
- * SELECT DISTINCT * FROM...\r
- * @return the sql query as plain text\r
- */\r
- public <K> String toSQL(boolean distinct, K k) {\r
- SQLStatement stat = new SQLStatement(getDb());\r
- if (updateColumnDeclarations.size() > 0) {\r
- stat.appendSQL("UPDATE ");\r
- from.appendSQL(stat);\r
- stat.appendSQL(" SET ");\r
- int i = 0;\r
- for (UpdateColumn declaration : updateColumnDeclarations) {\r
- if (i++ > 0) {\r
- stat.appendSQL(", ");\r
- }\r
- declaration.appendSQL(stat);\r
- }\r
- appendWhere(stat);\r
- } else {\r
- stat.appendSQL("SELECT ");\r
- if (distinct) {\r
- stat.appendSQL("DISTINCT ");\r
- }\r
- if (k != null) {\r
- SelectTable<?> sel = getSelectTable(k);\r
- if (sel == null) {\r
- // unknown alias, use wildcard\r
- IciqlLogger.warn("Alias {0} is not defined in the statement!", k.getClass());\r
- stat.appendSQL("*");\r
- } else if (isJoin()) {\r
- // join query, use AS alias\r
- String as = sel.getAs();\r
- stat.appendSQL(as + ".*");\r
- } else {\r
- // schema.table.*\r
- String schema = sel.getAliasDefinition().schemaName;\r
- String table = sel.getAliasDefinition().tableName;\r
- String as = getDb().getDialect().prepareTableName(schema, table);\r
- stat.appendSQL(as + ".*");\r
- }\r
- } else {\r
- // alias unspecified, use wildcard\r
- stat.appendSQL("*");\r
- }\r
- appendFromWhere(stat);\r
- }\r
- return stat.toSQL().trim();\r
- }\r
-\r
- <Z> String toSubQuery(Z z) {\r
- SQLStatement stat = getSelectStatement(false);\r
- SelectColumn<T> col = aliasMap.get(z);\r
- String columnName = col.getFieldDefinition().columnName;\r
- stat.appendColumn(columnName);\r
- appendFromWhere(stat);\r
- return stat.toSQL();\r
- }\r
-\r
- private List<T> select(boolean distinct) {\r
- List<T> result = Utils.newArrayList();\r
- TableDefinition<T> def = from.getAliasDefinition();\r
- SQLStatement stat = getSelectStatement(distinct);\r
- def.appendSelectList(stat);\r
- appendFromWhere(stat);\r
- ResultSet rs = stat.executeQuery();\r
- try {\r
- // SQLite returns pre-closed ResultSets for query results with 0 rows\r
- if (!rs.isClosed()) {\r
- int[] columns = def.mapColumns(false, rs);\r
- while (rs.next()) {\r
- T item = from.newObject();\r
- def.readRow(db.getDialect(), item, rs, columns);\r
- result.add(item);\r
- }\r
- }\r
- } catch (SQLException e) {\r
- throw IciqlException.fromSQL(stat.getSQL(), e);\r
- } finally {\r
- JdbcUtils.closeSilently(rs, true);\r
- }\r
- return result;\r
- }\r
-\r
- public int delete() {\r
- SQLStatement stat = new SQLStatement(db);\r
- stat.appendSQL("DELETE FROM ");\r
- from.appendSQL(stat);\r
- appendWhere(stat);\r
- IciqlLogger.delete(stat.getSQL());\r
- return stat.executeUpdate();\r
- }\r
-\r
- public <A> UpdateColumnSet<T, A> set(A field) {\r
- from.getAliasDefinition().checkMultipleEnums(field);\r
- return new UpdateColumnSet<T, A>(this, field);\r
- }\r
-\r
- public UpdateColumnSet<T, Boolean> set(boolean field) {\r
- from.getAliasDefinition().checkMultipleBooleans();\r
- return setPrimitive(field);\r
- }\r
-\r
- public UpdateColumnSet<T, Byte> set(byte field) {\r
- return setPrimitive(field);\r
- }\r
-\r
- public UpdateColumnSet<T, Short> set(short field) {\r
- return setPrimitive(field);\r
- }\r
-\r
- public UpdateColumnSet<T, Integer> set(int field) {\r
- return setPrimitive(field);\r
- }\r
-\r
- public UpdateColumnSet<T, Long> set(long field) {\r
- return setPrimitive(field);\r
- }\r
-\r
- public UpdateColumnSet<T, Float> set(float field) {\r
- return setPrimitive(field);\r
- }\r
-\r
- public UpdateColumnSet<T, Double> set(double field) {\r
- return setPrimitive(field);\r
- }\r
-\r
- private <A> UpdateColumnSet<T, A> setPrimitive(A field) {\r
- A alias = getPrimitiveAliasByValue(field);\r
- if (alias == null) {\r
- // this will result in an unmapped field exception\r
- return set(field);\r
- }\r
- return set(alias);\r
- }\r
-\r
- public <A> UpdateColumnIncrement<T, A> increment(A field) {\r
- return new UpdateColumnIncrement<T, A>(this, field);\r
- }\r
-\r
- public UpdateColumnIncrement<T, Byte> increment(byte field) {\r
- return incrementPrimitive(field);\r
- }\r
-\r
- public UpdateColumnIncrement<T, Short> increment(short field) {\r
- return incrementPrimitive(field);\r
- }\r
-\r
- public UpdateColumnIncrement<T, Integer> increment(int field) {\r
- return incrementPrimitive(field);\r
- }\r
-\r
- public UpdateColumnIncrement<T, Long> increment(long field) {\r
- return incrementPrimitive(field);\r
- }\r
-\r
- public UpdateColumnIncrement<T, Float> increment(float field) {\r
- return incrementPrimitive(field);\r
- }\r
-\r
- public UpdateColumnIncrement<T, Double> increment(double field) {\r
- return incrementPrimitive(field);\r
- }\r
-\r
- private <A> UpdateColumnIncrement<T, A> incrementPrimitive(A field) {\r
- A alias = getPrimitiveAliasByValue(field);\r
- if (alias == null) {\r
- // this will result in an unmapped field exception\r
- return increment(field);\r
- }\r
- return increment(alias);\r
- }\r
-\r
- public int update() {\r
- if (updateColumnDeclarations.size() == 0) {\r
- throw new IciqlException("Missing set or increment call.");\r
- }\r
- SQLStatement stat = new SQLStatement(db);\r
- stat.appendSQL("UPDATE ");\r
- from.appendSQL(stat);\r
- stat.appendSQL(" SET ");\r
- int i = 0;\r
- for (UpdateColumn declaration : updateColumnDeclarations) {\r
- if (i++ > 0) {\r
- stat.appendSQL(", ");\r
- }\r
- declaration.appendSQL(stat);\r
- }\r
- appendWhere(stat);\r
- IciqlLogger.update(stat.getSQL());\r
- return stat.executeUpdate();\r
- }\r
-\r
- public <X, Z> List<X> selectDistinct(Z x) {\r
- return select(x, true);\r
- }\r
-\r
- public <X, Z> List<X> select(Z x) {\r
- return select(x, false);\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- private <X, Z> List<X> select(Z x, boolean distinct) {\r
- Class<?> clazz = x.getClass();\r
- if (Utils.isSimpleType(clazz)) {\r
- return selectSimple((X) x, distinct);\r
- }\r
- Class<?> enclosingClass = clazz.getEnclosingClass();\r
- if (enclosingClass != null) {\r
- // anonymous inner class\r
- clazz = clazz.getSuperclass();\r
- }\r
- return select((Class<X>) clazz, (X) x, distinct);\r
- }\r
-\r
- private <X> List<X> select(Class<X> clazz, X x, boolean distinct) {\r
- List<X> result = Utils.newArrayList();\r
- TableDefinition<X> def = db.define(clazz);\r
- SQLStatement stat = getSelectStatement(distinct);\r
- def.appendSelectList(stat, this, x);\r
- appendFromWhere(stat);\r
- ResultSet rs = stat.executeQuery();\r
- try {\r
- // SQLite returns pre-closed ResultSets for query results with 0 rows\r
- if (!rs.isClosed()) {\r
- int[] columns = def.mapColumns(false, rs);\r
- while (rs.next()) {\r
- X row = Utils.newObject(clazz);\r
- def.readRow(db.getDialect(), row, rs, columns);\r
- result.add(row);\r
- }\r
- }\r
- } catch (SQLException e) {\r
- throw IciqlException.fromSQL(stat.getSQL(), e);\r
- } finally {\r
- JdbcUtils.closeSilently(rs, true);\r
- }\r
- return result;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- private <X> List<X> selectSimple(X x, boolean distinct) {\r
- SQLStatement stat = getSelectStatement(distinct);\r
- appendSQL(stat, null, x);\r
- appendFromWhere(stat);\r
- ResultSet rs = stat.executeQuery();\r
- List<X> result = Utils.newArrayList();\r
- Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(x.getClass().getAnnotations());\r
- if (typeAdapter != null) {\r
- DataTypeAdapter<?> dta = Utils.newObject(typeAdapter);\r
- db.getDialect().registerAdapter(dta);\r
- }\r
- try {\r
- // SQLite returns pre-closed ResultSets for query results with 0 rows\r
- if (!rs.isClosed()) {\r
- while (rs.next()) {\r
- X value = (X) db.getDialect().deserialize(rs, 1, x.getClass());\r
- result.add(value);\r
- }\r
- }\r
- } catch (Exception e) {\r
- throw IciqlException.fromSQL(stat.getSQL(), e);\r
- } finally {\r
- JdbcUtils.closeSilently(rs, true);\r
- }\r
- return result;\r
- }\r
-\r
- private SQLStatement getSelectStatement(boolean distinct) {\r
- SQLStatement stat = new SQLStatement(db);\r
- stat.appendSQL("SELECT ");\r
- if (distinct) {\r
- stat.appendSQL("DISTINCT ");\r
- }\r
- return stat;\r
- }\r
-\r
- /**\r
- * Begin a primitive boolean field condition clause.\r
- *\r
- * @param x\r
- * the primitive boolean field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Boolean> where(boolean x) {\r
- from.getAliasDefinition().checkMultipleBooleans();\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begin a primitive short field condition clause.\r
- *\r
- * @param x\r
- * the primitive short field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Byte> where(byte x) {\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begin a primitive short field condition clause.\r
- *\r
- * @param x\r
- * the primitive short field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Short> where(short x) {\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begin a primitive int field condition clause.\r
- *\r
- * @param x\r
- * the primitive int field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Integer> where(int x) {\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begin a primitive long field condition clause.\r
- *\r
- * @param x\r
- * the primitive long field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Long> where(long x) {\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begin a primitive float field condition clause.\r
- *\r
- * @param x\r
- * the primitive float field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Float> where(float x) {\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begin a primitive double field condition clause.\r
- *\r
- * @param x\r
- * the primitive double field to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public QueryCondition<T, Double> where(double x) {\r
- return wherePrimitive(x);\r
- }\r
-\r
- /**\r
- * Begins a primitive field condition clause.\r
- *\r
- * @param value\r
- * @return a query condition to continue building the condition\r
- */\r
- private <A> QueryCondition<T, A> wherePrimitive(A value) {\r
- A alias = getPrimitiveAliasByValue(value);\r
- if (alias == null) {\r
- // this will result in an unmapped field exception\r
- return where(value);\r
- }\r
- return where(alias);\r
- }\r
-\r
- /**\r
- * Begin an Object field condition clause.\r
- *\r
- * @param x\r
- * the mapped object to query\r
- * @return a query condition to continue building the condition\r
- */\r
- public <A> QueryCondition<T, A> where(A x) {\r
- from.getAliasDefinition().checkMultipleEnums(x);\r
- return new QueryCondition<T, A>(this, x);\r
- }\r
-\r
- public <A> QueryWhere<T> where(Filter filter) {\r
- HashMap<String, Object> fieldMap = Utils.newHashMap();\r
- for (Field f : filter.getClass().getDeclaredFields()) {\r
- f.setAccessible(true);\r
- try {\r
- Object obj = f.get(filter);\r
- if (obj == from.getAlias()) {\r
- List<TableDefinition.FieldDefinition> fields = from.getAliasDefinition().getFields();\r
- String name = f.getName();\r
- for (TableDefinition.FieldDefinition field : fields) {\r
- String n = name + "." + field.field.getName();\r
- Object o = field.field.get(obj);\r
- fieldMap.put(n, o);\r
- }\r
- }\r
- fieldMap.put(f.getName(), f.get(filter));\r
- } catch (Exception e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
- Token filterCode = new ClassReader().decompile(filter, fieldMap, "where");\r
- // String filterQuery = filterCode.toString();\r
- conditions.add(filterCode);\r
- return new QueryWhere<T>(this);\r
- }\r
-\r
- public QueryWhere<T> where(String fragment, List<?> args) {\r
- return this.where(fragment, args.toArray());\r
- }\r
-\r
- public QueryWhere<T> where(String fragment, Object... args) {\r
- conditions.add(new RuntimeToken(fragment, args));\r
- return new QueryWhere<T>(this);\r
- }\r
-\r
- public Query<T> where(And<T> conditions) {\r
- whereTrue();\r
- addConditionToken(conditions.where.query);\r
- return this;\r
- }\r
-\r
- public Query<T> where(Or<T> conditions) {\r
- whereFalse();\r
- addConditionToken(conditions.where.query);\r
- return this;\r
- }\r
-\r
- public QueryWhere<T> whereTrue() {\r
- return whereTrue(true);\r
- }\r
-\r
- public QueryWhere<T> whereFalse() {\r
- return whereTrue(false);\r
- }\r
-\r
- public QueryWhere<T> whereTrue(Boolean condition) {\r
- Token token = new Function("", condition);\r
- addConditionToken(token);\r
- return new QueryWhere<T>(this);\r
- }\r
-\r
- /**\r
- * Sets the Limit and Offset of a query.\r
- *\r
- * @return the query\r
- */\r
-\r
- public Query<T> limit(long limit) {\r
- this.limit = limit;\r
- return this;\r
- }\r
-\r
- public Query<T> offset(long offset) {\r
- this.offset = offset;\r
- return this;\r
- }\r
-\r
- public Query<T> orderBy(boolean field) {\r
- from.getAliasDefinition().checkMultipleBooleans();\r
- return orderByPrimitive(field);\r
- }\r
-\r
- public Query<T> orderBy(byte field) {\r
- return orderByPrimitive(field);\r
- }\r
-\r
- public Query<T> orderBy(short field) {\r
- return orderByPrimitive(field);\r
- }\r
-\r
- public Query<T> orderBy(int field) {\r
- return orderByPrimitive(field);\r
- }\r
-\r
- public Query<T> orderBy(long field) {\r
- return orderByPrimitive(field);\r
- }\r
-\r
- public Query<T> orderBy(float field) {\r
- return orderByPrimitive(field);\r
- }\r
-\r
- public Query<T> orderBy(double field) {\r
- return orderByPrimitive(field);\r
- }\r
-\r
- Query<T> orderByPrimitive(Object field) {\r
- Object alias = getPrimitiveAliasByValue(field);\r
- if (alias == null) {\r
- return orderBy(field);\r
- }\r
- return orderBy(alias);\r
- }\r
-\r
- public Query<T> orderBy(Object expr) {\r
- from.getAliasDefinition().checkMultipleEnums(expr);\r
- OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);\r
- addOrderBy(e);\r
- return this;\r
- }\r
-\r
- /**\r
- * Order by a number of columns.\r
- *\r
- * @param expressions\r
- * the columns\r
- * @return the query\r
- */\r
-\r
- public Query<T> orderBy(Object... expressions) {\r
- for (Object expr : expressions) {\r
- from.getAliasDefinition().checkMultipleEnums(expr);\r
- OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);\r
- addOrderBy(e);\r
- }\r
- return this;\r
- }\r
-\r
- public Query<T> orderByDesc(Object expr) {\r
- OrderExpression<T> e = new OrderExpression<T>(this, expr, true, false, false);\r
- addOrderBy(e);\r
- return this;\r
- }\r
-\r
- public Query<T> groupBy(boolean field) {\r
- from.getAliasDefinition().checkMultipleBooleans();\r
- return groupByPrimitive(field);\r
- }\r
-\r
- public Query<T> groupBy(byte field) {\r
- return groupByPrimitive(field);\r
- }\r
-\r
- public Query<T> groupBy(short field) {\r
- return groupByPrimitive(field);\r
- }\r
-\r
- public Query<T> groupBy(int field) {\r
- return groupByPrimitive(field);\r
- }\r
-\r
- public Query<T> groupBy(long field) {\r
- return groupByPrimitive(field);\r
- }\r
-\r
- public Query<T> groupBy(float field) {\r
- return groupByPrimitive(field);\r
- }\r
-\r
- public Query<T> groupBy(double field) {\r
- return groupByPrimitive(field);\r
- }\r
-\r
- Query<T> groupByPrimitive(Object field) {\r
- Object alias = getPrimitiveAliasByValue(field);\r
- if (alias == null) {\r
- return groupBy(field);\r
- }\r
- return groupBy(alias);\r
- }\r
-\r
- public Query<T> groupBy(Object expr) {\r
- from.getAliasDefinition().checkMultipleEnums(expr);\r
- groupByExpressions.add(expr);\r
- return this;\r
- }\r
-\r
- public Query<T> groupBy(Object... groupBy) {\r
- this.groupByExpressions.addAll(Arrays.asList(groupBy));\r
- return this;\r
- }\r
-\r
- /**\r
- * INTERNAL\r
- *\r
- * @param stat\r
- * the statement\r
- * @param alias\r
- * the alias object (can be null)\r
- * @param value\r
- * the value\r
- */\r
- public void appendSQL(SQLStatement stat, Object alias, Object value) {\r
- if (Function.count() == value) {\r
- stat.appendSQL("COUNT(*)");\r
- return;\r
- }\r
- if (RuntimeParameter.PARAMETER == value) {\r
- stat.appendSQL("?");\r
- addParameter(stat, alias, value);\r
- return;\r
- }\r
- Token token = Db.getToken(value);\r
- if (token != null) {\r
- token.appendSQL(stat, this);\r
- return;\r
- }\r
- if (alias != null && value.getClass().isEnum()) {\r
- // special case:\r
- // value is first enum constant which is also the alias object.\r
- // the first enum constant is used as the alias because we can not\r
- // instantiate an enum reflectively.\r
- stat.appendSQL("?");\r
- addParameter(stat, alias, value);\r
- return;\r
- }\r
- SelectColumn<T> col = getColumnByReference(value);\r
- if (col != null) {\r
- col.appendSQL(stat);\r
- return;\r
- }\r
- stat.appendSQL("?");\r
- addParameter(stat, alias, value);\r
- }\r
-\r
- /**\r
- * INTERNAL\r
- *\r
- * @param stat\r
- * the statement\r
- * @param alias\r
- * the alias object (can be null)\r
- * @param valueLeft\r
- * the value on the left of the compound clause\r
- * @param valueRight\r
- * the value on the right of the compound clause\r
- * @param compareType\r
- * the current compare type (e.g. BETWEEN)\r
- */\r
- public void appendSQL(SQLStatement stat, Object alias, Object valueLeft, Object valueRight,\r
- CompareType compareType) {\r
- stat.appendSQL("?");\r
- stat.appendSQL(" ");\r
- switch (compareType) {\r
- case BETWEEN:\r
- stat.appendSQL("AND");\r
- break;\r
- }\r
- stat.appendSQL(" ");\r
- stat.appendSQL("?");\r
- addParameter(stat, alias, valueLeft);\r
- addParameter(stat, alias, valueRight);\r
- }\r
-\r
- public void appendSQL(SQLStatement stat, Object alias, Iterable<Object> values,\r
- CompareType compareType) {\r
- boolean first = true;\r
- stat.appendSQL("(");\r
- for (Object value : values) {\r
- if (first) {\r
- first = false;\r
- } else {\r
- stat.appendSQL(", ");\r
- }\r
- stat.appendSQL("?");\r
- addParameter(stat, alias, value);\r
- }\r
- stat.appendSQL(")");\r
- }\r
-\r
- private void addParameter(SQLStatement stat, Object alias, Object value) {\r
- SelectColumn<T> col = getColumnByReference(alias);\r
- if (col != null && value.getClass().isEnum()) {\r
- // enum\r
- EnumType type = col.getFieldDefinition().enumType;\r
- Enum<?> anEnum = (Enum<?>) value;\r
- Object y = Utils.convertEnum(anEnum, type);\r
- stat.addParameter(y);\r
- } else if (col != null) {\r
- // object\r
- Object parameter = db.getDialect().serialize(value);\r
- stat.addParameter(parameter);\r
- } else {\r
- // primitive\r
- stat.addParameter(value);\r
- }\r
- }\r
-\r
- void addConditionToken(Token condition) {\r
- if (condition == ConditionOpenClose.OPEN) {\r
- conditionDepth ++;\r
- } else if (condition == ConditionOpenClose.CLOSE) {\r
- conditionDepth --;\r
- if (conditionDepth < 0) {\r
- throw new IciqlException("unmatch condition open-close count");\r
- }\r
- }\r
- conditions.add(condition);\r
- }\r
-\r
- void addConditionToken(Query<T> other) {\r
- for (Token condition : other.conditions) {\r
- addConditionToken(condition);\r
- }\r
- }\r
-\r
- void addUpdateColumnDeclaration(UpdateColumn declaration) {\r
- updateColumnDeclarations.add(declaration);\r
- }\r
-\r
- void appendWhere(SQLStatement stat) {\r
- if (conditionDepth != 0) {\r
- throw new IciqlException("unmatch condition open-close count");\r
- }\r
- if (!conditions.isEmpty()) {\r
- stat.appendSQL(" WHERE ");\r
-\r
- boolean skipNextConjunction = false;\r
-\r
- for (Token token : conditions) {\r
-\r
- if (skipNextConjunction && token instanceof ConditionAndOr) {\r
- skipNextConjunction = false;\r
- continue;\r
- }\r
-\r
- token.appendSQL(stat, this);\r
- stat.appendSQL(" ");\r
-\r
- if (ConditionOpenClose.OPEN == token) {\r
- skipNextConjunction = true;\r
- }\r
- }\r
- }\r
- }\r
-\r
- void appendFromWhere(SQLStatement stat) {\r
- appendFromWhere(stat, true);\r
- }\r
-\r
- void appendFromWhere(SQLStatement stat, boolean log) {\r
- stat.appendSQL(" FROM ");\r
- from.appendSQL(stat);\r
- for (SelectTable<T> join : joins) {\r
- join.appendSQLAsJoin(stat, this);\r
- }\r
- appendWhere(stat);\r
- if (!groupByExpressions.isEmpty()) {\r
- stat.appendSQL(" GROUP BY ");\r
- int i = 0;\r
- for (Object obj : groupByExpressions) {\r
- if (i++ > 0) {\r
- stat.appendSQL(", ");\r
- }\r
- appendSQL(stat, null, obj);\r
- stat.appendSQL(" ");\r
- }\r
- }\r
- if (!orderByList.isEmpty()) {\r
- stat.appendSQL(" ORDER BY ");\r
- int i = 0;\r
- for (OrderExpression<T> o : orderByList) {\r
- if (i++ > 0) {\r
- stat.appendSQL(", ");\r
- }\r
- o.appendSQL(stat);\r
- stat.appendSQL(" ");\r
- }\r
- }\r
- db.getDialect().appendLimitOffset(stat, limit, offset);\r
- if (log) {\r
- IciqlLogger.select(stat.getSQL());\r
- }\r
- }\r
-\r
- /**\r
- * Join another table.\r
- *\r
- * @param alias\r
- * an alias for the table to join\r
- * @return the joined query\r
- */\r
-\r
- public <A> QueryJoin<T> innerJoin(A alias) {\r
- return join(alias, false);\r
- }\r
-\r
- public <A> QueryJoin<T> leftJoin(A alias) {\r
- return join(alias, true);\r
- }\r
-\r
- @SuppressWarnings({ "unchecked", "rawtypes" })\r
- private <A> QueryJoin<T> join(A alias, boolean outerJoin) {\r
- TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());\r
- SelectTable<T> join = new SelectTable(db, this, alias, outerJoin);\r
- def.initSelectObject(join, alias, aliasMap, false);\r
- joins.add(join);\r
- return new QueryJoin(this, join);\r
- }\r
-\r
- Db getDb() {\r
- return db;\r
- }\r
-\r
- SelectTable<T> getFrom() {\r
- return from;\r
- }\r
-\r
- boolean isJoin() {\r
- return !joins.isEmpty();\r
- }\r
-\r
- SelectTable<?> getSelectTable(Object alias) {\r
- if (from.getAlias() == alias) {\r
- return from;\r
- } else {\r
- for (SelectTable<?> join : joins) {\r
- if (join.getAlias() == alias) {\r
- return join;\r
- }\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- /**\r
- * This method returns a mapped Object field by its reference.\r
- *\r
- * @param obj\r
- * @return\r
- */\r
- private SelectColumn<T> getColumnByReference(Object obj) {\r
- SelectColumn<T> col = aliasMap.get(obj);\r
- return col;\r
- }\r
-\r
- /**\r
- * This method returns the alias of a mapped primitive field by its value.\r
- *\r
- * @param obj\r
- * @return\r
- */\r
- @SuppressWarnings("unchecked")\r
- <A> A getPrimitiveAliasByValue(A obj) {\r
- for (Object alias : aliasMap.keySet()) {\r
- if (alias.equals(obj)) {\r
- SelectColumn<T> match = aliasMap.get(alias);\r
- if (match.getFieldDefinition().isPrimitive) {\r
- return (A) alias;\r
- }\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- void addOrderBy(OrderExpression<T> expr) {\r
- orderByList.add(expr);\r
- }\r
-\r
-}\r
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ *
+ * 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.iciql;
+
+import java.lang.reflect.Field;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+import com.iciql.Iciql.DataTypeAdapter;
+import com.iciql.Iciql.EnumType;
+import com.iciql.NestedConditions.And;
+import com.iciql.NestedConditions.Or;
+import com.iciql.bytecode.ClassReader;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.JdbcUtils;
+import com.iciql.util.Utils;
+
+/**
+ * This class represents a query.
+ *
+ * @param <T>
+ * the return type
+ */
+
+public class Query<T> {
+
+ private Db db;
+ private SelectTable<T> from;
+ private ArrayList<Token> conditions = Utils.newArrayList();
+ private ArrayList<UpdateColumn> updateColumnDeclarations = Utils.newArrayList();
+ private int conditionDepth = 0;
+ private ArrayList<SelectTable<T>> joins = Utils.newArrayList();
+ private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();
+ private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();
+ private ArrayList<Object> groupByExpressions = Utils.newArrayList();
+ private long limit;
+ private long offset;
+
+ private Query(Db db) {
+ this.db = db;
+ }
+
+ /**
+ * from() is a static factory method to build a Query object.
+ *
+ * @param db
+ * @param alias
+ * @return a query object
+ */
+ @SuppressWarnings("unchecked")
+ static <T> Query<T> from(Db db, T alias) {
+ Query<T> query = new Query<T>(db);
+ TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
+ query.from = new SelectTable<T>(db, query, alias, false);
+ def.initSelectObject(query.from, alias, query.aliasMap, false);
+ return query;
+ }
+
+ @SuppressWarnings("unchecked")
+ static <T> Query<T> rebuild(Db db, T alias) {
+ Query<T> query = new Query<T>(db);
+ TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
+ query.from = new SelectTable<T>(db, query, alias, false);
+ def.initSelectObject(query.from, alias, query.aliasMap, true);
+ return query;
+ }
+
+ public long selectCount() {
+ SQLStatement stat = getSelectStatement(false);
+ stat.appendSQL("COUNT(*) ");
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ try {
+ rs.next();
+ long value = rs.getLong(1);
+ return value;
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ }
+
+ public List<T> select() {
+ return select(false);
+ }
+
+ public T selectFirst() {
+ List<T> list = limit(1).select(false);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public List<T> selectDistinct() {
+ return select(true);
+ }
+
+ public <X, Z> X selectFirst(Z x) {
+ List<X> list = limit(1).select(x);
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public <X> void createView(Class<X> viewClass) {
+ TableDefinition<X> viewDef = db.define(viewClass);
+
+ SQLStatement fromWhere = new SQLStatement(db);
+ appendFromWhere(fromWhere, false);
+
+ SQLStatement stat = new SQLStatement(db);
+ db.getDialect().prepareCreateView(stat, viewDef, fromWhere.toSQL());
+ IciqlLogger.create(stat.toSQL());
+ stat.execute();
+ }
+
+ public <X> void replaceView(Class<X> viewClass) {
+ db.dropView(viewClass);
+ createView(viewClass);
+ }
+
+ public String getSQL() {
+ SQLStatement stat = getSelectStatement(false);
+ stat.appendSQL("*");
+ appendFromWhere(stat);
+ return stat.getSQL().trim();
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @return the sql query as plain text
+ */
+ public String toSQL() {
+ return toSQL(false);
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @param distinct
+ * if true SELECT DISTINCT is used for the query
+ * @return the sql query as plain text
+ */
+ public String toSQL(boolean distinct) {
+ return toSQL(distinct, null);
+ }
+
+ /**
+ * toSQL returns a static string version of the query with runtime variables
+ * properly encoded. This method is also useful when combined with the where
+ * clause methods like isParameter() or atLeastParameter() which allows
+ * iciql to generate re-usable parameterized string statements.
+ *
+ * @param distinct
+ * if true SELECT DISTINCT is used for the query
+ * @param k
+ * k is used to select only the columns of the specified alias
+ * for an inner join statement. An example of a generated
+ * statement is: SELECT DISTINCT t1.* FROM sometable AS t1 INNER
+ * JOIN othertable AS t2 ON t1.id = t2.id WHERE t2.flag = true
+ * without the alias parameter the statement would start with
+ * SELECT DISTINCT * FROM...
+ * @return the sql query as plain text
+ */
+ public <K> String toSQL(boolean distinct, K k) {
+ SQLStatement stat = new SQLStatement(getDb());
+ if (updateColumnDeclarations.size() > 0) {
+ stat.appendSQL("UPDATE ");
+ from.appendSQL(stat);
+ stat.appendSQL(" SET ");
+ int i = 0;
+ for (UpdateColumn declaration : updateColumnDeclarations) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ declaration.appendSQL(stat);
+ }
+ appendWhere(stat);
+ } else {
+ stat.appendSQL("SELECT ");
+ if (distinct) {
+ stat.appendSQL("DISTINCT ");
+ }
+ if (k != null) {
+ SelectTable<?> sel = getSelectTable(k);
+ if (sel == null) {
+ // unknown alias, use wildcard
+ IciqlLogger.warn("Alias {0} is not defined in the statement!", k.getClass());
+ stat.appendSQL("*");
+ } else if (isJoin()) {
+ // join query, use AS alias
+ String as = sel.getAs();
+ stat.appendSQL(as + ".*");
+ } else {
+ // schema.table.*
+ String schema = sel.getAliasDefinition().schemaName;
+ String table = sel.getAliasDefinition().tableName;
+ String as = getDb().getDialect().prepareTableName(schema, table);
+ stat.appendSQL(as + ".*");
+ }
+ } else {
+ // alias unspecified, use wildcard
+ stat.appendSQL("*");
+ }
+ appendFromWhere(stat);
+ }
+ return stat.toSQL().trim();
+ }
+
+ <Z> String toSubQuery(Z z) {
+ SQLStatement stat = getSelectStatement(false);
+ SelectColumn<T> col = aliasMap.get(z);
+ String columnName = col.getFieldDefinition().columnName;
+ stat.appendColumn(columnName);
+ appendFromWhere(stat);
+ return stat.toSQL();
+ }
+
+ private List<T> select(boolean distinct) {
+ List<T> result = Utils.newArrayList();
+ TableDefinition<T> def = from.getAliasDefinition();
+ SQLStatement stat = getSelectStatement(distinct);
+ def.appendSelectList(stat);
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ try {
+ // SQLite returns pre-closed ResultSets for query results with 0 rows
+ if (!rs.isClosed()) {
+ int[] columns = def.mapColumns(false, rs);
+ while (rs.next()) {
+ T item = from.newObject();
+ def.readRow(db.getDialect(), item, rs, columns);
+ result.add(item);
+ }
+ }
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ return result;
+ }
+
+ public int delete() {
+ SQLStatement stat = new SQLStatement(db);
+ stat.appendSQL("DELETE FROM ");
+ from.appendSQL(stat);
+ appendWhere(stat);
+ IciqlLogger.delete(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ public <A> UpdateColumnSet<T, A> set(A field) {
+ from.getAliasDefinition().checkMultipleEnums(field);
+ return new UpdateColumnSet<T, A>(this, field);
+ }
+
+ public UpdateColumnSet<T, Boolean> set(boolean field) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Byte> set(byte field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Short> set(short field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Integer> set(int field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Long> set(long field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Float> set(float field) {
+ return setPrimitive(field);
+ }
+
+ public UpdateColumnSet<T, Double> set(double field) {
+ return setPrimitive(field);
+ }
+
+ private <A> UpdateColumnSet<T, A> setPrimitive(A field) {
+ A alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return set(field);
+ }
+ return set(alias);
+ }
+
+ public <A> UpdateColumnIncrement<T, A> increment(A field) {
+ return new UpdateColumnIncrement<T, A>(this, field);
+ }
+
+ public UpdateColumnIncrement<T, Byte> increment(byte field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Short> increment(short field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Integer> increment(int field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Long> increment(long field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Float> increment(float field) {
+ return incrementPrimitive(field);
+ }
+
+ public UpdateColumnIncrement<T, Double> increment(double field) {
+ return incrementPrimitive(field);
+ }
+
+ private <A> UpdateColumnIncrement<T, A> incrementPrimitive(A field) {
+ A alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return increment(field);
+ }
+ return increment(alias);
+ }
+
+ public int update() {
+ if (updateColumnDeclarations.size() == 0) {
+ throw new IciqlException("Missing set or increment call.");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ stat.appendSQL("UPDATE ");
+ from.appendSQL(stat);
+ stat.appendSQL(" SET ");
+ int i = 0;
+ for (UpdateColumn declaration : updateColumnDeclarations) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ declaration.appendSQL(stat);
+ }
+ appendWhere(stat);
+ IciqlLogger.update(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ public <X, Z> List<X> selectDistinct(Z x) {
+ return select(x, true);
+ }
+
+ public <X, Z> List<X> select(Z x) {
+ return select(x, false);
+ }
+
+ @SuppressWarnings("unchecked")
+ private <X, Z> List<X> select(Z x, boolean distinct) {
+ Class<?> clazz = x.getClass();
+ if (Utils.isSimpleType(clazz)) {
+ return selectSimple((X) x, distinct);
+ }
+ Class<?> enclosingClass = clazz.getEnclosingClass();
+ if (enclosingClass != null) {
+ // anonymous inner class
+ clazz = clazz.getSuperclass();
+ }
+ return select((Class<X>) clazz, (X) x, distinct);
+ }
+
+ private <X> List<X> select(Class<X> clazz, X x, boolean distinct) {
+ List<X> result = Utils.newArrayList();
+ TableDefinition<X> def = db.define(clazz);
+ SQLStatement stat = getSelectStatement(distinct);
+ def.appendSelectList(stat, this, x);
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ try {
+ // SQLite returns pre-closed ResultSets for query results with 0 rows
+ if (!rs.isClosed()) {
+ int[] columns = def.mapColumns(false, rs);
+ while (rs.next()) {
+ X row = Utils.newObject(clazz);
+ def.readRow(db.getDialect(), row, rs, columns);
+ result.add(row);
+ }
+ }
+ } catch (SQLException e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private <X> List<X> selectSimple(X x, boolean distinct) {
+ SQLStatement stat = getSelectStatement(distinct);
+ appendSQL(stat, null, x);
+ appendFromWhere(stat);
+ ResultSet rs = stat.executeQuery();
+ List<X> result = Utils.newArrayList();
+ Class<? extends DataTypeAdapter<?>> typeAdapter = Utils.getDataTypeAdapter(x.getClass().getAnnotations());
+ try {
+ // SQLite returns pre-closed ResultSets for query results with 0 rows
+ if (!rs.isClosed()) {
+ while (rs.next()) {
+ X value = (X) db.getDialect().deserialize(rs, 1, x.getClass(), typeAdapter);
+ result.add(value);
+ }
+ }
+ } catch (Exception e) {
+ throw IciqlException.fromSQL(stat.getSQL(), e);
+ } finally {
+ JdbcUtils.closeSilently(rs, true);
+ }
+ return result;
+ }
+
+ private SQLStatement getSelectStatement(boolean distinct) {
+ SQLStatement stat = new SQLStatement(db);
+ stat.appendSQL("SELECT ");
+ if (distinct) {
+ stat.appendSQL("DISTINCT ");
+ }
+ return stat;
+ }
+
+ /**
+ * Begin a primitive boolean field condition clause.
+ *
+ * @param x
+ * the primitive boolean field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Boolean> where(boolean x) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive short field condition clause.
+ *
+ * @param x
+ * the primitive short field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Byte> where(byte x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive short field condition clause.
+ *
+ * @param x
+ * the primitive short field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Short> where(short x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive int field condition clause.
+ *
+ * @param x
+ * the primitive int field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Integer> where(int x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive long field condition clause.
+ *
+ * @param x
+ * the primitive long field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Long> where(long x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive float field condition clause.
+ *
+ * @param x
+ * the primitive float field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Float> where(float x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begin a primitive double field condition clause.
+ *
+ * @param x
+ * the primitive double field to query
+ * @return a query condition to continue building the condition
+ */
+ public QueryCondition<T, Double> where(double x) {
+ return wherePrimitive(x);
+ }
+
+ /**
+ * Begins a primitive field condition clause.
+ *
+ * @param value
+ * @return a query condition to continue building the condition
+ */
+ private <A> QueryCondition<T, A> wherePrimitive(A value) {
+ A alias = getPrimitiveAliasByValue(value);
+ if (alias == null) {
+ // this will result in an unmapped field exception
+ return where(value);
+ }
+ return where(alias);
+ }
+
+ /**
+ * Begin an Object field condition clause.
+ *
+ * @param x
+ * the mapped object to query
+ * @return a query condition to continue building the condition
+ */
+ public <A> QueryCondition<T, A> where(A x) {
+ from.getAliasDefinition().checkMultipleEnums(x);
+ return new QueryCondition<T, A>(this, x);
+ }
+
+ public <A> QueryWhere<T> where(Filter filter) {
+ HashMap<String, Object> fieldMap = Utils.newHashMap();
+ for (Field f : filter.getClass().getDeclaredFields()) {
+ f.setAccessible(true);
+ try {
+ Object obj = f.get(filter);
+ if (obj == from.getAlias()) {
+ List<TableDefinition.FieldDefinition> fields = from.getAliasDefinition().getFields();
+ String name = f.getName();
+ for (TableDefinition.FieldDefinition field : fields) {
+ String n = name + "." + field.field.getName();
+ Object o = field.field.get(obj);
+ fieldMap.put(n, o);
+ }
+ }
+ fieldMap.put(f.getName(), f.get(filter));
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+ Token filterCode = new ClassReader().decompile(filter, fieldMap, "where");
+ // String filterQuery = filterCode.toString();
+ conditions.add(filterCode);
+ return new QueryWhere<T>(this);
+ }
+
+ public QueryWhere<T> where(String fragment, List<?> args) {
+ return this.where(fragment, args.toArray());
+ }
+
+ public QueryWhere<T> where(String fragment, Object... args) {
+ conditions.add(new RuntimeToken(fragment, args));
+ return new QueryWhere<T>(this);
+ }
+
+ public Query<T> where(And<T> conditions) {
+ whereTrue();
+ addConditionToken(conditions.where.query);
+ return this;
+ }
+
+ public Query<T> where(Or<T> conditions) {
+ whereFalse();
+ addConditionToken(conditions.where.query);
+ return this;
+ }
+
+ public QueryWhere<T> whereTrue() {
+ return whereTrue(true);
+ }
+
+ public QueryWhere<T> whereFalse() {
+ return whereTrue(false);
+ }
+
+ public QueryWhere<T> whereTrue(Boolean condition) {
+ Token token = new Function("", condition);
+ addConditionToken(token);
+ return new QueryWhere<T>(this);
+ }
+
+ /**
+ * Sets the Limit and Offset of a query.
+ *
+ * @return the query
+ */
+
+ public Query<T> limit(long limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ public Query<T> offset(long offset) {
+ this.offset = offset;
+ return this;
+ }
+
+ public Query<T> orderBy(boolean field) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(byte field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(short field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(int field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(long field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(float field) {
+ return orderByPrimitive(field);
+ }
+
+ public Query<T> orderBy(double field) {
+ return orderByPrimitive(field);
+ }
+
+ Query<T> orderByPrimitive(Object field) {
+ Object alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ return orderBy(field);
+ }
+ return orderBy(alias);
+ }
+
+ public Query<T> orderBy(Object expr) {
+ from.getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);
+ addOrderBy(e);
+ return this;
+ }
+
+ /**
+ * Order by a number of columns.
+ *
+ * @param expressions
+ * the columns
+ * @return the query
+ */
+
+ public Query<T> orderBy(Object... expressions) {
+ for (Object expr : expressions) {
+ from.getAliasDefinition().checkMultipleEnums(expr);
+ OrderExpression<T> e = new OrderExpression<T>(this, expr, false, false, false);
+ addOrderBy(e);
+ }
+ return this;
+ }
+
+ public Query<T> orderByDesc(Object expr) {
+ OrderExpression<T> e = new OrderExpression<T>(this, expr, true, false, false);
+ addOrderBy(e);
+ return this;
+ }
+
+ public Query<T> groupBy(boolean field) {
+ from.getAliasDefinition().checkMultipleBooleans();
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(byte field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(short field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(int field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(long field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(float field) {
+ return groupByPrimitive(field);
+ }
+
+ public Query<T> groupBy(double field) {
+ return groupByPrimitive(field);
+ }
+
+ Query<T> groupByPrimitive(Object field) {
+ Object alias = getPrimitiveAliasByValue(field);
+ if (alias == null) {
+ return groupBy(field);
+ }
+ return groupBy(alias);
+ }
+
+ public Query<T> groupBy(Object expr) {
+ from.getAliasDefinition().checkMultipleEnums(expr);
+ groupByExpressions.add(expr);
+ return this;
+ }
+
+ public Query<T> groupBy(Object... groupBy) {
+ this.groupByExpressions.addAll(Arrays.asList(groupBy));
+ return this;
+ }
+
+ /**
+ * INTERNAL
+ *
+ * @param stat
+ * the statement
+ * @param alias
+ * the alias object (can be null)
+ * @param value
+ * the value
+ */
+ public void appendSQL(SQLStatement stat, Object alias, Object value) {
+ if (Function.count() == value) {
+ stat.appendSQL("COUNT(*)");
+ return;
+ }
+ if (RuntimeParameter.PARAMETER == value) {
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ return;
+ }
+ Token token = Db.getToken(value);
+ if (token != null) {
+ token.appendSQL(stat, this);
+ return;
+ }
+ if (alias != null && value.getClass().isEnum()) {
+ // special case:
+ // value is first enum constant which is also the alias object.
+ // the first enum constant is used as the alias because we can not
+ // instantiate an enum reflectively.
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ return;
+ }
+ SelectColumn<T> col = getColumnByReference(value);
+ if (col != null) {
+ col.appendSQL(stat);
+ return;
+ }
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ }
+
+ /**
+ * INTERNAL
+ *
+ * @param stat
+ * the statement
+ * @param alias
+ * the alias object (can be null)
+ * @param valueLeft
+ * the value on the left of the compound clause
+ * @param valueRight
+ * the value on the right of the compound clause
+ * @param compareType
+ * the current compare type (e.g. BETWEEN)
+ */
+ public void appendSQL(SQLStatement stat, Object alias, Object valueLeft, Object valueRight,
+ CompareType compareType) {
+ stat.appendSQL("?");
+ stat.appendSQL(" ");
+ switch (compareType) {
+ case BETWEEN:
+ stat.appendSQL("AND");
+ break;
+ }
+ stat.appendSQL(" ");
+ stat.appendSQL("?");
+ addParameter(stat, alias, valueLeft);
+ addParameter(stat, alias, valueRight);
+ }
+
+ public void appendSQL(SQLStatement stat, Object alias, Iterable<Object> values,
+ CompareType compareType) {
+ boolean first = true;
+ stat.appendSQL("(");
+ for (Object value : values) {
+ if (first) {
+ first = false;
+ } else {
+ stat.appendSQL(", ");
+ }
+ stat.appendSQL("?");
+ addParameter(stat, alias, value);
+ }
+ stat.appendSQL(")");
+ }
+
+ private void addParameter(SQLStatement stat, Object alias, Object value) {
+ SelectColumn<T> col = getColumnByReference(alias);
+ if (col != null && value.getClass().isEnum()) {
+ // enum
+ EnumType type = col.getFieldDefinition().enumType;
+ Enum<?> anEnum = (Enum<?>) value;
+ Object y = Utils.convertEnum(anEnum, type);
+ stat.addParameter(y);
+ } else if (col != null) {
+ // object
+ Class<? extends DataTypeAdapter<?>> typeAdapter = col.getFieldDefinition().typeAdapter;
+ Object parameter = db.getDialect().serialize(value, typeAdapter);
+ stat.addParameter(parameter);
+ } else {
+ // primitive
+ stat.addParameter(value);
+ }
+ }
+
+ void addConditionToken(Token condition) {
+ if (condition == ConditionOpenClose.OPEN) {
+ conditionDepth ++;
+ } else if (condition == ConditionOpenClose.CLOSE) {
+ conditionDepth --;
+ if (conditionDepth < 0) {
+ throw new IciqlException("unmatch condition open-close count");
+ }
+ }
+ conditions.add(condition);
+ }
+
+ void addConditionToken(Query<T> other) {
+ for (Token condition : other.conditions) {
+ addConditionToken(condition);
+ }
+ }
+
+ void addUpdateColumnDeclaration(UpdateColumn declaration) {
+ updateColumnDeclarations.add(declaration);
+ }
+
+ void appendWhere(SQLStatement stat) {
+ if (conditionDepth != 0) {
+ throw new IciqlException("unmatch condition open-close count");
+ }
+ if (!conditions.isEmpty()) {
+ stat.appendSQL(" WHERE ");
+
+ boolean skipNextConjunction = false;
+
+ for (Token token : conditions) {
+
+ if (skipNextConjunction && token instanceof ConditionAndOr) {
+ skipNextConjunction = false;
+ continue;
+ }
+
+ token.appendSQL(stat, this);
+ stat.appendSQL(" ");
+
+ if (ConditionOpenClose.OPEN == token) {
+ skipNextConjunction = true;
+ }
+ }
+ }
+ }
+
+ void appendFromWhere(SQLStatement stat) {
+ appendFromWhere(stat, true);
+ }
+
+ void appendFromWhere(SQLStatement stat, boolean log) {
+ stat.appendSQL(" FROM ");
+ from.appendSQL(stat);
+ for (SelectTable<T> join : joins) {
+ join.appendSQLAsJoin(stat, this);
+ }
+ appendWhere(stat);
+ if (!groupByExpressions.isEmpty()) {
+ stat.appendSQL(" GROUP BY ");
+ int i = 0;
+ for (Object obj : groupByExpressions) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ appendSQL(stat, null, obj);
+ stat.appendSQL(" ");
+ }
+ }
+ if (!orderByList.isEmpty()) {
+ stat.appendSQL(" ORDER BY ");
+ int i = 0;
+ for (OrderExpression<T> o : orderByList) {
+ if (i++ > 0) {
+ stat.appendSQL(", ");
+ }
+ o.appendSQL(stat);
+ stat.appendSQL(" ");
+ }
+ }
+ db.getDialect().appendLimitOffset(stat, limit, offset);
+ if (log) {
+ IciqlLogger.select(stat.getSQL());
+ }
+ }
+
+ /**
+ * Join another table.
+ *
+ * @param alias
+ * an alias for the table to join
+ * @return the joined query
+ */
+
+ public <A> QueryJoin<T> innerJoin(A alias) {
+ return join(alias, false);
+ }
+
+ public <A> QueryJoin<T> leftJoin(A alias) {
+ return join(alias, true);
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private <A> QueryJoin<T> join(A alias, boolean outerJoin) {
+ TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());
+ SelectTable<T> join = new SelectTable(db, this, alias, outerJoin);
+ def.initSelectObject(join, alias, aliasMap, false);
+ joins.add(join);
+ return new QueryJoin(this, join);
+ }
+
+ Db getDb() {
+ return db;
+ }
+
+ SelectTable<T> getFrom() {
+ return from;
+ }
+
+ boolean isJoin() {
+ return !joins.isEmpty();
+ }
+
+ SelectTable<?> getSelectTable(Object alias) {
+ if (from.getAlias() == alias) {
+ return from;
+ } else {
+ for (SelectTable<?> join : joins) {
+ if (join.getAlias() == alias) {
+ return join;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method returns a mapped Object field by its reference.
+ *
+ * @param obj
+ * @return
+ */
+ private SelectColumn<T> getColumnByReference(Object obj) {
+ SelectColumn<T> col = aliasMap.get(obj);
+ return col;
+ }
+
+ /**
+ * This method returns the alias of a mapped primitive field by its value.
+ *
+ * @param obj
+ * @return
+ */
+ @SuppressWarnings("unchecked")
+ <A> A getPrimitiveAliasByValue(A obj) {
+ for (Object alias : aliasMap.keySet()) {
+ if (alias.equals(obj)) {
+ SelectColumn<T> match = aliasMap.get(alias);
+ if (match.getFieldDefinition().isPrimitive) {
+ return (A) alias;
+ }
+ }
+ }
+ return null;
+ }
+
+ void addOrderBy(OrderExpression<T> expr) {
+ orderByList.add(expr);
+ }
+
+}