diff options
author | Sotaro SUZUKI <sotaro.suzuki@architector.jp> | 2014-10-01 16:17:34 +0900 |
---|---|---|
committer | James Moger <james.moger@gitblit.com> | 2014-10-06 10:22:39 -0400 |
commit | 48da60f625b53dccbc4a34737eba55eb0eda181b (patch) | |
tree | 0b048e6247ce0ec196df7015c8fa77bf36e062a0 /src | |
parent | 8ba3b92816e65acc0d1d71ba23259deed616eefe (diff) | |
download | iciql-48da60f625b53dccbc4a34737eba55eb0eda181b.tar.gz iciql-48da60f625b53dccbc4a34737eba55eb0eda181b.zip |
add support stackable condition: ex. X and (Y or Z)
see samples in
src/test/java/com/iciql/test/StackableConditionsTest.java
Diffstat (limited to 'src')
-rw-r--r-- | src/main/java/com/iciql/ConditionOpenClose.java | 33 | ||||
-rw-r--r-- | src/main/java/com/iciql/Conditions.java | 132 | ||||
-rw-r--r-- | src/main/java/com/iciql/Query.java | 54 | ||||
-rw-r--r-- | src/main/java/com/iciql/QueryWhere.java | 55 | ||||
-rw-r--r-- | src/main/java/com/iciql/TableDefinition.java | 11 | ||||
-rw-r--r-- | src/test/java/com/iciql/test/IciqlSuite.java | 2 | ||||
-rw-r--r-- | src/test/java/com/iciql/test/StackableConditionsTest.java | 178 |
7 files changed, 458 insertions, 7 deletions
diff --git a/src/main/java/com/iciql/ConditionOpenClose.java b/src/main/java/com/iciql/ConditionOpenClose.java new file mode 100644 index 0000000..5284abd --- /dev/null +++ b/src/main/java/com/iciql/ConditionOpenClose.java @@ -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); + } + +} diff --git a/src/main/java/com/iciql/Conditions.java b/src/main/java/com/iciql/Conditions.java new file mode 100644 index 0000000..1699641 --- /dev/null +++ b/src/main/java/com/iciql/Conditions.java @@ -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)); + } + +} diff --git a/src/main/java/com/iciql/Query.java b/src/main/java/com/iciql/Query.java index 5f29edf..7edd8fc 100644 --- a/src/main/java/com/iciql/Query.java +++ b/src/main/java/com/iciql/Query.java @@ -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);
}
diff --git a/src/main/java/com/iciql/QueryWhere.java b/src/main/java/com/iciql/QueryWhere.java index 5baa5ab..5ea8bdb 100644 --- a/src/main/java/com/iciql/QueryWhere.java +++ b/src/main/java/com/iciql/QueryWhere.java @@ -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;
diff --git a/src/main/java/com/iciql/TableDefinition.java b/src/main/java/com/iciql/TableDefinition.java index 6d8cb6e..e0cc169 100644 --- a/src/main/java/com/iciql/TableDefinition.java +++ b/src/main/java/com/iciql/TableDefinition.java @@ -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);
}
}
diff --git a/src/test/java/com/iciql/test/IciqlSuite.java b/src/test/java/com/iciql/test/IciqlSuite.java index 9c9ba39..a26bf08 100644 --- a/src/test/java/com/iciql/test/IciqlSuite.java +++ b/src/test/java/com/iciql/test/IciqlSuite.java @@ -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 = {
diff --git a/src/test/java/com/iciql/test/StackableConditionsTest.java b/src/test/java/com/iciql/test/StackableConditionsTest.java new file mode 100644 index 0000000..15ae295 --- /dev/null +++ b/src/test/java/com/iciql/test/StackableConditionsTest.java @@ -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"); + } + +} |