primitive bools in a model WITH explicit referencing.
type="java.lang.String"
transient="false"
volatile="false"
- value=""0.7.3""
+ value=""0.7.4-SNAPSHOT""
static="true"
final="true"
deprecated="not deprecated"
type="java.lang.String"
transient="false"
volatile="false"
- value=""2011-12-06""
+ value=""PENDING""
static="true"
final="true"
deprecated="not deprecated"
deprecated="not deprecated"
visibility="public"
>\r
+<method name="activateConsoleLogger"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>\r
+</method>\r
<method name="buildObjects"
return="java.util.List<T>"
abstract="false"
visibility="public"
>\r
</method>\r
+<method name="deactivateConsoleLogger"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>\r
+</method>\r
<method name="delete"
return="int"
abstract="false"
<parameter name="args" type="java.lang.Object...">\r
</parameter>\r
</method>\r
+<method name="executeQuery"
+ return="java.util.List<T>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>\r
+<parameter name="modelClass" type="java.lang.Class<? extends T>">\r
+</parameter>\r
+<parameter name="sql" type="java.lang.String">\r
+</parameter>\r
+<parameter name="args" type="java.util.List<?>">\r
+</parameter>\r
+</method>\r
<method name="executeQuery"
return="java.sql.ResultSet"
abstract="false"
<parameter name="args" type="java.lang.Object...">\r
</parameter>\r
</method>\r
+<method name="executeQuery"
+ return="java.sql.ResultSet"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>\r
+<parameter name="sql" type="java.lang.String">\r
+</parameter>\r
+<parameter name="args" type="java.util.List<?>">\r
+</parameter>\r
+</method>\r
<method name="executeUpdate"
return="int"
abstract="false"
<implements name="java.lang.annotation.Annotation">\r
</implements>\r
</class>\r
+<class name="Iciql.IQFunction"\r
+ extends="java.lang.Object"\r
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>\r
+<implements name="java.lang.annotation.Annotation">\r
+</implements>\r
+</class>\r
<class name="Iciql.IQIgnore"\r
extends="java.lang.Object"\r
abstract="true"
<parameter name="args" type="java.lang.Object...">\r
</parameter>\r
</method>\r
+<method name="where"
+ return="com.iciql.QueryWhere<T>"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>\r
+<parameter name="fragment" type="java.lang.String">\r
+</parameter>\r
+<parameter name="args" type="java.util.List<?>">\r
+</parameter>\r
+</method>\r
<method name="where"
return="com.iciql.QueryCondition<T, java.lang.Long>"
abstract="false"
<td>VARCHAR *(length > 0)* or CLOB *(length == 0)*</td></tr>\r
\r
<tr><td>java.lang.Boolean</td><td>boolean</td>\r
-<td>BOOLEAN</td></tr>\r
+<td>BOOLEAN<br/><i>can only **declare and explicitly reference** one <u>primitive boolean</u> per model<br/>multiple primitives are allowed if not using where/set/on/and/or/groupBy/orderBy(boolean)</i></td></tr>\r
\r
<tr><td>java.lang.Byte</td><td>byte</td>\r
<td>TINYINT</td></tr>\r
Customer c = new Customer();\r
List<Customer> waCustomers = db.from(c). where(c.region).is("WA").select();\r
\r
+public static class ProductPrice {\r
+ public String productName;\r
+ public String category;\r
+ @IQColumn(name = "unitPrice")\r
+ public Double price;\r
+}\r
+\r
// select with generation of new anonymous inner class\r
List<ProductPrice> productPrices =\r
db.from(p).\r
\r
**%VERSION%** ([zip](http://code.google.com/p/iciql/downloads/detail?name=%ZIP%)|[jar](http://code.google.com/p/iciql/downloads/detail?name=%JAR%)) *released %BUILDDATE%*\r
\r
-- Added list alternatives to the varargs methods because it was too easy to forget list.toArray() for the varargs methods \r
-List<T> Db.executeQuery(Class<? extends T> modelClass, String sql, List<?> args)\r
-ResultSet executeQuery(String sql, List<?> args)\r
+- Disallow **declaring and explicitly referencing** multiple primitive booleans in a single model.<br/>A runtime exception will be thrown if an attempt to use where/set/on/and/or/groupBy/orderBy(boolean) and your model has multiple mapped primitive boolean fields.\r
+- Added list alternatives to the varargs methods because it was too easy to forget list.toArray()<br/>\r
+*Db.executeQuery(Class<? extends T> modelClass, String sql, List<?> args)*<br/>\r
+*Db.executeQuery(String sql, List<?> args)*<br/>\r
+*Query.where(String fragment, List<?> args)*<br/>\r
+- Fixed inherited JaQu bug related to model classes and wildcard queries (select *).<p/>\r
+Iciql maps resultset columns by the index of the model class field from a list. This assumes that *all* columns in the resultset have a corresponding model field definition. This works fine for most queries because iciql explicitly selects columns from the table (*select alpha, beta...*) when you execute *select()*. The problem is when iciql issues a join or a custom wildcard query and your model does not represent all columns in the resultset: columns and fields fail to correctly line-up.<p/> \r
+The fix for this (building a column index from the resultset by column name lookup) breaks selecting into *some* anonymous inner classes. At issue is that the inner class field names must now match the column names or the fields must be explicitly annotated with the column names.<p/>**Example** (notice *IQColumn* annotation)<br/>\r
+%BEGINCODE%\r
+public static class ProductPrice {\r
+ public String productName;\r
+ public String category;\r
+ @IQColumn(name = "unitPrice")\r
+ public Double price;\r
+}\r
+\r
+db....select(new ProductPrice() {{\r
+ productName = p.productName;\r
+ category = p.category;\r
+ // or unitPrice = p.unitPrice;\r
+ price = p.unitPrice;\r
+}}\r
+%ENDCODE%\r
\r
### Older Releases\r
\r
<tr><td>databases</td><td>H2, HSQL, Derby, MySQL, and PostreSQL</td><td>H2 only</td></tr>\r
<tr><td>logging</td><td>console, SLF4J, or custom logging</td><td>console logging</td></tr>\r
<tr><td>exceptions</td><td>always includes generated statement in exception, when available</td><td>--</td></tr>\r
+<tr><td>column mappings</td><td>result sets built by column name</td><td>result sets built by field index<br/>this can fail for dynamic queries or joins</td></tr>\r
<tr><th colspan="3">syntax and api</th></tr>\r
<tr><td>dynamic queries</td><td>methods and where clauses for dynamic queries that build iciql objects</td><td>--</td></tr>\r
<tr><td>DROP</td><td>syntax to drop a table</td><td></td></tr>\r
throw new IciqlException(e);\r
}\r
}\r
+ \r
+ /**\r
+ * Convenience function to avoid import statements in application code.\r
+ */\r
+ public static void activateConsoleLogger() {\r
+ IciqlLogger.activateConsoleLogger();\r
+ }\r
+\r
+ /**\r
+ * Convenience function to avoid import statements in application code.\r
+ */\r
+ public static void deactivateConsoleLogger() {\r
+ IciqlLogger.deactivateConsoleLogger();\r
+ }\r
\r
public static Db open(String url) {\r
try {\r
List<T> result = new ArrayList<T>();\r
TableDefinition<T> def = (TableDefinition<T>) define(modelClass);\r
try {\r
+ int [] columns = def.mapColumns(rs);\r
while (rs.next()) {\r
T item = Utils.newObject(modelClass);\r
- def.readRow(item, rs);\r
+ def.readRow(item, rs, columns);\r
result.add(item);\r
}\r
} catch (SQLException e) {\r
EnumType value() default EnumType.NAME;\r
}\r
\r
+ /**\r
+ * Annotation to define a field that should contain the result a function.\r
+ * This annotation ensures that functions mapped in anonymous inner classes\r
+ * can still be referenced in the ResultSet after the switch to dynamic\r
+ * column-name mapping from fixed position column mapping.\r
+ */\r
+ @Retention(RetentionPolicy.RUNTIME)\r
+ @Target(ElementType.FIELD)\r
+ public @interface IQFunction{ \r
+ }\r
+ \r
/**\r
* Annotation to define an ignored field.\r
*/\r
appendFromWhere(stat);\r
ResultSet rs = stat.executeQuery();\r
try {\r
+ int[] columns = def.mapColumns(rs);\r
while (rs.next()) {\r
T item = from.newObject();\r
- from.getAliasDefinition().readRow(item, rs);\r
+ def.readRow(item, rs, columns);\r
result.add(item);\r
}\r
} catch (SQLException e) {\r
}\r
\r
public UpdateColumnSet<T, Boolean> set(boolean field) {\r
+ from.getAliasDefinition().checkMultipleBooleans();\r
return setPrimitive(field);\r
}\r
\r
appendFromWhere(stat);\r
ResultSet rs = stat.executeQuery();\r
try {\r
+ int[] columns = def.mapColumns(rs);\r
while (rs.next()) {\r
X row = Utils.newObject(clazz);\r
- def.readRow(row, rs);\r
+ def.readRow(row, rs, columns);\r
result.add(row);\r
}\r
} catch (SQLException e) {\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
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> orderBy(boolean field) {\r
+ from.getAliasDefinition().checkMultipleBooleans();\r
return orderByPrimitive(field);\r
}\r
\r
}\r
\r
public Query<T> groupBy(boolean field) {\r
+ from.getAliasDefinition().checkMultipleBooleans();\r
return groupByPrimitive(field);\r
}\r
\r
return db;\r
}\r
\r
+ SelectTable<T> getFrom() {\r
+ return from;\r
+ }\r
+\r
boolean isJoin() {\r
return !joins.isEmpty();\r
}\r
}\r
\r
public QueryJoinCondition<T, Boolean> on(boolean x) {\r
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();\r
return addPrimitive(x);\r
}\r
\r
return addPrimitive(x);\r
}\r
\r
- private <A> QueryJoinCondition<T, A> addPrimitive(A x) { \r
+ private <A> QueryJoinCondition<T, A> addPrimitive(A x) {\r
A alias = query.getPrimitiveAliasByValue(x);\r
if (alias == null) {\r
// this will result in an unmapped field exception\r
* @return a query condition to continue building the condition\r
*/\r
public QueryCondition<T, Boolean> and(boolean x) {\r
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();\r
return addPrimitive(ConditionAndOr.AND, x);\r
}\r
\r
return addPrimitive(ConditionAndOr.AND, x);\r
}\r
\r
- private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) {\r
+ private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) { \r
query.addConditionToken(condition);\r
A alias = query.getPrimitiveAliasByValue(x);\r
if (alias == null) {\r
* @return a query condition to continue building the condition\r
*/\r
public QueryCondition<T, Boolean> or(boolean x) {\r
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();\r
return addPrimitive(ConditionAndOr.OR, x);\r
}\r
\r
* @return the query\r
*/\r
public QueryWhere<T> orderBy(boolean field) {\r
+ query.getFrom().getAliasDefinition().checkMultipleBooleans();\r
return orderByPrimitive(field);\r
}\r
\r
import com.iciql.Iciql.EnumType;\r
import com.iciql.Iciql.IQColumn;\r
import com.iciql.Iciql.IQEnum;\r
+import com.iciql.Iciql.IQFunction;\r
import com.iciql.Iciql.IQIgnore;\r
import com.iciql.Iciql.IQIndex;\r
import com.iciql.Iciql.IQIndexes;\r
String dataType;\r
int length;\r
int scale;\r
+ boolean isFunction;\r
boolean isPrimaryKey;\r
boolean isAutoIncrement;\r
boolean trim;\r
}\r
\r
private Object read(ResultSet rs, int columnIndex) {\r
+ if (columnIndex == 0) {\r
+ // unmapped column or function field\r
+ return null;\r
+ }\r
try {\r
return rs.getObject(columnIndex);\r
} catch (SQLException e) {\r
int tableVersion;\r
List<String> primaryKeyColumnNames;\r
boolean memoryTable;\r
+ boolean multiplePrimitiveBools;\r
\r
private boolean createIfRequired = true;\r
private Class<T> clazz;\r
throw new IciqlException(e, "failed to get default object for {0}", columnName);\r
}\r
\r
+ boolean isFunction = f.isAnnotationPresent(IQFunction.class);\r
boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);\r
if (hasAnnotation) {\r
IQColumn col = f.getAnnotation(IQColumn.class);\r
fieldDef.scale = scale;\r
fieldDef.trim = trim;\r
fieldDef.nullable = nullable;\r
+ fieldDef.isFunction = isFunction;\r
fieldDef.defaultValue = defaultValue;\r
fieldDef.enumType = enumType;\r
fieldDef.dataType = ModelUtils.getDataType(fieldDef);\r
}\r
}\r
List<String> primaryKey = Utils.newArrayList();\r
+ int primitiveBoolean = 0;\r
for (FieldDefinition fieldDef : fields) {\r
if (fieldDef.isPrimaryKey) {\r
primaryKey.add(fieldDef.columnName);\r
}\r
+ if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) {\r
+ primitiveBoolean++;\r
+ }\r
+ }\r
+ if (primitiveBoolean > 1) {\r
+ multiplePrimitiveBools = true;\r
+ IciqlLogger\r
+ .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!");\r
}\r
if (primaryKey.size() > 0) {\r
setPrimaryKey(primaryKey);\r
}\r
}\r
\r
+ void checkMultipleBooleans() {\r
+ if (multiplePrimitiveBools) {\r
+ throw new IciqlException(\r
+ "Can not explicitly reference multiple primitive booleans in a model class!");\r
+ }\r
+ }\r
+\r
/**\r
* Optionally truncates strings to the maximum length and converts\r
* java.lang.Enum types to Strings or Integers.\r
}\r
}\r
\r
- void readRow(Object item, ResultSet rs) {\r
+ /**\r
+ * Most queries executed by iciql have named select lists (select alpha,\r
+ * beta where...) but sometimes a wildcard select is executed (select *).\r
+ * When a wildcard query is executed on a table that has more columns than\r
+ * are mapped in your model object this creates a column mapping issue. JaQu\r
+ * assumed that you can always use the integer index of the reflectively\r
+ * mapped field definition to determine position in the result set.\r
+ * \r
+ * This is not always true.\r
+ * \r
+ * So iciql maps column names to column index in the result set to properly\r
+ * map the results of wildcard queries.\r
+ * \r
+ * @param rs\r
+ * @return\r
+ */\r
+ int[] mapColumns(ResultSet rs) {\r
+ int[] columns = new int[fields.size()];\r
for (int i = 0; i < fields.size(); i++) {\r
- FieldDefinition def = fields.get(i);\r
- Object o = def.read(rs, i + 1);\r
+ try {\r
+ FieldDefinition def = fields.get(i);\r
+ int columnIndex;\r
+ if (def.isFunction) {\r
+ // XXX review functions _always_ map after fields?\r
+ columnIndex = i + 1;\r
+ } else {\r
+ columnIndex = rs.findColumn(def.columnName); \r
+ } \r
+ columns[i] = columnIndex;\r
+ } catch (SQLException s) {\r
+ throw new IciqlException(s);\r
+ }\r
+ }\r
+ return columns;\r
+ }\r
+\r
+ void readRow(Object item, ResultSet rs, int[] columns) {\r
+ for (int i = 0; i < fields.size(); i++) {\r
+ FieldDefinition def = fields.get(i); \r
+ int index = columns[i];\r
+ Object o = def.read(rs, index);\r
def.setValue(item, o);\r
}\r
}\r
import org.junit.Test;\r
\r
import com.iciql.Db;\r
+import com.iciql.IciqlException;\r
+import com.iciql.test.models.MultipleBoolsModel;\r
import com.iciql.test.models.PrimitivesModel;\r
\r
/**\r
\r
db.close();\r
}\r
+ \r
+ @Test\r
+ public void testMultipleBooleans() {\r
+ Db db = IciqlSuite.openNewDb();\r
+ db.insertAll(MultipleBoolsModel.getList());\r
+ \r
+ MultipleBoolsModel m = new MultipleBoolsModel();\r
+ try {\r
+ db.from(m).where(m.a).is(true).select();\r
+ assertTrue(false);\r
+ } catch (IciqlException e) {\r
+ assertTrue(true);\r
+ }\r
+ db.close();\r
+ }\r
}\r
\r
import com.iciql.Db;\r
import com.iciql.Filter;\r
+import com.iciql.Iciql.IQColumn;\r
+import com.iciql.Iciql.IQFunction;\r
import com.iciql.test.models.ComplexObject;\r
import com.iciql.test.models.Customer;\r
import com.iciql.test.models.Order;\r
public static class ProductPrice {\r
public String productName;\r
public String category;\r
+ @IQColumn(name = "unitPrice")\r
public Double price;\r
}\r
\r
*/\r
public static class ProductGroup {\r
public String category;\r
+ @IQFunction\r
public Long productCount;\r
\r
public String toString() {\r
productCount = count();\r
}\r
});\r
-\r
assertEquals("[Beverages:2, Condiments:5, Meat/Poultry:1, Produce:1, Seafood:1]", list.toString());\r
}\r
\r
--- /dev/null
+package com.iciql.test.models;\r
+\r
+import java.util.Arrays;\r
+import java.util.List;\r
+\r
+import com.iciql.Iciql.IQColumn;\r
+import com.iciql.Iciql.IQTable;\r
+\r
+/**\r
+ * Model class to test the runtime exception of too many primitive boolean\r
+ * fields in the model.\r
+ * \r
+ * @author James Moger\r
+ * \r
+ */\r
+@IQTable\r
+public class MultipleBoolsModel {\r
+\r
+ @IQColumn(autoIncrement = true, primaryKey = true)\r
+ public int id;\r
+\r
+ @IQColumn\r
+ public boolean a;\r
+\r
+ @IQColumn\r
+ public boolean b;\r
+\r
+ public MultipleBoolsModel() {\r
+ }\r
+\r
+ public MultipleBoolsModel(boolean a, boolean b) {\r
+ this.a = a;\r
+ this.b = b;\r
+ }\r
+\r
+ public static List<MultipleBoolsModel> getList() {\r
+ return Arrays.asList(new MultipleBoolsModel(true, true), new MultipleBoolsModel(true, false),\r
+ new MultipleBoolsModel(true, false), new MultipleBoolsModel(false, false));\r
+ }\r
+}
\ No newline at end of file