Browse Source

Columns mapped by name in result set instead of index. Disallow multiple

primitive bools in a model WITH explicit referencing.
tags/v0.7.4
James Moger 12 years ago
parent
commit
876c4e5157

+ 82
- 2
api/v8.xml View File

@@ -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&lt;T&gt;"
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&lt;T&gt;"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="modelClass" type="java.lang.Class&lt;? extends T&gt;">
</parameter>
<parameter name="sql" type="java.lang.String">
</parameter>
<parameter name="args" type="java.util.List&lt;?&gt;">
</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&lt;?&gt;">
</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&lt;T&gt;"
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&lt;?&gt;">
</parameter>
</method>
<method name="where"
return="com.iciql.QueryCondition&lt;T, java.lang.Long&gt;"
abstract="false"

+ 1
- 1
docs/01_model_classes.mkd View File

@@ -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>

+ 7
- 0
docs/04_examples.mkd View File

@@ -8,6 +8,13 @@ List&lt;Product&gt; allProducts = db.from(p).select();
Customer c = new Customer();
List&lt;Customer&gt; 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&lt;ProductPrice&gt; productPrices =
db.from(p).

+ 23
- 3
docs/05_releases.mkd View File

@@ -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%)) &nbsp; *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&lt;? extends T&gt; modelClass, String sql, List&lt;?&gt; args)*<br/>
*Db.executeQuery(String sql, List&lt;?&gt; args)*<br/>
*Query.where(String fragment, List&lt;?&gt; 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

+ 1
- 0
docs/06_jaqu_comparison.mkd View File

@@ -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>

+ 16
- 1
src/com/iciql/Db.java View File

@@ -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) {

+ 11
- 0
src/com/iciql/Iciql.java View File

@@ -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.
*/

+ 16
- 2
src/com/iciql/Query.java View File

@@ -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();
}

+ 2
- 1
src/com/iciql/QueryJoin.java View File

@@ -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

+ 4
- 1
src/com/iciql/QueryWhere.java View File

@@ -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);
}

+ 65
- 3
src/com/iciql/TableDefinition.java View File

@@ -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);
}
}

+ 17
- 0
tests/com/iciql/test/PrimitivesTest.java View File

@@ -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();
}
}

+ 4
- 1
tests/com/iciql/test/SamplesTest.java View File

@@ -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());
}

+ 40
- 0
tests/com/iciql/test/models/MultipleBoolsModel.java View File

@@ -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));
}
}

Loading…
Cancel
Save