]> source.dussan.org Git - iciql.git/commitdiff
add support stackable condition: ex. X and (Y or Z)
authorSotaro SUZUKI <sotaro.suzuki@architector.jp>
Wed, 1 Oct 2014 07:17:34 +0000 (16:17 +0900)
committerJames Moger <james.moger@gitblit.com>
Mon, 6 Oct 2014 14:22:39 +0000 (10:22 -0400)
see samples in
src/test/java/com/iciql/test/StackableConditionsTest.java

src/main/java/com/iciql/ConditionOpenClose.java [new file with mode: 0644]
src/main/java/com/iciql/Conditions.java [new file with mode: 0644]
src/main/java/com/iciql/Query.java
src/main/java/com/iciql/QueryWhere.java
src/main/java/com/iciql/TableDefinition.java
src/test/java/com/iciql/test/IciqlSuite.java
src/test/java/com/iciql/test/StackableConditionsTest.java [new file with mode: 0644]

diff --git a/src/main/java/com/iciql/ConditionOpenClose.java b/src/main/java/com/iciql/ConditionOpenClose.java
new file mode 100644 (file)
index 0000000..5284abd
--- /dev/null
@@ -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 (file)
index 0000000..1699641
--- /dev/null
@@ -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));
+       }
+
+}
index 5f29edf0ef0f412c898bc6b1b406a7eeae3379e5..7edd8fca294004e11ecafc22ffbf7335117e851c 100644 (file)
@@ -27,7 +27,8 @@ import java.util.Arrays;
 import java.util.HashMap;\r
 import java.util.IdentityHashMap;\r
 import java.util.List;\r
-\r
+import com.iciql.Conditions.And;\r
+import com.iciql.Conditions.Or;\r
 import com.iciql.Iciql.EnumType;\r
 import com.iciql.bytecode.ClassReader;\r
 import com.iciql.util.JdbcUtils;\r
@@ -47,6 +48,7 @@ public class Query<T> {
        private SelectTable<T> from;\r
        private ArrayList<Token> conditions = Utils.newArrayList();\r
        private ArrayList<UpdateColumn> updateColumnDeclarations = Utils.newArrayList();\r
+       private int conditionDepth = 0;\r
        private ArrayList<SelectTable<T>> joins = Utils.newArrayList();\r
        private final IdentityHashMap<Object, SelectColumn<T>> aliasMap = Utils.newIdentityHashMap();\r
        private ArrayList<OrderExpression<T>> orderByList = Utils.newArrayList();\r
@@ -70,7 +72,16 @@ public class Query<T> {
                Query<T> query = new Query<T>(db);\r
                TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());\r
                query.from = new SelectTable<T>(db, query, alias, false);\r
-               def.initSelectObject(query.from, alias, query.aliasMap);\r
+               def.initSelectObject(query.from, alias, query.aliasMap, false);\r
+               return query;\r
+       }\r
+\r
+       @SuppressWarnings("unchecked")\r
+       static <T> Query<T> rebuild(Db db, T alias) {\r
+               Query<T> query = new Query<T>(db);\r
+               TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());\r
+               query.from = new SelectTable<T>(db, query, alias, false);\r
+               def.initSelectObject(query.from, alias, query.aliasMap, true);\r
                return query;\r
        }\r
 \r
@@ -583,6 +594,26 @@ public class Query<T> {
                return new QueryWhere<T>(this);\r
        }\r
 \r
