| /* |
| * 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 |
| // 07/13/2012-2.5 Guy Pelletier |
| // - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls |
| // 08/24/2012-2.5 Guy Pelletier |
| // - 350487: JPA 2.1 Specification defined support for Stored Procedure Calls |
| // 09/03/2015 - Will Dazey |
| // - 456067 : Added support for defining query timeout units |
| package org.eclipse.persistence.internal.queries; |
| |
| import java.util.*; |
| import org.eclipse.persistence.internal.helper.*; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.databaseaccess.*; |
| import org.eclipse.persistence.internal.expressions.*; |
| import org.eclipse.persistence.descriptors.ClassDescriptor; |
| import org.eclipse.persistence.exceptions.*; |
| import org.eclipse.persistence.queries.*; |
| |
| /** |
| * <p><b>Purpose</b>: |
| * Mechanism used for custom SQL and stored procedure queries. |
| * </p> |
| * <p><b>Responsibilities</b>: |
| * Executes the appropriate call. |
| * </p> |
| * @author James Sutherland |
| * @since TOPLink/Java 2.0 |
| */ |
| public class CallQueryMechanism extends DatasourceCallQueryMechanism { |
| |
| public CallQueryMechanism() { |
| } |
| |
| /** |
| * Initialize the state of the query |
| * @param query - owner of mechanism |
| */ |
| public CallQueryMechanism(DatabaseQuery query) { |
| super(query); |
| } |
| |
| /** |
| * Initialize the state of the query |
| * @param query - owner of mechanism |
| * @param call - sql call |
| */ |
| public CallQueryMechanism(DatabaseQuery query, DatabaseCall call) { |
| super(query, call); |
| call.setIsFieldMatchingRequired(true); |
| } |
| |
| /** |
| * Return the call. |
| */ |
| public DatabaseCall getDatabaseCall() { |
| return (DatabaseCall)call; |
| } |
| |
| /** |
| * Unprepare the call if required. |
| * Clone and unprepare stored procedure calls, so they can be reprepared with possible different optional arguments. |
| */ |
| @Override |
| public void unprepare() { |
| DatabaseQuery query = this.query; |
| if (hasMultipleCalls()) { |
| this.calls = ((Vector)this.calls.clone()); |
| int size = this.calls.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseCall call = (DatabaseCall)this.calls.get(index); |
| if (call.isPrepared() && call.isStoredProcedureCall() |
| && ((StoredProcedureCall)call).hasOptionalArguments()) { |
| call = (DatabaseCall)call.clone(); |
| call.setIsPrepared(false); |
| call.setQuery(query); |
| this.calls.set(index, call); |
| } |
| } |
| } else if (this.call != null) { |
| if (this.call.isPrepared() && this.call.isStoredProcedureCall() |
| && ((StoredProcedureCall)this.call).hasOptionalArguments()) { |
| this.call = (DatabaseCall)this.call.clone(); |
| this.call.setIsPrepared(false); |
| this.call.setQuery(query); |
| } |
| } |
| } |
| |
| /** |
| * 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 prepareCall() throws QueryException { |
| DatabaseQuery query = this.query; |
| AbstractSession executionSession = query.getExecutionSession(); |
| if (hasMultipleCalls()) { |
| if (query.shouldCloneCall()) { |
| this.calls = ((Vector)this.calls.clone()); |
| } |
| int size = this.calls.size(); |
| for (int index = 0; index < size; index++) { |
| DatasourceCall call = (DatasourceCall)this.calls.get(index); |
| if (query.shouldCloneCall()) { |
| // Need to clone the call if setting query specific properties on it as the call may be shared. |
| call = (DatabaseCall)call.clone(); |
| call.setQuery(query); |
| this.calls.set(index, call); |
| } |
| if (call instanceof DatabaseCall) { |
| configureDatabaseCall((DatabaseCall)call); |
| } |
| call.prepare(executionSession); |
| } |
| } else if (this.call != null) { |
| if (query.shouldCloneCall()) { |
| // Need to clone the call if setting query specific properties on it as the call may be shared. |
| this.call = (DatasourceCall)this.call.clone(); |
| this.call.setQuery(query); |
| } |
| DatasourceCall call = this.call; |
| if (call instanceof DatabaseCall) { |
| configureDatabaseCall((DatabaseCall)call); |
| } |
| this.call.prepare(executionSession); |
| } |
| } |
| |
| /** |
| * Set the call level query options into the call. |
| */ |
| protected void configureDatabaseCall(DatabaseCall call) { |
| if (!this.query.shouldIgnoreBindAllParameters()) { |
| call.setUsesBinding(this.query.shouldBindAllParameters()); |
| } |
| 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.isReadQuery()) { |
| ReadQuery readQuery = (ReadQuery)this.query; |
| // Some DB don't support FirstRow in SELECT statements in spite of supporting MaxResults(Symfoware). |
| // We should check FirstRow and MaxResults separately. |
| if (!call.shouldIgnoreFirstRowSetting()){ |
| if (readQuery.getFirstResult() != 0) { |
| call.setFirstResult(readQuery.getFirstResult()); |
| call.setIsResultSetScrollable(true); |
| call.setResultSetType(java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE); |
| call.setResultSetConcurrency(java.sql.ResultSet.CONCUR_READ_ONLY); |
| } |
| } |
| if (!call.shouldIgnoreMaxResultsSetting()){ |
| call.setMaxRows(readQuery.getMaxRows()); |
| } |
| call.setResultSetFetchSize(readQuery.getFetchSize()); |
| } |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareCursorSelectAllRows() throws QueryException { |
| getCall().returnCursor(); |
| |
| ContainerPolicy cp; |
| DatabaseQuery query = getQuery(); |
| if (query.isReadAllQuery()) { |
| cp = ((ReadAllQuery)query).getContainerPolicy(); |
| } else { |
| cp = ((DataReadQuery)query).getContainerPolicy(); |
| } |
| if (cp.isScrollableCursorPolicy()) { |
| ScrollableCursorPolicy scp = (ScrollableCursorPolicy)cp; |
| DatabaseCall call = getDatabaseCall(); |
| call.setIsResultSetScrollable(true); |
| call.setResultSetType(scp.getResultSetType()); |
| call.setResultSetConcurrency(scp.getResultSetConcurrency()); |
| // Only set the fetch size to be the page size, if the fetch size was not set on the query. |
| if (((ReadQuery)getQuery()).getFetchSize() == 0) { |
| call.setResultSetFetchSize(scp.getPageSize()); |
| } |
| } |
| if (getQuery().isReportQuery()){ |
| prepareReportQueryItems(); |
| } |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareDeleteAll() { |
| if (hasMultipleCalls()) { |
| for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) { |
| DatabaseCall call = (DatabaseCall)callsEnum.nextElement(); |
| call.returnNothing(); |
| } |
| } else { |
| getCall().returnNothing(); |
| } |
| |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareDeleteObject() { |
| boolean usesOptimisticLocking = ((DeleteObjectQuery)getQuery()).usesOptimisticLocking(); |
| if (hasMultipleCalls()) { |
| for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) { |
| DatabaseCall call = (DatabaseCall)callsEnum.nextElement(); |
| call.returnNothing(); |
| if (usesOptimisticLocking) { |
| call.setHasOptimisticLock(true); |
| } |
| } |
| } else { |
| getCall().returnNothing(); |
| if (usesOptimisticLocking) { |
| getDatabaseCall().setHasOptimisticLock(true); |
| } |
| } |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareDoesExist(DatabaseField field) { |
| getCall().returnOneRow(); |
| Vector fields = org.eclipse.persistence.internal.helper.NonSynchronizedVector.newInstance(1); |
| fields.addElement(field); |
| getDatabaseCall().setFields(fields); |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call for an execute call. |
| */ |
| @Override |
| public void prepareExecute() { |
| DatabaseCall call = getDatabaseCall(); |
| call.setExecuteUpdate(); |
| call.setIsFieldMatchingRequired(isCallQueryMechanism()); |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareExecuteSelect() { |
| if (hasMultipleCalls()) { |
| for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) { |
| DatabaseCall databseCall = (DatabaseCall)callsEnum.nextElement(); |
| databseCall.returnManyRows(); |
| databseCall.setIsFieldMatchingRequired(isCallQueryMechanism()); |
| } |
| } else { |
| DatabaseCall call = getDatabaseCall(); |
| call.returnManyRows(); |
| call.setIsFieldMatchingRequired(isCallQueryMechanism()); |
| } |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareSelectAllRows() { |
| if (hasMultipleCalls()) { |
| for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) { |
| DatabaseCall call = (DatabaseCall)callsEnum.nextElement(); |
| call.returnManyRows(); |
| if (isCallQueryMechanism()) { |
| call.setIsFieldMatchingRequired(true); |
| // Set the fields including joined and partial fields and compute joined indexes, |
| // this requires and assume that the custom SQL returns the fields in the correct order. |
| call.setFields(((ObjectLevelReadQuery)getQuery()).getSelectionFields()); |
| prepareJoining((ObjectLevelReadQuery)getQuery()); |
| } |
| } |
| } else { |
| getCall().returnManyRows(); |
| if (isCallQueryMechanism()) { |
| DatabaseCall call = getDatabaseCall(); |
| call.setIsFieldMatchingRequired(true); |
| // Set the fields including joined and partial fields and compute joined indexes, |
| // this requires and assume that the custom SQL returns the fields in the correct order. |
| call.setFields(((ObjectLevelReadQuery)getQuery()).getSelectionFields()); |
| prepareJoining((ObjectLevelReadQuery)getQuery()); |
| } |
| } |
| prepareCall(); |
| } |
| |
| /** |
| * Prepare the joining indexes if joining. |
| */ |
| protected void prepareJoining(ObjectLevelReadQuery query) { |
| if (query.hasJoining()) { |
| query.getJoinedAttributeManager().computeJoiningMappingIndexes(true, getSession(), 0); |
| } |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareSelectOneRow() { |
| if (hasMultipleCalls()) { |
| for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) { |
| DatabaseCall call = (DatabaseCall)callsEnum.nextElement(); |
| call.returnOneRow(); |
| if (isCallQueryMechanism()) { |
| call.setIsFieldMatchingRequired(true); |
| // Set the fields including joined and partial fields and compute joined indexes, |
| // this requires and assume that the custom SQL returns the fields in the correct order. |
| call.setFields(((ObjectLevelReadQuery)getQuery()).getSelectionFields()); |
| prepareJoining((ObjectLevelReadQuery)getQuery()); |
| } |
| } |
| } else { |
| getCall().returnOneRow(); |
| if (isCallQueryMechanism()) { |
| DatabaseCall call = getDatabaseCall(); |
| call.setIsFieldMatchingRequired(true); |
| // Set the fields including joined and partial fields and compute joined indexes, |
| // this requires and assume that the custom SQL returns the fields in the correct order. |
| call.setFields(((ObjectLevelReadQuery)getQuery()).getSelectionFields()); |
| prepareJoining((ObjectLevelReadQuery)getQuery()); |
| } |
| } |
| prepareCall(); |
| } |
| |
| /** |
| * Pre-build configure the SQL call. |
| */ |
| @Override |
| public void prepareUpdateObject() { |
| if (hasMultipleCalls()) { |
| int size = this.calls.size(); |
| for (int index = 0; index < size; index++) { |
| DatabaseCall call = (DatabaseCall)this.calls.get(index); |
| if (!call.isReturnSet()) { |
| call.returnNothing(); |
| } |
| if (this.query.getDescriptor().usesOptimisticLocking()) { |
| call.setHasOptimisticLock(true); |
| } |
| } |
| } else if (this.call != null) { |
| if (!call.isReturnSet()) { |
| this.call.returnNothing(); |
| } |
| if (this.query.getDescriptor().usesOptimisticLocking()) { |
| ((DatabaseCall)this.call).setHasOptimisticLock(true); |
| } |
| } |
| prepareCall(); |
| } |
| |
| /** |
| * INTERNAL: |
| * Configure the call to be a dynamic custom SQL call, so that it ignores the # token. |
| */ |
| public void setCallHasCustomSQLArguments() { |
| if (hasMultipleCalls()) { |
| for (Enumeration callsEnum = getCalls().elements(); callsEnum.hasMoreElements();) { |
| DatabaseCall databaseCall = (DatabaseCall)callsEnum.nextElement(); |
| if (databaseCall.isSQLCall()) { |
| ((SQLCall)databaseCall).setHasCustomSQLArguments(true); |
| } |
| } |
| } else if (getCall().isSQLCall()) { |
| ((SQLCall)getCall()).setHasCustomSQLArguments(true); |
| } |
| } |
| |
| /** |
| * Update the foreign key fields when resolving a bi-directional reference in a UOW. |
| * This must always be dynamic as it is called within an insert query and is really part of the insert |
| * and does not fire update events or worry about locking. |
| */ |
| @Override |
| protected void updateForeignKeyFieldAfterInsert(WriteObjectQuery writeQuery) { |
| ClassDescriptor descriptor = getDescriptor(); |
| for (DatabaseTable table : descriptor.getTables()) { |
| AbstractRecord row = descriptor.getObjectBuilder().buildRowForUpdateAfterShallowInsert(writeQuery.getObject(), writeQuery.getSession(), table); |
| if (!row.isEmpty()) { |
| SQLUpdateStatement updateStatement = new SQLUpdateStatement(); |
| updateStatement.setModifyRow(row); |
| updateStatement.setTranslationRow(getTranslationRow()); |
| updateStatement.setTable(table); |
| updateStatement.setWhereClause(descriptor.getObjectBuilder().buildPrimaryKeyExpression(table));// Must not check version, ok as just inserted it. |
| // Bug 2996585 |
| StatementQueryMechanism updateMechanism = new StatementQueryMechanism(writeQuery, updateStatement); |
| writeQuery.setModifyRow(row); |
| updateMechanism.updateObject(); |
| } |
| } |
| } |
| |
| /** |
| * Update the foreign key fields to null when resolving a deletion cycle. |
| * This must always be dynamic as it is called within an delete query and is really part of the delete |
| * and does not fire update events or worry about locking. |
| */ |
| @Override |
| public void updateForeignKeyFieldBeforeDelete() { |
| ClassDescriptor descriptor = getDescriptor(); |
| DeleteObjectQuery deleteQuery = (DeleteObjectQuery)getQuery(); |
| for (DatabaseTable table : descriptor.getTables()) { |
| // need nullify the same fields that would be updated after shallow insert |
| AbstractRecord row = descriptor.getObjectBuilder().buildRowForUpdateBeforeShallowDelete(deleteQuery.getObject(), deleteQuery.getSession(), table); |
| if (!row.isEmpty()) { |
| SQLUpdateStatement updateStatement = new SQLUpdateStatement(); |
| updateStatement.setModifyRow(row); |
| updateStatement.setTranslationRow(getTranslationRow()); |
| updateStatement.setTable(table); |
| updateStatement.setWhereClause(descriptor.getObjectBuilder().buildPrimaryKeyExpression(table));// Must not check version, ok as delete will. |
| StatementQueryMechanism updateMechanism = new StatementQueryMechanism(deleteQuery, updateStatement); |
| deleteQuery.setModifyRow(row); |
| updateMechanism.updateObject(); |
| } |
| } |
| } |
| } |