see samples in src/test/java/com/iciql/test/StackableConditionsTest.javatags/v1.3.0
@@ -0,0 +1,33 @@ | |||
/* | |||
* 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; | |||
enum ConditionOpenClose implements Token { | |||
OPEN("("), CLOSE(")"); | |||
private String text; | |||
ConditionOpenClose(String text) { | |||
this.text = text; | |||
} | |||
public <T> void appendSQL(SQLStatement stat, Query<T> query) { | |||
stat.appendSQL(text); | |||
} | |||
} |
@@ -0,0 +1,132 @@ | |||
/* | |||
* Copyright (c) 2009-2014, Architector Inc., Japan | |||
* All rights reserved. | |||
* | |||
* 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; | |||
public abstract class Conditions<T> { | |||
public static class And<T> extends Conditions<T> { | |||
public And(Db db, T alias) { | |||
super(db, alias); | |||
} | |||
protected QueryCondition<T, Boolean> and(boolean x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Byte> and(byte x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Short> and(short x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Integer> and(int x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Long> and(long x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Float> and(float x) { | |||
return where.and(x); | |||
} | |||
protected QueryCondition<T, Double> and(double x) { | |||
return where.and(x); | |||
} | |||
protected <A> QueryCondition<T, A> and(A x) { | |||
return where.and(x); | |||
} | |||
protected QueryWhere<T> and(And<T> conditions) { | |||
where.andOpenTrue(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> and(Or<T> conditions) { | |||
where.andOpenFalse(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
} | |||
public static class Or<T> extends Conditions<T> { | |||
public Or(Db db, T alias) { | |||
super(db, alias); | |||
} | |||
protected QueryCondition<T, Boolean> or(boolean x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Byte> or(byte x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Short> or(short x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Integer> or(int x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Long> or(long x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Float> or(float x) { | |||
return where.or(x); | |||
} | |||
protected QueryCondition<T, Double> or(double x) { | |||
return where.or(x); | |||
} | |||
protected <A> QueryCondition<T, A> or(A x) { | |||
return where.or(x); | |||
} | |||
protected QueryWhere<T> or(And<T> conditions) { | |||
where.orOpenTrue(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
protected QueryWhere<T> or(Or<T> conditions) { | |||
where.orOpenFalse(); | |||
where.query.addConditionToken(conditions.where.query); | |||
return where.close(); | |||
} | |||
} | |||
QueryWhere<T> where; | |||
private Conditions(Db db, T alias) { | |||
where = new QueryWhere<T>(Query.rebuild(db, alias)); | |||
} | |||
} |
@@ -27,7 +27,8 @@ import java.util.Arrays; | |||
import java.util.HashMap; | |||
import java.util.IdentityHashMap; | |||
import java.util.List; | |||
import com.iciql.Conditions.And; | |||
import com.iciql.Conditions.Or; | |||
import com.iciql.Iciql.EnumType; | |||
import com.iciql.bytecode.ClassReader; | |||
import com.iciql.util.JdbcUtils; | |||
@@ -47,6 +48,7 @@ public class Query<T> { | |||
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(); | |||
@@ -70,7 +72,16 @@ public class Query<T> { | |||
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); | |||
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; | |||
} | |||
@@ -583,6 +594,26 @@ public class Query<T> { | |||
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); | |||
@@ -821,14 +852,31 @@ public class Query<T> { | |||
} | |||
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 "); | |||
for (Token token : conditions) { | |||
@@ -897,7 +945,7 @@ public class Query<T> { | |||
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); | |||
def.initSelectObject(join, alias, aliasMap, false); | |||
joins.add(join); | |||
return new QueryJoin(this, join); | |||
} |
@@ -19,6 +19,9 @@ package com.iciql; | |||
import java.util.List; | |||
import com.iciql.Conditions.And; | |||
import com.iciql.Conditions.Or; | |||
/** | |||
* This class represents a query with a condition. | |||
* | |||
@@ -135,6 +138,26 @@ public class QueryWhere<T> { | |||
return new QueryCondition<T, A>(query, x); | |||
} | |||
public QueryWhere<T> and(And<T> conditions) { | |||
andOpenTrue(); | |||
query.addConditionToken(conditions.where.query); | |||
return close(); | |||
} | |||
public QueryWhere<T> and(Or<T> conditions) { | |||
andOpenFalse(); | |||
query.addConditionToken(conditions.where.query); | |||
return close(); | |||
} | |||
public QueryWhere<T> andOpenTrue() { | |||
return open(ConditionAndOr.AND, true); | |||
} | |||
public QueryWhere<T> andOpenFalse() { | |||
return open(ConditionAndOr.AND, false); | |||
} | |||
/** | |||
* Specify an OR condition with a mapped primitive boolean. | |||
* | |||
@@ -226,6 +249,38 @@ public class QueryWhere<T> { | |||
return new QueryCondition<T, A>(query, x); | |||
} | |||
public QueryWhere<T> or(And<T> conditions) { | |||
orOpenTrue(); | |||
query.addConditionToken(conditions.where.query); | |||
return close(); | |||
} | |||
public QueryWhere<T> or(Or<T> conditions) { | |||
orOpenFalse(); | |||
query.addConditionToken(conditions.where.query); | |||
return close(); | |||
} | |||
public QueryWhere<T> orOpenTrue() { | |||
return open(ConditionAndOr.OR, true); | |||
} | |||
public QueryWhere<T> orOpenFalse() { | |||
return open(ConditionAndOr.OR, false); | |||
} | |||
private QueryWhere<T> open(ConditionAndOr andOr, Boolean condition) { | |||
query.addConditionToken(andOr); | |||
query.addConditionToken(ConditionOpenClose.OPEN); | |||
query.addConditionToken(new Function("", condition)); | |||
return this; | |||
} | |||
public QueryWhere<T> close() { | |||
query.addConditionToken(ConditionOpenClose.CLOSE); | |||
return this; | |||
} | |||
public QueryWhere<T> limit(long limit) { | |||
query.limit(limit); | |||
return this; |
@@ -1131,11 +1131,16 @@ public class TableDefinition<T> { | |||
} | |||
} | |||
void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map) { | |||
void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map, boolean reuse) { | |||
for (FieldDefinition def : fields) { | |||
Object newValue = def.initWithNewObject(obj); | |||
Object value; | |||
if (!reuse) { | |||
value = def.initWithNewObject(obj); | |||
} else { | |||
value = def.getValue(obj); | |||
} | |||
SelectColumn<T> column = new SelectColumn<T>(table, def); | |||
map.put(newValue, column); | |||
map.put(value, column); | |||
} | |||
} | |||
@@ -93,7 +93,7 @@ import com.iciql.util.Utils; | |||
@SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, BooleanModelTest.class, ClobTest.class, | |||
ConcurrencyTest.class, EnumsTest.class, ModelsTest.class, PrimitivesTest.class, OneOfTest.class, | |||
RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UpgradesTest.class, JoinTest.class, | |||
UUIDTest.class, ViewsTest.class, ForeignKeyTest.class, TransactionTest.class }) | |||
UUIDTest.class, ViewsTest.class, ForeignKeyTest.class, TransactionTest.class, StackableConditionsTest.class }) | |||
public class IciqlSuite { | |||
private static final TestDb[] TEST_DBS = { |
@@ -0,0 +1,178 @@ | |||
/* | |||
* Copyright (c) 2009-2014, Architector Inc., Japan | |||
* All rights reserved. | |||
* | |||
* 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.test; | |||
import static org.junit.Assert.assertEquals; | |||
import static org.junit.Assert.assertTrue; | |||
import org.junit.After; | |||
import org.junit.Before; | |||
import org.junit.Test; | |||
import com.iciql.Conditions.And; | |||
import com.iciql.Conditions.Or; | |||
import com.iciql.Db; | |||
import com.iciql.IciqlException; | |||
import com.iciql.QueryWhere; | |||
import com.iciql.test.models.Customer; | |||
public class StackableConditionsTest { | |||
enum Region { | |||
JP, FR | |||
} | |||
private Db db; | |||
@Before | |||
public void setUp() { | |||
db = IciqlSuite.openNewDb(); | |||
} | |||
@After | |||
public void tearDown() { | |||
db.close(); | |||
} | |||
private String search(Region region, String... customerIds) { | |||
Customer model; | |||
QueryWhere<Customer> query; | |||
model = new Customer(); | |||
query = db.from(model).whereTrue(); | |||
if (customerIds != null) { | |||
query.andOpenFalse(); | |||
for (String value : customerIds) { | |||
query.or(model.customerId).is(value); | |||
} | |||
query.close(); | |||
} | |||
if (region != null) { | |||
query.and(model.region).is(region.name()); | |||
} | |||
return query.toSQL(); | |||
} | |||
@SuppressWarnings("serial") | |||
@Test | |||
public void andOrTest() { | |||
assertEquals( | |||
search(null, (String[]) null), | |||
"SELECT * FROM Customer WHERE (true)"); | |||
assertEquals( | |||
search(null, new String[0]), | |||
"SELECT * FROM Customer WHERE (true) AND ( (false) )"); | |||
assertEquals( | |||
search(null, "0001"), | |||
"SELECT * FROM Customer WHERE (true) AND ( (false) OR customerId = '0001' )"); | |||
assertEquals( | |||
search(null, "0001", "0002"), | |||
"SELECT * FROM Customer WHERE (true) AND ( (false) OR customerId = '0001' OR customerId = '0002' )"); | |||
assertEquals( | |||
search(Region.JP, (String[]) null), | |||
"SELECT * FROM Customer WHERE (true) AND region = 'JP'"); | |||
assertEquals( | |||
search(Region.JP, new String[0]), | |||
"SELECT * FROM Customer WHERE (true) AND ( (false) ) AND region = 'JP'"); | |||
assertEquals( | |||
search(Region.JP, "0001"), | |||
"SELECT * FROM Customer WHERE (true) AND ( (false) OR customerId = '0001' ) AND region = 'JP'"); | |||
assertEquals( | |||
search(Region.JP, "0001", "0002"), | |||
"SELECT * FROM Customer WHERE (true) AND ( (false) OR customerId = '0001' OR customerId = '0002' ) AND region = 'JP'"); | |||
} | |||
@Test | |||
public void errorTest() { | |||
Customer model; | |||
model = new Customer(); | |||
try { | |||
db.from(model) | |||
.where(model.customerId).is("0001") | |||
.andOpenFalse() | |||
.or(model.region).is("FR") | |||
.or(model.region).is("JP") | |||
.close() | |||
.toSQL(); | |||
assertTrue(true); | |||
} | |||
catch (IciqlException error) { | |||
assertTrue(false); | |||
} | |||
try { | |||
db.from(model) | |||
.where(model.customerId).is("0001") | |||
.andOpenFalse() | |||
.or(model.region).is("FR") | |||
.or(model.region).is("JP") | |||
.toSQL(); | |||
assertTrue(false); | |||
} | |||
catch (IciqlException error) { | |||
assertTrue(true); | |||
} | |||
try { | |||
db.from(model) | |||
.where(model.customerId).is("0001") | |||
.andOpenFalse() | |||
.or(model.region).is("FR") | |||
.or(model.region).is("JP") | |||
.close() | |||
.close(); | |||
assertTrue(false); | |||
} | |||
catch (IciqlException error) { | |||
assertTrue(true); | |||
} | |||
} | |||
@Test | |||
public void fluentTest() { | |||
final Customer model = new Customer(); | |||
assertEquals( | |||
db.from(model).where(new And<Customer>(db, model) {{ | |||
and(model.customerId).is("0001"); | |||
and(new Or<Customer>(db, model) {{ | |||
or(model.region).is("CA"); | |||
or(model.region).is("LA"); | |||
}}); | |||
}}).toSQL(), | |||
"SELECT * FROM Customer WHERE (true) AND customerId = '0001' AND ( (false) OR region = 'CA' OR region = 'LA' )"); | |||
assertEquals( | |||
db.from(model).where(new Or<Customer>(db, model) {{ | |||
or(model.customerId).is("0001"); | |||
or(new And<Customer>(db, model) {{ | |||
and(model.customerId).is("0002"); | |||
and(model.region).is("LA"); | |||
}}); | |||
}}).toSQL(), | |||
"SELECT * FROM Customer WHERE (false) OR customerId = '0001' OR ( (true) AND customerId = '0002' AND region = 'LA' )"); | |||
assertEquals( | |||
db.from(model) | |||
.where(model.customerId).isNotNull() | |||
.and(new Or<Customer>(db, model) {{ | |||
or(model.region).is("LA"); | |||
or(model.region).is("CA"); | |||
}}) | |||
.and(model.region).isNotNull() | |||
.toSQL(), | |||
"SELECT * FROM Customer WHERE customerId IS NOT NULL AND ( (false) OR region = 'LA' OR region = 'CA' ) AND region IS NOT NULL"); | |||
} | |||
} |