[#834] Add support for the Firebird / Postgres UPDATE .. RETURNING

clause
This commit is contained in:
Lukas Eder 2013-02-12 16:22:04 +01:00
parent 5690504fea
commit cea755069d
13 changed files with 797 additions and 364 deletions

View File

@ -55,6 +55,7 @@ import static org.jooq.SQLDialect.SYBASE;
import static org.jooq.impl.Factory.cast;
import static org.jooq.impl.Factory.castNull;
import static org.jooq.impl.Factory.count;
import static org.jooq.impl.Factory.decode;
import static org.jooq.impl.Factory.falseCondition;
import static org.jooq.impl.Factory.inline;
import static org.jooq.impl.Factory.max;
@ -700,6 +701,59 @@ extends BaseTest<A, AP, B, S, B2S, BS, L, X, DATE, BOOL, D, T, U, UU, I, IPK, T7
assertEquals(2*ID, (int) triggered.getValue(TTriggers_COUNTER()));
}
@Test
public void testUpdateReturning() throws Exception {
switch (getDialect()) {
case ASE:
case CUBRID:
case DB2:
case DERBY:
case H2:
case HSQLDB:
case INGRES:
case MYSQL:
case ORACLE:
case SQLITE:
case SQLSERVER:
case SYBASE:
log.info("SKIPPING", "UPDATE .. RETURNING tests");
return;
}
jOOQAbstractTest.reset = false;
Result<?> result1 =
create().update(TBook())
.set(TBook_TITLE(), "XYZ")
.where(TBook_ID().eq(1))
.returning(TBook_ID(), TBook_TITLE())
.fetch();
assertEquals(1, result1.size());
assertEquals(1, (int) result1.get(0).getValue(TBook_ID()));
assertEquals("XYZ", result1.get(0).getValue(TBook_TITLE()));
switch (getDialect()) {
case FIREBIRD: {
break;
}
// Some databases do not support RETURNING clauses that affect more
// than one row.
default: {
Result<?> result2 =
create().update(TBook())
.set(TBook_TITLE(), decode().value(TBook_ID()).when(1, "ABC").otherwise(TBook_TITLE()))
.where(TBook_ID().in(1, 2))
.returning(TBook_ID(), TBook_TITLE())
.fetch();
assertEquals(2, result2.size());
assertEquals(asList(1, 2), result2.getValues(TBook_ID()));
assertEquals(asList("ABC", "Animal Farm"), result2.getValues(TBook_TITLE()));
}
}
}
@Test
public void testInsertOnDuplicateKeyUpdate() throws Exception {
switch (getDialect()) {

View File

@ -1378,6 +1378,11 @@ public abstract class jOOQAbstractTest<
new InsertUpdateTests(this).testInsertReturning();
}
@Test
public void testUpdateReturning() throws Exception {
new InsertUpdateTests(this).testUpdateReturning();
}
@Test
public void testMerge() throws Exception {
new InsertUpdateTests(this).testMerge();

View File

@ -170,107 +170,56 @@ public interface InsertQuery<R extends Record> extends StoreQuery<R>, Insert<R>
void addValuesForUpdate(Map<? extends Field<?>, ?> map);
/**
* Configure the <code>INSERT</code> statement to return all fields in
* <code>R</code>.
*
* @see #getReturnedRecords()
* {@inheritDoc}
* <p>
* This feature works with <code>INSERT</code> statements for all SQL dialects
*/
@Override
@Support
void setReturning();
/**
* Configure the <code>INSERT</code> statement to return the generated
* identity value.
*
* @param identity The table's identity
* @see #getReturnedRecords()
* {@inheritDoc}
* <p>
* This feature works with <code>INSERT</code> statements for all SQL dialects
*/
@Override
@Support
void setReturning(Identity<R, ? extends Number> identity);
/**
* Configure the <code>INSERT</code> statement to return a list of fields in
* <code>R</code>.
*
* @param fields Fields to be returned
* @see #getReturnedRecords()
* {@inheritDoc}
* <p>
* This feature works with <code>INSERT</code> statements for all SQL dialects
*/
@Override
@Support
void setReturning(Field<?>... fields);
/**
* Configure the <code>INSERT</code> statement to return a list of fields in
* <code>R</code>.
*
* @param fields Fields to be returned
* @see #getReturnedRecords()
* {@inheritDoc}
* <p>
* This feature works with <code>INSERT</code> statements for all SQL dialects
*/
@Override
@Support
void setReturning(Collection<? extends Field<?>> fields);
/**
* The record holding returned values as specified by any of the
* {@link #setReturning()} methods.
* {@inheritDoc}
* <p>
* If the insert statement returns several records, this is the same as
* calling <code>getReturnedRecords().get(0)</code>
* <p>
* This implemented differently for every dialect:
* <ul>
* <li>Firebird and Postgres have native support for
* <code>INSERT .. RETURNING</code> clauses</li>
* <li>HSQLDB, Oracle, and DB2 JDBC drivers allow for retrieving any table
* column as "generated key" in one statement</li>
* <li>Derby, H2, Ingres, MySQL, SQL Server only allow for retrieving
* IDENTITY column values as "generated key". If other fields are requested,
* a second statement is issued. Client code must assure transactional
* integrity between the two statements.</li>
* <li>Sybase and SQLite allow for retrieving IDENTITY values as
* <code>@@identity</code> or <code>last_inserted_rowid()</code> values.
* Those values are fetched in a separate <code>SELECT</code> statement. If
* other fields are requested, a second statement is issued. Client code
* must assure transactional integrity between the two statements.</li>
* </ul>
*
* @return The returned value as specified by any of the
* {@link #setReturning()} methods. This may return
* <code>null</code> in case jOOQ could not retrieve any generated
* keys from the JDBC driver.
* @see #getReturnedRecords()
* This feature works with <code>INSERT</code> statements for all SQL dialects
*/
@Override
@Support
R getReturnedRecord();
/**
* The records holding returned values as specified by any of the
* {@link #setReturning()} methods.
* {@inheritDoc}
* <p>
* This implemented differently for every dialect:
* <ul>
* <li>Firebird and Postgres have native support for
* <code>INSERT .. RETURNING</code> clauses</li>
* <li>HSQLDB, Oracle, and DB2 JDBC drivers allow for retrieving any table
* column as "generated key" in one statement</li>
* <li>Derby, H2, Ingres, MySQL, SQL Server only allow for retrieving
* IDENTITY column values as "generated key". If other fields are requested,
* a second statement is issued. Client code must assure transactional
* integrity between the two statements.</li>
* <li>Sybase and SQLite allow for retrieving IDENTITY values as
* <code>@@identity</code> or <code>last_inserted_rowid()</code> values.
* Those values are fetched in a separate <code>SELECT</code> statement. If
* other fields are requested, a second statement is issued. Client code
* must assure transactional integrity between the two statements.</li>
* </ul>
*
* @return The returned values as specified by any of the
* {@link #setReturning()} methods. Note:
* <ul>
* <li>Not all databases / JDBC drivers support returning several
* values on multi-row inserts!</li><li>This may return an empty
* <code>Result</code> in case jOOQ could not retrieve any generated
* keys from the JDBC driver.</li>
* </ul>
* This feature works with <code>INSERT</code> statements for all SQL dialects
*/
@Override
@Support
Result<R> getReturnedRecords();

View File

@ -35,6 +35,7 @@
*/
package org.jooq;
import java.util.Collection;
import java.util.Map;
/**
@ -83,4 +84,111 @@ public interface StoreQuery<R extends Record> extends Query {
@Support
void addValues(Map<? extends Field<?>, ?> map);
/**
* Configure the <code>INSERT</code> or <code>UPDATE</code> statement to return all fields in
* <code>R</code>.
*
* @see #getReturnedRecords()
*/
@Support
void setReturning();
/**
* Configure the <code>INSERT</code> or <code>UPDATE</code> statement to return the generated
* identity value.
*
* @param identity The table's identity
* @see #getReturnedRecords()
*/
@Support
void setReturning(Identity<R, ? extends Number> identity);
/**
* Configure the <code>INSERT</code> or <code>UPDATE</code> statement to return a list of fields in
* <code>R</code>.
*
* @param fields Fields to be returned
* @see #getReturnedRecords()
*/
@Support
void setReturning(Field<?>... fields);
/**
* Configure the <code>INSERT</code> or <code>UPDATE</code> statement to return a list of fields in
* <code>R</code>.
*
* @param fields Fields to be returned
* @see #getReturnedRecords()
*/
@Support
void setReturning(Collection<? extends Field<?>> fields);
/**
* The record holding returned values as specified by any of the
* {@link #setReturning()} methods.
* <p>
* If the insert statement returns several records, this is the same as
* calling <code>getReturnedRecords().get(0)</code>
* <p>
* This implemented differently for every dialect:
* <ul>
* <li>Firebird and Postgres have native support for
* <code>INSERT .. RETURNING</code> and <code>UPDATE .. RETURNING</code>
* clauses</li>
* <li>HSQLDB, Oracle, and DB2 JDBC drivers allow for retrieving any table
* column as "generated key" in one statement</li>
* <li>Derby, H2, Ingres, MySQL, SQL Server only allow for retrieving
* IDENTITY column values as "generated key". If other fields are requested,
* a second statement is issued. Client code must assure transactional
* integrity between the two statements.</li>
* <li>Sybase and SQLite allow for retrieving IDENTITY values as
* <code>@@identity</code> or <code>last_inserted_rowid()</code> values.
* Those values are fetched in a separate <code>SELECT</code> statement. If
* other fields are requested, a second statement is issued. Client code
* must assure transactional integrity between the two statements.</li>
* </ul>
*
* @return The returned value as specified by any of the
* {@link #setReturning()} methods. This may return
* <code>null</code> in case jOOQ could not retrieve any generated
* keys from the JDBC driver.
* @see #getReturnedRecords()
*/
@Support
R getReturnedRecord();
/**
* The records holding returned values as specified by any of the
* {@link #setReturning()} methods.
* <p>
* This implemented differently for every dialect:
* <ul>
* <li>Firebird and Postgres have native support for
* <code>INSERT .. RETURNING</code> and <code>UPDATE .. RETURNING</code>
* clauses</li>
* <li>HSQLDB, Oracle, and DB2 JDBC drivers allow for retrieving any table
* column as "generated key" in one statement</li>
* <li>Derby, H2, Ingres, MySQL, SQL Server only allow for retrieving
* IDENTITY column values as "generated key". If other fields are requested,
* a second statement is issued. Client code must assure transactional
* integrity between the two statements.</li>
* <li>Sybase and SQLite allow for retrieving IDENTITY values as
* <code>@@identity</code> or <code>last_inserted_rowid()</code> values.
* Those values are fetched in a separate <code>SELECT</code> statement. If
* other fields are requested, a second statement is issued. Client code
* must assure transactional integrity between the two statements.</li>
* </ul>
*
* @return The returned values as specified by any of the
* {@link #setReturning()} methods. Note:
* <ul>
* <li>Not all databases / JDBC drivers support returning several
* values on multi-row inserts!</li><li>This may return an empty
* <code>Result</code> in case jOOQ could not retrieve any generated
* keys from the JDBC driver.</li>
* </ul>
*/
@Support
Result<R> getReturnedRecords();
}

View File

@ -53,7 +53,7 @@ import org.jooq.impl.Factory;
*
* @author Lukas Eder
*/
public interface UpdateConditionStep<R extends Record> extends UpdateFinalStep<R> {
public interface UpdateConditionStep<R extends Record> extends UpdateFinalStep<R>, UpdateReturningStep<R> {
/**
* Combine the currently assembled conditions with another one using the

View File

@ -37,6 +37,7 @@
package org.jooq;
import static org.jooq.SQLDialect.DB2;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
import static org.jooq.SQLDialect.HSQLDB;
import static org.jooq.SQLDialect.INGRES;
@ -369,7 +370,7 @@ public interface UpdateQuery<R extends Record> extends StoreQuery<R>, ConditionP
// [jooq-tools] END [addValues]
// ------------------------------------------------------------------------
// Methods from ConditionProvider
// XXX: Methods from ConditionProvider
// ------------------------------------------------------------------------
/**
@ -400,4 +401,68 @@ public interface UpdateQuery<R extends Record> extends StoreQuery<R>, ConditionP
@Support
void addConditions(Operator operator, Collection<Condition> conditions);
// ------------------------------------------------------------------------
// XXX: Methods for the UPDATE .. RETURNING syntax
// ------------------------------------------------------------------------
/**
* {@inheritDoc}
* <p>
* This feature works with <code>UPDATE</code> statements for a subset of
* SQL dialects
*/
@Override
@Support({ FIREBIRD, POSTGRES })
void setReturning();
/**
* {@inheritDoc}
* <p>
* This feature works with <code>UPDATE</code> statements for a subset of
* SQL dialects
*/
@Override
@Support({ FIREBIRD, POSTGRES })
void setReturning(Identity<R, ? extends Number> identity);
/**
* {@inheritDoc}
* <p>
* This feature works with <code>UPDATE</code> statements for a subset of
* SQL dialects
*/
@Override
@Support({ FIREBIRD, POSTGRES })
void setReturning(Field<?>... fields);
/**
* {@inheritDoc}
* <p>
* This feature works with <code>UPDATE</code> statements for a subset of
* SQL dialects
*/
@Override
@Support({ FIREBIRD, POSTGRES })
void setReturning(Collection<? extends Field<?>> fields);
/**
* {@inheritDoc}
* <p>
* This feature works with <code>UPDATE</code> statements for a subset of
* SQL dialects
*/
@Override
@Support({ FIREBIRD, POSTGRES })
R getReturnedRecord();
/**
* {@inheritDoc}
* <p>
* This feature works with <code>UPDATE</code> statements for a subset of
* SQL dialects
*/
@Override
@Support({ FIREBIRD, POSTGRES })
Result<R> getReturnedRecords();
}

View File

@ -0,0 +1,100 @@
/**
* Copyright (c) 2009-2013, 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 static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.POSTGRES;
import org.jooq.exception.DataAccessException;
/**
* This type is used for the {@link Update}'s DSL API.
* <p>
* Example: <code><pre>
* Executor create = new Executor(config);
*
* TableRecord<?> record =
* create.update(table)
* .set(field1, value1)
* .set(field2, value2)
* .returning(field1)
* .fetchOne();
* </pre></code>
* <p>
* This implemented differently for every dialect:
* <ul>
* <li>Firebird and Postgres have native support for
* <code>UPDATE .. RETURNING</code> clauses</li>
* </ul>
*
* @author Lukas Eder
*/
public interface UpdateResultStep<R extends Record> extends Insert<R> {
/**
* The result holding returned values as specified by the
* {@link UpdateReturningStep}
* <p>
* This currently only works well for DB2, HSQLDB, MySQL, and Postgres
*
* @return The returned values as specified by the
* {@link UpdateReturningStep}. Note:
* <ul>
* <li>Not all databases / JDBC drivers support returning several
* values on multi-row inserts!</li><li>This may return an empty
* <code>Result</code> in case jOOQ could not retrieve any generated
* keys from the JDBC driver.</li>
* </ul>
* @throws DataAccessException if something went wrong executing the query
* @see UpdateQuery#getReturnedRecords()
*/
@Support({ FIREBIRD, POSTGRES })
Result<R> fetch() throws DataAccessException;
/**
* The record holding returned values as specified by the
* {@link UpdateReturningStep}
*
* @return The returned value as specified by the
* {@link UpdateReturningStep}. This may return <code>null</code> in
* case jOOQ could not retrieve any generated keys from the JDBC
* driver.
* @throws DataAccessException if something went wrong executing the query
* @see UpdateQuery#getReturnedRecord()
*/
@Support({ FIREBIRD, POSTGRES })
R fetchOne() throws DataAccessException;
}

View File

@ -0,0 +1,95 @@
/**
* Copyright (c) 2009-2013, 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 static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.POSTGRES;
import java.util.Collection;
/**
* This type is used for the {@link Update}'s DSL API.
* <p>
* Example: <code><pre>
* Executor create = new Executor(config);
*
* TableRecord<?> record =
* create.update(table)
* .set(field1, value1)
* .set(field2, value2)
* .returning(field1)
* .fetchOne();
* </pre></code>
* <p>
* This implemented differently for every dialect:
* <ul>
* <li>Firebird and Postgres have native support for
* <code>UPDATE .. RETURNING</code> clauses</li>
* </ul>
*
* @author Lukas Eder
*/
public interface UpdateReturningStep<R extends Record> {
/**
* Configure the <code>UPDATE</code> statement to return all fields in
* <code>R</code>.
*
* @see UpdateResultStep
*/
@Support({ FIREBIRD, POSTGRES })
UpdateResultStep<R> returning();
/**
* Configure the <code>UPDATE</code> statement to return a list of fields in
* <code>R</code>.
*
* @param fields Fields to be returned
* @see UpdateResultStep
*/
@Support({ FIREBIRD, POSTGRES })
UpdateResultStep<R> returning(Field<?>... fields);
/**
* Configure the <code>UPDATE</code> statement to return a list of fields in
* <code>R</code>.
*
* @param fields Fields to be returned
* @see UpdateResultStep
*/
@Support({ FIREBIRD, POSTGRES })
UpdateResultStep<R> returning(Collection<? extends Field<?>> fields);
}

View File

@ -54,7 +54,7 @@ import org.jooq.impl.Factory;
*
* @author Lukas Eder
*/
public interface UpdateWhereStep<R extends Record> extends UpdateFinalStep<R> {
public interface UpdateWhereStep<R extends Record> extends UpdateFinalStep<R>, UpdateReturningStep<R> {
/**
* Add conditions to the query

View File

@ -35,13 +35,33 @@
*/
package org.jooq.impl;
import static org.jooq.impl.Utils.fieldArray;
import static org.jooq.util.sqlite.SQLiteFactory.rowid;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.jooq.BindContext;
import org.jooq.Configuration;
import org.jooq.ExecuteContext;
import org.jooq.ExecuteListener;
import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.RenderContext;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.StoreQuery;
import org.jooq.Table;
import org.jooq.tools.jdbc.JDBCUtils;
/**
* A default implementation for store queries.
@ -53,14 +73,17 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
/**
* Generated UID
*/
private static final long serialVersionUID = 6864591335823160569L;
private static final long serialVersionUID = 6864591335823160569L;
private final Table<R> into;
private final Table<R> into;
private final QueryPartList<Field<?>> returning;
private Result<R> returned;
AbstractStoreQuery(Configuration configuration, Table<R> into) {
super(configuration);
this.into = into;
this.returning = new QueryPartList<Field<?>>();
}
protected abstract Map<Field<?>, Field<?>> getValues();
@ -82,4 +105,285 @@ abstract class AbstractStoreQuery<R extends Record> extends AbstractQuery implem
public final <T> void addValue(Field<T> field, Field<T> value) {
getValues().put(field, Utils.field(value, field));
}
@Override
public final void setReturning() {
setReturning(getInto().fields());
}
@Override
public final void setReturning(Identity<R, ? extends Number> identity) {
if (identity != null) {
setReturning(identity.getField());
}
}
@Override
public final void setReturning(Field<?>... fields) {
setReturning(Arrays.asList(fields));
}
@Override
public final void setReturning(Collection<? extends Field<?>> fields) {
returning.clear();
returning.addAll(fields);
}
@Override
public final R getReturnedRecord() {
if (getReturnedRecords().size() == 0) {
return null;
}
return getReturnedRecords().get(0);
}
@Override
public final Result<R> getReturnedRecords() {
if (returned == null) {
returned = new ResultImpl<R>(getConfiguration(), returning);
}
return returned;
}
final void toSQLReturning(RenderContext context) {
if (!returning.isEmpty()) {
switch (context.getDialect()) {
case FIREBIRD:
case POSTGRES:
context.formatSeparator()
.keyword("returning ")
.sql(returning);
break;
default:
// Other dialects don't render a RETURNING clause, but
// use JDBC's Statement.RETURN_GENERATED_KEYS mode instead
}
}
}
final void bindReturning(BindContext context) {
switch (context.getDialect()) {
case FIREBIRD:
case POSTGRES:
context.bind((QueryPart) returning);
break;
default:
// Other dialects don't bind a RETURNING clause, but
// use JDBC's Statement.RETURN_GENERATED_KEYS mode instead
}
}
@Override
protected final void prepare(ExecuteContext ctx) throws SQLException {
Connection connection = ctx.connection();
// Just in case, always set Sybase ASE statement mode to return
// Generated keys if client code wants to SELECT @@identity afterwards
if (ctx.getDialect() == SQLDialect.ASE) {
ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS));
return;
}
// Normal statement preparing if no values should be returned
else if (returning.isEmpty()) {
super.prepare(ctx);
return;
}
// Values should be returned from the INSERT
else {
switch (ctx.getDialect()) {
// Postgres uses the RETURNING clause in SQL
case FIREBIRD:
case POSTGRES:
// SQLite will select last_insert_rowid() after the INSER
case SQLITE:
// Sybase will select @@identity after the INSERT
case CUBRID:
case SYBASE:
super.prepare(ctx);
return;
// Some dialects can only return AUTO_INCREMENT values
// Other values have to be fetched in a second step
// [#1260] TODO CUBRID supports this, but there's a JDBC bug
case ASE:
case DERBY:
case H2:
case INGRES:
case MYSQL:
case SQLSERVER:
ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS));
return;
// The default is to return all requested fields directly
case DB2:
case HSQLDB:
case ORACLE:
default: {
List<String> names = new ArrayList<String>();
for (Field<?> field : returning) {
names.add(field.getName());
}
ctx.statement(connection.prepareStatement(ctx.sql(), names.toArray(new String[names.size()])));
return;
}
}
}
}
@Override
protected final int execute(ExecuteContext ctx, ExecuteListener listener) throws SQLException {
if (returning.isEmpty()) {
return super.execute(ctx, listener);
}
else {
int result = 1;
ResultSet rs;
switch (ctx.getDialect()) {
// SQLite can select _rowid_ after the insert
case SQLITE: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
Executor create = new Executor(ctx.connection(), SQLDialect.SQLITE, ctx.getSettings());
returned =
create.select(returning)
.from(getInto())
.where(rowid().equal(rowid().getDataType().convert(create.lastID())))
.fetchInto(getInto());
return result;
}
// Sybase can select @@identity after the insert
// TODO [#832] Fix this. This might be a driver issue. JDBC
// Generated keys don't work with jconn3, but they seem to work
// with jTDS (which is used for Sybase ASE integration)
case CUBRID:
case SYBASE: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
selectReturning(ctx.configuration(), create(ctx).lastID());
return result;
}
// Some dialects can only retrieve "identity" (AUTO_INCREMENT) values
// Additional values have to be fetched explicitly
// [#1260] TODO CUBRID supports this, but there's a JDBC bug
case ASE:
case DERBY:
case H2:
case INGRES:
case MYSQL:
case SQLSERVER: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
rs = ctx.statement().getGeneratedKeys();
try {
List<Object> list = new ArrayList<Object>();
// Some JDBC drivers seem to illegally return null
// from getGeneratedKeys() sometimes
if (rs != null) {
while (rs.next()) {
list.add(rs.getObject(1));
}
}
selectReturning(ctx, list.toArray());
return result;
}
finally {
JDBCUtils.safeClose(rs);
}
}
// Firebird and Postgres can execute the INSERT .. RETURNING
// clause like a select clause. JDBC support is not implemented
// in the Postgres JDBC driver
case FIREBIRD:
case POSTGRES: {
listener.executeStart(ctx);
rs = ctx.statement().executeQuery();
listener.executeEnd(ctx);
break;
}
// These dialects have full JDBC support
case DB2:
case HSQLDB:
case ORACLE:
default: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
rs = ctx.statement().getGeneratedKeys();
break;
}
}
ExecuteContext ctx2 = new DefaultExecuteContext(ctx.configuration());
ExecuteListener listener2 = new ExecuteListeners(ctx2);
ctx2.resultSet(rs);
returned = new CursorImpl<R>(ctx2, listener2, fieldArray(returning), null, false).fetch().into(getInto());
return result;
}
}
/**
* Get the returning record in those dialects that do not support fetching
* arbitrary fields from JDBC's {@link Statement#getGeneratedKeys()} method.
*/
@SuppressWarnings("unchecked")
private final void selectReturning(Configuration configuration, Object... values) {
if (values != null && values.length > 0) {
// This shouldn't be null, as relevant dialects should
// return empty generated keys ResultSet
if (into.getIdentity() != null) {
Field<Number> field = (Field<Number>) into.getIdentity().getField();
Number[] ids = new Number[values.length];
for (int i = 0; i < values.length; i++) {
ids[i] = field.getDataType().convert(values[i]);
}
// Only the IDENTITY value was requested. No need for an
// additional query
if (returning.size() == 1 && returning.get(0).equals(field)) {
for (Number id : ids) {
R typed = Utils.newRecord(into, configuration);
((AbstractRecord) typed).setValue(field, new Value<Number>(id));
getReturnedRecords().add(typed);
}
}
// Other values are requested, too. Run another query
else {
returned =
create(configuration).select(returning)
.from(into)
.where(field.in(ids))
.fetchInto(into);
}
}
}
}
}

