return stat.getSQL().trim();\r
}\r
\r
+ /**\r
+ * toSQL returns a static string version of the query with runtime variables\r
+ * properly encoded. This method is also useful when combined with the where\r
+ * clause methods like isParameter() or atLeastParameter() which allows\r
+ * iciql to generate re-usable parameterized string statements.\r
+ * \r
+ * @return the sql query as plain text\r
+ */\r
+ public String toSQL() {\r
+ return toSQL(false);\r
+ }\r
+ \r
+ /**\r
+ * toSQL returns a static string version of the query with runtime variables\r
+ * properly encoded. This method is also useful when combined with the where\r
+ * clause methods like isParameter() or atLeastParameter() which allows\r
+ * iciql to generate re-usable parameterized string statements.\r
+ * \r
+ * @param distinct\r
+ * if true SELECT DISTINCT is used for the query\r
+ * @return the sql query as plain text\r
+ */ \r
+ public String toSQL(boolean distinct) {\r
+ SQLStatement stat = getSelectStatement(distinct);\r
+ stat.appendSQL("*");\r
+ appendFromWhere(stat);\r
+ return stat.toSQL().trim();\r
+ }\r
+\r
private List<T> select(boolean distinct) {\r
List<T> result = Utils.newArrayList();\r
TableDefinition<T> def = from.getAliasDefinition();\r
* the value\r
*/\r
public void appendSQL(SQLStatement stat, Object alias, Object value) {\r
- if (value == Function.count()) {\r
+ if (Function.count() == value) {\r
stat.appendSQL("COUNT(*)");\r
return;\r
}\r
+ if (RuntimeParameter.PARAMETER == value) {\r
+ stat.appendSQL("?");\r
+ addParameter(stat, alias, value);\r
+ return;\r
+ }\r
Token token = Db.getToken(value);\r
if (token != null) {\r
token.appendSQL(stat, this);\r
return new QueryWhere<T>(query);
}
+ /*
+ * These method allows you to generate "x=?", "x!=?", etc where conditions.
+ * Parameter substitution must be done manually later with db.executeQuery.
+ * This allows for building re-usable SQL string statements from your model
+ * classes.
+ */
+ public QueryWhere<T> isParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EQUAL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> isNotParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.NOT_EQUAL));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> exceedsParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.EXCEEDS));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> lessThanParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LESS_THAN));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> atMostParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.AT_MOST));
+ return new QueryWhere<T>(query);
+ }
+
+ public QueryWhere<T> likeParameter() {
+ query.addConditionToken(new RuntimeParameter<A>(x, CompareType.LIKE));
+ return new QueryWhere<T>(query);
+ }
}
return addPrimitive(ConditionAndOr.AND, x);\r
}\r
\r
- private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) { \r
+ private <A> QueryCondition<T, A> addPrimitive(ConditionAndOr condition, A x) {\r
query.addConditionToken(condition);\r
A alias = query.getPrimitiveAliasByValue(x);\r
if (alias == null) {\r
return this;\r
}\r
\r
- public <X, Z> List<X> select(Z x) {\r
- return query.select(x);\r
- }\r
-\r
public String getSQL() {\r
SQLStatement stat = new SQLStatement(query.getDb());\r
stat.appendSQL("SELECT *");\r
return stat.getSQL().trim();\r
}\r
\r
+ /**\r
+ * toSQL returns a static string version of the query with runtime variables\r
+ * properly encoded. This method is also useful when combined with the where\r
+ * clause methods like isParameter() or atLeastParameter() which allows\r
+ * iciql to generate re-usable parameterized string statements.\r
+ * \r
+ * @return the sql query as plain text\r
+ */\r
+ public String toSQL() {\r
+ return this.toSQL(false);\r
+ }\r
+\r
+ /**\r
+ * toSQL returns a static string version of the query with runtime variables\r
+ * properly encoded. This method is also useful when combined with the where\r
+ * clause methods like isParameter() or atLeastParameter() which allows\r
+ * iciql to generate re-usable parameterized string statements.\r
+ * \r
+ * @param distinct\r
+ * if true SELECT DISTINCT is used for the query\r
+ * @return the sql query as plain text\r
+ */\r
+ public String toSQL(boolean distinct) {\r
+ SQLStatement stat = new SQLStatement(query.getDb());\r
+ if (distinct) {\r
+ stat.appendSQL("SELECT DISTINCT *");\r
+ } else {\r
+ stat.appendSQL("SELECT *");\r
+ }\r
+ query.appendFromWhere(stat);\r
+ return stat.toSQL().trim();\r
+ }\r
+\r
+ public <X, Z> List<X> select(Z x) {\r
+ return query.select(x);\r
+ }\r
+\r
public <X, Z> List<X> selectDistinct(Z x) {\r
return query.selectDistinct(x);\r
}\r
--- /dev/null
+/*\r
+ * Copyright 2012 James Moger.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package com.iciql;\r
+\r
+/**\r
+ * A runtime parameter is used to generate x=? conditions so that iciql can\r
+ * build re-usable dynamic queries with parameter substitution done manually at\r
+ * runtime.\r
+ * \r
+ * @param <A>\r
+ * the operand type\r
+ */\r
+\r
+class RuntimeParameter<A> implements Token {\r
+ \r
+ public final static String PARAMETER = "";\r
+ \r
+ A x;\r
+ CompareType compareType;\r
+\r
+ RuntimeParameter(A x, CompareType type) {\r
+ this.x = x;\r
+ this.compareType = type;\r
+ }\r
+\r
+ public <T> void appendSQL(SQLStatement stat, Query<T> query) {\r
+ query.appendSQL(stat, null, x);\r
+ stat.appendSQL(" ");\r
+ stat.appendSQL(compareType.getString());\r
+ if (compareType.hasRightExpression()) {\r
+ stat.appendSQL(" ");\r
+ query.appendSQL(stat, x, PARAMETER);\r
+ }\r
+ }\r
+}\r
* @return preferred DATETIME class
*/
Class<? extends java.util.Date> getDateTimeClass();
+
+ /**
+ * When building static string statements this method flattens an object to
+ * a string representation suitable for a static string statement.
+ *
+ * @param o
+ * @return the string equivalent of this object
+ */
+ String prepareParameter(Object o);
}
import java.sql.DatabaseMetaData;\r
import java.sql.SQLException;\r
import java.text.MessageFormat;\r
+import java.text.SimpleDateFormat;\r
\r
import com.iciql.TableDefinition.FieldDefinition;\r
import com.iciql.TableDefinition.IndexDefinition;\r
* Default implementation of an SQL dialect.\r
*/\r
public class SQLDialectDefault implements SQLDialect {\r
+ \r
+ final String LITERAL = "'";\r
+\r
float databaseVersion;\r
String databaseName;\r
String productVersion;\r
stat.appendSQL(" OFFSET " + offset);\r
}\r
}\r
+ \r
+ @Override\r
+ public String prepareParameter(Object o) {\r
+ if (o instanceof String) {\r
+ return LITERAL + o.toString().replace(LITERAL, "''") + LITERAL;\r
+ } else if (o instanceof Character) {\r
+ return LITERAL + o.toString() + LITERAL;\r
+ } else if (o instanceof java.sql.Time) {\r
+ return LITERAL + new SimpleDateFormat("HH:mm:ss").format(o) + LITERAL;\r
+ } else if (o instanceof java.sql.Date) {\r
+ return LITERAL + new SimpleDateFormat("yyyy-MM-dd").format(o) + LITERAL;\r
+ } else if (o instanceof java.util.Date) {\r
+ return LITERAL + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(o) + LITERAL;\r
+ }\r
+ return o.toString();\r
+ }\r
}
\ No newline at end of file
import java.sql.ResultSet;\r
import java.sql.SQLException;\r
import java.util.ArrayList;\r
+import java.util.StringTokenizer;\r
\r
import com.iciql.util.JdbcUtils;\r
\r
return appendSQL(db.getDialect().prepareColumnName(column));\r
}\r
\r
+ /**\r
+ * getSQL returns a simple string representation of the parameterized\r
+ * statement which will be used later, internally, with prepareStatement.\r
+ * \r
+ * @return a simple sql statement\r
+ */\r
String getSQL() {\r
if (sql == null) {\r
sql = buff.toString();\r
return sql;\r
}\r
\r
+ /**\r
+ * toSQL creates a static sql statement with the referenced parameters\r
+ * encoded in the statement.\r
+ * \r
+ * @return a complete sql statement\r
+ */\r
+ String toSQL() {\r
+ if (sql == null) {\r
+ sql = buff.toString();\r
+ }\r
+ if (params.size() == 0) {\r
+ return sql;\r
+ }\r
+ StringBuilder sb = new StringBuilder();\r
+ // TODO this needs to me more sophisticated\r
+ StringTokenizer st = new StringTokenizer(sql, "?", false);\r
+ int i = 0;\r
+ while (st.hasMoreTokens()) {\r
+ sb.append(st.nextToken());\r
+ if (i < params.size()) {\r
+ Object o = params.get(i);\r
+ if (RuntimeParameter.PARAMETER == o) {\r
+ // dynamic parameter\r
+ sb.append('?');\r
+ } else {\r
+ // static parameter\r
+ sb.append(db.getDialect().prepareParameter(o));\r
+ }\r
+ i++;\r
+ }\r
+ }\r
+ return sb.toString();\r
+ }\r
+\r
public SQLStatement addParameter(Object o) {\r
// Automatically convert java.util.Date to java.sql.Timestamp\r
// if the dialect requires java.sql.Timestamp objects (e.g. Derby)\r
\r
import java.sql.ResultSet;\r
import java.sql.SQLException;\r
+import java.text.SimpleDateFormat;\r
import java.util.List;\r
\r
+import org.junit.Assume;\r
import org.junit.Test;\r
\r
import com.iciql.Db;\r
+import com.iciql.test.models.EnumModels.Tree;\r
import com.iciql.test.models.Product;\r
+import com.iciql.test.models.StaticQueries;\r
import com.iciql.util.JdbcUtils;\r
\r
/**\r
*/\r
public class RuntimeQueryTest {\r
\r
+ @Test\r
+ public void testParameters() {\r
+ Db db = IciqlSuite.openNewDb();\r
+ \r
+ // do not test non-H2 databases because dialects will get in the way\r
+ // e.g. column quoting, etc\r
+ Assume.assumeTrue(IciqlSuite.isH2(db));\r
+\r
+ Product p = new Product();\r
+ String q1 = db.from(p).where(p.unitsInStock).isParameter().and(p.productName).likeParameter().orderBy(p.productId).toSQL();\r
+ String q2 = db.from(p).where(p.unitsInStock).lessThan(100).and(p.productName).like("test").or(p.productName).likeParameter().orderBy(p.productId).toSQL();\r
+ \r
+ StaticQueries.StaticModel1 m1 = new StaticQueries.StaticModel1();\r
+ String q3 = db.from(m1).where(m1.myTree).is(Tree.MAPLE).and(m1.myTree).isParameter().toSQL();\r
+ \r
+ StaticQueries.StaticModel2 m2 = new StaticQueries.StaticModel2();\r
+ String q4 = db.from(m2).where(m2.myTree).is(Tree.MAPLE).and(m2.myTree).isParameter().toSQL();\r
+\r
+ StaticQueries.StaticModel3 m3 = new StaticQueries.StaticModel3();\r
+ String q5 = db.from(m3).where(m3.myTree).is(Tree.MAPLE).and(m3.myTree).isParameter().toSQL();\r
+\r
+ long now = System.currentTimeMillis();\r
+ java.sql.Date aDate = new java.sql.Date(now);\r
+ java.sql.Time aTime = new java.sql.Time(now);\r
+ java.sql.Timestamp aTimestamp = new java.sql.Timestamp(now);\r
+ \r
+ String q6 = db.from(m1).where(m1.myDate).is(aDate).and(m1.myDate).isParameter().toSQL();\r
+ String q7 = db.from(m1).where(m1.myTime).is(aTime).and(m1.myTime).isParameter().toSQL();\r
+ String q8 = db.from(m1).where(m1.myTimestamp).is(aTimestamp).and(m1.myTimestamp).isParameter().toSQL();\r
+\r
+ db.close();\r
+ assertEquals("SELECT * FROM Product WHERE unitsInStock = ? AND productName LIKE ? ORDER BY productId", q1);\r
+ assertEquals("SELECT * FROM Product WHERE unitsInStock < 100 AND productName LIKE 'test' OR productName LIKE ? ORDER BY productId", q2);\r
+ \r
+ assertEquals("SELECT * FROM StaticQueryTest1 WHERE myTree = 'MAPLE' AND myTree = ?", q3);\r
+ assertEquals("SELECT * FROM StaticQueryTest2 WHERE myTree = 50 AND myTree = ?", q4);\r
+ assertEquals("SELECT * FROM StaticQueryTest3 WHERE myTree = 4 AND myTree = ?", q5);\r
+\r
+ java.util.Date refDate = new java.util.Date(now);\r
+ assertEquals("SELECT * FROM StaticQueryTest1 WHERE myDate = '" + new SimpleDateFormat("yyyy-MM-dd").format(refDate) + "' AND myDate = ?", q6);\r
+ assertEquals("SELECT * FROM StaticQueryTest1 WHERE myTime = '" + new SimpleDateFormat("HH:mm:ss").format(refDate) + "' AND myTime = ?", q7);\r
+ assertEquals("SELECT * FROM StaticQueryTest1 WHERE myTimestamp = '" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(refDate) + "' AND myTimestamp = ?", q8);\r
+ }\r
+ \r
@Test\r
public void testRuntimeQuery() {\r
Db db = IciqlSuite.openNewDb();\r
--- /dev/null
+/*\r
+ * Copyright 2011 James Moger.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.iciql.test.models;\r
+\r
+import java.sql.Timestamp;\r
+\r
+import com.iciql.Iciql.EnumType;\r
+import com.iciql.Iciql.IQColumn;\r
+import com.iciql.Iciql.IQEnum;\r
+import com.iciql.Iciql.IQTable;\r
+import com.iciql.test.models.EnumModels.Tree;\r
+\r
+/**\r
+ * Static query models.\r
+ */\r
+public class StaticQueries {\r
+\r
+ @IQTable(name = "StaticQueryTest1")\r
+ public static class StaticModel1 {\r
+\r
+ @IQColumn(primaryKey = true, autoIncrement = true)\r
+ public Integer id;\r
+\r
+ @IQColumn\r
+ @IQEnum(EnumType.NAME)\r
+ public Tree myTree;\r
+\r
+ @IQColumn\r
+ public String myString;\r
+\r
+ @IQColumn\r
+ public Boolean myBool;\r
+\r
+ @IQColumn\r
+ public Timestamp myTimestamp;\r
+\r
+ @IQColumn\r
+ public java.sql.Date myDate;\r
+\r
+ @IQColumn\r
+ public java.sql.Time myTime;\r
+\r
+ public StaticModel1() {\r
+ }\r
+ }\r
+\r
+ @IQTable(name = "StaticQueryTest2")\r
+ public static class StaticModel2 {\r
+\r
+ @IQColumn(primaryKey = true, autoIncrement = true)\r
+ public Integer id;\r
+\r
+ @IQColumn\r
+ @IQEnum(EnumType.ENUMID)\r
+ public Tree myTree;\r
+\r
+ public StaticModel2() {\r
+ }\r
+ }\r
+\r
+ @IQTable(name = "StaticQueryTest3")\r
+ public static class StaticModel3 {\r
+\r
+ @IQColumn(primaryKey = true, autoIncrement = true)\r
+ public Integer id;\r
+\r
+ @IQColumn\r
+ @IQEnum(EnumType.ORDINAL)\r
+ public Tree myTree;\r
+\r
+ public StaticModel3() {\r
+ }\r
+ }\r
+}\r