From 48da60f625b53dccbc4a34737eba55eb0eda181b Mon Sep 17 00:00:00 2001 From: Sotaro SUZUKI Date: Wed, 1 Oct 2014 16:17:34 +0900 Subject: [PATCH] add support stackable condition: ex. X and (Y or Z) see samples in src/test/java/com/iciql/test/StackableConditionsTest.java --- .../java/com/iciql/ConditionOpenClose.java | 33 ++++ src/main/java/com/iciql/Conditions.java | 132 +++++++++++++ src/main/java/com/iciql/Query.java | 54 +++++- src/main/java/com/iciql/QueryWhere.java | 55 ++++++ src/main/java/com/iciql/TableDefinition.java | 11 +- src/test/java/com/iciql/test/IciqlSuite.java | 2 +- .../iciql/test/StackableConditionsTest.java | 178 ++++++++++++++++++ 7 files changed, 458 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/iciql/ConditionOpenClose.java create mode 100644 src/main/java/com/iciql/Conditions.java create mode 100644 src/test/java/com/iciql/test/StackableConditionsTest.java 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 void appendSQL(SQLStatement stat, Query 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 { + + public static class And extends Conditions { + + public And(Db db, T alias) { + super(db, alias); + } + + protected QueryCondition and(boolean x) { + return where.and(x); + } + + protected QueryCondition and(byte x) { + return where.and(x); + } + + protected QueryCondition and(short x) { + return where.and(x); + } + + protected QueryCondition and(int x) { + return where.and(x); + } + + protected QueryCondition and(long x) { + return where.and(x); + } + + protected QueryCondition and(float x) { + return where.and(x); + } + + protected QueryCondition and(double x) { + return where.and(x); + } + + protected QueryCondition and(A x) { + return where.and(x); + } + + protected QueryWhere and(And conditions) { + where.andOpenTrue(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } + + protected QueryWhere and(Or conditions) { + where.andOpenFalse(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } + + } + + public static class Or extends Conditions { + + public Or(Db db, T alias) { + super(db, alias); + } + + protected QueryCondition or(boolean x) { + return where.or(x); + } + + protected QueryCondition or(byte x) { + return where.or(x); + } + + protected QueryCondition or(short x) { + return where.or(x); + } + + protected QueryCondition or(int x) { + return where.or(x); + } + + protected QueryCondition or(long x) { + return where.or(x); + } + + protected QueryCondition or(float x) { + return where.or(x); + } + + protected QueryCondition or(double x) { + return where.or(x); + } + + protected QueryCondition or(A x) { + return where.or(x); + } + + protected QueryWhere or(And conditions) { + where.orOpenTrue(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } + + protected QueryWhere or(Or conditions) { + where.orOpenFalse(); + where.query.addConditionToken(conditions.where.query); + return where.close(); + } + + } + + QueryWhere where; + + private Conditions(Db db, T alias) { + where = new QueryWhere(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 { private SelectTable from; private ArrayList conditions = Utils.newArrayList(); private ArrayList updateColumnDeclarations = Utils.newArrayList(); + private int conditionDepth = 0; private ArrayList> joins = Utils.newArrayList(); private final IdentityHashMap> aliasMap = Utils.newIdentityHashMap(); private ArrayList> orderByList = Utils.newArrayList(); @@ -70,7 +72,16 @@ public class Query { Query query = new Query(db); TableDefinition def = (TableDefinition) db.define(alias.getClass()); query.from = new SelectTable(db, query, alias, false); - def.initSelectObject(query.from, alias, query.aliasMap); + def.initSelectObject(query.from, alias, query.aliasMap, false); + return query; + } + + @SuppressWarnings("unchecked") + static Query rebuild(Db db, T alias) { + Query query = new Query(db); + TableDefinition def = (TableDefinition) db.define(alias.getClass()); + query.from = new SelectTable(db, query, alias, false); + def.initSelectObject(query.from, alias, query.aliasMap, true); return query; } @@ -583,6 +594,26 @@ public class Query { return new QueryWhere(this); } + public Query where(And conditions) { + whereTrue(); + addConditionToken(conditions.where.query); + return this; + } + + public Query where(Or conditions) { + whereFalse(); + addConditionToken(conditions.where.query); + return this; + } + + public QueryWhere whereTrue() { + return whereTrue(true); + } + + public QueryWhere whereFalse() { + return whereTrue(false); + } + public QueryWhere whereTrue(Boolean condition) { Token token = new Function("", condition); addConditionToken(token); @@ -821,14 +852,31 @@ public class Query { } 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 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 { private QueryJoin join(A alias, boolean outerJoin) { TableDefinition def = (TableDefinition) db.define(alias.getClass()); SelectTable 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 { return new QueryCondition(query, x); } + public QueryWhere and(And conditions) { + andOpenTrue(); + query.addConditionToken(conditions.where.query); + return close(); + } + + public QueryWhere and(Or conditions) { + andOpenFalse(); + query.addConditionToken(conditions.where.query); + return close(); + } + + public QueryWhere andOpenTrue() { + return open(ConditionAndOr.AND, true); + } + + public QueryWhere andOpenFalse() { + return open(ConditionAndOr.AND, false); + } + /** * Specify an OR condition with a mapped primitive boolean. * @@ -226,6 +249,38 @@ public class QueryWhere { return new QueryCondition(query, x); } + public QueryWhere or(And conditions) { + orOpenTrue(); + query.addConditionToken(conditions.where.query); + return close(); + } + + public QueryWhere or(Or conditions) { + orOpenFalse(); + query.addConditionToken(conditions.where.query); + return close(); + } + + public QueryWhere orOpenTrue() { + return open(ConditionAndOr.OR, true); + } + + public QueryWhere orOpenFalse() { + return open(ConditionAndOr.OR, false); + } + + private QueryWhere open(ConditionAndOr andOr, Boolean condition) { + query.addConditionToken(andOr); + query.addConditionToken(ConditionOpenClose.OPEN); + query.addConditionToken(new Function("", condition)); + return this; + } + + public QueryWhere close() { + query.addConditionToken(ConditionOpenClose.CLOSE); + return this; + } + public QueryWhere 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 { } } - void initSelectObject(SelectTable table, Object obj, Map> map) { + void initSelectObject(SelectTable table, Object obj, Map> 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 column = new SelectColumn(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 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(db, model) {{ + and(model.customerId).is("0001"); + and(new Or(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(db, model) {{ + or(model.customerId).is("0001"); + or(new And(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(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"); + } + +} -- 2.39.5