blob: ce248168fd5f68e48318feb0ca5072f8d642a6af [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. 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
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.queries;
import java.util.Enumeration;
import java.util.Vector;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.exceptions.QueryException;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.internal.databaseaccess.DatasourceCall;
import org.eclipse.persistence.internal.expressions.SQLModifyStatement;
import org.eclipse.persistence.internal.expressions.SQLStatement;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sessions.SessionProfiler;
/**
* <p><b>Purpose</b>:
* Mechanism used for all statement objects.
* <p><b>Responsibilities</b>:
* Executes the appropriate statement.
*
* @author Yvon Lavoie
* @since TOPLink/Java 1.0
*/
public class StatementQueryMechanism extends CallQueryMechanism {
protected SQLStatement sqlStatement;
/** Normally only a single statement is used, however multiple table may require multiple statements on write. */
protected Vector sqlStatements;
public StatementQueryMechanism() {
}
/**
* INTERNAL:
* Return a new mechanism for the query
* @param query - owner of mechanism
*/
public StatementQueryMechanism(DatabaseQuery query) {
super(query);
}
/**
* Return a new mechanism for the query
* @param query - owner of mechanism
* @param statement - sql statement
*/
public StatementQueryMechanism(DatabaseQuery query, SQLStatement statement) {
super(query);
this.sqlStatement = statement;
}
/**
* The statement is no longer require after prepare so can be released.
*/
public void clearStatement() {
// Only clear the statement if it is an expression query, otherwise the statement may still be needed.
}
/**
* Clone the mechanism for the specified query clone.
*/
@Override
public DatabaseQueryMechanism clone(DatabaseQuery queryClone) {
StatementQueryMechanism clone = (StatementQueryMechanism)super.clone(queryClone);
if ((!hasMultipleStatements()) && (getSQLStatement() != null)) {
clone.setSQLStatement((SQLStatement)sqlStatement.clone());
} else {
Vector currentStatements = getSQLStatements();
if (currentStatements != null) {
Vector statementClone = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(currentStatements.size());
Enumeration enumtr = currentStatements.elements();
while (enumtr.hasMoreElements()) {
statementClone.addElement(((SQLStatement)enumtr.nextElement()).clone());
}
clone.setSQLStatements(statementClone);
}
}
return clone;
}
/**
* INTERNAL:
* delete the object
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
@Override
public Integer deleteObject() throws DatabaseException {
// Prepare the calls if not already set (prepare may not have had the modify row).
if ((this.call == null) && (!hasMultipleCalls())) {
prepareDeleteObject();
if ((this.call == null) && (!hasMultipleCalls())) {
return 1;// Must be 1 otherwise locking error will occur.
}
}
return super.deleteObject();
}
/**
* Update the object
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
@Override
public Integer executeNoSelect() throws DatabaseException {
// Prepare the calls if not already set (prepare may not have had the modify row).
if ((this.call == null) && (!hasMultipleCalls())) {
prepareExecuteNoSelect();
}
return super.executeNoSelect();
}
/**
* Return the selection criteria for the statement.
*/
@Override
public Expression getSelectionCriteria() {
return getSQLStatement().getWhereClause();
}
/**
* INTERNAL:
* Return the sqlStatement
*/
public SQLStatement getSQLStatement() {
return sqlStatement;
}
/**
* Normally only a single statement is used, however multiple table may require multiple statements on write.
* This is lazy initialied to conserv space.
*/
public Vector getSQLStatements() {
if (sqlStatements == null) {
sqlStatements = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(3);
}
return sqlStatements;
}
/**
* Normally only a single statement is used, however multiple table may require multiple statements on write.
* This is lazy initialized to conserve space.
*/
public boolean hasMultipleStatements() {
return (sqlStatements != null) && (!sqlStatements.isEmpty());
}
/**
* Insert the object
* @exception DatabaseException - an error has occurred on the database.
*/
@Override
public void insertObject() throws DatabaseException {
// Prepare the calls if not already set (prepare may not have had the modify row).
if ((this.call == null) && (!hasMultipleCalls())) {
prepareInsertObject();
}
super.insertObject();
}
/**
* Insert the object if the reprepare flag is set, first reprepare the query.
* Added for CR#3237
* @param reprepare whether to reprepare the query.
*/
@Override
public void insertObject(boolean reprepare) {
if (reprepare) {
// Clear old calls, and reprepare.
setCalls(null);
trimFieldsForInsert();
prepareInsertObject();
}
insertObject();
}
/**
* INTERNAL
* Remove a potential sequence number field and invoke the ReturningPolicy trimModifyRowsForInsert method
*/
public void trimFieldsForInsert() {
getDescriptor().getObjectBuilder().trimFieldsForInsert(getSession(), getModifyRow());
}
/**
* Return true if this is a call query mechanism
*/
@Override
public boolean isCallQueryMechanism() {
return false;
}
/**
* Return true if this is a statement query mechanism
*/
@Override
public boolean isStatementQueryMechanism() {
return true;
}
/**
* INTERNAL:
* This is different from 'prepareForExecution' in that this is called on the original query,
* and the other is called on the copy of the query.
* This query is copied for concurrency so this prepare can only setup things that
* will apply to any future execution of this query.
*/
@Override
public void prepare() {
if ((!hasMultipleStatements()) && (getSQLStatement() == null)) {
throw QueryException.sqlStatementNotSetProperly(getQuery());
}
// Cannot call super yet as the call is not built.
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareCursorSelectAllRows() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareCursorSelectAllRows();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareDeleteAll() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareDeleteAll();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareDeleteObject() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareDeleteObject();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareDoesExist(DatabaseField field) {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
getCall().returnOneRow();
prepareCall();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareExecuteNoSelect() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareExecuteNoSelect();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareExecuteSelect() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareExecuteSelect();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareInsertObject() {
// Require modify row to prepare.
if (getModifyRow() == null) {
return;
}
if (hasMultipleStatements()) {
for (Enumeration statementEnum = getSQLStatements().elements();
statementEnum.hasMoreElements();) {
((SQLModifyStatement)statementEnum.nextElement()).setModifyRow(getModifyRow());
}
} else if (getSQLStatement() != null) {
((SQLModifyStatement)getSQLStatement()).setModifyRow(getModifyRow());
}
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareInsertObject();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareSelectAllRows() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareSelectAllRows();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareSelectOneRow() {
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareSelectOneRow();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareUpdateObject() {
// Require modify row to prepare.
if (getModifyRow() == null) {
return;
}
if (hasMultipleStatements()) {
for (Enumeration statementEnum = getSQLStatements().elements();
statementEnum.hasMoreElements();) {
((SQLModifyStatement)statementEnum.nextElement()).setModifyRow(getModifyRow());
}
} else if (getSQLStatement() != null) {
((SQLModifyStatement)getSQLStatement()).setModifyRow(getModifyRow());
}
setCallFromStatement();
// The statement is no longer require so can be released.
clearStatement();
super.prepareUpdateObject();
}
/**
* Pre-build the SQL call from the statement.
*/
@Override
public void prepareUpdateAll() {
setCallFromStatement();// Will build an SQLUpdateAllStatement
clearStatement();// The statement is no longer require so can be released.
super.prepareUpdateAll();
}
/**
* Pre-build the SQL call from the statement.
*/
protected void setCallFromStatement() {
// Profile SQL generation.
getSession().startOperationProfile(SessionProfiler.SqlGeneration, getQuery(), SessionProfiler.ALL);
try {
if (hasMultipleStatements()) {
for (Enumeration statementEnum = getSQLStatements().elements(); statementEnum.hasMoreElements();) {
DatasourceCall call = null;
if (getDescriptor() != null) {
call = getDescriptor().buildCallFromStatement((SQLStatement)statementEnum.nextElement(), getQuery(), getExecutionSession());
} else {
call = ((SQLStatement)statementEnum.nextElement()).buildCall(getExecutionSession());
}
// In case of update call may be null if no update required.
if (call != null) {
addCall(call);
}
}
} else {
DatasourceCall call = null;
if (getDescriptor() != null) {
call = getDescriptor().buildCallFromStatement(getSQLStatement(), getQuery(), getExecutionSession());
} else {
call = getSQLStatement().buildCall(getExecutionSession());
}
// In case of update call may be null if no update required.
if (call != null) {
setCall(call);
}
}
} finally {
// Profile SQL generation.
getSession().endOperationProfile(SessionProfiler.SqlGeneration, getQuery(), SessionProfiler.ALL);
}
}
/**
* Set the sqlStatement
*/
public void setSQLStatement(SQLStatement statement) {
this.sqlStatement = statement;
}
/**
* Normally only a single statement is used, however multiple table may require multiple statements on write.
* This is lazy initialized to conserve space.
*/
protected void setSQLStatements(Vector sqlStatements) {
this.sqlStatements = sqlStatements;
}
/**
* Update the object
* @exception DatabaseException - an error has occurred on the database.
* @return the row count.
*/
@Override
public Integer updateObject() throws DatabaseException {
// Prepare the calls if not already set (prepare may not have had the modify row).
if ((this.call == null) && (!hasMultipleCalls())) {
prepareUpdateObject();
if ((this.call == null) && (!hasMultipleCalls())) {
return 1;// Must be 1 otherwise locking error will occur.
}
}
return super.updateObject();
}
}