[#834] Add support for the Firebird / Postgres UPDATE .. RETURNING
clause
This commit is contained in:
parent
5690504fea
commit
cea755069d
@ -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()) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
}
|
||||
|
||||
100
jOOQ/src/main/java/org/jooq/UpdateResultStep.java
Normal file
100
jOOQ/src/main/java/org/jooq/UpdateResultStep.java
Normal 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;
|
||||
}
|
||||
95
jOOQ/src/main/java/org/jooq/UpdateReturningStep.java
Normal file
95
jOOQ/src/main/java/org/jooq/UpdateReturningStep.java
Normal 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);
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user