From ea48c9dcd0cf06f16b8346dcd9ded90696216973 Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Sun, 11 Dec 2011 16:06:50 +0000 Subject: [PATCH] [#914] Allow for modifying bind values of existing QueryParts / Queries [#980] Add support for named parameters, to better interact with Spring --- .../org/jooq/util/spring/FactoryProxy.java | 5 + .../src/org/jooq/test/jOOQAbstractTest.java | 48 +- jOOQ/src/main/java/org/jooq/BindContext.java | 25 +- jOOQ/src/main/java/org/jooq/Context.java | 22 + .../main/java/org/jooq/FactoryOperations.java | 12 + jOOQ/src/main/java/org/jooq/Field.java | 1 + jOOQ/src/main/java/org/jooq/Param.java | 91 +++ jOOQ/src/main/java/org/jooq/Query.java | 33 +- .../main/java/org/jooq/QueryPartInternal.java | 32 +- .../src/main/java/org/jooq/RenderContext.java | 14 + .../org/jooq/impl/AbstractBindContext.java | 175 ++++++ .../java/org/jooq/impl/AbstractContext.java | 18 +- .../java/org/jooq/impl/AbstractQueryPart.java | 42 +- .../org/jooq/impl/BindValueCollector.java | 529 ------------------ .../src/main/java/org/jooq/impl/Constant.java | 151 ----- .../org/jooq/impl/DefaultBindContext.java | 105 +--- .../org/jooq/impl/DefaultRenderContext.java | 20 +- jOOQ/src/main/java/org/jooq/impl/Factory.java | 93 ++- .../java/org/jooq/impl/FieldTypeHelper.java | 127 ----- .../java/org/jooq/impl/ParamCollector.java | 96 ++++ jOOQ/src/main/java/org/jooq/impl/Util.java | 4 +- jOOQ/src/main/java/org/jooq/impl/Val.java | 307 ++++++++++ .../src/test/java/org/jooq/test/jOOQTest.java | 225 ++++++-- 23 files changed, 1155 insertions(+), 1020 deletions(-) create mode 100644 jOOQ/src/main/java/org/jooq/Param.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java delete mode 100644 jOOQ/src/main/java/org/jooq/impl/BindValueCollector.java delete mode 100644 jOOQ/src/main/java/org/jooq/impl/Constant.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/ParamCollector.java create mode 100644 jOOQ/src/main/java/org/jooq/impl/Val.java diff --git a/jOOQ-spring/src/main/java/org/jooq/util/spring/FactoryProxy.java b/jOOQ-spring/src/main/java/org/jooq/util/spring/FactoryProxy.java index 9a2076245f..bfa0d577f6 100644 --- a/jOOQ-spring/src/main/java/org/jooq/util/spring/FactoryProxy.java +++ b/jOOQ-spring/src/main/java/org/jooq/util/spring/FactoryProxy.java @@ -185,6 +185,11 @@ public class FactoryProxy implements FactoryOperations, MethodInterceptor { return getDelegate().render(part); } + @Override + public final String renderNamedParams(QueryPart part) { + return getDelegate().renderNamedParams(part); + } + @Override public final String renderInlined(QueryPart part) { return getDelegate().renderInlined(part); diff --git a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java index 233f9d9acc..c944586976 100644 --- a/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java +++ b/jOOQ-test/src/org/jooq/test/jOOQAbstractTest.java @@ -1561,9 +1561,9 @@ public abstract class jOOQAbstractTest< // CRUD with plain SQL Table table = table(TAuthor().getName()); - Field id = field(TAuthor_ID().getName()); - Field firstName = field(TAuthor_FIRST_NAME().getName()); - Field lastName = field(TAuthor_LAST_NAME().getName()); + Field id = field(TAuthor_ID().getName(), Integer.class); + Field firstName = field(TAuthor_FIRST_NAME().getName(), String.class); + Field lastName = field(TAuthor_LAST_NAME().getName(), String.class); assertEquals(2, create().insertInto(table, id, firstName, lastName) @@ -1579,8 +1579,8 @@ public abstract class jOOQAbstractTest< .fetch(); assertEquals(2, authors1.size()); - assertEquals(10, authors1.getValue(0, id)); - assertEquals(11, authors1.getValue(1, id)); + assertEquals(10, (int) authors1.getValue(0, id)); + assertEquals(11, (int) authors1.getValue(1, id)); assertEquals("Herbert", authors1.getValue(0, firstName)); assertEquals("Friedrich", authors1.getValue(1, firstName)); assertEquals("Meier", authors1.getValue(0, lastName)); @@ -1601,8 +1601,8 @@ public abstract class jOOQAbstractTest< .fetch(); assertEquals(2, authors2.size()); - assertEquals(10, authors2.getValue(0, id)); - assertEquals(11, authors2.getValue(1, id)); + assertEquals(10, (int) authors2.getValue(0, id)); + assertEquals(11, (int) authors2.getValue(1, id)); assertEquals("Friedrich", authors2.getValue(0, firstName)); assertEquals("Friedrich", authors2.getValue(1, firstName)); assertEquals("Schiller", authors2.getValue(0, lastName)); @@ -8788,6 +8788,40 @@ public abstract class jOOQAbstractTest< .fetch(TAuthor_LAST_NAME())); } + @Test + public void testNamedParams() throws Exception { + Select select = + create().select( + TAuthor_ID(), + param("p1", String.class)) + .from(TAuthor()) + .where(TAuthor_ID().in( + param("p2", Integer.class), + param("p3", Integer.class))) + .orderBy(TAuthor_ID().asc()); + + // Should execute fine, but no results due to IN (null, null) filter + assertEquals(0, select.fetch().size()); + + // Set both parameters to the same value + select.getParam("p2").setConverted(1L); + select.getParam("p3").setConverted("1"); + Result result1 = select.fetch(); + assertEquals(1, result1.size()); + assertEquals(1, result1.getValue(0, 0)); + assertNull(result1.getValue(0, 1)); + + // Set more parameters + select.getParam("p1").setConverted("asdf"); + select.getParam("p3").setConverted("2"); + Result result2 = select.fetch(); + assertEquals(2, result2.size()); + assertEquals(1, result2.getValue(0, 0)); + assertEquals(2, result2.getValue(1, 0)); + assertEquals("asdf", result2.getValue(0, 1)); + assertEquals("asdf", result2.getValue(1, 1)); + } + @Test public void testLoader() throws Exception { reset = false; diff --git a/jOOQ/src/main/java/org/jooq/BindContext.java b/jOOQ/src/main/java/org/jooq/BindContext.java index b22e1b9d81..bc37d0e9ca 100644 --- a/jOOQ/src/main/java/org/jooq/BindContext.java +++ b/jOOQ/src/main/java/org/jooq/BindContext.java @@ -47,7 +47,7 @@ import org.jooq.exception.DataAccessException; * will then pass the same context to their components *

* This interface is for JOOQ INTERNAL USE only. Do not reference directly - * + * * @author Lukas Eder * @see RenderContext */ @@ -58,23 +58,10 @@ public interface BindContext extends Context { */ PreparedStatement statement(); - /** - * Get the next bind index. This increments an internal counter. Client code - * must assure that calling {@link #nextIndex()} is followed by setting a - * bind value to {@link #statement()} - */ - int nextIndex(); - - /** - * Peek the next bind index. This won't increment the internal counter, - * unlike {@link #nextIndex()} - */ - int peekIndex(); - /** * Bind values from a {@link QueryPart}. This will also increment the * internal counter. - * + * * @throws DataAccessException If something went wrong while binding a * variable */ @@ -83,7 +70,7 @@ public interface BindContext extends Context { /** * Bind values from several {@link QueryPart}'s. This will also increment * the internal counter. - * + * * @throws DataAccessException If something went wrong while binding a * variable */ @@ -92,7 +79,7 @@ public interface BindContext extends Context { /** * Bind values from several {@link QueryPart}'s. This will also increment * the internal counter. - * + * * @throws DataAccessException If something went wrong while binding a * variable */ @@ -101,7 +88,7 @@ public interface BindContext extends Context { /** * Bind a value using a specific type. This will also increment the internal * counter. - * + * * @throws DataAccessException If something went wrong while binding a * variable */ @@ -109,7 +96,7 @@ public interface BindContext extends Context { /** * Bind several values. This will also increment the internal counter. - * + * * @throws DataAccessException If something went wrong while binding a * variable */ diff --git a/jOOQ/src/main/java/org/jooq/Context.java b/jOOQ/src/main/java/org/jooq/Context.java index 795d637ecf..d87928a7bf 100644 --- a/jOOQ/src/main/java/org/jooq/Context.java +++ b/jOOQ/src/main/java/org/jooq/Context.java @@ -35,6 +35,8 @@ */ package org.jooq; +import java.sql.PreparedStatement; + /** * A context type that is used for rendering SQL or for binding *

@@ -78,4 +80,24 @@ public interface Context> extends Configuration { * Set the new context value for {@link #subquery()} */ C subquery(boolean subquery); + + /** + * Get the next bind index. This increments an internal counter. This is + * relevant for two use-cases: + *

    + *
  • When binding variables to a {@link PreparedStatement}. Client code + * must assure that calling {@link #nextIndex()} is followed by setting a + * bind value to {@link #statement()}
  • + *
  • When rendering unnamed bind variables with + * {@link RenderContext#namedParams()} being to true
  • + *
+ */ + int nextIndex(); + + /** + * Peek the next bind index. This won't increment the internal counter, + * unlike {@link #nextIndex()} + */ + int peekIndex(); + } diff --git a/jOOQ/src/main/java/org/jooq/FactoryOperations.java b/jOOQ/src/main/java/org/jooq/FactoryOperations.java index 85ac558cfb..ecd2181bca 100644 --- a/jOOQ/src/main/java/org/jooq/FactoryOperations.java +++ b/jOOQ/src/main/java/org/jooq/FactoryOperations.java @@ -69,6 +69,18 @@ public interface FactoryOperations extends Configuration { */ String render(QueryPart part); + /** + * Render a QueryPart in the context of this factory, rendering bind + * variables as named parameters. + *

+ * This is the same as calling + * renderContext().namedParams(true).render(part) + * + * @param part The {@link QueryPart} to be rendered + * @return The rendered SQL + */ + String renderNamedParams(QueryPart part); + /** * Render a QueryPart in the context of this factory, inlining all bind * variables. diff --git a/jOOQ/src/main/java/org/jooq/Field.java b/jOOQ/src/main/java/org/jooq/Field.java index 3b0a717e64..26e6eac0e9 100644 --- a/jOOQ/src/main/java/org/jooq/Field.java +++ b/jOOQ/src/main/java/org/jooq/Field.java @@ -62,6 +62,7 @@ public interface Field extends NamedTypeProviderQueryPart, AliasProviderThe formal name of the field, if it is a physical table/view field *

  • The alias of an aliased field
  • *
  • A generated / unspecified value for any other expression
  • + *
  • The name of a parameter if it is a named {@link Param}
  • * */ @Override diff --git a/jOOQ/src/main/java/org/jooq/Param.java b/jOOQ/src/main/java/org/jooq/Param.java new file mode 100644 index 0000000000..25f277d10a --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/Param.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2009-2011, 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; + +import org.jooq.impl.Factory; +import org.jooq.tools.Convert; + +/** + * A named parameter + * + * @author Lukas Eder + * @see Factory#param(String, Object) + */ +public interface Param extends Field { + + /** + * {@inheritDoc} + *
    + * The Param's value for {@link #getName()} coincides with + * {@link #getParamName()} + */ + @Override + String getName(); + + /** + * The parameter name. This name is useful for two things: + *
      + *
    • Named parameters in frameworks that support them, such as Spring's + * JdbcTemplate
    • + *
    • Accessing the parameter from the {@link Query} API, with + * {@link Query#getParam(String)}, {@link Query#getParams()}
    • + *
    + */ + String getParamName(); + + /** + * Get the parameter's underlying value. This returns null if + * no value has been set yet. + */ + T getValue(); + + /** + * Set the parameter's underlying value. This is the same as + * {@link #setConverted(Object)}, but ensures generic type-safety. + * + * @see #setConverted(Object) + */ + void setValue(T value); + + /** + * Sets a converted value, using this {@link Param}'s underlying + * {@link DataType}, obtained from {@link #getDataType()} + * + * @see DataType#convert(Object) + * @see Convert#convert(Object, Class) + */ + void setConverted(Object value); +} diff --git a/jOOQ/src/main/java/org/jooq/Query.java b/jOOQ/src/main/java/org/jooq/Query.java index 91343253b9..603ea882c6 100644 --- a/jOOQ/src/main/java/org/jooq/Query.java +++ b/jOOQ/src/main/java/org/jooq/Query.java @@ -37,13 +37,14 @@ package org.jooq; import java.util.List; +import java.util.Map; import org.jooq.exception.DataAccessException; import org.jooq.impl.Factory; /** * Any query - * + * * @author Lukas Eder */ public interface Query extends QueryPart { @@ -51,7 +52,7 @@ public interface Query extends QueryPart { /** * Execute the query, if it has been created with a properly configured * factory - * + * * @return A result value, depending on the concrete implementation of * {@link Query}: *
      @@ -84,8 +85,34 @@ public interface Query extends QueryPart { String getSQL(); /** - * Retrieve the bind values that will be bound by this Query + * Retrieve the bind values that will be bound by this Query. This + * List cannot be modified. To modify bind values, use + * {@link #getParams()} instead. */ List getBindValues(); + /** + * Get a Map of named parameters. The Map itself + * cannot be modified, but the {@link Param} elements allow for modifying + * bind values on an existing {@link Query}. + *

      + * Bind values created with {@link Factory#val(Object)} will have their bind + * index as name. + * + * @see Param + * @see Factory#param(String, Object) + */ + Map> getParams(); + + /** + * Get a named parameter from the {@link Query}, provided its name. + *

      + * Bind values created with {@link Factory#val(Object)} will have their bind + * index as name. + * + * @see Param + * @see Factory#param(String, Object) + */ + Param getParam(String name); + } diff --git a/jOOQ/src/main/java/org/jooq/QueryPartInternal.java b/jOOQ/src/main/java/org/jooq/QueryPartInternal.java index b2154081d3..4c2440f115 100644 --- a/jOOQ/src/main/java/org/jooq/QueryPartInternal.java +++ b/jOOQ/src/main/java/org/jooq/QueryPartInternal.java @@ -37,6 +37,7 @@ package org.jooq; import java.util.List; +import java.util.Map; import org.jooq.exception.DataAccessException; @@ -60,14 +61,35 @@ public interface QueryPartInternal extends QueryPart { void toSQL(RenderContext context); /** - * Retrieve the bind values that will be bound by this QueryPart + * Retrieve the SQL that will be rendered by this {@link QueryPart} + *

      + * This method is exposed publicly in {@link Query#getSQL()} + */ + String getSQL(); + + /** + * Retrieve the bind values that will be bound by this {@link QueryPart} *

      * This method is exposed publicly in {@link Query#getBindValues()} */ List getBindValues(); /** - * Bind all parameters of this QueryPart to a PreparedStatement + * Retrieve the named parameters that will be bound by this {@link QueryPart} + *

      + * This method is exposed publicly in {@link Query#getParams()} + */ + Map> getParams(); + + /** + * Retrieve a named parameter that will be bound by this {@link QueryPart} + *

      + * This method is exposed publicly in {@link Query#getParam(String)} + */ + Param getParam(String name); + + /** + * Bind all parameters of this {@link QueryPart} to a PreparedStatement *

      * This method is for JOOQ INTERNAL USE only. Do not reference directly * @@ -79,7 +101,7 @@ public interface QueryPartInternal extends QueryPart { void bind(BindContext context) throws DataAccessException; /** - * Reproduce the SQL dialect this QueryPart was created with + * Reproduce the SQL dialect this {@link QueryPart} was created with *

      * This method is for JOOQ INTERNAL USE only. Do not reference directly * @@ -88,7 +110,7 @@ public interface QueryPartInternal extends QueryPart { SQLDialect getDialect(); /** - * Check whether this QueryPart is able to declare fields in a + * Check whether this {@link QueryPart} is able to declare fields in a * SELECT clause. *

      * This method can be used by any {@link Context} to check how a certain SQL @@ -99,7 +121,7 @@ public interface QueryPartInternal extends QueryPart { boolean declaresFields(); /** - * Check whether this QueryPart is able to declare tables in a + * Check whether this {@link QueryPart} is able to declare tables in a * FROM clause or JOIN clause. *

      * This method can be used by any {@link Context} to check how a certain SQL diff --git a/jOOQ/src/main/java/org/jooq/RenderContext.java b/jOOQ/src/main/java/org/jooq/RenderContext.java index 514128f9fe..7c5a6b397d 100644 --- a/jOOQ/src/main/java/org/jooq/RenderContext.java +++ b/jOOQ/src/main/java/org/jooq/RenderContext.java @@ -106,4 +106,18 @@ public interface RenderContext extends Context { * Set the new context value for {@link #inline()} */ RenderContext inline(boolean inline); + + /** + * Whether bind variables should be rendered as named parameters:
      + *   :1, :2, :custom_name + *

      + * or as JDBC bind variables
      + *   ? + */ + boolean namedParams(); + + /** + * Set the new context value for {@link #namedParams()} + */ + RenderContext namedParams(boolean renderNamedParams); } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java new file mode 100644 index 0000000000..8d245fe184 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractBindContext.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2009-2011, 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.sql.SQLException; +import java.util.Arrays; +import java.util.Collection; + +import org.jooq.BindContext; +import org.jooq.Configuration; +import org.jooq.QueryPart; +import org.jooq.QueryPartInternal; + +/** + * A base class for {@link BindContext} implementations + * + * @author Lukas Eder + */ +abstract class AbstractBindContext extends AbstractContext implements BindContext { + + /** + * Generated UID + */ + private static final long serialVersionUID = -319766597723101571L; + + AbstractBindContext(Configuration configuration) { + super(configuration); + } + + AbstractBindContext(BindContext context) { + this((Configuration) context); + + declareFields(context.declareFields()); + declareTables(context.declareTables()); + } + + // ------------------------------------------------------------------------ + // BindContext API + // ------------------------------------------------------------------------ + + @Override + public final BindContext bind(Collection parts) { + for (QueryPart part : parts) { + bind(part); + } + + return this; + } + + @Override + public final BindContext bind(QueryPart[] parts) { + bind(Arrays.asList(parts)); + return this; + } + + @Override + public final BindContext bind(QueryPart part) { + QueryPartInternal internal = part.internalAPI(QueryPartInternal.class); + + // If this is supposed to be a declaration section and the part isn't + // able to declare anything, then disable declaration temporarily + + // We're declaring fields, but "part" does not declare fields + if (declareFields() && !internal.declaresFields()) { + declareFields(false); + bindInternal(internal); + declareFields(true); + } + + // We're declaring tables, but "part" does not declare tables + else if (declareTables() && !internal.declaresTables()) { + declareTables(false); + bindInternal(internal); + declareTables(true); + } + + // We're not declaring, or "part" can declare + else { + bindInternal(internal); + } + + return this; + } + + @Override + public final BindContext bindValues(Object... values) { + + // [#724] When values is null, this is probably due to API-misuse + // The user probably meant new Object[] { null } + if (values == null) { + bindValues(new Object[] { null }); + } + else { + for (Object value : values) { + Class type = (value == null) ? Object.class : value.getClass(); + bindValue(value, type); + } + } + + return this; + } + + @Override + public final BindContext bindValue(Object value, Class type) { + try { + return bindValue0(value, type); + } + catch (SQLException e) { + throw Util.translate("DefaultBindContext.bindValue", null, e); + } + } + + // ------------------------------------------------------------------------ + // AbstractBindContext template methods + // ------------------------------------------------------------------------ + + /** + * Subclasses may override this method to achieve different behaviour + */ + protected void bindInternal(QueryPartInternal internal) { + internal.bind(this); + } + + /** + * Subclasses may override this method to achieve different behaviour + */ + @SuppressWarnings("unused") + protected BindContext bindValue0(Object value, Class type) throws SQLException { + return this; + } + + // ------------------------------------------------------------------------ + // Object API + // ------------------------------------------------------------------------ + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + toString(sb); + return sb.toString(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java index 834a618b09..5cec96f229 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractContext.java @@ -56,6 +56,7 @@ abstract class AbstractContext> implements Context { boolean declareFields; boolean declareTables; boolean subquery; + int index; AbstractContext(Configuration configuration) { this.configuration = configuration; @@ -107,6 +108,16 @@ abstract class AbstractContext> implements Context { return (C) this; } + @Override + public final int nextIndex() { + return ++index; + } + + @Override + public final int peekIndex() { + return index + 1; + } + // ------------------------------------------------------------------------ // Configuration API // ------------------------------------------------------------------------ @@ -127,7 +138,10 @@ abstract class AbstractContext> implements Context { } void toString(StringBuilder sb) { - sb.append("\ndeclaring ["); + sb.append( "bind index ["); + sb.append(index); + sb.append("]"); + sb.append("\ndeclaring ["); if (declareFields) { sb.append("fields"); @@ -139,7 +153,7 @@ abstract class AbstractContext> implements Context { sb.append("-"); } - sb.append("]\nsubquery ["); + sb.append("]\nsubquery ["); sb.append(subquery); sb.append("]"); } diff --git a/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java b/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java index 367d76f132..49792da6d4 100644 --- a/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java +++ b/jOOQ/src/main/java/org/jooq/impl/AbstractQueryPart.java @@ -42,10 +42,12 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import org.jooq.Attachable; import org.jooq.AttachableInternal; import org.jooq.Configuration; +import org.jooq.Param; import org.jooq.Query; import org.jooq.QueryPart; import org.jooq.QueryPartInternal; @@ -107,16 +109,50 @@ abstract class AbstractQueryPart implements QueryPartInternal, AttachableInterna /** * This method is also declared as {@link Query#getSQL()} + *

      + * {@inheritDoc} */ + @Override public final String getSQL() { return create().render(this); } + /** + * This method is also declared as {@link Query#getBindValues()} + *

      + * {@inheritDoc} + */ @Override public final List getBindValues() { - BindValueCollector collector = new BindValueCollector(); - create(getConfiguration()).bind(this, collector); - return collector.result; + List result = new ArrayList(); + + for (Param param : getParams().values()) { + result.add(param.getValue()); + } + + return Collections.unmodifiableList(result); + } + + /** + * This method is also declared as {@link Query#getParams()} + *

      + * {@inheritDoc} + */ + @Override + public final Map> getParams() { + ParamCollector collector = new ParamCollector(getConfiguration()); + collector.bind(this); + return Collections.unmodifiableMap(collector.result); + } + + /** + * This method is also declared as {@link Query#getParam(String)} + *

      + * {@inheritDoc} + */ + @Override + public final Param getParam(String name) { + return getParams().get(name); } /** diff --git a/jOOQ/src/main/java/org/jooq/impl/BindValueCollector.java b/jOOQ/src/main/java/org/jooq/impl/BindValueCollector.java deleted file mode 100644 index e24652786b..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/BindValueCollector.java +++ /dev/null @@ -1,529 +0,0 @@ -/** - * Copyright (c) 2009-2011, 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.io.InputStream; -import java.io.Reader; -import java.math.BigDecimal; -import java.net.URL; -import java.sql.Array; -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.Date; -import java.sql.NClob; -import java.sql.ParameterMetaData; -import java.sql.PreparedStatement; -import java.sql.Ref; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.RowId; -import java.sql.SQLWarning; -import java.sql.SQLXML; -import java.sql.Time; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import org.jooq.Query; - -/** - * A stub prepared statement that acts as a collector of bind values, in order - * to retrieve the bound values in correct order for - * {@link Query#getBindValues()} - * - * @author Lukas Eder - */ -class BindValueCollector implements PreparedStatement { - - final List result = new ArrayList(); - - @Override - public T unwrap(Class iface) { - return null; - } - - @Override - public boolean isWrapperFor(Class iface) { - return false; - } - - @Override - public ResultSet executeQuery(String sql) { - return null; - } - - @Override - public int executeUpdate(String sql) { - return 0; - } - - @Override - public void close() {} - - @Override - public int getMaxFieldSize() { - return 0; - } - - @Override - public void setMaxFieldSize(int max) {} - - @Override - public int getMaxRows() { - return 0; - } - - @Override - public void setMaxRows(int max) {} - - @Override - public void setEscapeProcessing(boolean enable) {} - - @Override - public int getQueryTimeout() { - return 0; - } - - @Override - public void setQueryTimeout(int seconds) {} - - @Override - public void cancel() {} - - @Override - public SQLWarning getWarnings() { - return null; - } - - @Override - public void clearWarnings() {} - - @Override - public void setCursorName(String name) {} - - @Override - public boolean execute(String sql) { - return false; - } - - @Override - public ResultSet getResultSet() { - return null; - } - - @Override - public int getUpdateCount() { - return 0; - } - - @Override - public boolean getMoreResults() { - return false; - } - - @Override - public void setFetchDirection(int direction) {} - - @Override - public int getFetchDirection() { - return 0; - } - - @Override - public void setFetchSize(int rows) {} - - @Override - public int getFetchSize() { - return 0; - } - - @Override - public int getResultSetConcurrency() { - return 0; - } - - @Override - public int getResultSetType() { - return 0; - } - - @Override - public void addBatch(String sql) {} - - @Override - public void clearBatch() {} - - @Override - public int[] executeBatch() { - return null; - } - - @Override - public Connection getConnection() { - return null; - } - - @Override - public boolean getMoreResults(int current) { - return false; - } - - @Override - public ResultSet getGeneratedKeys() { - return null; - } - - @Override - public int executeUpdate(String sql, int autoGeneratedKeys) { - return 0; - } - - @Override - public int executeUpdate(String sql, int[] columnIndexes) { - return 0; - } - - @Override - public int executeUpdate(String sql, String[] columnNames) { - return 0; - } - - @Override - public boolean execute(String sql, int autoGeneratedKeys) { - return false; - } - - @Override - public boolean execute(String sql, int[] columnIndexes) { - return false; - } - - @Override - public boolean execute(String sql, String[] columnNames) { - return false; - } - - @Override - public int getResultSetHoldability() { - return 0; - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public void setPoolable(boolean poolable) {} - - @Override - public boolean isPoolable() { - return false; - } - - @Override - public ResultSet executeQuery() { - return null; - } - - @Override - public int executeUpdate() { - return 0; - } - - @Override - public void setNull(int parameterIndex, int sqlType) { - result.add(null); - } - - @Override - public void setBoolean(int parameterIndex, boolean x) { - result.add(x); - } - - @Override - public void setByte(int parameterIndex, byte x) { - result.add(x); - } - - @Override - public void setShort(int parameterIndex, short x) { - result.add(x); - } - - @Override - public void setInt(int parameterIndex, int x) { - result.add(x); - } - - @Override - public void setLong(int parameterIndex, long x) { - result.add(x); - } - - @Override - public void setFloat(int parameterIndex, float x) { - result.add(x); - } - - @Override - public void setDouble(int parameterIndex, double x) { - result.add(x); - } - - @Override - public void setBigDecimal(int parameterIndex, BigDecimal x) { - result.add(x); - } - - @Override - public void setString(int parameterIndex, String x) { - result.add(x); - } - - @Override - public void setBytes(int parameterIndex, byte[] x) { - result.add(x); - } - - @Override - public void setDate(int parameterIndex, Date x) { - result.add(x); - } - - @Override - public void setTime(int parameterIndex, Time x) { - result.add(x); - } - - @Override - public void setTimestamp(int parameterIndex, Timestamp x) { - result.add(x); - } - - @Override - public void setAsciiStream(int parameterIndex, InputStream x, int length) { - result.add(x); - } - - @Override - public void setUnicodeStream(int parameterIndex, InputStream x, int length) { - result.add(x); - } - - @Override - public void setBinaryStream(int parameterIndex, InputStream x, int length) { - result.add(x); - } - - @Override - public void clearParameters() {} - - @Override - public void setObject(int parameterIndex, Object x, int targetSqlType) { - result.add(x); - } - - @Override - public void setObject(int parameterIndex, Object x) { - result.add(x); - } - - @Override - public boolean execute() { - return false; - } - - @Override - public void addBatch() {} - - @Override - public void setCharacterStream(int parameterIndex, Reader x, int length) { - result.add(x); - } - - @Override - public void setRef(int parameterIndex, Ref x) { - result.add(x); - } - - @Override - public void setBlob(int parameterIndex, Blob x) { - result.add(x); - } - - @Override - public void setClob(int parameterIndex, Clob x) { - result.add(x); - } - - @Override - public void setArray(int parameterIndex, Array x) { - result.add(x); - } - - @Override - public ResultSetMetaData getMetaData() { - return null; - } - - @Override - public void setDate(int parameterIndex, Date x, Calendar cal) { - result.add(x); - } - - @Override - public void setTime(int parameterIndex, Time x, Calendar cal) { - result.add(x); - } - - @Override - public void setTimestamp(int parameterIndex, Timestamp x, Calendar cal) { - result.add(x); - } - - @Override - public void setNull(int parameterIndex, int sqlType, String typeName) { - result.add(null); - } - - @Override - public void setURL(int parameterIndex, URL x) { - result.add(x); - } - - @Override - public ParameterMetaData getParameterMetaData() { - return null; - } - - @Override - public void setRowId(int parameterIndex, RowId x) { - result.add(x); - } - - @Override - public void setNString(int parameterIndex, String x) { - result.add(x); - } - - @Override - public void setNCharacterStream(int parameterIndex, Reader x, long length) { - result.add(x); - } - - @Override - public void setNClob(int parameterIndex, NClob x) { - result.add(x); - } - - @Override - public void setClob(int parameterIndex, Reader x, long length) { - result.add(x); - } - - @Override - public void setBlob(int parameterIndex, InputStream x, long length) { - result.add(x); - } - - @Override - public void setNClob(int parameterIndex, Reader x, long length) { - result.add(x); - } - - @Override - public void setSQLXML(int parameterIndex, SQLXML x) { - result.add(x); - } - - @Override - public void setObject(int parameterIndex, Object x, int targetSqlType, int scaleOrLength) { - result.add(x); - } - - @Override - public void setAsciiStream(int parameterIndex, InputStream x, long length) { - result.add(x); - } - - @Override - public void setBinaryStream(int parameterIndex, InputStream x, long length) { - result.add(x); - } - - @Override - public void setCharacterStream(int parameterIndex, Reader x, long length) { - result.add(x); - } - - @Override - public void setAsciiStream(int parameterIndex, InputStream x) { - result.add(x); - } - - @Override - public void setBinaryStream(int parameterIndex, InputStream x) { - result.add(x); - } - - @Override - public void setCharacterStream(int parameterIndex, Reader x) { - result.add(x); - } - - @Override - public void setNCharacterStream(int parameterIndex, Reader x) { - result.add(x); - } - - @Override - public void setClob(int parameterIndex, Reader x) { - result.add(x); - } - - @Override - public void setBlob(int parameterIndex, InputStream x) { - result.add(x); - } - - @Override - public void setNClob(int parameterIndex, Reader x) { - result.add(x); - } -} diff --git a/jOOQ/src/main/java/org/jooq/impl/Constant.java b/jOOQ/src/main/java/org/jooq/impl/Constant.java deleted file mode 100644 index 956dd3cbc2..0000000000 --- a/jOOQ/src/main/java/org/jooq/impl/Constant.java +++ /dev/null @@ -1,151 +0,0 @@ -/** - * Copyright (c) 2009-2011, 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.math.BigDecimal; -import java.util.Collections; -import java.util.List; - -import org.jooq.Attachable; -import org.jooq.BindContext; -import org.jooq.DataType; -import org.jooq.EnumType; -import org.jooq.MasterDataType; -import org.jooq.RenderContext; - -/** - * @author Lukas Eder - */ -class Constant extends AbstractField { - - private static final long serialVersionUID = 6807729087019209084L; - private final T value; - - Constant(T value, DataType type) { - super("" + value, type); - - this.value = value; - } - - @Override - public final List getAttachables() { - return Collections.emptyList(); - } - - @Override - public final void toSQL(RenderContext context) { - - // Casting is only done when parameters are NOT inlined - if (!context.inline()) { - - // Generated enums should not be cast... - // The exception's exception - if (!(value instanceof EnumType) && !(value instanceof MasterDataType)) { - switch (context.getDialect()) { - - // These dialects can hardly detect the type of a bound constant. - case DB2: - case DERBY: - - // These dialects have some trouble, when they mostly get it right. - case H2: - case HSQLDB: - - // [#722] TODO This is probably not entirely right. - case INGRES: - - // [#632] Sybase needs explicit casting in very rare cases. - case SYBASE: { - toSQLCast(context); - return; - } - } - } - } - - // Most RDBMS can handle constants as typeless literals - FieldTypeHelper.toSQL(context, value, this); - } - - /** - * Render the bind variable including a cast, if necessary - */ - private void toSQLCast(RenderContext context) { - switch (context.getDialect()) { - - // [#822] Some RDBMS need precision / scale information on BigDecimals - case DB2: - case DERBY: - case HSQLDB: { - - // Add precision / scale on BigDecimals - if (getType() == BigDecimal.class) { - int scale = ((BigDecimal) value).scale(); - int precision = scale + ((BigDecimal) value).precision(); - - context.sql("cast(? as ") - .sql(getDataType(context).getCastTypeName(context, precision, scale)) - .sql(")"); - break; - } - - // No break, fall through - else { - } - } - - // These dialects don't need precision / scale info on BigDecimals - case H2: - case INGRES: - case SYBASE: { - context.sql("cast(? as ") - .sql(getDataType(context).getCastTypeName(context)) - .sql(")"); - } - } - } - - @Override - public final void bind(BindContext context) { - context.bindValue(value, getType()); - } - - @Override - public final boolean isNullLiteral() { - return value == null; - } -} diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java index c5feea16ee..06f0a8540f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBindContext.java @@ -46,15 +46,12 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; import java.util.Arrays; -import java.util.Collection; import org.jooq.ArrayRecord; import org.jooq.BindContext; import org.jooq.Configuration; import org.jooq.EnumType; import org.jooq.MasterDataType; -import org.jooq.QueryPart; -import org.jooq.QueryPartInternal; import org.jooq.SQLDialect; import org.jooq.exception.SQLDialectNotSupportedException; import org.jooq.tools.JooqLogger; @@ -63,7 +60,7 @@ import org.jooq.tools.unsigned.UNumber; /** * @author Lukas Eder */ -class DefaultBindContext extends AbstractContext implements BindContext { +class DefaultBindContext extends AbstractBindContext { /** * Generated UID @@ -72,7 +69,6 @@ class DefaultBindContext extends AbstractContext implements BindCon private static final JooqLogger log = JooqLogger.getLogger(Util.class); private final PreparedStatement stmt; - private int index; DefaultBindContext(Configuration configuration, PreparedStatement stmt) { super(configuration); @@ -93,89 +89,8 @@ class DefaultBindContext extends AbstractContext implements BindCon } @Override - public final int nextIndex() { - return ++index; - } - - @Override - public final int peekIndex() { - return index + 1; - } - - @Override - public final BindContext bind(QueryPart part) { - QueryPartInternal internal = part.internalAPI(QueryPartInternal.class); - - // If this is supposed to be a declaration section and the part isn't - // able to declare anything, then disable declaration temporarily - - // We're declaring fields, but "part" does not declare fields - if (declareFields() && !internal.declaresFields()) { - declareFields(false); - internal.bind(this); - declareFields(true); - } - - // We're declaring tables, but "part" does not declare tables - else if (declareTables() && !internal.declaresTables()) { - declareTables(false); - internal.bind(this); - declareTables(true); - } - - // We're not declaring, or "part" can declare - else { - internal.bind(this); - } - - return this; - } - - @Override - public final BindContext bind(Collection parts) { - for (QueryPart part : parts) { - bind(part); - } - - return this; - } - - @Override - public final BindContext bind(QueryPart[] parts) { - bind(Arrays.asList(parts)); - return this; - } - - @Override - public final BindContext bindValues(Object... values) { - - // [#724] When values is null, this is probably due to API-misuse - // The user probably meant new Object[] { null } - if (values == null) { - bindValues(new Object[] { null }); - } - else { - for (Object value : values) { - Class type = (value == null) ? Object.class : value.getClass(); - bindValue(value, type); - } - } - - return this; - } - - @Override - public final BindContext bindValue(Object value, Class type) { - try { - return bindValue0(value, type); - } - catch (SQLException e) { - throw Util.translate("DefaultBindContext.bindValue", null, e); - } - } - @SuppressWarnings("unchecked") - private final BindContext bindValue0(Object value, Class type) throws SQLException { + protected final BindContext bindValue0(Object value, Class type) throws SQLException { SQLDialect dialect = configuration.getDialect(); if (log.isTraceEnabled()) { @@ -335,20 +250,4 @@ class DefaultBindContext extends AbstractContext implements BindCon return this; } - - // ------------------------------------------------------------------------ - // Object API - // ------------------------------------------------------------------------ - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - sb.append("binding [index "); - sb.append(index); - sb.append("]"); - - toString(sb); - return sb.toString(); - } } diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java index 332cdc5033..935bcfaa49 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultRenderContext.java @@ -52,6 +52,7 @@ class DefaultRenderContext extends AbstractContext implements Ren private final StringBuilder sql; private boolean inline; + private boolean renderNamedParams; private int alias; DefaultRenderContext(Configuration configuration) { @@ -64,6 +65,7 @@ class DefaultRenderContext extends AbstractContext implements Ren this((Configuration) context); inline(context.inline()); + namedParams(context.namedParams()); declareFields(context.declareFields()); declareTables(context.declareTables()); } @@ -187,6 +189,17 @@ class DefaultRenderContext extends AbstractContext implements Ren return this; } + @Override + public final boolean namedParams() { + return renderNamedParams; + } + + @Override + public final RenderContext namedParams(boolean r) { + this.renderNamedParams = r; + return this; + } + // ------------------------------------------------------------------------ // Object API // ------------------------------------------------------------------------ @@ -195,12 +208,15 @@ class DefaultRenderContext extends AbstractContext implements Ren public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("rendering ["); + sb.append( "rendering ["); sb.append(render()); sb.append("]"); - sb.append("\ninlining ["); + sb.append("\ninlining ["); sb.append(inline); sb.append("]"); + sb.append("\nnamed params ["); + sb.append(renderNamedParams); + sb.append("]"); toString(sb); return sb.toString(); diff --git a/jOOQ/src/main/java/org/jooq/impl/Factory.java b/jOOQ/src/main/java/org/jooq/impl/Factory.java index bede50e589..d7eb4d672a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Factory.java +++ b/jOOQ/src/main/java/org/jooq/impl/Factory.java @@ -79,6 +79,7 @@ import org.jooq.InsertSetStep; import org.jooq.InsertValuesStep; import org.jooq.LoaderOptionsStep; import org.jooq.MergeUsingStep; +import org.jooq.Param; import org.jooq.Query; import org.jooq.QueryPart; import org.jooq.Record; @@ -218,6 +219,7 @@ public class Factory implements FactoryOperations { *
    • {@link RenderContext#declareFields()} == false
    • *
    • {@link RenderContext#declareTables()} == false
    • *
    • {@link RenderContext#inline()} == false
    • + *
    • {@link RenderContext#namedParams()} == false
    • * *

      * RenderContext for JOOQ INTERNAL USE only. Avoid referencing it directly @@ -234,6 +236,14 @@ public class Factory implements FactoryOperations { return renderContext().render(part); } + /** + * {@inheritDoc} + */ + @Override + public final String renderNamedParams(QueryPart part) { + return renderContext().namedParams(true).render(part); + } + /** * {@inheritDoc} */ @@ -3531,18 +3541,87 @@ public class Factory implements FactoryOperations { // Bind values // ------------------------------------------------------------------------- + /** + * Create a named parameter with a generic type ({@link Object} / + * {@link SQLDataType#OTHER}) and no initial value. + *

      + * Try to avoid this method when using any of these databases, as these + * databases may have trouble inferring the type of the bind value. Use + * typed named parameters instead, using {@link #param(String, Class)} or + * {@link #param(String, DataType)} + *

        + *
      • {@link SQLDialect#DB2}
      • + *
      • {@link SQLDialect#DERBY}
      • + *
      • {@link SQLDialect#H2}
      • + *
      • {@link SQLDialect#HSQLDB}
      • + *
      • {@link SQLDialect#INGRES}
      • + *
      • {@link SQLDialect#SYBASE}
      • + *
      + * + * @see #param(String, Object) + */ + public static Param param(String name) { + return param(name, Object.class); + } + + /** + * Create a named parameter with a defined type and no initial value. + * + * @see #param(String, Object) + */ + public static Param param(String name, Class type) { + return param(name, SQLDataType.getDataType(null, type)); + } + + /** + * Create a named parameter with a defined type and no initial value. + * + * @see #param(String, Object) + */ + public static Param param(String name, DataType type) { + return new Val(null, type, name); + } + + /** + * Create a named parameter with an initial value. + *

      + * Named parameters are useful for several use-cases: + *

        + *
      • They can be used with Spring's JdbcTemplate, which + * supports named parameters. Use + * {@link FactoryOperations#renderNamedParams(QueryPart)} to render + * parameter names in SQL
      • + *
      • Named parameters can be retrieved using a well-known name from + * {@link Query#getParam(String)} and {@link Query#getParams()}.
      • + *
      + * + * @see Query#getParam(String) + * @see Query#getParams() + * @see #renderNamedParams(QueryPart) + */ + public static Param param(String name, T value) { + return new Val(value, val(value).getDataType(), name); + } + /** * Get a value *

      * jOOQ tries to derive the RDBMS {@link DataType} from the provided Java * type <T>. This may not always be accurate, which can - * lead to problems in some strongly typed RDMBS (namely: - * {@link SQLDialect#DERBY}, {@link SQLDialect#DB2}, {@link SQLDialect#H2}, - * {@link SQLDialect#HSQLDB}), especially when value is null. + * lead to problems in some strongly typed RDMBS, especially when value is + * null. These databases are namely: + *

        + *
      • {@link SQLDialect#DERBY}
      • + *
      • {@link SQLDialect#DB2}
      • + *
      • {@link SQLDialect#H2}
      • + *
      • {@link SQLDialect#HSQLDB} + *
      • {@link SQLDialect#INGRES} + *
      • {@link SQLDialect#SYBASE} + *
      *

      - * If you need more type-safety, please use - * {@link #val(Object, DataType)} instead, and provide the precise - * RDMBS-specific data type, that is needed. + * If you need more type-safety, please use {@link #val(Object, DataType)} + * instead, and provide the precise RDMBS-specific data type, that is + * needed. * * @param The generic value type * @param value The constant value @@ -3624,7 +3703,7 @@ public class Factory implements FactoryOperations { // The default behaviour else { - return new Constant(type.convert(value), type); + return new Val(type.convert(value), type); } } diff --git a/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java b/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java index bca28daec1..da4d49141a 100644 --- a/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java +++ b/jOOQ/src/main/java/org/jooq/impl/FieldTypeHelper.java @@ -53,7 +53,6 @@ import java.sql.Types; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -65,9 +64,7 @@ import org.jooq.EnumType; import org.jooq.Field; import org.jooq.FieldProvider; import org.jooq.MasterDataType; -import org.jooq.NamedTypeProviderQueryPart; import org.jooq.Record; -import org.jooq.RenderContext; import org.jooq.Result; import org.jooq.SQLDialect; import org.jooq.UDTRecord; @@ -109,130 +106,6 @@ public final class FieldTypeHelper { private static final JooqLogger log = JooqLogger.getLogger(FieldTypeHelper.class); - public static void toSQL(RenderContext context, Object value) { - if (value == null) { - toSQL(context, value, Object.class); - } - else { - toSQL(context, value, value.getClass()); - } - } - - public static void toSQL(RenderContext context, Object value, NamedTypeProviderQueryPart field) { - toSQL(context, value, field.getType()); - } - - public static void toSQL(RenderContext context, Object value, Class type) { - if (context.inline()) { - if (value == null) { - context.sql("null"); - } - else if (type == Blob.class) { - - // blob's are treated as byte[] by jOOQ - context.sql("[BLOB]"); - } - else if (type == Boolean.class) { - context.sql(value.toString()); - } - else if (type == BigInteger.class) { - context.sql(value.toString()); - } - else if (type == BigDecimal.class) { - context.sql(value.toString()); - } - else if (type == Byte.class) { - context.sql(value.toString()); - } - else if (type == byte[].class) { - context.sql("'") - .sql(new String((byte[]) value).replace("'", "''")) - .sql("'"); - } - else if (type == Clob.class) { - - // clob's are treated as String by jOOQ - context.sql("[CLOB]"); - } - else if (type == Date.class) { - context.sql("'").sql(value.toString()).sql("'"); - } - else if (type == Double.class) { - context.sql(value.toString()); - } - else if (type == Float.class) { - context.sql(value.toString()); - } - else if (type == Integer.class) { - context.sql(value.toString()); - } - else if (type == Long.class) { - context.sql(value.toString()); - } - else if (type == Short.class) { - context.sql(value.toString()); - } - else if (type == String.class) { - context.sql("'") - .sql(value.toString().replace("'", "''")) - .sql("'"); - } - else if (type == Time.class) { - context.sql("'").sql(value.toString()).sql("'"); - } - else if (type == Timestamp.class) { - context.sql("'").sql(value.toString()).sql("'"); - } - else if (type.isArray()) { - context.sql("ARRAY") - .sql(Arrays.asList((Object[]) value).toString()); - } - else if (UNumber.class.isAssignableFrom(type)) { - context.sql(value.toString()); - } - else if (ArrayRecord.class.isAssignableFrom(type)) { - context.sql(value.toString()); - } - else if (EnumType.class.isAssignableFrom(type)) { - toSQL(context, ((EnumType) value).getLiteral()); - } - else if (MasterDataType.class.isAssignableFrom(type)) { - toSQL(context, ((MasterDataType) value).getPrimaryKey()); - } - else if (UDTRecord.class.isAssignableFrom(type)) { - context.sql("[UDT]"); - } - else { - throw new UnsupportedOperationException("Class " + type + " is not supported"); - } - } - - // In Postgres, some additional casting must be done in some cases... - // TODO: Improve this implementation with [#215] (cast support) - else if (context.getDialect() == SQLDialect.POSTGRES) { - - // Postgres needs explicit casting for array types - if (type.isArray() && byte[].class != type) { - context.sql("?::"); - context.sql(getDataType(context.getDialect(), type).getCastTypeName(context)); - } - - // ... and also for enum types - else if (EnumType.class.isAssignableFrom(type)) { - context.sql("?::"); - context.literal(((EnumType) value).getName()); - } - - else { - context.sql("?"); - } - } - - else { - context.sql("?"); - } - } - @SuppressWarnings("unchecked") public static T getFromSQLInput(Configuration configuration, SQLInput stream, Field field) throws SQLException { Class type = field.getType(); diff --git a/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java b/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java new file mode 100644 index 0000000000..25ee715517 --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/ParamCollector.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2009-2011, 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.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.jooq.BindContext; +import org.jooq.Configuration; +import org.jooq.Param; +import org.jooq.QueryPart; +import org.jooq.QueryPartInternal; +import org.jooq.tools.StringUtils; + +/** + * A stub {@link BindContext} that acts as a collector of {@link Param} + * {@link QueryPart}'s + * + * @author Lukas Eder + */ +class ParamCollector extends AbstractBindContext { + + /** + * Generated UID + */ + private static final long serialVersionUID = -3741599479523459297L; + + final Map> result = new LinkedHashMap>(); + + ParamCollector(Configuration configuration) { + super(configuration); + } + + @Override + public final PreparedStatement statement() { + throw new UnsupportedOperationException(); + } + + @Override + protected final void bindInternal(QueryPartInternal internal) { + if (internal instanceof Param) { + Param param = (Param) internal; + String i = String.valueOf(nextIndex()); + + if (StringUtils.isBlank(param.getParamName())) { + result.put(i, param); + } + else { + result.put(param.getParamName(), param); + } + } + else { + super.bindInternal(internal); + } + } + + @Override + protected final BindContext bindValue0(Object value, Class type) throws SQLException { + throw new UnsupportedOperationException(); + } +} diff --git a/jOOQ/src/main/java/org/jooq/impl/Util.java b/jOOQ/src/main/java/org/jooq/impl/Util.java index 4fc7dcbc7f..e2ca03e823 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Util.java +++ b/jOOQ/src/main/java/org/jooq/impl/Util.java @@ -35,6 +35,8 @@ */ package org.jooq.impl; +import static org.jooq.impl.Factory.val; + import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -173,7 +175,7 @@ final class Util { context.sql(split[i]); if (i < bindings.length) { - FieldTypeHelper.toSQL(context, bindings[i]); + context.sql(val(bindings[i])); } } } diff --git a/jOOQ/src/main/java/org/jooq/impl/Val.java b/jOOQ/src/main/java/org/jooq/impl/Val.java new file mode 100644 index 0000000000..cc9f00b93b --- /dev/null +++ b/jOOQ/src/main/java/org/jooq/impl/Val.java @@ -0,0 +1,307 @@ +/** + * Copyright (c) 2009-2011, 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.math.BigDecimal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.jooq.ArrayRecord; +import org.jooq.Attachable; +import org.jooq.BindContext; +import org.jooq.DataType; +import org.jooq.EnumType; +import org.jooq.MasterDataType; +import org.jooq.Param; +import org.jooq.RenderContext; +import org.jooq.SQLDialect; +import org.jooq.UDTRecord; +import org.jooq.tools.StringUtils; + +/** + * @author Lukas Eder + */ +class Val extends AbstractField implements Param { + + private static final long serialVersionUID = 6807729087019209084L; + private final String paramName; + private T value; + + Val(T value, DataType type) { + this(value, type, null); + } + + Val(T value, DataType type, String paramName) { + super(name(value, paramName), type); + + this.paramName = paramName; + this.value = value; + } + + private static String name(Object value, String paramName) { + return paramName == null ? String.valueOf(value) : paramName; + } + + // ------------------------------------------------------------------------ + // Field API + // ------------------------------------------------------------------------ + + @Override + public final List getAttachables() { + return Collections.emptyList(); + } + + @Override + public final void toSQL(RenderContext context) { + + // Casting is only done when parameters are NOT inlined + if (!context.inline()) { + + // Generated enums should not be cast... + // The exception's exception + if (!(getValue() instanceof EnumType) && !(getValue() instanceof MasterDataType)) { + switch (context.getDialect()) { + + // These dialects can hardly detect the type of a bound constant. + case DB2: + case DERBY: + + // These dialects have some trouble, when they mostly get it right. + case H2: + case HSQLDB: + + // [#722] TODO This is probably not entirely right. + case INGRES: + + // [#632] Sybase needs explicit casting in very rare cases. + case SYBASE: { + toSQLCast(context); + return; + } + } + } + } + + // Most RDBMS can handle constants as typeless literals + toSQL(context, getValue(), this.getType()); + } + + /** + * Render the bind variable including a cast, if necessary + */ + private void toSQLCast(RenderContext context) { + switch (context.getDialect()) { + + // [#822] Some RDBMS need precision / scale information on BigDecimals + case DB2: + case DERBY: + case HSQLDB: { + + // Add precision / scale on BigDecimals + if (getType() == BigDecimal.class) { + int scale = ((BigDecimal) getValue()).scale(); + int precision = scale + ((BigDecimal) getValue()).precision(); + + context.sql("cast(") + .sql(getBindVariable(context)) + .sql(" as ") + .sql(getDataType(context).getCastTypeName(context, precision, scale)) + .sql(")"); + break; + } + + // No break, fall through + else { + } + } + + // These dialects don't need precision / scale info on BigDecimals + case H2: + case INGRES: + case SYBASE: { + context.sql("cast(") + .sql(getBindVariable(context)) + .sql(" as ") + .sql(getDataType(context).getCastTypeName(context)) + .sql(")"); + } + } + } + + /** + * Get a bind variable, depending on value of + * {@link RenderContext#namedParams()} + */ + private final String getBindVariable(RenderContext context) { + if (context.namedParams()) { + int index = context.nextIndex(); + + if (StringUtils.isBlank(getParamName())) { + return ":" + index; + } + else { + return ":" + getName(); + } + } + else { + return "?"; + } + } + + /** + * Inlining abstraction + */ + private void toSQL(RenderContext context, Object val) { + if (val == null) { + toSQL(context, val, Object.class); + } + else { + toSQL(context, val, val.getClass()); + } + } + + /** + * Inlining abstraction + */ + private void toSQL(RenderContext context, Object val, Class type) { + if (context.inline()) { + if (val == null) { + context.sql("null"); + } + else if (type == Boolean.class) { + context.sql(val.toString()); + } + else if (type == byte[].class) { + + // TODO [#990] This can cause issues + context.sql("'") + .sql(new String((byte[]) val).replace("'", "''")) + .sql("'"); + } + else if (Number.class.isAssignableFrom(type)) { + context.sql(val.toString()); + } + else if (type.isArray()) { + context.sql("ARRAY") + .sql(Arrays.asList((Object[]) val).toString()); + } + else if (ArrayRecord.class.isAssignableFrom(type)) { + context.sql(val.toString()); + } + else if (EnumType.class.isAssignableFrom(type)) { + toSQL(context, ((EnumType) val).getLiteral()); + } + else if (MasterDataType.class.isAssignableFrom(type)) { + toSQL(context, ((MasterDataType) val).getPrimaryKey()); + } + else if (UDTRecord.class.isAssignableFrom(type)) { + context.sql("[UDT]"); + } + + // Known fall-through types: + // - Blob, Clob (both not supported by jOOQ) + // - String + // - java.util.Date subtypes + else { + context.sql("'") + .sql(val.toString().replace("'", "''")) + .sql("'"); + } + } + + // In Postgres, some additional casting must be done in some cases... + // TODO: Improve this implementation with [#215] (cast support) + else if (context.getDialect() == SQLDialect.POSTGRES) { + + // Postgres needs explicit casting for array types + if (type.isArray() && byte[].class != type) { + context.sql(getBindVariable(context)); + context.sql("::"); + context.sql(FieldTypeHelper.getDataType(context.getDialect(), type).getCastTypeName(context)); + } + + // ... and also for enum types + else if (EnumType.class.isAssignableFrom(type)) { + context.sql(getBindVariable(context)); + context.sql("::"); + context.literal(((EnumType) val).getName()); + } + + else { + context.sql(getBindVariable(context)); + } + } + + else { + context.sql(getBindVariable(context)); + } + } + + @Override + public final void bind(BindContext context) { + context.bindValue(getValue(), getType()); + } + + @Override + public final boolean isNullLiteral() { + return getValue() == null; + } + + // ------------------------------------------------------------------------ + // Param API + // ------------------------------------------------------------------------ + + @Override + public final void setValue(T value) { + setConverted(value); + } + + @Override + public final void setConverted(Object value) { + this.value = getDataType().convert(value); + } + + @Override + public final T getValue() { + return value; + } + + @Override + public final String getParamName() { + return paramName; + } +} diff --git a/jOOQ/src/test/java/org/jooq/test/jOOQTest.java b/jOOQ/src/test/java/org/jooq/test/jOOQTest.java index f4fba0a568..6200fd401e 100644 --- a/jOOQ/src/test/java/org/jooq/test/jOOQTest.java +++ b/jOOQ/src/test/java/org/jooq/test/jOOQTest.java @@ -48,6 +48,7 @@ import static org.jooq.impl.Factory.falseCondition; import static org.jooq.impl.Factory.field; import static org.jooq.impl.Factory.max; import static org.jooq.impl.Factory.min; +import static org.jooq.impl.Factory.param; import static org.jooq.impl.Factory.replace; import static org.jooq.impl.Factory.round; import static org.jooq.impl.Factory.sum; @@ -67,6 +68,8 @@ import java.sql.Connection; import java.sql.Date; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -83,6 +86,8 @@ import org.jooq.Insert; import org.jooq.InsertQuery; import org.jooq.Merge; import org.jooq.Operator; +import org.jooq.Param; +import org.jooq.Query; import org.jooq.RenderContext; import org.jooq.Select; import org.jooq.SelectFinalStep; @@ -184,8 +189,12 @@ public class jOOQTest { return r_decT().inline(true); } + protected final RenderContext r_refP() { + return r_ref().namedParams(true); + } + @Test - public final void testNullPointerExceptionSafety() throws Exception { + public void testNullPointerExceptionSafety() throws Exception { // Functions created from a field // ------------------------------ assertEquals( @@ -524,7 +533,7 @@ public class jOOQTest { } @Test - public final void testTruncate() throws Exception { + public void testTruncate() throws Exception { Truncate t = create.truncate(TABLE1); assertEquals("truncate table \"TABLE1\"", r_dec().render(t)); @@ -532,7 +541,7 @@ public class jOOQTest { } @Test - public final void testAliasing() throws Exception { + public void testAliasing() throws Exception { assertEquals("\"TABLE1\"", r_decT().render(TABLE1)); assertEquals("\"TABLE1\"", r_decF().render(TABLE1)); assertEquals("\"TABLE1\"", r_ref().render(TABLE1)); @@ -581,7 +590,7 @@ public class jOOQTest { } @Test - public final void testMultipleCombinedCondition() throws Exception { + public void testMultipleCombinedCondition() throws Exception { Condition c1 = FIELD_ID1.equal(10); Condition c2 = FIELD_ID2.equal(20); Condition c3 = FIELD_ID1.equal(30); @@ -617,7 +626,7 @@ public class jOOQTest { } @Test - public final void testBetweenCondition() throws Exception { + public void testBetweenCondition() throws Exception { Condition c = FIELD_ID1.between(1, 10); assertEquals("\"TABLE1\".\"ID1\" between 1 and 10", r_refI().render(c)); assertEquals("\"TABLE1\".\"ID1\" between ? and ?", r_ref().render(c)); @@ -634,7 +643,7 @@ public class jOOQTest { } @Test - public final void testInCondition() throws Exception { + public void testInCondition() throws Exception { Condition c = FIELD_ID1.in(new Integer[0]); assertEquals(falseCondition(), c); @@ -657,7 +666,7 @@ public class jOOQTest { } @Test - public final void testInSelectCondition() throws Exception { + public void testInSelectCondition() throws Exception { Condition c = FIELD_ID1.in(create.selectFrom(TABLE1).where(FIELD_NAME1.equal("x"))); assertEquals("\"TABLE1\".\"ID1\" in (select \"TABLE1\".\"ID1\", \"TABLE1\".\"NAME1\", \"TABLE1\".\"DATE1\" from \"TABLE1\" where \"TABLE1\".\"NAME1\" = 'x')", r_refI().render(c)); assertEquals("\"TABLE1\".\"ID1\" in (select \"TABLE1\".\"ID1\", \"TABLE1\".\"NAME1\", \"TABLE1\".\"DATE1\" from \"TABLE1\" where \"TABLE1\".\"NAME1\" = ?)", r_ref().render(c)); @@ -677,7 +686,7 @@ public class jOOQTest { } @Test - public final void testCompareCondition() throws Exception { + public void testCompareCondition() throws Exception { Condition c = FIELD_ID1.equal(10); assertEquals("\"TABLE1\".\"ID1\" = 10", r_refI().render(c)); assertEquals("\"TABLE1\".\"ID1\" = ?", r_ref().render(c)); @@ -693,7 +702,7 @@ public class jOOQTest { } @Test - public final void testNotCondition() throws Exception { + public void testNotCondition() throws Exception { Condition c = FIELD_ID1.equal(10).not(); assertEquals("not(\"TABLE1\".\"ID1\" = 10)", r_refI().render(c)); assertEquals("not(\"TABLE1\".\"ID1\" = ?)", r_ref().render(c)); @@ -712,7 +721,7 @@ public class jOOQTest { } @Test - public final void testPlainSQLCondition() throws Exception { + public void testPlainSQLCondition() throws Exception { Condition c1 = condition("TABLE1.ID = 10"); Condition c2 = condition("TABLE1.ID = ? and TABLE2.ID = ?", 10, "20"); @@ -734,7 +743,7 @@ public class jOOQTest { } @Test - public final void testCustomCondition() throws Exception { + public void testCustomCondition() throws Exception { Condition c = new CustomCondition() { private static final long serialVersionUID = 6302350477408137757L; @@ -772,7 +781,7 @@ public class jOOQTest { } @Test - public final void testPlainSQLField() throws Exception { + public void testPlainSQLField() throws Exception { Field f1 = field("DECODE(TABLE1.ID, 1, 'a', 'b')"); Field f2 = field("DECODE(TABLE1.ID, 1, ?, ?)", "a", "b"); @@ -794,7 +803,7 @@ public class jOOQTest { } @Test - public final void testCustomField() throws Exception { + public void testCustomField() throws Exception { Field f = new CustomField("test", TestDataType.INTEGER_TYPE) { private static final long serialVersionUID = 1L; @@ -827,7 +836,7 @@ public class jOOQTest { } @Test - public final void testIsNullCondition() throws Exception { + public void testIsNullCondition() throws Exception { Condition c1 = FIELD_ID1.isNull(); assertEquals("\"TABLE1\".\"ID1\" is null", r_refI().render(c1)); assertEquals("\"TABLE1\".\"ID1\" is null", r_ref().render(c1)); @@ -844,7 +853,7 @@ public class jOOQTest { } @Test - public final void testCaseValueFunction() throws Exception { + public void testCaseValueFunction() throws Exception { Case decode = decode(); CaseValueStep value = decode.value(FIELD_ID1); CaseWhenStep c = value.when(1, "one"); @@ -883,7 +892,7 @@ public class jOOQTest { } @Test - public final void testCaseConditionFunction() throws Exception { + public void testCaseConditionFunction() throws Exception { Case decode = decode(); CaseConditionStep c = decode.when(FIELD_ID1.equal(1), "one"); @@ -921,7 +930,7 @@ public class jOOQTest { } @Test - public final void testNullFunction() throws Exception { + public void testNullFunction() throws Exception { Field f = val((Object) null); assertEquals("null", r_refI().render(f)); assertEquals("null", r_ref().render(f)); @@ -931,7 +940,7 @@ public class jOOQTest { } @Test - public final void testConstantFunction() throws Exception { + public void testConstantFunction() throws Exception { Field f1 = val(Integer.valueOf(1)); assertEquals(Integer.class, f1.getType()); assertEquals("1", r_refI().render(f1)); @@ -971,7 +980,7 @@ public class jOOQTest { } @Test - public final void testArithmeticSumExpressions() throws Exception { + public void testArithmeticSumExpressions() throws Exception { Field sum1 = FIELD_ID1.add(FIELD_ID1).add(1).add(2); assertEquals(Integer.class, sum1.getType()); assertEquals("(\"TABLE1\".\"ID1\" + \"TABLE1\".\"ID1\" + 1 + 2)", r_refI().render(sum1)); @@ -999,7 +1008,7 @@ public class jOOQTest { } @Test - public final void testArithmeticDifferenceExpressions() throws Exception { + public void testArithmeticDifferenceExpressions() throws Exception { Field difference1 = FIELD_ID1.sub(FIELD_ID1).sub(1).sub(2); assertEquals(Integer.class, difference1.getType()); assertEquals("(((\"TABLE1\".\"ID1\" - \"TABLE1\".\"ID1\") - 1) - 2)", r_refI().render(difference1)); @@ -1027,7 +1036,7 @@ public class jOOQTest { } @Test - public final void testArithmeticProductExpressions() throws Exception { + public void testArithmeticProductExpressions() throws Exception { Field product1 = FIELD_ID1.mul(FIELD_ID1).mul(1).mul(2); assertEquals(Integer.class, product1.getType()); assertEquals("(\"TABLE1\".\"ID1\" * \"TABLE1\".\"ID1\" * 1 * 2)", r_refI().render(product1)); @@ -1055,7 +1064,7 @@ public class jOOQTest { } @Test - public final void testArithmeticDivisionExpressions() throws Exception { + public void testArithmeticDivisionExpressions() throws Exception { Field division1 = FIELD_ID1.div(FIELD_ID1).div(1).div(2); assertEquals(Integer.class, division1.getType()); assertEquals("(((\"TABLE1\".\"ID1\" / \"TABLE1\".\"ID1\") / 1) / 2)", r_refI().render(division1)); @@ -1083,14 +1092,14 @@ public class jOOQTest { } @Test - public final void testFunctions() { + public void testFunctions() { Field f = replace(FIELD_NAME1, "a", "b"); assertEquals("replace(\"TABLE1\".\"NAME1\", 'a', 'b')", r_refI().render(f)); assertEquals("replace(\"TABLE1\".\"NAME1\", ?, ?)", r_ref().render(f)); } @Test - public final void testArithmeticExpressions() { + public void testArithmeticExpressions() { Field f; f = FIELD_ID1.add(1).sub(2).add(3); @@ -1119,7 +1128,7 @@ public class jOOQTest { } @Test - public final void testArithmeticFunctions() throws Exception { + public void testArithmeticFunctions() throws Exception { Field sum1 = sum(FIELD_ID1); assertEquals(BigDecimal.class, sum1.getType()); assertEquals("sum(\"TABLE1\".\"ID1\")", r_refI().render(sum1)); @@ -1234,7 +1243,7 @@ public class jOOQTest { } @Test - public final void testInsertQuery1() throws Exception { + public void testInsertQuery1() throws Exception { InsertQuery q = create.insertQuery(TABLE1); q.addValue(FIELD_ID1, 10); @@ -1254,7 +1263,7 @@ public class jOOQTest { } @Test - public final void testInsertQuery2() throws Exception { + public void testInsertQuery2() throws Exception { InsertQuery q = create.insertQuery(TABLE1); q.addValue(FIELD_ID1, 10); @@ -1278,7 +1287,7 @@ public class jOOQTest { } @Test - public final void testInsertSelect1() throws Exception { + public void testInsertSelect1() throws Exception { InsertQuery q = create.insertQuery(TABLE1); q.addValue(FIELD_ID1, round(val(10))); @@ -1298,7 +1307,7 @@ public class jOOQTest { } @Test - public final void testInsertSelect2() throws Exception { + public void testInsertSelect2() throws Exception { Insert q = create.insertInto(TABLE1, create.selectQuery()); assertEquals("insert into \"TABLE1\" (\"ID1\", \"NAME1\", \"DATE1\") select 1 from dual", r_refI().render(q)); @@ -1321,7 +1330,7 @@ public class jOOQTest { } @Test - public final void testUpdateQuery1() throws Exception { + public void testUpdateQuery1() throws Exception { UpdateQuery q = create.updateQuery(TABLE1); q.addValue(FIELD_ID1, 10); @@ -1340,7 +1349,7 @@ public class jOOQTest { } @Test - public final void testUpdateQuery2() throws Exception { + public void testUpdateQuery2() throws Exception { UpdateQuery q = create.updateQuery(TABLE1); q.addValue(FIELD_ID1, 10); @@ -1361,7 +1370,7 @@ public class jOOQTest { } @Test - public final void testUpdateQuery3() throws Exception { + public void testUpdateQuery3() throws Exception { UpdateQuery q = create.updateQuery(TABLE1); Condition c = FIELD_ID1.equal(10); @@ -1385,7 +1394,7 @@ public class jOOQTest { } @Test - public final void testUpdateQuery4() throws Exception { + public void testUpdateQuery4() throws Exception { UpdateQuery q = create.updateQuery(TABLE1); Condition c1 = FIELD_ID1.equal(10); Condition c2 = FIELD_ID1.equal(20); @@ -1412,7 +1421,7 @@ public class jOOQTest { } @Test - public final void testUpdateQuery5() throws Exception { + public void testUpdateQuery5() throws Exception { UpdateQuery q = create.updateQuery(TABLE1); Condition c1 = FIELD_ID1.equal(10); Condition c2 = FIELD_ID1.equal(20); @@ -1442,7 +1451,7 @@ public class jOOQTest { } @Test - public final void testMergeQuery() throws Exception { + public void testMergeQuery() throws Exception { Merge q = create.mergeInto(TABLE1) .using(create.select(FIELD_ID2).from(TABLE2)) @@ -1475,7 +1484,7 @@ public class jOOQTest { } @Test - public final void testDeleteQuery1() throws Exception { + public void testDeleteQuery1() throws Exception { DeleteQuery q = create.deleteQuery(TABLE1); assertEquals("delete from \"TABLE1\"", r_refI().render(q)); @@ -1484,7 +1493,7 @@ public class jOOQTest { } @Test - public final void testDeleteQuery2() throws Exception { + public void testDeleteQuery2() throws Exception { DeleteQuery q = create.deleteQuery(TABLE1); q.addConditions(falseCondition()); @@ -1494,7 +1503,7 @@ public class jOOQTest { } @Test - public final void testDeleteQuery3() throws Exception { + public void testDeleteQuery3() throws Exception { DeleteQuery q = create.deleteQuery(TABLE1); Condition c1 = FIELD_ID1.equal(10); Condition c2 = FIELD_ID1.equal(20); @@ -1517,7 +1526,7 @@ public class jOOQTest { } @Test - public final void testDeleteQuery4() throws Exception { + public void testDeleteQuery4() throws Exception { DeleteQuery q = create.deleteQuery(TABLE1); Condition c1 = FIELD_ID1.equal(10); Condition c2 = FIELD_ID1.equal(20); @@ -1543,7 +1552,7 @@ public class jOOQTest { } @Test - public final void testConditionalSelectQuery1() throws Exception { + public void testConditionalSelectQuery1() throws Exception { Select q = create.selectQuery(); Select s = create.select(); @@ -1553,7 +1562,7 @@ public class jOOQTest { } @Test - public final void testConditionalSelectQuery2() throws Exception { + public void testConditionalSelectQuery2() throws Exception { SelectQuery q = create.selectQuery(); q.addConditions(falseCondition()); @@ -1563,7 +1572,7 @@ public class jOOQTest { } @Test - public final void testConditionalSelectQuery3() throws Exception { + public void testConditionalSelectQuery3() throws Exception { SelectQuery q = create.selectQuery(); q.addConditions(falseCondition()); @@ -1574,7 +1583,7 @@ public class jOOQTest { } @Test - public final void testConditionalSelectQuery4() throws Exception { + public void testConditionalSelectQuery4() throws Exception { SelectQuery q = create.selectQuery(); Condition c1 = FIELD_ID1.equal(10); Condition c2 = FIELD_ID1.equal(20); @@ -1600,7 +1609,7 @@ public class jOOQTest { } @Test - public final void testConditionalSelectQuery5() throws Exception { + public void testConditionalSelectQuery5() throws Exception { SelectQuery q = create.selectQuery(); Condition c1 = condition("\"TABLE1\".\"ID1\" = ?", "10"); Condition c2 = condition("\"TABLE2\".\"ID2\" = 20 or \"TABLE2\".\"ID2\" = ?", 30); @@ -1623,7 +1632,7 @@ public class jOOQTest { } @Test - public final void testDistinctSelectQuery() throws Exception { + public void testDistinctSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addSelect(FIELD_ID1, FIELD_ID2); q.setDistinct(true); @@ -1637,7 +1646,7 @@ public class jOOQTest { } @Test - public final void testProductSelectQuery() throws Exception { + public void testProductSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); @@ -1652,7 +1661,7 @@ public class jOOQTest { } @Test - public final void testJoinSelectQuery() throws Exception { + public void testJoinSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); @@ -1666,7 +1675,7 @@ public class jOOQTest { } @Test - public final void testJoinOnConditionSelectQuery() throws Exception { + public void testJoinOnConditionSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); q.addJoin(TABLE2, FIELD_ID1.equal(FIELD_ID2)); @@ -1687,7 +1696,7 @@ public class jOOQTest { } @Test - public final void testJoinComplexSelectQuery() throws Exception { + public void testJoinComplexSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); @@ -1741,7 +1750,7 @@ public class jOOQTest { } @Test - public final void testJoinSelf() throws Exception { + public void testJoinSelf() throws Exception { Table t1 = TABLE1.as("t1"); Table t2 = TABLE1.as("t2"); @@ -1760,7 +1769,7 @@ public class jOOQTest { } @Test - public final void testJoinTypeSelectQuery() throws Exception { + public void testJoinTypeSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); q.addJoin(TABLE2, LEFT_OUTER_JOIN, FIELD_ID1.equal(FIELD_ID2)); @@ -1773,7 +1782,7 @@ public class jOOQTest { } @Test - public final void testGroupSelectQuery() throws Exception { + public void testGroupSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); @@ -1837,7 +1846,7 @@ public class jOOQTest { } @Test - public final void testOrderSelectQuery() throws Exception { + public void testOrderSelectQuery() throws Exception { SimpleSelectQuery q = create.selectQuery(TABLE1); q.addOrderBy(FIELD_ID1); @@ -1857,7 +1866,7 @@ public class jOOQTest { } @Test - public final void testCompleteSelectQuery() throws Exception { + public void testCompleteSelectQuery() throws Exception { SelectQuery q = create.selectQuery(); q.addFrom(TABLE1); q.addJoin(TABLE2, FIELD_ID1.equal(FIELD_ID2)); @@ -1889,7 +1898,7 @@ public class jOOQTest { } @Test - public final void testCombinedSelectQuery() throws Exception { + public void testCombinedSelectQuery() throws Exception { Select combine = createCombinedSelectQuery(); assertEquals("(select \"TABLE1\".\"ID1\", \"TABLE1\".\"NAME1\", \"TABLE1\".\"DATE1\" from \"TABLE1\" where \"TABLE1\".\"ID1\" = 1) union (select \"TABLE1\".\"ID1\", \"TABLE1\".\"NAME1\", \"TABLE1\".\"DATE1\" from \"TABLE1\" where \"TABLE1\".\"ID1\" = 2)", r_refI().render(combine)); @@ -1949,7 +1958,7 @@ public class jOOQTest { } @Test - public final void testInnerSelect1() throws Exception { + public void testInnerSelect1() throws Exception { SimpleSelectQuery q1 = create.selectQuery(TABLE1); SimpleSelectQuery q2 = create.selectQuery(q1.asTable().as("inner_temp_table")); SimpleSelectQuery q3 = create.selectQuery(q2.asTable().as("outer_temp_table")); @@ -1962,7 +1971,7 @@ public class jOOQTest { } @Test - public final void testInnerSelect2() throws Exception { + public void testInnerSelect2() throws Exception { SelectQuery q1 = create.selectQuery(); SelectQuery q2 = create.selectQuery(); @@ -1978,7 +1987,7 @@ public class jOOQTest { } @Test - public final void testInnerSelect3() throws Exception { + public void testInnerSelect3() throws Exception { SelectQuery q1 = create.selectQuery(); SelectQuery q2 = create.selectQuery(); @@ -1993,7 +2002,7 @@ public class jOOQTest { } @Test - public final void testInnerSelect4() throws Exception { + public void testInnerSelect4() throws Exception { SelectQuery q1 = create.selectQuery(); SelectQuery q2 = create.selectQuery(); @@ -2008,7 +2017,7 @@ public class jOOQTest { } @Test - public final void testInnerSelect5() throws Exception { + public void testInnerSelect5() throws Exception { SelectQuery q1 = create.selectQuery(); SelectQuery q2 = create.selectQuery(); @@ -2023,7 +2032,7 @@ public class jOOQTest { } @Test - public final void testInnerSelect6() throws Exception { + public void testInnerSelect6() throws Exception { SelectQuery q1 = create.selectQuery(); SelectQuery q2 = create.selectQuery(); @@ -2036,4 +2045,98 @@ public class jOOQTest { assertEquals("select \"TABLE1\".\"ID1\", \"TABLE1\".\"NAME1\", \"TABLE1\".\"DATE1\" from \"TABLE1\" where exists (select \"TABLE2\".\"ID2\" from \"TABLE2\")", r_refI().render(q1)); assertEquals("select \"TABLE1\".\"ID1\", \"TABLE1\".\"NAME1\", \"TABLE1\".\"DATE1\" from \"TABLE1\" where exists (select \"TABLE2\".\"ID2\" from \"TABLE2\")", r_ref().render(q1)); } + + @Test + public void testNamedParams() throws Exception { + Query q1 = create.select(val(1)).from(TABLE1).where(FIELD_ID1.equal(val(2))); + Query q2 = create.select(param("p1", 1)).from(TABLE1).where(FIELD_ID1.equal(param("p2", 2))); + Query q3 = create.select(param("p1", 1)).from(TABLE1).where(FIELD_ID1.equal(2)); + Query q4 = create.select(val(1)).from(TABLE1).where(FIELD_ID1.equal(param("p2", 2))); + + assertEquals("select 1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 2", r_refI().render(q1)); + assertEquals("select :1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :2", r_refP().render(q1)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q1)); + + assertEquals("select 1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 2", r_refI().render(q2)); + assertEquals("select :p1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :p2", r_refP().render(q2)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q2)); + + assertEquals("select 1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 2", r_refI().render(q3)); + assertEquals("select :p1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :2", r_refP().render(q3)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q3)); + + assertEquals("select 1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 2", r_refI().render(q4)); + assertEquals("select :1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :p2", r_refP().render(q4)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q4)); + + // Param / Val queries should be equal as toString() doesn't consider params + assertEquals(q1, q2); + assertEquals(q1, q3); + assertEquals(q1, q4); + + // Params + Param p11 = q1.getParam("1"); + Param p21 = q2.getParam("p1"); + Param p31 = q3.getParam("p1"); + Param p41 = q4.getParam("1"); + + Param p12 = q1.getParam("2"); + Param p22 = q2.getParam("p2"); + Param p32 = q3.getParam("2"); + Param p42 = q4.getParam("p2"); + + assertEquals(Arrays.asList("1", "2"), new ArrayList(q1.getParams().keySet())); + assertEquals(Arrays.asList("p1", "p2"), new ArrayList(q2.getParams().keySet())); + assertEquals(Arrays.asList("p1", "2"), new ArrayList(q3.getParams().keySet())); + assertEquals(Arrays.asList("1", "p2"), new ArrayList(q4.getParams().keySet())); + + // Types + assertEquals(Integer.class, p11.getType()); + assertEquals(Integer.class, p21.getType()); + assertEquals(Integer.class, p31.getType()); + assertEquals(Integer.class, p41.getType()); + + assertEquals(Integer.class, p12.getType()); + assertEquals(Integer.class, p22.getType()); + assertEquals(Integer.class, p32.getType()); + assertEquals(Integer.class, p42.getType()); + + // Values + assertEquals(Integer.valueOf(1), p11.getValue()); + assertEquals(Integer.valueOf(1), p21.getValue()); + assertEquals(Integer.valueOf(1), p31.getValue()); + assertEquals(Integer.valueOf(1), p41.getValue()); + + assertEquals(Integer.valueOf(2), p12.getValue()); + assertEquals(Integer.valueOf(2), p22.getValue()); + assertEquals(Integer.valueOf(2), p32.getValue()); + assertEquals(Integer.valueOf(2), p42.getValue()); + + // Param replacement + p11.setConverted(3); + p21.setConverted(3); + p31.setConverted(3); + p41.setConverted(3); + + p12.setConverted(4); + p22.setConverted(4); + p32.setConverted(4); + p42.setConverted(4); + + assertEquals("select 3 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 4", r_refI().render(q1)); + assertEquals("select :1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :2", r_refP().render(q1)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q1)); + + assertEquals("select 3 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 4", r_refI().render(q2)); + assertEquals("select :p1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :p2", r_refP().render(q2)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q2)); + + assertEquals("select 3 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 4", r_refI().render(q3)); + assertEquals("select :p1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :2", r_refP().render(q3)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q3)); + + assertEquals("select 3 from \"TABLE1\" where \"TABLE1\".\"ID1\" = 4", r_refI().render(q4)); + assertEquals("select :1 from \"TABLE1\" where \"TABLE1\".\"ID1\" = :p2", r_refP().render(q4)); + assertEquals("select ? from \"TABLE1\" where \"TABLE1\".\"ID1\" = ?", r_ref().render(q4)); + } }