Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[3.0] Bug 579409: Add support for Statement.getGeneratedKeys for Identity Generation #1482

Merged
merged 2 commits into from
Jun 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021 IBM Corporation. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -1868,6 +1868,13 @@ public class PersistenceUnitProperties {
*
* {@code
* <property name="eclipselink.target-database-properties" value="shouldBindLiterals=true"/>}
* <p>
* <b> Example 2 : </b> To change the value of
* DatabasePlatform.supportsReturnGeneratedKeys via configuration, provide the
* following :<br><br>
*
* {@code
* <property name="eclipselink.target-database-properties" value="supportsReturnGeneratedKeys=true"/>}
* @see TargetDatabase
* @see DatabasePlatform
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2018 IBM Corporation. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -644,6 +644,15 @@ public Object basicExecuteCall(Call call, AbstractRecord translationRow, Abstrac
// Bug 2804663 - LOBValueWriter is no longer a singleton
getLOBWriter().addCall(dbCall);
}

if(dbCall.shouldReturnGeneratedKeys()) {
resultSet = statement.getGeneratedKeys();

dbCall.setStatement(statement);
dbCall.setGeneratedKeys(resultSet);
this.possibleFailure = false;
return dbCall;
}
} else if ((!dbCall.getReturnsResultSet() || (dbCall.getReturnsResultSet() && dbCall.shouldBuildOutputRow()))) {
result = session.getPlatform().executeStoredProcedure(dbCall, (PreparedStatement)statement, this, session);
this.storedProcedureStatementsCount++;
Expand Down Expand Up @@ -883,7 +892,7 @@ public void run() {
/**
* Execute the statement.
*/
public Integer executeDirectNoSelect(Statement statement, DatabaseCall call, AbstractSession session) throws DatabaseException {
public Object executeDirectNoSelect(Statement statement, DatabaseCall call, AbstractSession session) throws DatabaseException {
int rowCount = 0;

try {
Expand Down Expand Up @@ -966,8 +975,8 @@ protected int executeJDK12BatchStatement(Statement statement, DatabaseCall dbCal
/**
* Execute the statement.
*/
protected Integer executeNoSelect(DatabaseCall call, Statement statement, AbstractSession session) throws DatabaseException {
Integer rowCount = executeDirectNoSelect(statement, call, session);
protected Object executeNoSelect(DatabaseCall call, Statement statement, AbstractSession session) throws DatabaseException {
Object rowCount = executeDirectNoSelect(statement, call, session);

// Allow for procs with outputs to be raised as events for error handling.
if (call.shouldBuildOutputRow()) {
Expand Down Expand Up @@ -1594,6 +1603,8 @@ public Statement prepareStatement(DatabaseCall call, AbstractSession session, bo
} else if (call.isDynamicCall(session)) {
// PERF: Dynamic statements are used for dynamic SQL.
statement = allocateDynamicStatement(nativeConnection);
} else if(call.shouldReturnGeneratedKeys()) {
statement = nativeConnection.prepareStatement(call.getSQLString(), Statement.RETURN_GENERATED_KEYS);
} else {
statement = nativeConnection.prepareStatement(call.getSQLString());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 IBM Corporation. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -98,12 +98,20 @@ public abstract class DatabaseCall extends DatasourceCall {
transient protected Statement statement;
transient protected ResultSet result;

// The generated keys are cached for lookup later
transient protected ResultSet generatedKeys;

// The call may specify that its parameters should be bound.
protected Boolean usesBinding;

// Bound calls can use prepared statement caching.
protected Boolean shouldCacheStatement;

/*
* Indicate this call should return generated keys. Only supported for INSERT calls.
*/
protected boolean shouldReturnGeneratedKeys;

// The returned fields.
transient protected Vector fields;
// PERF: fields array
Expand Down Expand Up @@ -547,6 +555,13 @@ public int getQueryTimeout() {
return this.queryTimeout;
}

/**
* The result set that stores the generated keys from the Statement
*/
public ResultSet getGeneratedKeys() {
return this.generatedKeys;
}

/**
* The result set is stored for the return value of cursor selects.
*/
Expand Down Expand Up @@ -792,13 +807,12 @@ protected void prepareInternalParameters(AbstractSession session) {
if (this.returnsResultSet == null) {
setReturnsResultSet(!isCallableStatementRequired());
}
// if there is nothing returned and we are not using optimistic locking then batch
//if it is a StoredProcedure with in/out or out parameters then do not batch
//logic may be weird but we must not batch if we are not using JDBC batchwriting and we have parameters
// we may want to refactor this some day
// if it is a StoredProcedure with in/out or out parameters then do not batch
// (DatasourceCallQueryMechanism.executeCall() will return an AbstractRecord)
// logic may be weird but we must not batch if we are not using JDBC batchwriting and we have parameters
this.isBatchExecutionSupported = (isNothingReturned()
&& (!hasOptimisticLock() || session.getPlatform().canBatchWriteWithOptimisticLocking(this))
&& (!shouldBuildOutputRow())
&& (!shouldBuildOutputRow() && !shouldReturnGeneratedKeys())
&& (session.getPlatform().usesJDBCBatchWriting() || (!hasParameters()))
&& (!isLOBLocatorNeeded()))
&& (getQuery().isModifyQuery() && ((ModifyQuery)getQuery()).isBatchExecutionSupported());
Expand Down Expand Up @@ -905,6 +919,15 @@ public void setIgnoreMaxResultsSetting(boolean ignoreMaxResultsSetting){
this.ignoreMaxResultsSetting = ignoreMaxResultsSetting;
}

/**
* Indicate that this call should set {@link java.sql.Statement#RETURN_GENERATED_KEYS} when executing
* <p>
* Only set to true if {@link DatabasePlatform#supportsReturnGeneratedKeys()}
*/
public boolean setShouldReturnGeneratedKeys(boolean shouldReturnGeneratedKeys) {
return this.shouldReturnGeneratedKeys = shouldReturnGeneratedKeys;
}

/**
* Callable statement is required if there is an output parameter.
*/
Expand Down Expand Up @@ -967,6 +990,13 @@ public void setQueryTimeoutUnit(TimeUnit queryTimeoutUnit) {
this.queryTimeoutUnit = queryTimeoutUnit;
}

/**
* The result set that stores the generated keys from the Statement
*/
public void setGeneratedKeys(ResultSet generatedKeys) {
this.generatedKeys = generatedKeys;
}

/**
* The result set is stored for the return value of cursor selects.
*/
Expand Down Expand Up @@ -1085,6 +1115,13 @@ public boolean shouldIgnoreMaxResultsSetting(){
return this.ignoreMaxResultsSetting;
}

/**
* Indicate that this call should set {@link java.sql.Statement#RETURN_GENERATED_KEYS} when executing
*/
public boolean shouldReturnGeneratedKeys() {
return this.shouldReturnGeneratedKeys;
}

/**
* INTERNAL:
* Print the SQL string.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2020 IBM Corporation. All rights reserved.
* Copyright (c) 2019, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -98,10 +98,28 @@ public class DatasourcePlatform implements Platform {
/** If sequences should start at Next Value */
protected boolean defaultSeqenceAtNextValue;

/**
* This property configures if the database platform will use {@link java.sql.Statement#getGeneratedKeys()},
* or a separate query, in order to obtain javax.persistence.GenerationType.IDENTITY generated values.
* <p>
* <b>Allowed Values:</b>
* <ul>
* <li>"<code>true</code>" - IDENTITY generated values will be obtained with {@link java.sql.Statement#getGeneratedKeys()}
* <li>"<code>false</code>" (DEFAULT) - IDENTITY generated values will be obtained with a separate query {@link #buildSelectQueryForIdentity()}
* </ul>
* <p>
* See:
* <ul>
* <li>{@link #buildSelectQueryForIdentity()} will be disabled if this property is enabled
* </ul>
*/
protected boolean supportsReturnGeneratedKeys;

public DatasourcePlatform() {
this.tableQualifier = "";
this.startDelimiter = "";
this.endDelimiter = "";
this.supportsReturnGeneratedKeys = false;
}

/**
Expand Down Expand Up @@ -776,6 +794,15 @@ public void addSequence(Sequence sequence) {
addSequence(sequence, false);
}

/**
* Indicates whether the platform supports the use of {@link java.sql.Statement#RETURN_GENERATED_KEYS}.
* If supported, IDENTITY values will be obtained through {@link java.sql.Statement#getGeneratedKeys()}
* and will replace usage of {@link #buildSelectQueryForIdentity()}
*/
public void setSupportsReturnGeneratedKeys(boolean supportsReturnGeneratedKeys) {
this.supportsReturnGeneratedKeys = supportsReturnGeneratedKeys;
}

/**
* Add sequence corresponding to the name.
* Use this method with isSessionConnected parameter set to true
Expand Down Expand Up @@ -978,6 +1005,15 @@ public boolean supportsSequenceObjects() {
return false;
}

/**
* Indicates whether the platform supports the use of {@link java.sql.Statement#RETURN_GENERATED_KEYS}.
* If supported, IDENTITY values will be obtained through {@link java.sql.Statement#getGeneratedKeys()}
* and will replace usage of {@link #buildSelectQueryForIdentity()}
*/
public boolean supportsReturnGeneratedKeys() {
return supportsReturnGeneratedKeys;
}

/**
* INTERNAL:
* Returns query used to read value generated by sequence object (like Oracle sequence).
Expand Down Expand Up @@ -1009,6 +1045,9 @@ public ValueReadQuery buildSelectQueryForSequenceObject(String qualifiedSeqName,
* the returned query used until the sequence is disconnected.
* If the platform supportsIdentity then (at least) one of buildSelectQueryForIdentity
* methods should return non-null query.
* <p>
* Alternatively, if the platform supports {@link java.sql.Statement#getGeneratedKeys()},
* see {@link DatabasePlatform#supportsReturnGeneratedKeys()}
*/
public ValueReadQuery buildSelectQueryForIdentity() {
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -150,9 +151,9 @@ private void executeBatch(AbstractSession session) {
if (this.sqlStrings.size() == 1) {
// If only one call, just execute normally.
try {
int rowCount = (Integer)this.databaseAccessor.basicExecuteCall(this.lastCallAppended, null, session, false);
if (this.usesOptimisticLocking) {
if (rowCount != 1) {
Object rowCount = this.databaseAccessor.basicExecuteCall(this.lastCallAppended, null, session, false);
if (this.usesOptimisticLocking && rowCount instanceof Integer) {
if ((Integer)rowCount != 1) {
throw OptimisticLockException.batchStatementExecutionFailure();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -151,9 +152,9 @@ private void executeBatch(AbstractSession session) {
if (this.parameters.size() == 1) {
// If only one call, just execute normally.
try {
int rowCount = (Integer)this.databaseAccessor.basicExecuteCall(this.previousCall, null, session, false);
if (this.previousCall.hasOptimisticLock()) {
if (rowCount != 1) {
Object rowCount = this.databaseAccessor.basicExecuteCall(this.previousCall, null, session, false);
if (this.previousCall.hasOptimisticLock() && rowCount instanceof Integer) {
if ((Integer)rowCount != 1) {
throw OptimisticLockException.batchStatementExecutionFailure();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2021 IBM Corporation. All rights reserved.
* Copyright (c) 1998, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -390,7 +390,7 @@ protected void assignReturnValueToMapping(Object object, ReadObjectQuery query,
* @exception DatabaseException - an error has occurred on the database.
*/
public Object assignSequenceNumber(Object object, AbstractSession writeSession) throws DatabaseException {
return assignSequenceNumber(object, writeSession, null);
return assignSequenceNumber(object, null, writeSession, null);
}

/**
Expand All @@ -404,7 +404,21 @@ public Object assignSequenceNumber(Object object, AbstractSession writeSession)
* @exception DatabaseException - an error has occurred on the database.
*/
public Object assignSequenceNumber(WriteObjectQuery writeQuery) throws DatabaseException {
return assignSequenceNumber(writeQuery.getObject(), writeQuery.getSession(), writeQuery);
return assignSequenceNumber(writeQuery.getObject(), null, writeQuery.getSession(), writeQuery);
}

/**
* INTERNAL:
* Update the writeQuery's object primary key by fetching a new sequence number from the accessor.
* This assume the uses sequence numbers check has already been done.
* Adds the assigned sequence value to writeQuery's modify row.
* If object has a changeSet then sets sequence value into change set as an Id
* adds it also to object's change set in a ChangeRecord if required.
* @return the sequence value or null if not assigned.
* @exception DatabaseException - an error has occurred on the database.
*/
public Object assignSequenceNumber(WriteObjectQuery writeQuery, Object sequenceValue) throws DatabaseException {
return assignSequenceNumber(writeQuery.getObject(), sequenceValue, writeQuery.getSession(), writeQuery);
}

/**
Expand All @@ -417,7 +431,7 @@ public Object assignSequenceNumber(WriteObjectQuery writeQuery) throws DatabaseE
* @return the sequence value or null if not assigned.
* @exception DatabaseException - an error has occurred on the database.
*/
protected Object assignSequenceNumber(Object object, AbstractSession writeSession, WriteObjectQuery writeQuery) throws DatabaseException {
protected Object assignSequenceNumber(Object object, Object sequenceValue, AbstractSession writeSession, WriteObjectQuery writeQuery) throws DatabaseException {
DatabaseField sequenceNumberField = this.descriptor.getSequenceNumberField();
Object existingValue = null;
if (this.sequenceMapping != null) {
Expand All @@ -428,12 +442,12 @@ protected Object assignSequenceNumber(Object object, AbstractSession writeSessio

// PERF: The (internal) support for letting the sequence decide this was removed,
// as anything other than primitive should allow null and default as such.
Object sequenceValue;
int index = this.descriptor.getPrimaryKeyFields().indexOf(sequenceNumberField);
if (isPrimaryKeyComponentInvalid(existingValue, index) || this.descriptor.getSequence().shouldAlwaysOverrideExistingValue()) {
sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass());
} else {
return null;
// If no sequence value was passed, obtain one from the Sequence
if(sequenceValue == null) {
sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass());
}
}

// Check that the value is not null, this occurs on any databases using IDENTITY type sequencing.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/*
* Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
Expand Down Expand Up @@ -153,11 +154,18 @@ protected void configureDatabaseCall(DatabaseCall call) {
if (!this.query.shouldIgnoreCacheStatement()) {
call.setShouldCacheStatement(this.query.shouldCacheStatement());
}

call.setQueryTimeout(this.query.getQueryTimeout());
call.setQueryTimeoutUnit(this.query.getQueryTimeoutUnit());

if (this.query.isNativeConnectionRequired()) {
call.setIsNativeConnectionRequired(true);
}

if (this.query.isInsertObjectQuery()) {
call.setShouldReturnGeneratedKeys(this.query.shouldReturnGeneratedKeys());
}

if (this.query.isReadQuery()) {
ReadQuery readQuery = (ReadQuery)this.query;
// Some DB don't support FirstRow in SELECT statements in spite of supporting MaxResults(Symfoware).
Expand Down