+       public Query<T> where(And<T> conditions) {\r
+               whereTrue();\r
+               addConditionToken(conditions.where.query);\r
+               return this;\r
+       }\r
+\r
+       public Query<T> where(Or<T> conditions) {\r
+               whereFalse();\r
+               addConditionToken(conditions.where.query);\r
+               return this;\r
+       }\r
+\r
+       public QueryWhere<T> whereTrue() {\r
+               return whereTrue(true);\r
+       }\r
+\r
+       public QueryWhere<T> whereFalse() {\r
+               return whereTrue(false);\r
+       }\r
+\r
        public QueryWhere<T> whereTrue(Boolean condition) {\r
                Token token = new Function("", condition);\r
                addConditionToken(token);\r
@@ -821,14 +852,31 @@ public class Query<T> {
        }\r
 \r
        void addConditionToken(Token condition) {\r
+               if (condition == ConditionOpenClose.OPEN) {\r
+                       conditionDepth ++;\r
+               } else if (condition == ConditionOpenClose.CLOSE) {\r
+                       conditionDepth --;\r
+                       if (conditionDepth < 0) {\r
+                               throw new IciqlException("unmatch condition open-close count");\r
+                       }\r
+               }\r
                conditions.add(condition);\r
        }\r
 \r
+       void addConditionToken(Query<T> other) {\r
+               for (Token condition : other.conditions) {\r
+                       addConditionToken(condition);\r
+               }\r
+       }\r
+\r
        void addUpdateColumnDeclaration(UpdateColumn declaration) {\r
                updateColumnDeclarations.add(declaration);\r
        }\r
 \r
        void appendWhere(SQLStatement stat) {\r
+               if (conditionDepth != 0) {\r
+                       throw new IciqlException("unmatch condition open-close count");\r
+               }\r
                if (!conditions.isEmpty()) {\r
                        stat.appendSQL(" WHERE ");\r
                        for (Token token : conditions) {\r
@@ -897,7 +945,7 @@ public class Query<T> {
     private <A> QueryJoin<T> join(A alias, boolean outerJoin) {\r
         TableDefinition<T> def = (TableDefinition<T>) db.define(alias.getClass());\r
         SelectTable<T> join = new SelectTable(db, this, alias, outerJoin);\r
-        def.initSelectObject(join, alias, aliasMap);\r
+        def.initSelectObject(join, alias, aliasMap, false);\r
         joins.add(join);\r
         return new QueryJoin(this, join);\r
     }\r
index 5baa5ab893d5331ba8ad1d6225a4fb2bc8cd3345..5ea8bdb481f93261a0cc591c7c8aa156fb1e2af7 100644 (file)
@@ -19,6 +19,9 @@ package com.iciql;
 \r
 import java.util.List;\r
 \r
+import com.iciql.Conditions.And;\r
+import com.iciql.Conditions.Or;\r
+\r
 /**\r
  * This class represents a query with a condition.\r
  * \r
@@ -135,6 +138,26 @@ public class QueryWhere<T> {
                return new QueryCondition<T, A>(query, x);\r
        }\r
 \r
+       public QueryWhere<T> and(And<T> conditions) {\r
+               andOpenTrue();\r
+               query.addConditionToken(conditions.where.query);\r
+               return close();\r
+       }\r
+\r
+       public QueryWhere<T> and(Or<T> conditions) {\r
+               andOpenFalse();\r
+               query.addConditionToken(conditions.where.query);\r
+               return close();\r
+       }\r
+\r
+       public QueryWhere<T> andOpenTrue() {\r
+               return open(ConditionAndOr.AND, true);\r
+       }\r
+\r
+       public QueryWhere<T> andOpenFalse() {\r
+               return open(ConditionAndOr.AND, false);\r
+       }\r
+\r
        /**\r
         * Specify an OR condition with a mapped primitive boolean.\r
         * \r
@@ -226,6 +249,38 @@ public class QueryWhere<T> {
                return new QueryCondition<T, A>(query, x);\r
        }\r
 \r
+       public QueryWhere<T> or(And<T> conditions) {\r
+               orOpenTrue();\r
+               query.addConditionToken(conditions.where.query);\r
+               return close();\r
+       }\r
+\r
+       public QueryWhere<T> or(Or<T> conditions) {\r
+               orOpenFalse();\r
+               query.addConditionToken(conditions.where.query);\r
+               return close();\r
+       }\r
+\r
+       public QueryWhere<T> orOpenTrue() {\r
+               return open(ConditionAndOr.OR, true);\r
+       }\r
+\r
+       public QueryWhere<T> orOpenFalse() {\r
+               return open(ConditionAndOr.OR, false);\r
+       }\r
+\r
+       private QueryWhere<T> open(ConditionAndOr andOr, Boolean condition) {\r
+               query.addConditionToken(andOr);\r
+               query.addConditionToken(ConditionOpenClose.OPEN);\r
+               query.addConditionToken(new Function("", condition));\r
+               return this;\r
+       }\r
+\r
+       public QueryWhere<T> close() {\r
+               query.addConditionToken(ConditionOpenClose.CLOSE);\r
+               return this;\r
+       }\r
+\r
        public QueryWhere<T> limit(long limit) {\r
                query.limit(limit);\r
                return this;\r
index 6d8cb6e3175f2b762623a97a9249f0c9740ae56c..e0cc1690d8eaa005550e3b4f33dfb0992f45b900 100644 (file)
@@ -1131,11 +1131,16 @@ public class TableDefinition<T> {
                }\r
        }\r
 \r
-       void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map) {\r
+       void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map, boolean reuse) {\r
                for (FieldDefinition def : fields) {\r
-                       Object newValue = def.initWithNewObject(obj);\r
+                       Object value;\r
+                       if (!reuse) {\r
+                               value = def.initWithNewObject(obj);\r
+                       } else {\r
+                               value = def.getValue(obj);\r
+                       }\r
                        SelectColumn<T> column = new SelectColumn<T>(table, def);\r
-                       map.put(newValue, column);\r
+                       map.put(value, column);\r
                }\r
        }\r
 \r
index 9c9ba39e7e6469fdf626673e95da3e32df936a20..a26bf0841df5c5b095fd57e6eec4ad12aec5e6c8 100644 (file)
@@ -93,7 +93,7 @@ import com.iciql.util.Utils;
 @SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, BooleanModelTest.class, ClobTest.class,\r
                ConcurrencyTest.class, EnumsTest.class, ModelsTest.class, PrimitivesTest.class, OneOfTest.class,\r
                RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UpgradesTest.class, JoinTest.class,\r
-               UUIDTest.class, ViewsTest.class, ForeignKeyTest.class, TransactionTest.class })\r
+               UUIDTest.class, ViewsTest.class, ForeignKeyTest.class, TransactionTest.class, StackableConditionsTest.class })\r
 public class IciqlSuite {\r
 \r
        private static final TestDb[] TEST_DBS = {\r
diff --git a/src/test/java/com/iciql/test/StackableConditionsTest.java b/src/test/java/com/iciql/test/StackableConditionsTest.java
new file mode 100644 (file)
index 0000000..15ae295
--- /dev/null
@@ -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");
+       }
+
+}