View File

@ -37,35 +37,21 @@
package org.jooq.impl;
import static org.jooq.SQLDialect.MYSQL;
import static org.jooq.impl.Utils.fieldArray;
import static org.jooq.util.sqlite.SQLiteFactory.rowid;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.jooq.BindContext;
import org.jooq.Condition;
import org.jooq.Configuration;
import org.jooq.ExecuteContext;
import org.jooq.ExecuteListener;
import org.jooq.Field;
import org.jooq.Identity;
import org.jooq.InsertQuery;
import org.jooq.Merge;
import org.jooq.MergeNotMatchedStep;
import org.jooq.MergeOnConditionStep;
import org.jooq.QueryPart;
import org.jooq.Record;
import org.jooq.RenderContext;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.Table;
import org.jooq.UpdatableTable;
import org.jooq.exception.SQLDialectNotSupportedException;
@ -79,8 +65,6 @@ class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
private final FieldMapForUpdate updateMap;
private final FieldMapsForInsert insertMaps;
private final QueryPartList<Field<?>> returning;
private Result<R> returned;
private boolean onDuplicateKeyUpdate;
private boolean onDuplicateKeyIgnore;
@ -89,7 +73,6 @@ class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
updateMap = new FieldMapForUpdate();
insertMaps = new FieldMapsForInsert();
returning = new QueryPartList<Field<?>>();
}
@Override
@ -334,20 +317,7 @@ class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
.sql(" ")
.sql(insertMaps);
if (!returning.isEmpty()) {
switch (context.getDialect()) {
case FIREBIRD:
case POSTGRES:
context.formatSeparator()
.keyword("returning ")
.sql(returning);
break;
default:
// Other dialects don't render a RETURNING clause, but
// use JDBC's Statement.RETURN_GENERATED_KEYS mode instead
}
}
toSQLReturning(context);
}
private final void bindInsert(BindContext context) {
@ -355,16 +325,7 @@ class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
.bind(insertMaps)
.bind(updateMap);
switch (context.getDialect()) {
case FIREBIRD:
case POSTGRES:
context.bind((QueryPart) returning);
break;
default:
// Other dialects don't bind a RETURNING clause, but
// use JDBC's Statement.RETURN_GENERATED_KEYS mode instead
}
bindReturning(context);
}
@SuppressWarnings("unchecked")
@ -415,248 +376,4 @@ class InsertQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
public final boolean isExecutable() {
return insertMaps.isExecutable();
}
@Override
protected final void prepare(ExecuteContext ctx) throws SQLException {
Connection connection = ctx.connection();
// Just in case, always set Sybase ASE statement mode to return
// Generated keys if client code wants to SELECT @@identity afterwards
if (ctx.getDialect() == SQLDialect.ASE) {
ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS));
return;
}
// Normal statement preparing if no values should be returned
else if (returning.isEmpty()) {
super.prepare(ctx);
return;
}
// Values should be returned from the INSERT
else {
switch (ctx.getDialect()) {
// Postgres uses the RETURNING clause in SQL
case FIREBIRD:
case POSTGRES:
// SQLite will select last_insert_rowid() after the INSER
case SQLITE:
// Sybase will select @@identity after the INSERT
case CUBRID:
case SYBASE:
super.prepare(ctx);
return;
// Some dialects can only return AUTO_INCREMENT values
// Other values have to be fetched in a second step
// [#1260] TODO CUBRID supports this, but there's a JDBC bug
case ASE:
case DERBY:
case H2:
case INGRES:
case MYSQL:
case SQLSERVER:
ctx.statement(connection.prepareStatement(ctx.sql(), Statement.RETURN_GENERATED_KEYS));
return;
// The default is to return all requested fields directly
default: {
List<String> names = new ArrayList<String>();
for (Field<?> field : returning) {
names.add(field.getName());
}
ctx.statement(connection.prepareStatement(ctx.sql(), names.toArray(new String[names.size()])));
return;
}
}
}
}
@Override
protected final int execute(ExecuteContext ctx, ExecuteListener listener) throws SQLException {
if (returning.isEmpty()) {
return super.execute(ctx, listener);
}
else {
int result = 1;
ResultSet rs;
switch (ctx.getDialect()) {
// SQLite can select _rowid_ after the insert
case SQLITE: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
Executor create = new Executor(ctx.connection(), SQLDialect.SQLITE, ctx.getSettings());
returned =
create.select(returning)
.from(getInto())
.where(rowid().equal(rowid().getDataType().convert(create.lastID())))
.fetchInto(getInto());
return result;
}
// Sybase can select @@identity after the insert
// TODO [#832] Fix this. This might be a driver issue. JDBC
// Generated keys don't work with jconn3, but they seem to work
// with jTDS (which is used for Sybase ASE integration)
case CUBRID:
case SYBASE: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
selectReturning(ctx.configuration(), create(ctx).lastID());
return result;
}
// Some dialects can only retrieve "identity" (AUTO_INCREMENT) values
// Additional values have to be fetched explicitly
// [#1260] TODO CUBRID supports this, but there's a JDBC bug
case ASE:
case DERBY:
case H2:
case INGRES:
case MYSQL:
case SQLSERVER: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
rs = ctx.statement().getGeneratedKeys();
try {
List<Object> list = new ArrayList<Object>();
while (rs.next()) {
list.add(rs.getObject(1));
}
selectReturning(ctx, list.toArray());
return result;
}
finally {
rs.close();
}
}
// Firebird and Postgres can execute the INSERT .. RETURNING
// clause like a select clause. JDBC support is not implemented
// in the Postgres JDBC driver
case FIREBIRD:
case POSTGRES: {
listener.executeStart(ctx);
rs = ctx.statement().executeQuery();
listener.executeEnd(ctx);
break;
}
// These dialects have full JDBC support
case DB2:
case HSQLDB:
case ORACLE:
default: {
listener.executeStart(ctx);
result = ctx.statement().executeUpdate();
listener.executeEnd(ctx);
rs = ctx.statement().getGeneratedKeys();
break;
}
}
ExecuteContext ctx2 = new DefaultExecuteContext(ctx.configuration());
ExecuteListener listener2 = new ExecuteListeners(ctx2);
ctx2.resultSet(rs);
returned = new CursorImpl<R>(ctx2, listener2, fieldArray(returning), null, false).fetch().into(getInto());
return result;
}
}
/**
* Get the returning record in those dialects that do not support fetching
* arbitrary fields from JDBC's {@link Statement#getGeneratedKeys()} method.
*/
@SuppressWarnings("unchecked")
private final void selectReturning(Configuration configuration, Object... values) {
if (values != null && values.length > 0) {
Table<R> into = getInto();
// This shouldn't be null, as relevant dialects should
// return empty generated keys ResultSet
if (into.getIdentity() != null) {
Field<Number> field = (Field<Number>) into.getIdentity().getField();
Number[] ids = new Number[values.length];
for (int i = 0; i < values.length; i++) {
ids[i] = field.getDataType().convert(values[i]);
}
// Only the IDENTITY value was requested. No need for an
// additional query
if (returning.size() == 1 && returning.get(0).equals(field)) {
for (Number id : ids) {
R typed = Utils.newRecord(into, configuration);
((AbstractRecord) typed).setValue(field, new Value<Number>(id));
getReturnedRecords().add(typed);
}
}
// Other values are requested, too. Run another query
else {
returned =
create(configuration).select(returning)
.from(into)
.where(field.in(ids))
.fetchInto(into);
}
}
}
}
@Override
public final void setReturning() {
setReturning(getInto().fields());
}
@Override
public final void setReturning(Identity<R, ? extends Number> identity) {
if (identity != null) {
setReturning(identity.getField());
}
}
@Override
public final void setReturning(Field<?>... fields) {
setReturning(Arrays.asList(fields));
}
@Override
public final void setReturning(Collection<? extends Field<?>> fields) {
returning.clear();
returning.addAll(fields);
}
@Override
public final R getReturnedRecord() {
if (getReturnedRecords().size() == 0) {
return null;
}
return getReturnedRecords().get(0);
}
@Override
public final Result<R> getReturnedRecords() {
if (returned == null) {
returned = new ResultImpl<R>(getConfiguration(), returning);
}
return returned;
}
}

