diff options
author | James Moger <james.moger@gitblit.com> | 2015-05-11 17:20:00 -0400 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2015-05-11 17:20:00 -0400 |
commit | 3af80dbf94433709c8f2db760d6bc758b972e632 (patch) | |
tree | 1e837924c03e2c6b27e6a5a35901ac43b94ea9ab | |
parent | c55bbebd2a0cec264477cb3cd95556e159970cd6 (diff) | |
download | iciql-3af80dbf94433709c8f2db760d6bc758b972e632.tar.gz iciql-3af80dbf94433709c8f2db760d6bc758b972e632.zip |
Do not register DataTypeAdapters by the target Java type
-rw-r--r-- | build.moxie | 2 | ||||
-rw-r--r-- | releases.moxie | 13 | ||||
-rw-r--r-- | src/main/java/com/iciql/DaoProxy.java | 14 | ||||
-rw-r--r-- | src/main/java/com/iciql/Query.java | 2067 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialect.java | 12 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialectDefault.java | 35 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialectH2.java | 2 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialectHSQL.java | 2 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialectMySQL.java | 2 | ||||
-rw-r--r-- | src/main/java/com/iciql/SQLDialectSQLite.java | 11 | ||||
-rw-r--r-- | src/main/java/com/iciql/TableDefinition.java | 13 |
11 files changed, 1088 insertions, 1085 deletions
diff --git a/build.moxie b/build.moxie index b70e2db..5a69346 100644 --- a/build.moxie +++ b/build.moxie @@ -10,7 +10,7 @@ name: Iciql description: 'a model-based database access wrapper for JDBC' groupId: com.iciql artifactId: iciql -version: 1.7.0-SNAPSHOT +version: 1.6.2-SNAPSHOT packaging: jar+zip inceptionYear: 2011 diff --git a/releases.moxie b/releases.moxie index 3e66d0e..d2fbc52 100644 --- a/releases.moxie +++ b/releases.moxie @@ -9,7 +9,8 @@ r26: { html: ~ text: ~ security: ~ - fixes: ~ + fixes: + - Reverted change which keyed DataTypeAdapters by the target Java type changes: ~ additions: ~ dependencyChanges: ~ @@ -30,8 +31,13 @@ r25: { fixes: - Fix column inheritance from superclasses (pr-14) - Use webapp classloader rather than global classloader (pr-12) + - Fix deserialization of null values changes: - Improve SQLite dialect based on upstream JDBC improvements + - Key DataTypeAdapters by target Java type + - Drop precision and length from SQL->Java type determination + - Improve readability of the generated performance benchmark table + - Added Derby TCP benchmark test additions: ~ dependencyChanges: - SQLite 3.8.10 @@ -56,8 +62,13 @@ r24: { fixes: - Fix column inheritance from superclasses (pr-14) - Use webapp classloader rather than global classloader (pr-12) + - Fix deserialization of null values changes: - Improve SQLite dialect based on upstream JDBC improvements + - Key DataTypeAdapters by target Java type + - Drop precision and length from SQL->Java type determination + - Improve readability of the generated performance benchmark table + - Added Derby TCP benchmark test additions: ~ dependencyChanges: - SQLite 3.8.10 diff --git a/src/main/java/com/iciql/DaoProxy.java b/src/main/java/com/iciql/DaoProxy.java index deebeca..34187c4 100644 --- a/src/main/java/com/iciql/DaoProxy.java +++ b/src/main/java/com/iciql/DaoProxy.java @@ -193,18 +193,13 @@ final class DaoProxy<X extends Dao> implements InvocationHandler, Dao { } else { // query of (array of) standard Java type or a DataTypeAdapter type - if (adapter != null) { - DataTypeAdapter<?> dta = Utils.newObject(adapter); - db.getDialect().registerAdapter(dta); - } - objects = Utils.newArrayList(); ResultSet rs = db.executeQuery(preparedSql.sql, preparedSql.parameters); try { while (rs.next()) { - Object value = db.getDialect().deserialize(rs, 1, returnType); + Object value = db.getDialect().deserialize(rs, 1, returnType, adapter); objects.add(value); if (!isArray) { @@ -688,13 +683,8 @@ final class DaoProxy<X extends Dao> implements InvocationHandler, Dao { typeAdapter = Utils.getDataTypeAdapter(methodArg.getClass().getAnnotations()); } - if (typeAdapter != null) { - DataTypeAdapter<?> dta = Utils.newObject(typeAdapter); - db.getDialect().registerAdapter(dta); - } - // prepare the parameter - parameters[i] = db.getDialect().serialize(value); + parameters[i] = db.getDialect().serialize(value, typeAdapter); } diff --git a/src/main/java/com/iciql/Query.java b/src/main/java/com/iciql/Query.java index 1a8cbc7..fd8d021 100644 --- a/src/main/java/com/iciql/Query.java +++ b/src/main/java/com/iciql/Query.java @@ -1,1035 +1,1032 @@ -/*
- * 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());
- if (typeAdapter != null) {
- DataTypeAdapter<?> dta = Utils.newObject(typeAdapter);
- db.getDialect().registerAdapter(dta);
- }
- 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());
- 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
- Object parameter = db.getDialect().serialize(value);
- 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);
- }
-
-}
+/* + * 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); + } + +} diff --git a/src/main/java/com/iciql/SQLDialect.java b/src/main/java/com/iciql/SQLDialect.java index 11b1af8..ccc0452 100644 --- a/src/main/java/com/iciql/SQLDialect.java +++ b/src/main/java/com/iciql/SQLDialect.java @@ -37,20 +37,21 @@ public interface SQLDialect { void registerAdapter(DataTypeAdapter<?> typeAdapter); /** - * Returns the registered instance of the type adapter for the specified object class. + * Returns the registered instance of the type adapter. * - * @param objectClass + * @param typeAdapter * @return the type adapter instance */ - DataTypeAdapter<?> getAdapter(Class<?> objectClass); + DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter); /** * Serialize the Java object into a type or format that the database will accept. * * @param value + * @param typeAdapter * @return the serialized object */ - <T> Object serialize(T value); + <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter); /** * Deserialize the object received from the database into a Java type. @@ -58,9 +59,10 @@ public interface SQLDialect { * @param rs * @param columnIndex * @param targetType + * @param typeAdapter * @return the deserialized object */ - Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType); + Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter); /** * Configure the dialect. diff --git a/src/main/java/com/iciql/SQLDialectDefault.java b/src/main/java/com/iciql/SQLDialectDefault.java index 19b0fcd..e5b6ca6 100644 --- a/src/main/java/com/iciql/SQLDialectDefault.java +++ b/src/main/java/com/iciql/SQLDialectDefault.java @@ -55,10 +55,10 @@ public class SQLDialectDefault implements SQLDialect { String databaseName; String productVersion; Mode mode; - Map<Class<?>, DataTypeAdapter<?>> typeAdapters; + Map<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>> typeAdapters; public SQLDialectDefault() { - typeAdapters = new ConcurrentHashMap<Class<?>, DataTypeAdapter<?>>(); + typeAdapters = new ConcurrentHashMap<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>>(); } @Override @@ -420,7 +420,7 @@ public class SQLDialectDefault implements SQLDialect { buff.appendExceptFirst(", "); buff.append('?'); Object value = def.getValue(obj, field); - Object parameter = serialize(value); + Object parameter = serialize(value, field.typeAdapter); stat.addParameter(parameter); } buff.append(" FROM "); @@ -432,7 +432,7 @@ public class SQLDialectDefault implements SQLDialect { buff.appendExceptFirst(" AND "); buff.append(MessageFormat.format("{0} = ?", prepareColumnName(field.columnName))); Object value = def.getValue(obj, field); - Object parameter = serialize(value); + Object parameter = serialize(value, field.typeAdapter); stat.addParameter(parameter); } } @@ -452,35 +452,37 @@ public class SQLDialectDefault implements SQLDialect { @Override public void registerAdapter(DataTypeAdapter<?> typeAdapter) { - typeAdapters.put(typeAdapter.getJavaType(), typeAdapter); + typeAdapters.put((Class<? extends DataTypeAdapter<?>>) typeAdapter.getClass(), typeAdapter); } @Override - public DataTypeAdapter<?> getAdapter(Class<?> objectClass) { - DataTypeAdapter<?> dta = typeAdapters.get(objectClass); - if (dta != null) { - dta.setMode(mode); + public DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter) { + DataTypeAdapter<?> dta = typeAdapters.get(typeAdapter); + if (dta == null) { + dta = Utils.newObject(typeAdapter); + typeAdapters.put(typeAdapter, dta); } + dta.setMode(mode); return dta; } @SuppressWarnings("unchecked") @Override - public <T> Object serialize(T value) { - DataTypeAdapter dta = getAdapter(value.getClass()); - if (dta == null) { + public <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter) { + if (typeAdapter == null) { // pass-through return value; } + + DataTypeAdapter<T> dta = (DataTypeAdapter<T>) getAdapter(typeAdapter); return dta.serialize(value); } @Override - public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType) { + public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) { Object value = null; try { - DataTypeAdapter<?> dta = getAdapter(targetType); - if (dta == null) { + if (typeAdapter == null) { // standard object deserialization Object o = rs.getObject(columnIndex); if (o == null) { @@ -495,6 +497,7 @@ public class SQLDialectDefault implements SQLDialect { } } else { // custom object deserialization with a DataTypeAdapter + DataTypeAdapter<?> dta = getAdapter(typeAdapter); Object object = rs.getObject(columnIndex); value = dta.deserialize(object); } @@ -521,4 +524,4 @@ public class SQLDialectDefault implements SQLDialect { return o.toString(); } -}
\ No newline at end of file +} diff --git a/src/main/java/com/iciql/SQLDialectH2.java b/src/main/java/com/iciql/SQLDialectH2.java index 206937c..2d7d0fd 100644 --- a/src/main/java/com/iciql/SQLDialectH2.java +++ b/src/main/java/com/iciql/SQLDialectH2.java @@ -127,7 +127,7 @@ public class SQLDialectH2 extends SQLDialectDefault { buff.appendExceptFirst(", ");
buff.append('?');
Object value = def.getValue(obj, field);
- Object parameter = serialize(value);
+ Object parameter = serialize(value, field.typeAdapter);
stat.addParameter(parameter);
}
buff.append(')');
diff --git a/src/main/java/com/iciql/SQLDialectHSQL.java b/src/main/java/com/iciql/SQLDialectHSQL.java index 4425667..8b05ca4 100644 --- a/src/main/java/com/iciql/SQLDialectHSQL.java +++ b/src/main/java/com/iciql/SQLDialectHSQL.java @@ -91,7 +91,7 @@ public class SQLDialectHSQL extends SQLDialectDefault { }
buff.append(')');
Object value = def.getValue(obj, field);
- Object parameter = serialize(value);
+ Object parameter = serialize(value, field.typeAdapter);
stat.addParameter(parameter);
}
diff --git a/src/main/java/com/iciql/SQLDialectMySQL.java b/src/main/java/com/iciql/SQLDialectMySQL.java index ea19384..ec5923f 100644 --- a/src/main/java/com/iciql/SQLDialectMySQL.java +++ b/src/main/java/com/iciql/SQLDialectMySQL.java @@ -77,7 +77,7 @@ public class SQLDialectMySQL extends SQLDialectDefault { buff.appendExceptFirst(", ");
buff.append('?');
Object value = def.getValue(obj, field);
- Object parameter = serialize(value);
+ Object parameter = serialize(value, field.typeAdapter);
stat.addParameter(parameter);
}
buff.append(") ON DUPLICATE KEY UPDATE ");
diff --git a/src/main/java/com/iciql/SQLDialectSQLite.java b/src/main/java/com/iciql/SQLDialectSQLite.java index acb9b31..436af54 100644 --- a/src/main/java/com/iciql/SQLDialectSQLite.java +++ b/src/main/java/com/iciql/SQLDialectSQLite.java @@ -22,6 +22,7 @@ import java.sql.SQLException; import java.sql.Time;
import java.sql.Timestamp;
+import com.iciql.Iciql.DataTypeAdapter;
import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.TableDefinition.IndexDefinition;
import com.iciql.util.IciqlLogger;
@@ -131,7 +132,7 @@ public class SQLDialectSQLite extends SQLDialectDefault { buff.appendExceptFirst(", ");
buff.append('?');
Object value = def.getValue(obj, field);
- Object parameter = serialize(value);
+ Object parameter = serialize(value, field.typeAdapter);
stat.addParameter(parameter);
}
buff.append(')');
@@ -139,11 +140,11 @@ public class SQLDialectSQLite extends SQLDialectDefault { }
@Override
- public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType) {
+ public Object deserialize(ResultSet rs, int columnIndex, Class<?> targetType, Class<? extends DataTypeAdapter<?>> typeAdapter) {
try {
- return super.deserialize(rs, columnIndex, targetType);
+ return super.deserialize(rs, columnIndex, targetType, typeAdapter);
} catch (IciqlException e) {
- if (e.getMessage().startsWith("Can not convert")) {
+ if (typeAdapter == null && e.getMessage().startsWith("Can not convert")) {
try {
// give the SQLite JDBC driver an opportunity to deserialize DateTime objects
if (Timestamp.class.equals(targetType)) {
@@ -176,4 +177,4 @@ public class SQLDialectSQLite extends SQLDialectDefault { }
return super.prepareStringParameter(o);
}
-}
\ No newline at end of file +} diff --git a/src/main/java/com/iciql/TableDefinition.java b/src/main/java/com/iciql/TableDefinition.java index 6ed9462..8248f18 100644 --- a/src/main/java/com/iciql/TableDefinition.java +++ b/src/main/java/com/iciql/TableDefinition.java @@ -475,9 +475,8 @@ public class TableDefinition<T> { } if (typeAdapter != null) { - DataTypeAdapter<?> dta = Utils.newObject(typeAdapter); - dataType = dta.getDataType(); - db.getDialect().registerAdapter(dta); + DataTypeAdapter<?> dtt = db.getDialect().getAdapter(typeAdapter); + dataType = dtt.getDataType(); } boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class); @@ -674,7 +673,7 @@ public class TableDefinition<T> { // try to interpret and instantiate a default value value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); } - Object parameter = db.getDialect().serialize(value); + Object parameter = db.getDialect().serialize(value, field.typeAdapter); stat.addParameter(parameter); } buff.append(')'); @@ -710,7 +709,7 @@ public class TableDefinition<T> { // try to interpret and instantiate a default value value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass()); } - Object parameter = db.getDialect().serialize(value); + Object parameter = db.getDialect().serialize(value, field.typeAdapter); stat.addParameter(parameter); } buff.append(')'); @@ -785,7 +784,7 @@ public class TableDefinition<T> { buff.appendExceptFirst(", "); buff.append(db.getDialect().prepareColumnName(field.columnName)); buff.append(" = ?"); - Object parameter = db.getDialect().serialize(value); + Object parameter = db.getDialect().serialize(value, field.typeAdapter); stat.addParameter(parameter); } } @@ -1178,7 +1177,7 @@ public class TableDefinition<T> { } o = Utils.convertEnum(obj, targetType, def.enumType); } else { - o = dialect.deserialize(rs, columns[i], targetType); + o = dialect.deserialize(rs, columns[i], targetType, def.typeAdapter); } def.setValue(item, o); } |