diff --git a/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java b/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java index aff126e512..c0b532e057 100644 --- a/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java +++ b/jOOQ-test/src/org/jooq/test/_/testcases/PlainSQLTests.java @@ -42,6 +42,8 @@ import static junit.framework.Assert.assertTrue; import static org.jooq.impl.Factory.field; import static org.jooq.impl.Factory.fieldByName; import static org.jooq.impl.Factory.function; +import static org.jooq.impl.Factory.inline; +import static org.jooq.impl.Factory.name; import static org.jooq.impl.Factory.param; import static org.jooq.impl.Factory.table; import static org.jooq.impl.Factory.tableByName; @@ -56,6 +58,7 @@ import org.jooq.Condition; import org.jooq.Cursor; import org.jooq.Field; import org.jooq.FutureResult; +import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.RecordHandler; import org.jooq.RenderContext; @@ -182,7 +185,7 @@ extends BaseTest result = create().fetch(sql, parts); + Cursor cursor = create().fetchLazy(sql, parts); + List> many = create().fetchMany(sql, parts); + + assertEquals(author, record); + assertEquals(author, result.get(0)); + assertEquals(author, cursor.fetchOne()); + assertEquals(author, many.get(0).get(0)); + } + @Test public void testPlainSQLResultQuery() throws Exception { String sql = create().select(param("p", String.class).as("p")).getSQL(false); diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index d3814e5a12..cfaaff4d94 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -820,6 +820,11 @@ public abstract class jOOQAbstractTest< new PlainSQLTests(this).testPlainSQLCRUD(); } + @Test + public void testPlainSQLWithQueryParts() throws Exception { + new PlainSQLTests(this).testPlainSQLWithQueryParts(); + } + @Test public void testPlainSQLResultQuery() throws Exception { new PlainSQLTests(this).testPlainSQLResultQuery(); diff --git a/jOOQ/src/main/java/org/jooq/FactoryOperations.java b/jOOQ/src/main/java/org/jooq/FactoryOperations.java index 82e44bf625..45d46ba7e6 100644 --- a/jOOQ/src/main/java/org/jooq/FactoryOperations.java +++ b/jOOQ/src/main/java/org/jooq/FactoryOperations.java @@ -160,7 +160,7 @@ public interface FactoryOperations extends Configuration { Query query(String sql); /** - * Create a new query holding plain SQL. There must be as many binding + * Create a new query holding plain SQL. There must be as many bind * variables contained in the SQL, as passed in the bindings parameter *

* Example: @@ -180,6 +180,35 @@ public interface FactoryOperations extends Configuration { @Support Query query(String sql, Object... bindings); + /** + * Create a new query holding plain SQL. + *

+ * Unlike {@link #query(String, Object...)}, the SQL passed to this method + * should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * query("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will render this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return A query wrapping the plain SQL + */ + @Support + Query query(String sql, QueryPart... parts); + // ------------------------------------------------------------------------- // XXX JDBC convenience methods // ------------------------------------------------------------------------- @@ -839,8 +868,8 @@ public interface FactoryOperations extends Configuration { * @param table The table holding records of type <R> * @param source The source to be used to fill the new record * @return The new record - * @throws MappingException wrapping any reflection or data type conversion exception that might - * have occurred while mapping records + * @throws MappingException wrapping any reflection or data type conversion + * exception that might have occurred while mapping records * @see Record#from(Object) * @see Record#into(Class) */ @@ -1015,7 +1044,7 @@ public interface FactoryOperations extends Configuration { Result fetch(String sql) throws DataAccessException; /** - * Execute a new query holding plain SQL. There must be as many binding + * Execute a new query holding plain SQL. There must be as many bind * variables contained in the SQL, as passed in the bindings parameter *

* Example (Postgres): @@ -1042,6 +1071,36 @@ public interface FactoryOperations extends Configuration { @Support Result fetch(String sql, Object... bindings) throws DataAccessException; + /** + * Execute a new query holding plain SQL. + *

+ * Unlike {@link #fetch(String, Object...)}, the SQL passed to this method + * should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * fetch("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will execute this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return The results from the executed query + * @throws DataAccessException if something went wrong executing the query + */ + @Support + Result fetch(String sql, QueryPart... parts) throws DataAccessException; + /** * Execute a new query holding plain SQL and "lazily" return the generated * result. @@ -1076,7 +1135,43 @@ public interface FactoryOperations extends Configuration { /** * Execute a new query holding plain SQL and "lazily" return the generated - * result. There must be as many binding variables contained in the SQL, as + * result. + *

+ * The returned {@link Cursor} holds a reference to the executed + * {@link PreparedStatement} and the associated {@link ResultSet}. Data can + * be fetched (or iterated over) lazily, fetching records from the + * {@link ResultSet} one by one. + *

+ * Unlike {@link #fetchLazy(String, Object...)}, the SQL passed to this + * method should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * fetchLazy("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will execute this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return The results from the executed query + * @throws DataAccessException if something went wrong executing the query + */ + @Support + Cursor fetchLazy(String sql, QueryPart... parts) throws DataAccessException; + + /** + * Execute a new query holding plain SQL and "lazily" return the generated + * result. There must be as many bind variables contained in the SQL, as * passed in the bindings parameter *

* The returned {@link Cursor} holds a reference to the executed @@ -1133,7 +1228,7 @@ public interface FactoryOperations extends Configuration { /** * Execute a new query holding plain SQL, possibly returning several result - * sets. There must be as many binding variables contained in the SQL, as + * sets. There must be as many bind variables contained in the SQL, as * passed in the bindings parameter *

* Example (Sybase ASE): @@ -1156,6 +1251,37 @@ public interface FactoryOperations extends Configuration { @Support List> fetchMany(String sql, Object... bindings) throws DataAccessException; + /** + * Execute a new query holding plain SQL, possibly returning several result + * sets. + *

+ * Unlike {@link #fetchMany(String, Object...)}, the SQL passed to this + * method should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * fetchMany("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will execute this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return The results from the executed query + * @throws DataAccessException if something went wrong executing the query + */ + @Support + List> fetchMany(String sql, QueryPart... parts) throws DataAccessException; + /** * Execute a new query holding plain SQL. *

@@ -1183,7 +1309,7 @@ public interface FactoryOperations extends Configuration { Record fetchOne(String sql) throws DataAccessException; /** - * Execute a new query holding plain SQL. There must be as many binding + * Execute a new query holding plain SQL. There must be as many bind * variables contained in the SQL, as passed in the bindings parameter *

* Example (Postgres): @@ -1202,14 +1328,44 @@ public interface FactoryOperations extends Configuration { * * @param sql The SQL * @param bindings The bindings - * @return The results from the executed query. This is never - * null, even if the database returns no - * {@link ResultSet} + * @return The results from the executed query. This may be + * null if the database returned no records * @throws DataAccessException if something went wrong executing the query */ @Support Record fetchOne(String sql, Object... bindings) throws DataAccessException; + /** + * Execute a new query holding plain SQL. + *

+ * Unlike {@link #fetchOne(String, Object...)}, the SQL passed to this + * method should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * fetchOne("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will execute this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return The results from the executed query. This may be + * null if the database returned no records + * @throws DataAccessException if something went wrong executing the query + */ + @Support + Record fetchOne(String sql, QueryPart... parts) throws DataAccessException; + /** * Execute a query holding plain SQL. *

@@ -1226,10 +1382,9 @@ public interface FactoryOperations extends Configuration { int execute(String sql) throws DataAccessException; /** - * Execute a new query holding plain SQL. There must be as many binding + * Execute a new query holding plain SQL. There must be as many bind * variables contained in the SQL, as passed in the bindings parameter *

- *

* NOTE: When inserting plain SQL into jOOQ objects, you must * guarantee syntax integrity. You may also create the possibility of * malicious SQL injection. Be sure to properly use bind variables and/or @@ -1243,6 +1398,36 @@ public interface FactoryOperations extends Configuration { @Support int execute(String sql, Object... bindings) throws DataAccessException; + /** + * Execute a new query holding plain SQL. + *

+ * Unlike {@link #execute(String, Object...)}, the SQL passed to this method + * should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * execute("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will execute this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return The results from the executed query + * @throws DataAccessException if something went wrong executing the query + */ + @Support + int execute(String sql, QueryPart... parts) throws DataAccessException; + /** * Create a new query holding plain SQL. There must not be any binding * variables contained in the SQL @@ -1292,7 +1477,7 @@ public interface FactoryOperations extends Configuration { ResultQuery resultQuery(String sql) throws DataAccessException; /** - * Create a new query holding plain SQL. There must be as many binding + * Create a new query holding plain SQL. There must be as many bind * variables contained in the SQL, as passed in the bindings parameter *

* Use this method, when you want to take advantage of the many ways to @@ -1334,9 +1519,37 @@ public interface FactoryOperations extends Configuration { * * @param sql The SQL * @param bindings The bindings - * @return An executable query - * @throws DataAccessException if something went wrong executing the query + * @return A query wrapping the plain SQL */ @Support - ResultQuery resultQuery(String sql, Object... bindings) throws DataAccessException; + ResultQuery resultQuery(String sql, Object... bindings); + + /** + * Create a new query holding plain SQL. + *

+ * Unlike {@link #resultQuery(String, Object...)}, the SQL passed to this + * method should not contain any bind variables. Instead, you can pass + * {@link QueryPart} objects to the method which will be rendered at indexed + * locations of your SQL string as such:

+     * // The following query
+     * resultQuery("select {0}, {1} from {2}", val(1), inline("test"), name("DUAL"));
+     *
+     * // Will render this SQL on an Oracle database with RenderNameStyle.QUOTED:
+     * select ?, 'test' from "DUAL"
+     * 
+ *

+ * NOTE: When inserting plain SQL into jOOQ objects, you must + * guarantee syntax integrity. You may also create the possibility of + * malicious SQL injection. Be sure to properly use bind variables and/or + * escape literals when concatenated into SQL clauses! One way to escape + * literals is to use {@link Factory#name(String...)} and similar methods + * + * @param sql The SQL clause, containing {numbered placeholders} where query + * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations + * @return A query wrapping the plain SQL + */ + @Support + ResultQuery resultQuery(String sql, QueryPart... parts); } diff --git a/jOOQ/src/main/java/org/jooq/impl/BindingProvider.java b/jOOQ/src/main/java/org/jooq/impl/BindingProvider.java deleted file mode 100644 index 23450e4c76..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/BindingProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2009-2012, Lukas Eder, lukas.eder@gmail.com - * All rights reserved. - * - * This software is licensed to you under the Apache License, Version 2.0 - * (the "License"); You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * . Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * . Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * . Neither the name "jOOQ" nor the names of its contributors may be - * used to endorse or promote products derived from this software without - * specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package org.jooq.impl; - -import java.util.List; - -import org.jooq.Param; -import org.jooq.QueryPart; -import org.jooq.QueryPartInternal; - -/** - * An internal interface implemented by those {@link QueryPart} types that - * contain bind values (bound as {@link Param}) - *

- * This interface is for JOOQ INTERNAL USE only. Do not reference directly - * - * @author Lukas Eder - */ -interface BindingProvider extends QueryPartInternal { - - /** - * Get the list of bind values (bound as {@link Param}), contained in this - * {@link QueryPart} - */ - List> getBindings(); -} diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index 8a9535b653..ea41c8709e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -797,17 +797,19 @@ public class Factory implements FactoryOperations { * *

* The above MySQL function can be expressed as such:

-     * clause("GROUP_CONCAT(DISTINCT {0} ORDER BY {1} ASC DEPARATOR '-')", expr1, expr2);
+     * field("GROUP_CONCAT(DISTINCT {0} ORDER BY {1} ASC DEPARATOR '-')", expr1, expr2);
      * 
*

* NOTE: When inserting plain SQL into jOOQ objects, you must * guarantee syntax integrity. You may also create the possibility of * malicious SQL injection. Be sure to properly use bind variables and/or * escape literals when concatenated into SQL clauses! One way to escape - * literals is to use {@link #fieldByName(String...)} and similar methods + * literals is to use {@link #name(String...)} and similar methods * * @param sql The SQL clause, containing {numbered placeholders} where query * parts can be injected + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations * @return A field wrapping the plain SQL */ public static Field field(String sql, QueryPart... parts) { @@ -828,18 +830,20 @@ public class Factory implements FactoryOperations { * *

* The above MySQL function can be expressed as such:

-     * clause("GROUP_CONCAT(DISTINCT {0} ORDER BY {1} ASC DEPARATOR '-')", expr1, expr2);
+     * field("GROUP_CONCAT(DISTINCT {0} ORDER BY {1} ASC DEPARATOR '-')", expr1, expr2);
      * 
*

* NOTE: When inserting plain SQL into jOOQ objects, you must * guarantee syntax integrity. You may also create the possibility of * malicious SQL injection. Be sure to properly use bind variables and/or * escape literals when concatenated into SQL clauses! One way to escape - * literals is to use {@link #fieldByName(String...)} and similar methods + * literals is to use {@link #name(String...)} and similar methods * * @param sql The SQL clause, containing {numbered placeholders} where query * parts can be injected * @param type The field type + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations * @return A field wrapping the plain SQL */ public static Field field(String sql, Class type, QueryPart... parts) { @@ -860,18 +864,20 @@ public class Factory implements FactoryOperations { * *

* The above MySQL function can be expressed as such:

-     * clause("GROUP_CONCAT(DISTINCT {0} ORDER BY {1} ASC DEPARATOR '-')", expr1, expr2);
+     * field("GROUP_CONCAT(DISTINCT {0} ORDER BY {1} ASC DEPARATOR '-')", expr1, expr2);
      * 
*

* NOTE: When inserting plain SQL into jOOQ objects, you must * guarantee syntax integrity. You may also create the possibility of * malicious SQL injection. Be sure to properly use bind variables and/or * escape literals when concatenated into SQL clauses! One way to escape - * literals is to use {@link #fieldByName(String...)} and similar methods + * literals is to use {@link #name(String...)} and similar methods * * @param sql The SQL clause, containing {numbered placeholders} where query * parts can be injected * @param type The field type + * @param parts The {@link QueryPart} objects that are rendered at the + * {numbered placeholder} locations * @return A field wrapping the plain SQL */ public static Field field(String sql, DataType type, QueryPart... parts) { @@ -1244,7 +1250,15 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final Result fetch(String sql) { + public final Query query(String sql, QueryPart... parts) { + return new SQLQuery(this, sql, parts); + } + + /** + * {@inheritDoc} + */ + @Override + public final Result fetch(String sql) throws DataAccessException { return resultQuery(sql).fetch(); } @@ -1252,10 +1266,18 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final Result fetch(String sql, Object... bindings) { + public final Result fetch(String sql, Object... bindings) throws DataAccessException { return resultQuery(sql, bindings).fetch(); } + /** + * {@inheritDoc} + */ + @Override + public final Result fetch(String sql, QueryPart... parts) throws DataAccessException { + return resultQuery(sql, parts).fetch(); + } + /** * {@inheritDoc} */ @@ -1276,7 +1298,15 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final List> fetchMany(String sql) { + public final Cursor fetchLazy(String sql, QueryPart... parts) throws DataAccessException { + return resultQuery(sql, parts).fetchLazy(); + } + + /** + * {@inheritDoc} + */ + @Override + public final List> fetchMany(String sql) throws DataAccessException { return resultQuery(sql).fetchMany(); } @@ -1284,7 +1314,7 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final List> fetchMany(String sql, Object... bindings) { + public final List> fetchMany(String sql, Object... bindings) throws DataAccessException { return resultQuery(sql, bindings).fetchMany(); } @@ -1292,7 +1322,15 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final Record fetchOne(String sql) { + public final List> fetchMany(String sql, QueryPart... parts) throws DataAccessException { + return resultQuery(sql, parts).fetchMany(); + } + + /** + * {@inheritDoc} + */ + @Override + public final Record fetchOne(String sql) throws DataAccessException { return resultQuery(sql).fetchOne(); } @@ -1300,10 +1338,18 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final Record fetchOne(String sql, Object... bindings) { + public final Record fetchOne(String sql, Object... bindings) throws DataAccessException { return resultQuery(sql, bindings).fetchOne(); } + /** + * {@inheritDoc} + */ + @Override + public final Record fetchOne(String sql, QueryPart... parts) throws DataAccessException { + return resultQuery(sql, parts).fetchOne(); + } + /** * {@inheritDoc} */ @@ -1324,7 +1370,15 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final ResultQuery resultQuery(String sql) throws DataAccessException { + public final int execute(String sql, QueryPart... parts) throws DataAccessException { + return query(sql, parts).execute(); + } + + /** + * {@inheritDoc} + */ + @Override + public final ResultQuery resultQuery(String sql) { return resultQuery(sql, new Object[0]); } @@ -1332,10 +1386,18 @@ public class Factory implements FactoryOperations { * {@inheritDoc} */ @Override - public final ResultQuery resultQuery(String sql, Object... bindings) throws DataAccessException { + public final ResultQuery resultQuery(String sql, Object... bindings) { return new SQLResultQuery(this, sql, bindings); } + /** + * {@inheritDoc} + */ + @Override + public final ResultQuery resultQuery(String sql, QueryPart... parts) { + return new SQLResultQuery(this, sql, parts); + } + // ------------------------------------------------------------------------- // XXX JDBC convenience methods // ------------------------------------------------------------------------- @@ -2894,26 +2956,44 @@ public class Factory implements FactoryOperations { // [#470] TRUNC(datetime) will be implemented in a future release // ------------------------------------------------------------------------- + /** + * This is not yet implemented + */ static Field trunc(Date date) { return trunc(date, DatePart.DAY); } + /** + * This is not yet implemented + */ static Field trunc(Date date, DatePart part) { return trunc(val(date), part); } + /** + * This is not yet implemented + */ static Field trunc(Timestamp timestamp) { return trunc(timestamp, DatePart.DAY); } + /** + * This is not yet implemented + */ static Field trunc(Timestamp timestamp, DatePart part) { return trunc(val(timestamp), part); } + /** + * This is not yet implemented + */ static Field trunc(Field date) { return trunc(date, DatePart.DAY); } + /** + * This is not yet implemented + */ static Field trunc(Field date, DatePart part) { throw new UnsupportedOperationException("This is not yet implemented"); } diff --git a/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java b/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java index 8a3d098dcc..8bfefec7e4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java +++ b/jOOQ/src/main/java/org/jooq/impl/FactoryProxy.java @@ -229,6 +229,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().query(sql, bindings); } + @Override + public final Query query(String sql, QueryPart... parts) { + return getDelegate().query(sql, parts); + } + @Override public final SimpleSelectWhereStep selectFrom(Table table) { return getDelegate().selectFrom(table); @@ -390,6 +395,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().fetch(sql, bindings); } + @Override + public final Result fetch(String sql, QueryPart... parts) { + return getDelegate().fetch(sql, parts); + } + @Override public final Cursor fetchLazy(String sql) throws DataAccessException { return getDelegate().fetchLazy(sql); @@ -400,6 +410,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().fetchLazy(sql, bindings); } + @Override + public final Cursor fetchLazy(String sql, QueryPart... parts) throws DataAccessException { + return getDelegate().fetchLazy(sql, parts); + } + @Override public final List> fetchMany(String sql) { return getDelegate().fetchMany(sql); @@ -410,6 +425,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().fetchMany(sql, bindings); } + @Override + public final List> fetchMany(String sql, QueryPart... parts) { + return getDelegate().fetchMany(sql, parts); + } + @Override public final Record fetchOne(String sql) { return getDelegate().fetchOne(sql); @@ -420,6 +440,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().fetchOne(sql, bindings); } + @Override + public final Record fetchOne(String sql, QueryPart... parts) { + return getDelegate().fetchOne(sql, parts); + } + @Override public final int execute(String sql) throws DataAccessException { return getDelegate().execute(sql); @@ -430,6 +455,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().execute(sql, bindings); } + @Override + public final int execute(String sql, QueryPart... parts) throws DataAccessException { + return getDelegate().execute(sql, parts); + } + @Override public final ResultQuery resultQuery(String sql) throws DataAccessException { return getDelegate().resultQuery(sql); @@ -440,6 +470,11 @@ public final class FactoryProxy implements FactoryOperations { return getDelegate().resultQuery(sql, bindings); } + @Override + public final ResultQuery resultQuery(String sql, QueryPart... parts) { + return getDelegate().resultQuery(sql, parts); + } + @Override public final BigInteger lastID() { return getDelegate().lastID(); diff --git a/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java b/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java index a3b74677e3..079a7537e1 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java @@ -73,18 +73,15 @@ class ParamCollector extends AbstractBindContext { @Override protected final void bindInternal(QueryPartInternal internal) { - if (internal instanceof BindingProvider) { - BindingProvider provider = (BindingProvider) internal; + if (internal instanceof Param) { + Param param = (Param) internal; + String i = String.valueOf(nextIndex()); - for (Param param : provider.getBindings()) { - String i = String.valueOf(nextIndex()); - - if (StringUtils.isBlank(param.getParamName())) { - result.put(i, param); - } - else { - result.put(param.getParamName(), param); - } + if (StringUtils.isBlank(param.getParamName())) { + result.put(i, param); + } + else { + result.put(param.getParamName(), param); } } else { diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLCondition.java b/jOOQ/src/main/java/org/jooq/impl/SQLCondition.java index 7d33488b4d..7794e7849b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLCondition.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLCondition.java @@ -40,26 +40,26 @@ import java.util.List; import org.jooq.Attachable; import org.jooq.BindContext; -import org.jooq.Param; +import org.jooq.QueryPart; import org.jooq.RenderContext; -class SQLCondition extends AbstractCondition implements BindingProvider { +class SQLCondition extends AbstractCondition { /** * Generated UID */ - private static final long serialVersionUID = -7661748411414898501L; + private static final long serialVersionUID = -7661748411414898501L; - private final String sql; - private final List> bindings; + private final String sql; + private final List substitutes; - SQLCondition(String sql, Object[] bindings) { + SQLCondition(String sql, Object[] substitutes) { this.sql = sql; - this.bindings = Util.bindings(bindings); + this.substitutes = Util.queryParts(substitutes); } // ------------------------------------------------------------------------ - // Condition API + // QueryPart API // ------------------------------------------------------------------------ @Override @@ -72,20 +72,11 @@ class SQLCondition extends AbstractCondition implements BindingProvider { // We have no control over the plain SQL content, hence we MUST put it // in parentheses to ensure correct semantics - Util.toSQLReferenceWithParentheses(context, sql, bindings); + Util.toSQLReferenceWithParentheses(context, sql, substitutes); } @Override public final void bind(BindContext context) { - context.bind(bindings); - } - - // ------------------------------------------------------------------------ - // QueryPart API - // ------------------------------------------------------------------------ - - @Override - public final List> getBindings() { - return bindings; + context.bind(substitutes); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLField.java b/jOOQ/src/main/java/org/jooq/impl/SQLField.java index 6c8cc22f0d..fd5141f5d4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLField.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLField.java @@ -41,24 +41,24 @@ import java.util.List; import org.jooq.Attachable; import org.jooq.BindContext; import org.jooq.DataType; -import org.jooq.Param; +import org.jooq.QueryPart; import org.jooq.RenderContext; -class SQLField extends AbstractField implements BindingProvider { +class SQLField extends AbstractField { /** * Generated UID */ - private static final long serialVersionUID = 6937002867156868761L; + private static final long serialVersionUID = 6937002867156868761L; - private final String sql; - private final List> bindings; + private final String sql; + private final List substitutes; - SQLField(String sql, DataType type, Object[] bindings) { + SQLField(String sql, DataType type, Object[] substitutes) { super(sql, type); this.sql = sql; - this.bindings = Util.bindings(bindings); + this.substitutes = Util.queryParts(substitutes); } // ------------------------------------------------------------------------ @@ -72,25 +72,16 @@ class SQLField extends AbstractField implements BindingProvider { @Override public final void toSQL(RenderContext context) { - Util.toSQLReference(context, sql, bindings); + Util.toSQLReference(context, sql, substitutes); } @Override public final void bind(BindContext context) { - context.bind(bindings); + context.bind(substitutes); } @Override public final boolean isNullLiteral() { return "null".equalsIgnoreCase(("" + sql).trim()); } - - // ------------------------------------------------------------------------ - // QueryPart API - // ------------------------------------------------------------------------ - - @Override - public final List> getBindings() { - return bindings; - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLQuery.java b/jOOQ/src/main/java/org/jooq/impl/SQLQuery.java index 1f3485a157..0609114181 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLQuery.java @@ -41,27 +41,27 @@ import java.util.List; import org.jooq.Attachable; import org.jooq.BindContext; import org.jooq.Configuration; -import org.jooq.Param; +import org.jooq.QueryPart; import org.jooq.RenderContext; /** * @author Lukas Eder */ -class SQLQuery extends AbstractQuery implements BindingProvider { +class SQLQuery extends AbstractQuery { /** * Generated UID */ - private static final long serialVersionUID = 1740879770879469220L; + private static final long serialVersionUID = 1740879770879469220L; - private final String sql; - private final List> bindings; + private final String sql; + private final List substitutes; - public SQLQuery(Configuration configuration, String sql, Object[] bindings) { + public SQLQuery(Configuration configuration, String sql, Object[] substitutes) { super(configuration); this.sql = sql; - this.bindings = Util.bindings(bindings); + this.substitutes = Util.queryParts(substitutes); } // ------------------------------------------------------------------------ @@ -70,25 +70,16 @@ class SQLQuery extends AbstractQuery implements BindingProvider { @Override public final void toSQL(RenderContext context) { - Util.toSQLReference(context, sql, bindings); + Util.toSQLReference(context, sql, substitutes); } @Override public final void bind(BindContext context) { - context.bind(bindings); + context.bind(substitutes); } @Override public final List getAttachables() { return Collections.emptyList(); } - - // ------------------------------------------------------------------------ - // QueryPart API - // ------------------------------------------------------------------------ - - @Override - public final List> getBindings() { - return bindings; - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLResultQuery.java b/jOOQ/src/main/java/org/jooq/impl/SQLResultQuery.java index b6bcc7a746..f0253ee398 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLResultQuery.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLResultQuery.java @@ -43,7 +43,7 @@ import org.jooq.Attachable; import org.jooq.BindContext; import org.jooq.Configuration; import org.jooq.Field; -import org.jooq.Param; +import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.RenderContext; @@ -52,21 +52,21 @@ import org.jooq.RenderContext; * * @author Lukas Eder */ -class SQLResultQuery extends AbstractResultQuery implements BindingProvider { +class SQLResultQuery extends AbstractResultQuery { /** * Generated UID */ - private static final long serialVersionUID = 1740879770879469220L; + private static final long serialVersionUID = 1740879770879469220L; - private final String sql; - private final List> bindings; + private final String sql; + private final List substitutes; - public SQLResultQuery(Configuration configuration, String sql, Object[] bindings) { + public SQLResultQuery(Configuration configuration, String sql, Object[] substitutes) { super(configuration); this.sql = sql; - this.bindings = Util.bindings(bindings); + this.substitutes = Util.queryParts(substitutes); } // ------------------------------------------------------------------------ @@ -75,12 +75,12 @@ class SQLResultQuery extends AbstractResultQuery implements BindingProvi @Override public final void toSQL(RenderContext context) { - Util.toSQLReference(context, sql, bindings); + Util.toSQLReference(context, sql, substitutes); } @Override public final void bind(BindContext context) { - context.bind(bindings); + context.bind(substitutes); } @Override @@ -108,13 +108,4 @@ class SQLResultQuery extends AbstractResultQuery implements BindingProvi final boolean isForUpdate() { return false; } - - // ------------------------------------------------------------------------ - // QueryPart API - // ------------------------------------------------------------------------ - - @Override - public final List> getBindings() { - return bindings; - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/SQLTable.java b/jOOQ/src/main/java/org/jooq/impl/SQLTable.java index d21c30cb8a..bcc50d447d 100644 --- a/jOOQ/src/main/java/org/jooq/impl/SQLTable.java +++ b/jOOQ/src/main/java/org/jooq/impl/SQLTable.java @@ -40,7 +40,7 @@ import java.util.List; import org.jooq.Attachable; import org.jooq.BindContext; -import org.jooq.Param; +import org.jooq.QueryPart; import org.jooq.Record; import org.jooq.RenderContext; import org.jooq.Table; @@ -48,18 +48,18 @@ import org.jooq.Table; /** * @author Lukas Eder */ -class SQLTable extends AbstractTable implements BindingProvider { +class SQLTable extends AbstractTable { - private static final long serialVersionUID = -5122023013463718796L; + private static final long serialVersionUID = -5122023013463718796L; - private final String sql; - private final List> bindings; + private final String sql; + private final List substitutes; - public SQLTable(String sql, Object[] bindings) { + public SQLTable(String sql, Object[] substitutes) { super("sql"); this.sql = sql; - this.bindings = Util.bindings(bindings); + this.substitutes = Util.queryParts(substitutes); } // ------------------------------------------------------------------------ @@ -83,25 +83,16 @@ class SQLTable extends AbstractTable implements BindingProvider { @Override public final void toSQL(RenderContext context) { - Util.toSQLReference(context, sql, bindings); + Util.toSQLReference(context, sql, substitutes); } @Override public final void bind(BindContext context) { - context.bind(bindings); + context.bind(substitutes); } @Override protected final FieldList getFieldList() { return new FieldList(); } - - // ------------------------------------------------------------------------ - // QueryPart API - // ------------------------------------------------------------------------ - - @Override - public final List> getBindings() { - return bindings; - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Util.java b/jOOQ/src/main/java/org/jooq/impl/Util.java index b3cc244b8d..876e1a9b8e 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Util.java +++ b/jOOQ/src/main/java/org/jooq/impl/Util.java @@ -344,69 +344,96 @@ final class Util { /** * Create SQL */ - static final void toSQLReference(RenderContext context, String sql, List> bindings) { + static final void toSQLReference(RenderContext context, String sql, List substitutes) { + int bindIndex = 0; + char[] sqlChars = sql.toCharArray(); + boolean stringLiteral = false; - // Replace bind variables by their associated bind values - if (context.inline()) { + for (int i = 0; i < sqlChars.length; i++) { // [#1031] [#1032] Skip ? inside of string literals, e.g. // insert into x values ('Hello? Anybody out there?'); - int bindIndex = 0; - char[] sqlChars = sql.toCharArray(); - boolean stringLiteral = false; + if (sqlChars[i] == '\'') { - for (int i = 0; i < sqlChars.length; i++) { + // Delimiter is actually an escaping apostrophe + if (i + 1 < sqlChars.length && sqlChars[i + 1] == '\'') { - // String literal delimiter - if (sqlChars[i] == '\'') { - - // Delimiter is actually an escaping apostrophe - if (i + 1 < sqlChars.length && sqlChars[i + 1] == '\'') { - - // Skip subsequent character - context.sql(sqlChars[i++]); - context.sql(sqlChars[i]); - } - - else { - stringLiteral = !stringLiteral; - context.sql(sqlChars[i]); - } + // Skip subsequent character + context.sql(sqlChars[i++]); + context.sql(sqlChars[i]); } - // Replace bind variables only outside of string literals - else if (sqlChars[i] == '?' && !stringLiteral && bindIndex < bindings.size()) { - context.sql(bindings.get(bindIndex++)); - } - - // Any other character else { + stringLiteral = !stringLiteral; context.sql(sqlChars[i]); } } - } - // If not inlining, just append the plain SQL the way it is - else { - context.sql(sql); + // TODO: This case should be mutually exclusive with the next one + // Inline bind variables only outside of string literals + else if (sqlChars[i] == '?' && context.inline() && !stringLiteral && bindIndex < substitutes.size()) { + context.sql(substitutes.get(bindIndex++)); + } + + // TODO: This case should be mutually exclusive with the previous one + // [#1432] Inline substitues for {numbered placeholders} outside of string literals + else if (sqlChars[i] == '{' && !stringLiteral && bindIndex < substitutes.size()) { + + // Consume the whole token + int start = ++i; + for (; i < sqlChars.length && sqlChars[i] != '}'; i++); + int end = i; + + String token = sql.substring(start, end); + + // Be careful not to replace any JDBC escape syntax + if ("(fn|d|t|ts)\\b)[\\w\\s]+".matches(token)) { + context.sql(token); + } + + // Try getting the {numbered placeholder} + else { + try { + int substituteIndex = Integer.valueOf(token); + context.sql(substitutes.get(substituteIndex)); + } + + // If this failed, then we're dealing with a {keyword} + catch (NumberFormatException e) { + context.keyword(token); + } + } + } + + // Any other character + else { + context.sql(sqlChars[i]); + } } } /** - * Create {@link Param} objects from bind values + * Create {@link QueryPart} objects from bind values or substitutes */ - static final List> bindings(Object... bindings) { + static final List queryParts(Object... substitutes) { // [#724] When bindings is null, this is probably due to API-misuse // The user probably meant new Object[] { null } - if (bindings == null) { - return bindings(new Object[] { null }); + if (substitutes == null) { + return queryParts(new Object[] { null }); } else { - List> result = new ArrayList>(); + List result = new ArrayList(); - for (Object binding : bindings) { - Class type = binding != null ? binding.getClass() : Object.class; - result.add(new Val(binding, Factory.getDataType(type))); + for (Object substitute : substitutes) { + + // [#1432] Distinguish between QueryParts and other objects + if (substitute instanceof QueryPart) { + result.add((QueryPart) substitute); + } + else { + Class type = substitute != null ? substitute.getClass() : Object.class; + result.add(new Val(substitute, Factory.getDataType(type))); + } } return result; @@ -418,9 +445,9 @@ final class Util { * * @see #toSQLReference(RenderContext, String, List) */ - static final void toSQLReferenceWithParentheses(RenderContext context, String sql, List> bindings) { + static final void toSQLReferenceWithParentheses(RenderContext context, String sql, List substitutes) { context.sql("("); - toSQLReference(context, sql, bindings); + toSQLReference(context, sql, substitutes); context.sql(")"); } diff --git a/jOOQ/src/main/java/org/jooq/impl/Val.java b/jOOQ/src/main/java/org/jooq/impl/Val.java index 2df6c206cf..f097a7e5e9 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Val.java +++ b/jOOQ/src/main/java/org/jooq/impl/Val.java @@ -76,7 +76,7 @@ import org.jooq.types.Interval; /** * @author Lukas Eder */ -class Val extends AbstractField implements Param, BindingProvider { +class Val extends AbstractField implements Param { private static final long serialVersionUID = 6807729087019209084L; @@ -552,13 +552,4 @@ class Val extends AbstractField implements Param, BindingProvider { private final boolean isInline(RenderContext context) { return isInline() || context.inline(); } - - // ------------------------------------------------------------------------ - // XXX: BindingProvider API - // ------------------------------------------------------------------------ - - @Override - public final List> getBindings() { - return Arrays.>asList(this); - } }