primitive bools in a model WITH explicit referencing.tags/v0.7.4
@@ -43,7 +43,7 @@ | |||
type="java.lang.String" | |||
transient="false" | |||
volatile="false" | |||
value=""0.7.3"" | |||
value=""0.7.4-SNAPSHOT"" | |||
static="true" | |||
final="true" | |||
deprecated="not deprecated" | |||
@@ -54,7 +54,7 @@ | |||
type="java.lang.String" | |||
transient="false" | |||
volatile="false" | |||
value=""2011-12-06"" | |||
value=""PENDING"" | |||
static="true" | |||
final="true" | |||
deprecated="not deprecated" | |||
@@ -70,6 +70,17 @@ | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
<method name="activateConsoleLogger" | |||
return="void" | |||
abstract="false" | |||
native="false" | |||
synchronized="false" | |||
static="true" | |||
final="false" | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
</method> | |||
<method name="buildObjects" | |||
return="java.util.List<T>" | |||
abstract="false" | |||
@@ -96,6 +107,17 @@ | |||
visibility="public" | |||
> | |||
</method> | |||
<method name="deactivateConsoleLogger" | |||
return="void" | |||
abstract="false" | |||
native="false" | |||
synchronized="false" | |||
static="true" | |||
final="false" | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
</method> | |||
<method name="delete" | |||
return="int" | |||
abstract="false" | |||
@@ -152,6 +174,23 @@ | |||
<parameter name="args" type="java.lang.Object..."> | |||
</parameter> | |||
</method> | |||
<method name="executeQuery" | |||
return="java.util.List<T>" | |||
abstract="false" | |||
native="false" | |||
synchronized="false" | |||
static="false" | |||
final="false" | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
<parameter name="modelClass" type="java.lang.Class<? extends T>"> | |||
</parameter> | |||
<parameter name="sql" type="java.lang.String"> | |||
</parameter> | |||
<parameter name="args" type="java.util.List<?>"> | |||
</parameter> | |||
</method> | |||
<method name="executeQuery" | |||
return="java.sql.ResultSet" | |||
abstract="false" | |||
@@ -167,6 +206,21 @@ | |||
<parameter name="args" type="java.lang.Object..."> | |||
</parameter> | |||
</method> | |||
<method name="executeQuery" | |||
return="java.sql.ResultSet" | |||
abstract="false" | |||
native="false" | |||
synchronized="false" | |||
static="false" | |||
final="false" | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
<parameter name="sql" type="java.lang.String"> | |||
</parameter> | |||
<parameter name="args" type="java.util.List<?>"> | |||
</parameter> | |||
</method> | |||
<method name="executeUpdate" | |||
return="int" | |||
abstract="false" | |||
@@ -1157,6 +1211,17 @@ | |||
<implements name="java.lang.annotation.Annotation"> | |||
</implements> | |||
</class> | |||
<class name="Iciql.IQFunction" | |||
extends="java.lang.Object" | |||
abstract="true" | |||
static="true" | |||
final="false" | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
<implements name="java.lang.annotation.Annotation"> | |||
</implements> | |||
</class> | |||
<class name="Iciql.IQIgnore" | |||
extends="java.lang.Object" | |||
abstract="true" | |||
@@ -2157,6 +2222,21 @@ | |||
<parameter name="args" type="java.lang.Object..."> | |||
</parameter> | |||
</method> | |||
<method name="where" | |||
return="com.iciql.QueryWhere<T>" | |||
abstract="false" | |||
native="false" | |||
synchronized="false" | |||
static="false" | |||
final="false" | |||
deprecated="not deprecated" | |||
visibility="public" | |||
> | |||
<parameter name="fragment" type="java.lang.String"> | |||
</parameter> | |||
<parameter name="args" type="java.util.List<?>"> | |||
</parameter> | |||
</method> | |||
<method name="where" | |||
return="com.iciql.QueryCondition<T, java.lang.Long>" | |||
abstract="false" |
@@ -23,7 +23,7 @@ can be used for all iciql expressions | |||
<td>VARCHAR *(length > 0)* or CLOB *(length == 0)*</td></tr> | |||
<tr><td>java.lang.Boolean</td><td>boolean</td> | |||
<td>BOOLEAN</td></tr> | |||
<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> | |||
<tr><td>java.lang.Byte</td><td>byte</td> | |||
<td>TINYINT</td></tr> |
@@ -8,6 +8,13 @@ List<Product> allProducts = db.from(p).select(); | |||
Customer c = new Customer(); | |||
List<Customer> waCustomers = db.from(c). where(c.region).is("WA").select(); | |||
public static class ProductPrice { | |||
public String productName; | |||
public String category; | |||
@IQColumn(name = "unitPrice") | |||
public Double price; | |||
} | |||
// select with generation of new anonymous inner class | |||
List<ProductPrice> productPrices = | |||
db.from(p). |
@@ -6,9 +6,29 @@ | |||
**%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%* | |||
- Added list alternatives to the varargs methods because it was too easy to forget list.toArray() for the varargs methods | |||
List<T> Db.executeQuery(Class<? extends T> modelClass, String sql, List<?> args) | |||
ResultSet executeQuery(String sql, List<?> args) | |||
- 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. | |||
- Added list alternatives to the varargs methods because it was too easy to forget list.toArray()<br/> | |||
*Db.executeQuery(Class<? extends T> modelClass, String sql, List<?> args)*<br/> | |||
*Db.executeQuery(String sql, List<?> args)*<br/> | |||
*Query.where(String fragment, List<?> args)*<br/> | |||
- Fixed inherited JaQu bug related to model classes and wildcard queries (select *).<p/> | |||
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/> | |||
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/> | |||
%BEGINCODE% | |||
public static class ProductPrice { | |||
public String productName; | |||
public String category; | |||
@IQColumn(name = "unitPrice") | |||
public Double price; | |||
} | |||
db....select(new ProductPrice() {{ | |||
productName = p.productName; | |||
category = p.category; | |||
// or unitPrice = p.unitPrice; | |||
price = p.unitPrice; | |||
}} | |||
%ENDCODE% | |||
### Older Releases | |||
@@ -10,6 +10,7 @@ This is an overview of the fundamental differences between the original JaQu pro | |||
<tr><td>databases</td><td>H2, HSQL, Derby, MySQL, and PostreSQL</td><td>H2 only</td></tr> | |||
<tr><td>logging</td><td>console, SLF4J, or custom logging</td><td>console logging</td></tr> | |||
<tr><td>exceptions</td><td>always includes generated statement in exception, when available</td><td>--</td></tr> | |||
<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> | |||
<tr><th colspan="3">syntax and api</th></tr> | |||
<tr><td>dynamic queries</td><td>methods and where clauses for dynamic queries that build iciql objects</td><td>--</td></tr> | |||
<tr><td>DROP</td><td>syntax to drop a table</td><td></td></tr> |
@@ -138,6 +138,20 @@ public class Db { | |||
throw new IciqlException(e); | |||
} | |||
} | |||
/** | |||
* Convenience function to avoid import statements in application code. | |||
*/ | |||
public static void activateConsoleLogger() { | |||
IciqlLogger.activateConsoleLogger(); | |||
} | |||
/** | |||
* Convenience function to avoid import statements in application code. | |||
*/ | |||
public static void deactivateConsoleLogger() { | |||
IciqlLogger.deactivateConsoleLogger(); | |||
} | |||
public static Db open(String url) { | |||
try { | |||
@@ -273,9 +287,10 @@ public class Db { | |||
List<T> result = new ArrayList<T>(); | |||
TableDefinition<T> def = (TableDefinition<T>) define(modelClass); | |||
try { | |||
int [] columns = def.mapColumns(rs); | |||
while (rs.next()) { | |||
T item = Utils.newObject(modelClass); | |||
def.readRow(item, rs); | |||
def.readRow(item, rs, columns); | |||
result.add(item); | |||
} | |||
} catch (SQLException e) { |
@@ -499,6 +499,17 @@ public interface Iciql { | |||
EnumType value() default EnumType.NAME; | |||
} | |||
/** | |||
* Annotation to define a field that should contain the result a function. | |||
* This annotation ensures that functions mapped in anonymous inner classes | |||
* can still be referenced in the ResultSet after the switch to dynamic | |||
* column-name mapping from fixed position column mapping. | |||
*/ | |||
@Retention(RetentionPolicy.RUNTIME) | |||
@Target(ElementType.FIELD) | |||
public @interface IQFunction{ | |||
} | |||
/** | |||
* Annotation to define an ignored field. | |||
*/ |
@@ -123,9 +123,10 @@ public class Query<T> { | |||
appendFromWhere(stat); | |||
ResultSet rs = stat.executeQuery(); | |||
try { | |||
int[] columns = def.mapColumns(rs); | |||
while (rs.next()) { | |||
T item = from.newObject(); | |||
from.getAliasDefinition().readRow(item, rs); | |||
def.readRow(item, rs, columns); | |||
result.add(item); | |||
} | |||
} catch (SQLException e) { | |||
@@ -150,6 +151,7 @@ public class Query<T> { | |||
} | |||
public UpdateColumnSet<T, Boolean> set(boolean field) { | |||
from.getAliasDefinition().checkMultipleBooleans(); | |||
return setPrimitive(field); | |||
} | |||
@@ -269,9 +271,10 @@ public class Query<T> { | |||
appendFromWhere(stat); | |||
ResultSet rs = stat.executeQuery(); | |||
try { | |||
int[] columns = def.mapColumns(rs); | |||
while (rs.next()) { | |||
X row = Utils.newObject(clazz); | |||
def.readRow(row, rs); | |||
def.readRow(row, rs, columns); | |||
result.add(row); | |||
} | |||
} catch (SQLException e) { | |||
@@ -328,6 +331,7 @@ public class Query<T> { | |||
* @return a query condition to continue building the condition | |||
*/ | |||
public QueryCondition<T, Boolean> where(boolean x) { | |||
from.getAliasDefinition().checkMultipleBooleans(); | |||
return wherePrimitive(x); | |||
} | |||
@@ -449,6 +453,10 @@ public class Query<T> { | |||
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); | |||
@@ -477,6 +485,7 @@ public class Query<T> { | |||
} | |||
public Query<T> orderBy(boolean field) { | |||
from.getAliasDefinition().checkMultipleBooleans(); | |||
return orderByPrimitive(field); | |||
} | |||
@@ -541,6 +550,7 @@ public class Query<T> { | |||
} | |||
public Query<T> groupBy(boolean field) { | |||
from.getAliasDefinition().checkMultipleBooleans(); | |||
return groupByPrimitive(field); | |||
} | |||
@@ -737,6 +747,10 @@ public class Query<T> { | |||
return db; | |||
} | |||
SelectTable<T> getFrom() { | |||
return from; | |||
} | |||
boolean isJoin() { | |||
return !joins.isEmpty(); | |||
} |
@@ -32,6 +32,7 @@ public class QueryJoin<T> { | |||
} | |||
public QueryJoinCondition<T, Boolean> on(boolean x) { | |||
query.getFrom().getAliasDefinition().checkMultipleBooleans(); | |||
return addPrimitive(x); | |||
} | |||
@@ -59,7 +60,7 @@ public class QueryJoin<T> { | |||
return addPrimitive(x); | |||
} | |||
private <A> QueryJoinCondition<T, A> addPrimitive(A x) { | |||
private <A> QueryJoinCondition<T, A> addPrimitive(A x) { | |||
A alias = query.getPrimitiveAliasByValue(x); | |||
if (alias == null) { | |||
// this will result in an unmapped field exception |
@@ -42,6 +42,7 @@ public class QueryWhere<T> { | |||
* @return a query condition to continue building the condition | |||
*/ | |||
public QueryCondition<T, Boolean> and(boolean x) { | |||
query.getFrom().getAliasDefinition().checkMultipleBooleans(); | |||
return addPrimitive(ConditionAndOr.AND, x); | |||
} | |||
@@ -111,7 +112,7 @@ public class QueryWhere<T> { | |||
return addPrimitive(ConditionAndOr.AND, x); | |||
} | |||
private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) { | |||
private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) { | |||
query.addConditionToken(condition); | |||
A alias = query.getPrimitiveAliasByValue(x); | |||
if (alias == null) { | |||
@@ -141,6 +142,7 @@ public class QueryWhere<T> { | |||
* @return a query condition to continue building the condition | |||
*/ | |||
public QueryCondition<T, Boolean> or(boolean x) { | |||
query.getFrom().getAliasDefinition().checkMultipleBooleans(); | |||
return addPrimitive(ConditionAndOr.OR, x); | |||
} | |||
@@ -273,6 +275,7 @@ public class QueryWhere<T> { | |||
* @return the query | |||
*/ | |||
public QueryWhere<T> orderBy(boolean field) { | |||
query.getFrom().getAliasDefinition().checkMultipleBooleans(); | |||
return orderByPrimitive(field); | |||
} | |||
@@ -30,6 +30,7 @@ import com.iciql.Iciql.EnumId; | |||
import com.iciql.Iciql.EnumType; | |||
import com.iciql.Iciql.IQColumn; | |||
import com.iciql.Iciql.IQEnum; | |||
import com.iciql.Iciql.IQFunction; | |||
import com.iciql.Iciql.IQIgnore; | |||
import com.iciql.Iciql.IQIndex; | |||
import com.iciql.Iciql.IQIndexes; | |||
@@ -73,6 +74,7 @@ public class TableDefinition<T> { | |||
String dataType; | |||
int length; | |||
int scale; | |||
boolean isFunction; | |||
boolean isPrimaryKey; | |||
boolean isAutoIncrement; | |||
boolean trim; | |||
@@ -115,6 +117,10 @@ public class TableDefinition<T> { | |||
} | |||
private Object read(ResultSet rs, int columnIndex) { | |||
if (columnIndex == 0) { | |||
// unmapped column or function field | |||
return null; | |||
} | |||
try { | |||
return rs.getObject(columnIndex); | |||
} catch (SQLException e) { | |||
@@ -129,6 +135,7 @@ public class TableDefinition<T> { | |||
int tableVersion; | |||
List<String> primaryKeyColumnNames; | |||
boolean memoryTable; | |||
boolean multiplePrimitiveBools; | |||
private boolean createIfRequired = true; | |||
private Class<T> clazz; | |||
@@ -354,6 +361,7 @@ public class TableDefinition<T> { | |||
throw new IciqlException(e, "failed to get default object for {0}", columnName); | |||
} | |||
boolean isFunction = f.isAnnotationPresent(IQFunction.class); | |||
boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class); | |||
if (hasAnnotation) { | |||
IQColumn col = f.getAnnotation(IQColumn.class); | |||
@@ -385,6 +393,7 @@ public class TableDefinition<T> { | |||
fieldDef.scale = scale; | |||
fieldDef.trim = trim; | |||
fieldDef.nullable = nullable; | |||
fieldDef.isFunction = isFunction; | |||
fieldDef.defaultValue = defaultValue; | |||
fieldDef.enumType = enumType; | |||
fieldDef.dataType = ModelUtils.getDataType(fieldDef); | |||
@@ -392,16 +401,32 @@ public class TableDefinition<T> { | |||
} | |||
} | |||
List<String> primaryKey = Utils.newArrayList(); | |||
int primitiveBoolean = 0; | |||
for (FieldDefinition fieldDef : fields) { | |||
if (fieldDef.isPrimaryKey) { | |||
primaryKey.add(fieldDef.columnName); | |||
} | |||
if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) { | |||
primitiveBoolean++; | |||
} | |||
} | |||
if (primitiveBoolean > 1) { | |||
multiplePrimitiveBools = true; | |||
IciqlLogger | |||
.warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!"); | |||
} | |||
if (primaryKey.size() > 0) { | |||
setPrimaryKey(primaryKey); | |||
} | |||
} | |||
void checkMultipleBooleans() { | |||
if (multiplePrimitiveBools) { | |||
throw new IciqlException( | |||
"Can not explicitly reference multiple primitive booleans in a model class!"); | |||
} | |||
} | |||
/** | |||
* Optionally truncates strings to the maximum length and converts | |||
* java.lang.Enum types to Strings or Integers. | |||
@@ -698,10 +723,47 @@ public class TableDefinition<T> { | |||
} | |||
} | |||
void readRow(Object item, ResultSet rs) { | |||
/** | |||
* Most queries executed by iciql have named select lists (select alpha, | |||
* beta where...) but sometimes a wildcard select is executed (select *). | |||
* When a wildcard query is executed on a table that has more columns than | |||
* are mapped in your model object this creates a column mapping issue. JaQu | |||
* assumed that you can always use the integer index of the reflectively | |||
* mapped field definition to determine position in the result set. | |||
* | |||
* This is not always true. | |||
* | |||
* So iciql maps column names to column index in the result set to properly | |||
* map the results of wildcard queries. | |||
* | |||
* @param rs | |||
* @return | |||
*/ | |||
int[] mapColumns(ResultSet rs) { | |||
int[] columns = new int[fields.size()]; | |||
for (int i = 0; i < fields.size(); i++) { | |||
FieldDefinition def = fields.get(i); | |||
Object o = def.read(rs, i + 1); | |||
try { | |||
FieldDefinition def = fields.get(i); | |||
int columnIndex; | |||
if (def.isFunction) { | |||
// XXX review functions _always_ map after fields? | |||
columnIndex = i + 1; | |||
} else { | |||
columnIndex = rs.findColumn(def.columnName); | |||
} | |||
columns[i] = columnIndex; | |||
} catch (SQLException s) { | |||
throw new IciqlException(s); | |||
} | |||
} | |||
return columns; | |||
} | |||
void readRow(Object item, ResultSet rs, int[] columns) { | |||
for (int i = 0; i < fields.size(); i++) { | |||
FieldDefinition def = fields.get(i); | |||
int index = columns[i]; | |||
Object o = def.read(rs, index); | |||
def.setValue(item, o); | |||
} | |||
} |
@@ -25,6 +25,8 @@ import java.util.List; | |||
import org.junit.Test; | |||
import com.iciql.Db; | |||
import com.iciql.IciqlException; | |||
import com.iciql.test.models.MultipleBoolsModel; | |||
import com.iciql.test.models.PrimitivesModel; | |||
/** | |||
@@ -82,4 +84,19 @@ public class PrimitivesTest { | |||
db.close(); | |||
} | |||
@Test | |||
public void testMultipleBooleans() { | |||
Db db = IciqlSuite.openNewDb(); | |||
db.insertAll(MultipleBoolsModel.getList()); | |||
MultipleBoolsModel m = new MultipleBoolsModel(); | |||
try { | |||
db.from(m).where(m.a).is(true).select(); | |||
assertTrue(false); | |||
} catch (IciqlException e) { | |||
assertTrue(true); | |||
} | |||
db.close(); | |||
} | |||
} |
@@ -39,6 +39,8 @@ import org.junit.Test; | |||
import com.iciql.Db; | |||
import com.iciql.Filter; | |||
import com.iciql.Iciql.IQColumn; | |||
import com.iciql.Iciql.IQFunction; | |||
import com.iciql.test.models.ComplexObject; | |||
import com.iciql.test.models.Customer; | |||
import com.iciql.test.models.Order; | |||
@@ -161,6 +163,7 @@ public class SamplesTest { | |||
public static class ProductPrice { | |||
public String productName; | |||
public String category; | |||
@IQColumn(name = "unitPrice") | |||
public Double price; | |||
} | |||
@@ -406,6 +409,7 @@ public class SamplesTest { | |||
*/ | |||
public static class ProductGroup { | |||
public String category; | |||
@IQFunction | |||
public Long productCount; | |||
public String toString() { | |||
@@ -432,7 +436,6 @@ public class SamplesTest { | |||
productCount = count(); | |||
} | |||
}); | |||
assertEquals("[Beverages:2, Condiments:5, Meat/Poultry:1, Produce:1, Seafood:1]", list.toString()); | |||
} | |||
@@ -0,0 +1,40 @@ | |||
package com.iciql.test.models; | |||
import java.util.Arrays; | |||
import java.util.List; | |||
import com.iciql.Iciql.IQColumn; | |||
import com.iciql.Iciql.IQTable; | |||
/** | |||
* Model class to test the runtime exception of too many primitive boolean | |||
* fields in the model. | |||
* | |||
* @author James Moger | |||
* | |||
*/ | |||
@IQTable | |||
public class MultipleBoolsModel { | |||
@IQColumn(autoIncrement = true, primaryKey = true) | |||
public int id; | |||
@IQColumn | |||
public boolean a; | |||
@IQColumn | |||
public boolean b; | |||
public MultipleBoolsModel() { | |||
} | |||
public MultipleBoolsModel(boolean a, boolean b) { | |||
this.a = a; | |||
this.b = b; | |||
} | |||
public static List<MultipleBoolsModel> getList() { | |||
return Arrays.asList(new MultipleBoolsModel(true, true), new MultipleBoolsModel(true, false), | |||
new MultipleBoolsModel(true, false), new MultipleBoolsModel(false, false)); | |||
} | |||
} |