View File

@ -72,6 +72,7 @@ import org.jooq.Record6;
import org.jooq.Record7;
import org.jooq.Record8;
import org.jooq.Record9;
import org.jooq.Result;
import org.jooq.Row1;
import org.jooq.Row10;
import org.jooq.Row11;
@ -98,6 +99,7 @@ import org.jooq.Select;
import org.jooq.Table;
import org.jooq.UpdateConditionStep;
import org.jooq.UpdateQuery;
import org.jooq.UpdateResultStep;
import org.jooq.UpdateSetFirstStep;
import org.jooq.UpdateSetMoreStep;
import org.jooq.UpdateWhereStep;
@ -114,7 +116,8 @@ final class UpdateImpl<R extends Record>
// Cascading interface implementations for Update behaviour
UpdateSetFirstStep<R>,
UpdateSetMoreStep<R>,
UpdateConditionStep<R> {
UpdateConditionStep<R>,
UpdateResultStep<R> {
/**
* Generated UID
@ -567,4 +570,34 @@ final class UpdateImpl<R extends Record>
public final UpdateImpl<R> orNotExists(Select<?> select) {
return or(notExists(select));
}
@Override
public final UpdateImpl<R> returning() {
getDelegate().setReturning();
return this;
}
@Override
public final UpdateImpl<R> returning(Field<?>... f) {
getDelegate().setReturning(f);
return this;
}
@Override
public final UpdateImpl<R> returning(Collection<? extends Field<?>> f) {
getDelegate().setReturning(f);
return this;
}
@Override
public final Result<R> fetch() {
getDelegate().execute();
return getDelegate().getReturnedRecords();
}
@Override
public final R fetchOne() {
getDelegate().execute();
return getDelegate().getReturnedRecord();
}
}

View File

@ -499,6 +499,8 @@ class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
.keyword("where ")
.sql(getWhere());
}
toSQLReturning(context);
}
@Override
@ -525,6 +527,7 @@ class UpdateQueryImpl<R extends Record> extends AbstractStoreQuery<R> implements
}
context.bind(condition);
bindReturning(context);
}
@Override