| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 2019 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 |
| * 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.platform.database.oracle.plsql; |
| |
| //javase imports |
| import static java.lang.Integer.MIN_VALUE; |
| import static org.eclipse.persistence.internal.helper.DatabaseType.DatabaseTypeHelper.databaseTypeHelper; |
| import static org.eclipse.persistence.internal.helper.Helper.INDENT; |
| import static org.eclipse.persistence.internal.helper.Helper.NL; |
| import static org.eclipse.persistence.platform.database.jdbc.JDBCTypes.getDatabaseTypeForCode; |
| import static org.eclipse.persistence.platform.database.oracle.plsql.OraclePLSQLType.PLSQLBoolean_IN_CONV; |
| import static org.eclipse.persistence.platform.database.oracle.plsql.OraclePLSQLType.PLSQLBoolean_OUT_CONV; |
| import static org.eclipse.persistence.platform.database.oracle.plsql.OraclePLSQLTypes.PLSQLBoolean; |
| import static org.eclipse.persistence.platform.database.oracle.plsql.OraclePLSQLTypes.XMLType; |
| |
| import java.io.Serializable; |
| import java.sql.CallableStatement; |
| import java.sql.PreparedStatement; |
| import java.sql.SQLException; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| import java.util.concurrent.TimeUnit; |
| |
| // EclipseLink imports |
| import org.eclipse.persistence.exceptions.QueryException; |
| import org.eclipse.persistence.internal.databaseaccess.Accessor; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor; |
| import org.eclipse.persistence.internal.helper.ComplexDatabaseType; |
| import org.eclipse.persistence.internal.helper.DatabaseField; |
| import org.eclipse.persistence.internal.helper.DatabaseType; |
| import org.eclipse.persistence.internal.helper.Helper; |
| import org.eclipse.persistence.internal.sessions.AbstractRecord; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.mappings.structures.ObjectRelationalDatabaseField; |
| import org.eclipse.persistence.platform.database.oracle.jdbc.OracleArrayType; |
| import org.eclipse.persistence.queries.StoredProcedureCall; |
| import org.eclipse.persistence.sessions.DatabaseRecord; |
| /** |
| * <b>Purpose</b>: |
| * Generates an Anonymous PL/SQL block to invoke the specified Stored Procedure |
| * with arguments that may or may not have JDBC equivalents. |
| * This handles conversion of PLSQL Record and Table types into SQL ARRAY (VARRAY) and STRUCT (OBJECT TYPE). |
| * It also handles conversion of flat PLSQL Record types and PLSQL BOOLEAN and other basic types. |
| */ |
| @SuppressWarnings("unchecked") |
| public class PLSQLStoredProcedureCall extends StoredProcedureCall { |
| |
| // can't use Helper.cr(), Oracle PL/SQL parser only likes Unix-style newlines '\n' |
| final static String BEGIN_DECLARE_BLOCK = NL + "DECLARE" + NL; |
| final static String BEGIN_BEGIN_BLOCK = "BEGIN" + NL; |
| final static String END_BEGIN_BLOCK = "END;"; |
| final static String PL2SQL_PREFIX = "EL_PL2SQL_"; |
| final static String SQL2PL_PREFIX = "EL_SQL2PL_"; |
| final static String BEGIN_DECLARE_FUNCTION = "FUNCTION "; |
| final static String RTURN = "RETURN "; |
| |
| /** |
| * List of procedure IN/OUT/INOUT arguments. |
| */ |
| protected List<PLSQLargument> arguments = new ArrayList<>(); |
| |
| /** |
| * Keeps track of the next procedure argument index. |
| */ |
| protected int originalIndex = 0; |
| /** |
| * Translation row stored after translation on the call clone, used only for logging. |
| */ |
| protected AbstractRecord translationRow; |
| /** |
| * Map of conversion function routines for converting complex PLSQL types. |
| */ |
| protected Map<String, TypeInfo> typesInfo; |
| /** |
| * Id used to generate unique local functions. |
| */ |
| protected int functionId = 0; |
| |
| public PLSQLStoredProcedureCall() { |
| super(); |
| setIsCallableStatementRequired(true); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add a named IN argument to the stored procedure. The databaseType parameter classifies the |
| * parameter (JDBCType vs. OraclePLSQLType, simple vs. complex) |
| */ |
| public void addNamedArgument(String procedureParameterName, DatabaseType databaseType) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, IN, dt)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add a named IN argument to the stored procedure. The databaseType parameter classifies the |
| * parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). The extra length parameter |
| * indicates that this parameter, when used in an Anonymous PL/SQL block, requires a length. |
| */ |
| public void addNamedArgument(String procedureParameterName, DatabaseType databaseType, |
| int length) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, IN, dt, length)); |
| } |
| |
| /** |
| * PUBLIC: |
| * Add a named IN argument to the stored procedure. The databaseType parameter classifies the |
| * parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). The extra scale and precision |
| * parameters indicates that this parameter, when used in an Anonymous PL/SQL block, requires |
| * scale and precision specification |
| */ |
| public void addNamedArgument(String procedureParameterName, DatabaseType databaseType, |
| int precision, int scale) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, IN, dt, precision, scale)); |
| } |
| |
| @Override |
| public void addNamedArgument(String procedureParameterName, String argumentFieldName, int type) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, IN, |
| getDatabaseTypeForCode(type))); // figure out databaseType from the sqlType |
| } |
| |
| @Override |
| public void addNamedArgument(String procedureParameterName, String argumentFieldName, int type, |
| String typeName) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, IN, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| /** |
| * PUBLIC: Add a named IN OUT argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex) |
| */ |
| public void addNamedInOutputArgument(String procedureParameterName, DatabaseType databaseType) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, dt)); |
| } |
| |
| /** |
| * PUBLIC: Add a named IN OUT argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). The extra length |
| * parameter indicates that this parameter, when used in an Anonymous PL/SQL block, requires a |
| * length. |
| */ |
| public void addNamedInOutputArgument(String procedureParameterName, DatabaseType databaseType, |
| int length) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, dt, length)); |
| } |
| |
| /** |
| * PUBLIC: Add a named IN OUT argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). The extra scale |
| * and precision parameters indicates that this parameter, when used in an Anonymous PL/SQL |
| * block, requires scale and precision specification |
| */ |
| public void addNamedInOutputArgument(String procedureParameterName, DatabaseType databaseType, |
| int precision, int scale) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, dt, |
| precision, scale)); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String inArgumentFieldName, |
| String outArgumentFieldName, int type) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String inArgumentFieldName, |
| String outArgumentFieldName, int type, String typeName) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String inArgumentFieldName, |
| String outArgumentFieldName, int type, String typeName, Class classType) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String inArgumentFieldName, |
| String outArgumentFieldName, int type, String typeName, Class javaType, |
| DatabaseField nestedType) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, INOUT, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| /** |
| * PUBLIC: Add a named OUT argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex) |
| */ |
| public void addNamedOutputArgument(String procedureParameterName, DatabaseType databaseType) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, dt)); |
| } |
| |
| /** |
| * PUBLIC: Add a named OUT argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). The extra length |
| * parameter indicates that this parameter, when used in an Anonymous PL/SQL block, requires a |
| * length. |
| */ |
| public void addNamedOutputArgument(String procedureParameterName, DatabaseType databaseType, |
| int length) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, dt, length)); |
| } |
| |
| /** |
| * PUBLIC: Add a named OUT argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). The extra scale |
| * and precision parameters indicates that this parameter, when used in an Anonymous PL/SQL |
| * block, requires scale and precision specification |
| */ |
| public void addNamedOutputArgument(String procedureParameterName, DatabaseType databaseType, |
| int precision, int scale) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, dt, |
| precision, scale)); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterName, String argumentFieldName, |
| int jdbcType, String typeName, Class javaType) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, |
| getDatabaseTypeForCode(jdbcType))); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterName, String argumentFieldName, |
| int jdbcType, String typeName, Class javaType, DatabaseField nestedType) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, |
| getDatabaseTypeForCode(jdbcType))); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterName, String argumentFieldName, |
| int type, String typeName) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterName, String argumentFieldName, |
| int type) { |
| arguments.add(new PLSQLargument(procedureParameterName, originalIndex++, OUT, |
| getDatabaseTypeForCode(type))); |
| } |
| |
| // un-supported addXXX operations |
| |
| @Override |
| public void addNamedArgument(String procedureParameterAndArgumentFieldName) { |
| throw QueryException.addArgumentsNotSupported("named arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedArgumentValue(String procedureParameterName, Object argumentValue) { |
| throw QueryException.addArgumentsNotSupported("named argument values without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedArgument(String procedureParameterName, String argumentFieldName) { |
| throw QueryException.addArgumentsNotSupported("named argument values without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterAndArgumentFieldName) { |
| throw QueryException.addArgumentsNotSupported("named IN OUT argument without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String argumentFieldName) { |
| throw QueryException.addArgumentsNotSupported("named IN OUT arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String argumentFieldName, |
| Class type) { |
| throw QueryException.addArgumentsNotSupported("named IN OUT arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedInOutputArgument(String procedureParameterName, String inArgumentFieldName, |
| String outArgumentFieldName, Class type) { |
| throw QueryException.addArgumentsNotSupported("named IN OUT arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedInOutputArgumentValue(String procedureParameterName, |
| Object inArgumentValue, String outArgumentFieldName, Class type) { |
| throw QueryException.addArgumentsNotSupported("named IN OUT argument values without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterAndArgumentFieldName) { |
| throw QueryException.addArgumentsNotSupported("named OUT arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterName, String argumentFieldName) { |
| throw QueryException.addArgumentsNotSupported("named OUT arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void addNamedOutputArgument(String procedureParameterName, String argumentFieldName, |
| Class type) { |
| throw QueryException.addArgumentsNotSupported("named OUT arguments without DatabaseType classification"); |
| } |
| |
| @Override |
| public void useNamedCursorOutputAsResultSet(String argumentName) { |
| throw QueryException.addArgumentsNotSupported("named OUT cursor arguments without DatabaseType classification"); |
| } |
| |
| // unlikely we will EVER support unnamed parameters |
| @Override |
| public void addUnamedArgument(String argumentFieldName, Class type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedArgument(String argumentFieldName, int type, String typeName, |
| DatabaseField nestedType) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedArgument(String argumentFieldName, int type, String typeName) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedArgument(String argumentFieldName, int type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedArgument(String argumentFieldName) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedArgumentValue(Object argumentValue) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String argumentFieldName, Class type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String inArgumentFieldName, String outArgumentFieldName, |
| Class type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String inArgumentFieldName, String outArgumentFieldName, |
| int type, String typeName, Class collection, DatabaseField nestedType) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String inArgumentFieldName, String outArgumentFieldName, |
| int type, String typeName, Class collection) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String inArgumentFieldName, String outArgumentFieldName, |
| int type, String typeName) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String inArgumentFieldName, String outArgumentFieldName, |
| int type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgument(String argumentFieldName) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedInOutputArgumentValue(Object inArgumentValue, String outArgumentFieldName, |
| Class type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedOutputArgument(String argumentFieldName, Class type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedOutputArgument(String argumentFieldName, int jdbcType, String typeName, |
| Class javaType, DatabaseField nestedType) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedOutputArgument(String argumentFieldName, int jdbcType, String typeName, |
| Class javaType) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedOutputArgument(String argumentFieldName, int type, String typeName) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedOutputArgument(String argumentFieldName, int type) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void addUnamedOutputArgument(String argumentFieldName) { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| @Override |
| public void useUnnamedCursorOutputAsResultSet() { |
| throw QueryException.unnamedArgumentsNotSupported(); |
| } |
| |
| /** |
| * PUBLIC: Add a named OUT cursor argument to the stored procedure. The databaseType parameter |
| * classifies the parameter (JDBCType vs. OraclePLSQLType, simple vs. complex). |
| */ |
| public void useNamedCursorOutputAsResultSet(String argumentName, DatabaseType databaseType) { |
| DatabaseType dt = databaseType.isComplexDatabaseType() ? |
| ((ComplexDatabaseType)databaseType).clone() : databaseType; |
| PLSQLargument newArg = new PLSQLargument(argumentName, originalIndex++, OUT, dt); |
| newArg.cursorOutput = true; |
| arguments.add(newArg); |
| } |
| |
| /** |
| * INTERNAL compute the re-ordered indices - Do the IN args first, then the |
| * 'IN-half' of the INOUT args next, the OUT args, then the 'OUT-half' of |
| * the INOUT args |
| */ |
| protected void assignIndices() { |
| List<PLSQLargument> inArguments = getArguments(arguments, IN); |
| List<PLSQLargument> inOutArguments = getArguments(arguments, INOUT); |
| inArguments.addAll(inOutArguments); |
| int newIndex = 1; |
| List<PLSQLargument> expandedArguments = new ArrayList<>(); |
| // Must move any expanded types to the end, as they are assigned after |
| // in the BEGIN clause. |
| for (ListIterator<PLSQLargument> inArgsIter = inArguments.listIterator(); inArgsIter.hasNext();) { |
| PLSQLargument inArg = inArgsIter.next(); |
| if (inArg.databaseType.isComplexDatabaseType() && (!((ComplexDatabaseType) inArg.databaseType).hasCompatibleType())) { |
| expandedArguments.add(inArg); |
| inArgsIter.remove(); |
| } |
| } |
| inArguments.addAll(expandedArguments); |
| for (ListIterator<PLSQLargument> inArgsIter = inArguments.listIterator(); inArgsIter.hasNext();) { |
| PLSQLargument inArg = inArgsIter.next(); |
| // delegate to arg's DatabaseType - ComplexTypes may expand arguments |
| // use ListIterator so that computeInIndex can add expanded args |
| newIndex = inArg.databaseType.computeInIndex(inArg, newIndex, inArgsIter); |
| } |
| for (PLSQLargument inArg : inArguments) { |
| DatabaseType type = inArg.databaseType; |
| String inArgName = inArg.name; |
| if (!type.isComplexDatabaseType()) { |
| // for XMLType, we need to set type name parameter (will be "XMLTYPE") |
| if (type == XMLType) { |
| super.addNamedArgument(inArgName, inArgName, type.getConversionCode(), type.getTypeName()); |
| } else { |
| super.addNamedArgument(inArgName, inArgName, type.getConversionCode()); |
| } |
| } else { |
| ComplexDatabaseType complexType = (ComplexDatabaseType) type; |
| if (inArg.inIndex != MIN_VALUE) { |
| if (complexType.isStruct()) { |
| super.addNamedArgument(inArgName, inArgName, complexType.getSqlCode(), complexType.getTypeName()); |
| } else if (complexType.isArray()) { |
| DatabaseType nestedType = ((OracleArrayType) complexType).getNestedType(); |
| if (nestedType != null) { |
| ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(""); |
| field.setSqlType(nestedType.getSqlCode()); |
| field.setSqlTypeName(nestedType.getTypeName()); |
| super.addNamedArgument(inArgName, inArgName, complexType.getSqlCode(), complexType.getTypeName(), field); |
| } else { |
| super.addNamedArgument(inArgName, inArgName, complexType.getSqlCode(), complexType.getTypeName()); |
| } |
| } else if (complexType.isCollection()) { |
| DatabaseType nestedType = ((PLSQLCollection) complexType).getNestedType(); |
| if (nestedType != null) { |
| ObjectRelationalDatabaseField field = new ObjectRelationalDatabaseField(""); |
| field.setSqlType(nestedType.getConversionCode()); |
| if (nestedType.isComplexDatabaseType()) { |
| field.setSqlTypeName(((ComplexDatabaseType) nestedType).getCompatibleType()); |
| } |
| super.addNamedArgument(inArgName, inArgName, type.getConversionCode(), complexType.getCompatibleType(), field); |
| } else { |
| super.addNamedArgument(inArgName, inArgName, type.getConversionCode(), complexType.getCompatibleType()); |
| } |
| } else { |
| super.addNamedArgument(inArgName, inArgName, type.getConversionCode(), complexType.getCompatibleType()); |
| } |
| } |
| } |
| } |
| List<PLSQLargument> outArguments = getArguments(arguments, OUT); |
| outArguments.addAll(inOutArguments); |
| for (ListIterator<PLSQLargument> outArgsIter = outArguments.listIterator(); outArgsIter.hasNext();) { |
| PLSQLargument outArg = outArgsIter.next(); |
| newIndex = outArg.databaseType.computeOutIndex(outArg, newIndex, outArgsIter); |
| } |
| for (PLSQLargument outArg : outArguments) { |
| String outArgName = outArg.name; |
| if (outArg.cursorOutput) { |
| super.useNamedCursorOutputAsResultSet(outArgName); |
| } else { |
| DatabaseType type = outArg.databaseType; |
| if (!type.isComplexDatabaseType()) { |
| // for XMLType, we need to set type name parameter (will be "XMLTYPE") |
| if (type == XMLType) { |
| super.addNamedOutputArgument(outArgName, outArgName, type.getConversionCode(), type.getTypeName()); |
| } else { |
| super.addNamedOutputArgument(outArgName, outArgName, type.getConversionCode()); |
| } |
| } else { |
| ComplexDatabaseType complexType = (ComplexDatabaseType) type; |
| if (outArg.outIndex != MIN_VALUE) { |
| if (complexType.isStruct()) { |
| super.addNamedOutputArgument(outArgName, outArgName, complexType.getSqlCode(), complexType.getTypeName(), complexType.getJavaType()); |
| } else if (complexType.isArray()) { |
| DatabaseType nestedType = ((OracleArrayType) complexType).getNestedType(); |
| if (nestedType != null) { |
| ObjectRelationalDatabaseField nestedField = new ObjectRelationalDatabaseField(""); |
| nestedField.setSqlType(nestedType.getSqlCode()); |
| if (nestedType.isComplexDatabaseType()) { |
| ComplexDatabaseType complexNestedType = (ComplexDatabaseType) nestedType; |
| nestedField.setType(complexNestedType.getJavaType()); |
| nestedField.setSqlTypeName(complexNestedType.getCompatibleType()); |
| } |
| super.addNamedOutputArgument(outArgName, outArgName, type.getSqlCode(), complexType.getTypeName(), complexType.getJavaType(), nestedField); |
| } else { |
| super.addNamedOutputArgument(outArgName, outArgName, type.getSqlCode(), complexType.getTypeName(), complexType.getJavaType()); |
| } |
| } else if (complexType.isCollection()) { |
| DatabaseType nestedType = ((PLSQLCollection) complexType).getNestedType(); |
| if (nestedType != null) { |
| ObjectRelationalDatabaseField nestedField = new ObjectRelationalDatabaseField(outArgName); |
| nestedField.setSqlType(nestedType.getSqlCode()); |
| if (nestedType.isComplexDatabaseType()) { |
| ComplexDatabaseType complexNestedType = (ComplexDatabaseType) nestedType; |
| nestedField.setType(complexNestedType.getJavaType()); |
| nestedField.setSqlTypeName(complexNestedType.getCompatibleType()); |
| } |
| super.addNamedOutputArgument(outArgName, outArgName, type.getSqlCode(), complexType.getCompatibleType(), complexType.getJavaType(), nestedField); |
| } else { |
| super.addNamedOutputArgument(outArgName, outArgName, type.getSqlCode(), complexType.getCompatibleType()); |
| } |
| } else if (complexType.hasCompatibleType()) { |
| super.addNamedOutputArgument(outArgName, outArgName, type.getSqlCode(), complexType.getCompatibleType(), complexType.getJavaType()); |
| } else { |
| // If there is no STRUCT type set, then the output is |
| // expanded, so one output for each field. |
| super.addNamedOutputArgument(outArgName, outArgName, type.getSqlCode()); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Generate portion of the Anonymous PL/SQL block that declares the temporary variables |
| * in the DECLARE section. |
| */ |
| protected void buildDeclareBlock(StringBuilder sb, List<PLSQLargument> arguments) { |
| List<PLSQLargument> inArguments = getArguments(arguments, IN); |
| List<PLSQLargument> inOutArguments = getArguments(arguments, INOUT); |
| inArguments.addAll(inOutArguments); |
| List<PLSQLargument> outArguments = getArguments(arguments, OUT); |
| Collections.sort(inArguments, new InArgComparer()); |
| for (PLSQLargument arg : inArguments) { |
| arg.databaseType.buildInDeclare(sb, arg); |
| } |
| Collections.sort(outArguments, new OutArgComparer()); |
| for (PLSQLargument arg : outArguments) { |
| arg.databaseType.buildOutDeclare(sb, arg); |
| } |
| } |
| |
| /** |
| * INTERNAL |
| * Add the nested function string required for the type and its subtypes. The functions |
| * must be added in inverse order to resolve dependencies. |
| */ |
| protected void addNestedFunctionsForArgument(List functions, PLSQLargument argument, |
| DatabaseType databaseType, Set<DatabaseType> processed) { |
| if ((databaseType == null) |
| || !databaseType.isComplexDatabaseType() |
| || databaseType.isJDBCType() |
| || argument.cursorOutput |
| || processed.contains(databaseType)) { |
| return; |
| } |
| ComplexDatabaseType type = (ComplexDatabaseType)databaseType; |
| if (!type.hasCompatibleType()) { |
| return; |
| } |
| processed.add(type); |
| boolean isNestedTable = false; |
| if (type.isCollection()) { |
| isNestedTable = ((PLSQLCollection)type).isNestedTable(); |
| DatabaseType nestedType = ((PLSQLCollection)type).getNestedType(); |
| addNestedFunctionsForArgument(functions, argument, nestedType, processed); |
| } else if (type.isRecord()) { |
| for (PLSQLargument field : ((PLSQLrecord)type).getFields()) { |
| DatabaseType nestedType = field.databaseType; |
| addNestedFunctionsForArgument(functions, argument, nestedType, processed); |
| } |
| } |
| TypeInfo info = this.typesInfo.get(type.getTypeName()); |
| // If the info was not found in publisher, then generate it. |
| if (info == null) { |
| info = generateNestedFunction(type, isNestedTable); |
| } |
| if (argument.direction == IN) { |
| if (!functions.contains(info.sql2PlConv)) { |
| functions.add(info.sql2PlConv); |
| } |
| } else if (argument.direction == INOUT) { |
| if (!functions.contains(info.sql2PlConv)) { |
| functions.add(info.sql2PlConv); |
| } |
| if (!functions.contains(info.pl2SqlConv)) { |
| functions.add(info.pl2SqlConv); |
| } |
| } else if (argument.direction == OUT) { |
| if (!functions.contains(info.pl2SqlConv)) { |
| functions.add(info.pl2SqlConv); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL: Generate the nested function to convert the PLSQL type to its compatible SQL type. |
| */ |
| protected TypeInfo generateNestedFunction(ComplexDatabaseType type) { |
| return generateNestedFunction(type, false); |
| } |
| |
| /** |
| * INTERNAL: Generate the nested function to convert the PLSQL type to its compatible SQL type. |
| */ |
| protected TypeInfo generateNestedFunction(ComplexDatabaseType type, boolean isNonAssociativeCollection) { |
| TypeInfo info = new TypeInfo(); |
| info.pl2SqlName = PL2SQL_PREFIX + (this.functionId++); |
| info.sql2PlName = SQL2PL_PREFIX + (this.functionId++); |
| if (type.isRecord()) { |
| PLSQLrecord record = (PLSQLrecord)type; |
| StringBuilder sb = new StringBuilder(); |
| sb.append(INDENT); |
| sb.append(BEGIN_DECLARE_FUNCTION); |
| sb.append(info.pl2SqlName); |
| sb.append("(aPlsqlItem "); |
| sb.append(record.getTypeName()); |
| sb.append(")"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append(record.getCompatibleType()); |
| sb.append(" IS"); |
| sb.append(NL);sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem "); |
| sb.append(record.getCompatibleType()); |
| sb.append(";"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(BEGIN_BEGIN_BLOCK); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem := "); |
| sb.append(record.getCompatibleType()); |
| sb.append("("); |
| int size = record.getFields().size(); |
| for (int index = 0; index < size; index++) { |
| sb.append("NULL"); |
| if ((index + 1) != size) { |
| sb.append(", "); |
| } |
| } |
| sb.append(");"); |
| sb.append(NL); |
| for (PLSQLargument argument : record.getFields()) { |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem."); |
| sb.append(argument.name); |
| if (argument.databaseType.isComplexDatabaseType() && !argument.databaseType.isJDBCType()) { |
| sb.append(" := "); |
| sb.append(getPl2SQLName((ComplexDatabaseType)argument.databaseType)); |
| sb.append("(aPlsqlItem."); |
| sb.append(argument.name); |
| sb.append(");"); |
| } |
| else if (argument.databaseType.equals(PLSQLBoolean)) { |
| sb.append(" := "); |
| sb.append(PLSQLBoolean_OUT_CONV); |
| sb.append("(aPlsqlItem."); |
| sb.append(argument.name); |
| sb.append(");"); |
| } |
| else { |
| sb.append(" := aPlsqlItem."); |
| sb.append(argument.name); |
| sb.append(";"); |
| } |
| sb.append(NL); |
| } |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append("aSqlItem;"); |
| sb.append(NL); |
| sb.append(INDENT); |
| sb.append("END "); |
| sb.append(info.pl2SqlName); |
| sb.append(";"); |
| sb.append(NL); |
| |
| info.pl2SqlConv = sb.toString(); |
| |
| sb = new StringBuilder(); |
| sb.append(INDENT); |
| sb.append(BEGIN_DECLARE_FUNCTION); |
| sb.append(info.sql2PlName); |
| sb.append("(aSqlItem "); |
| sb.append(record.getCompatibleType()); |
| sb.append(") "); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append(record.getTypeName()); |
| sb.append(" IS"); |
| sb.append(NL);sb.append(INDENT);sb.append(INDENT); |
| sb.append("aPlsqlItem "); |
| sb.append(record.getTypeName()); |
| sb.append(";"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(BEGIN_BEGIN_BLOCK); |
| for (PLSQLargument argument : record.getFields()) { |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("aPlsqlItem."); |
| sb.append(argument.name); |
| if (argument.databaseType.isComplexDatabaseType() && !argument.databaseType.isJDBCType()) { |
| sb.append(" := "); |
| sb.append(getSQL2PlName((ComplexDatabaseType)argument.databaseType)); |
| sb.append("(aSqlItem."); |
| sb.append(argument.name); |
| sb.append(");"); |
| } |
| else if (argument.databaseType.equals(PLSQLBoolean)) { |
| sb.append(" := "); |
| sb.append(PLSQLBoolean_IN_CONV); |
| sb.append("(aSqlItem."); |
| sb.append(argument.name); |
| sb.append(");"); |
| } |
| else { |
| sb.append(" := aSqlItem."); |
| sb.append(argument.name); |
| sb.append(";"); |
| } |
| sb.append(NL); |
| } |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append("aPlsqlItem;"); |
| sb.append(NL); |
| sb.append(INDENT); |
| sb.append("END "); |
| sb.append(info.sql2PlName); |
| sb.append(";"); |
| sb.append(NL); |
| |
| info.sql2PlConv = sb.toString(); |
| } |
| else if (type.isCollection()) { |
| PLSQLCollection collection = (PLSQLCollection)type; |
| StringBuilder sb = new StringBuilder(); |
| sb.append(INDENT); |
| sb.append(BEGIN_DECLARE_FUNCTION); |
| sb.append(info.pl2SqlName); |
| sb.append("(aPlsqlItem "); |
| sb.append(collection.getTypeName()); |
| sb.append(")"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append(collection.getCompatibleType()); |
| sb.append(" IS"); |
| sb.append(NL);sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem "); |
| sb.append(collection.getCompatibleType()); |
| sb.append(";"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(BEGIN_BEGIN_BLOCK); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem := "); |
| sb.append(collection.getCompatibleType()); |
| sb.append("();"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem.EXTEND(aPlsqlItem.COUNT);"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("IF aPlsqlItem.COUNT > 0 THEN"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("FOR I IN aPlsqlItem.FIRST..aPlsqlItem.LAST LOOP"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT);sb.append(INDENT); |
| sb.append("aSqlItem(I + 1 - aPlsqlItem.FIRST) := "); |
| if (collection.nestedType != null && (collection.nestedType.isComplexDatabaseType() && !collection.nestedType.isJDBCType())) { |
| sb.append(getPl2SQLName((ComplexDatabaseType)collection.nestedType)); |
| sb.append("(aPlsqlItem(I));"); |
| } |
| else if (PLSQLBoolean.equals(collection.nestedType)) { |
| sb.append(PLSQLBoolean_OUT_CONV); |
| sb.append("(aPlsqlItem(I));"); |
| } |
| else { |
| sb.append("aPlsqlItem(I);"); |
| } |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT);sb.append(INDENT); |
| sb.append("END LOOP;"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("END IF;"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append("aSqlItem;"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append("END "); |
| sb.append(info.pl2SqlName); |
| sb.append(";"); |
| sb.append(NL); |
| |
| info.pl2SqlConv = sb.toString(); |
| |
| sb = new StringBuilder(); |
| sb.append(INDENT); |
| sb.append(BEGIN_DECLARE_FUNCTION); |
| sb.append(info.sql2PlName); |
| sb.append("(aSqlItem "); |
| sb.append(collection.getCompatibleType()); |
| sb.append(")"); |
| sb.append(NL); |
| sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append(collection.getTypeName()); |
| sb.append(" IS"); |
| sb.append(NL);sb.append(INDENT);sb.append(INDENT); |
| sb.append("aPlsqlItem "); |
| sb.append(collection.getTypeName()); |
| sb.append(";"); |
| sb.append(NL);sb.append(INDENT); |
| sb.append(BEGIN_BEGIN_BLOCK); |
| sb.append(INDENT);sb.append(INDENT); |
| // if the collection is non-associative we need to initialize it |
| if (isNonAssociativeCollection) { |
| sb.append("aPlsqlItem := "); |
| sb.append(collection.getTypeName()); |
| sb.append("();"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("aPlsqlItem.EXTEND(aSqlItem.COUNT);"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| } |
| sb.append("IF aSqlItem.COUNT > 0 THEN"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT);sb.append(INDENT); |
| sb.append("FOR I IN 1..aSqlItem.COUNT LOOP"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT);sb.append(INDENT);sb.append(INDENT); |
| if ((collection.nestedType != null) && collection.nestedType.isComplexDatabaseType()) { |
| sb.append("aPlsqlItem(I) := "); |
| sb.append(getSQL2PlName((ComplexDatabaseType)collection.nestedType)); |
| sb.append("(aSqlItem(I));"); |
| } |
| else if (PLSQLBoolean.equals(collection.nestedType)) { |
| sb.append("aPlsqlItem(I + 1 - aSqlItem.FIRST) := "); |
| sb.append(PLSQLBoolean_IN_CONV); |
| sb.append("(aSqlItem(I));"); |
| } |
| else { |
| sb.append("aPlsqlItem(I) := aSqlItem(I);"); |
| } |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT);sb.append(INDENT); |
| sb.append("END LOOP;"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append("END IF;"); |
| sb.append(NL); |
| sb.append(INDENT);sb.append(INDENT); |
| sb.append(RTURN); |
| sb.append("aPlsqlItem;"); |
| sb.append(NL); |
| sb.append(INDENT); |
| sb.append("END "); |
| sb.append(info.sql2PlName); |
| sb.append(";"); |
| sb.append(NL); |
| |
| info.sql2PlConv = sb.toString(); |
| } |
| this.typesInfo.put(type.getTypeName(), info); |
| return info; |
| } |
| |
| /** |
| * INTERNAL |
| * Generate portion of the Anonymous PL/SQL block with PL/SQL conversion routines as |
| * nested functions. |
| */ |
| protected void buildNestedFunctions(StringBuilder stream, List<PLSQLargument> arguments) { |
| List<String> nestedFunctions = new ArrayList<>(); |
| Set<DatabaseType> processed = new HashSet<>(); |
| for (PLSQLargument arg : arguments) { |
| DatabaseType type = arg.databaseType; |
| addNestedFunctionsForArgument(nestedFunctions, arg, type, processed); |
| } |
| if (!nestedFunctions.isEmpty()) { |
| for (String function : nestedFunctions) { |
| stream.append(function); |
| } |
| } |
| } |
| |
| /** |
| * INTERNAL Generate portion of the Anonymous PL/SQL block that assigns fields at the beginning |
| * of the BEGIN block (before invoking the target procedure). |
| */ |
| protected void buildBeginBlock(StringBuilder sb, List<PLSQLargument> arguments) { |
| List<PLSQLargument> inArguments = getArguments(arguments, IN); |
| inArguments.addAll(getArguments(arguments, INOUT)); |
| for (PLSQLargument arg : inArguments) { |
| arg.databaseType.buildBeginBlock(sb, arg, this); |
| } |
| } |
| |
| /** |
| * INTERNAL Generate portion of the Anonymous PL/SQL block that invokes the target procedure. |
| */ |
| protected void buildProcedureInvocation(StringBuilder sb, List<PLSQLargument> arguments) { |
| sb.append(" "); |
| sb.append(getProcedureName()); |
| sb.append("("); |
| int size = arguments.size(), idx = 1; |
| for (PLSQLargument argument : arguments) { |
| sb.append(argument.name); |
| sb.append("=>"); |
| sb.append(databaseTypeHelper.buildTarget(argument)); |
| if (idx < size) { |
| sb.append(", "); |
| idx++; |
| } |
| } |
| sb.append(");"); |
| sb.append(NL); |
| } |
| |
| /** |
| * INTERNAL Generate portion of the Anonymous PL/SQL block after the target procedures has been |
| * invoked and OUT parameters must be handled. |
| */ |
| protected void buildOutAssignments(StringBuilder sb, List<PLSQLargument> arguments) { |
| List<PLSQLargument> outArguments = getArguments(arguments, OUT); |
| outArguments.addAll(getArguments(arguments, INOUT)); |
| for (PLSQLargument arg : outArguments) { |
| arg.databaseType.buildOutAssignment(sb, arg, this); |
| } |
| } |
| |
| /** |
| * Generate the Anonymous PL/SQL block |
| */ |
| @Override |
| protected void prepareInternal(AbstractSession session) { |
| // build any and all required type conversion routines for |
| // complex PL/SQL types in packages |
| this.typesInfo = new HashMap<>(); |
| // Rest parameters to be recomputed if being reprepared. |
| this.parameters = null; |
| // create a copy of the arguments re-ordered with different indices |
| assignIndices(); |
| |
| // Filter out any optional arguments that are null. |
| List<PLSQLargument> specifiedArguments = this.arguments; |
| AbstractRecord row = getQuery().getTranslationRow(); |
| if ((row != null) && hasOptionalArguments()) { |
| for (PLSQLargument argument : this.arguments) { |
| DatabaseField queryArgument = new DatabaseField(argument.name); |
| if (this.optionalArguments.contains(queryArgument) && (row.get(queryArgument) == null)) { |
| if (specifiedArguments == this.arguments) { |
| specifiedArguments = new ArrayList<>(this.arguments); |
| } |
| specifiedArguments.remove(argument); |
| } |
| } |
| } |
| // build the Anonymous PL/SQL block in sections |
| StringBuilder sb = new StringBuilder(); |
| if (!specifiedArguments.isEmpty()) { |
| sb.append(BEGIN_DECLARE_BLOCK); |
| buildDeclareBlock(sb, specifiedArguments); |
| buildNestedFunctions(sb, specifiedArguments); |
| } |
| sb.append(BEGIN_BEGIN_BLOCK); |
| buildBeginBlock(sb, specifiedArguments); |
| buildProcedureInvocation(sb, specifiedArguments); |
| buildOutAssignments(sb, specifiedArguments); |
| sb.append(END_BEGIN_BLOCK); |
| setSQLStringInternal(sb.toString()); |
| super.prepareInternalParameters(session); |
| } |
| |
| /** |
| * INTERNAL: |
| * Prepare the JDBC statement, this may be parameterize or a call statement. |
| * If caching statements this must check for the pre-prepared statement and re-bind to it. |
| */ |
| @Override |
| public Statement prepareStatement(DatabaseAccessor accessor, AbstractRecord translationRow, AbstractSession session) throws SQLException { |
| //#Bug5200836 pass shouldUnwrapConnection flag to indicate whether or not using unwrapped connection. |
| Statement statement = accessor.prepareStatement(this, session); |
| |
| // Setup the max rows returned and query timeout limit. |
| if (this.queryTimeout > 0 && this.queryTimeoutUnit != null) { |
| long timeout = TimeUnit.SECONDS.convert(this.queryTimeout, this.queryTimeoutUnit); |
| |
| if(timeout > Integer.MAX_VALUE){ |
| timeout = Integer.MAX_VALUE; |
| } |
| |
| //Round up the timeout if SECONDS are larger than the given units |
| if(TimeUnit.SECONDS.compareTo(this.queryTimeoutUnit) > 0 && this.queryTimeout % 1000 > 0){ |
| timeout += 1; |
| } |
| statement.setQueryTimeout((int)timeout); |
| } |
| if (!this.ignoreMaxResultsSetting && this.maxRows > 0) { |
| statement.setMaxRows(this.maxRows); |
| } |
| if (this.resultSetFetchSize > 0) { |
| statement.setFetchSize(this.resultSetFetchSize); |
| } |
| |
| if (this.parameters == null) { |
| return statement; |
| } |
| List parameters = getParameters(); |
| int size = parameters.size(); |
| for (int index = 0; index < size; index++) { |
| session.getPlatform().setParameterValueInDatabaseCall(parameters.get(index), (PreparedStatement)statement, index+1, session); |
| } |
| |
| return statement; |
| } |
| |
| /** |
| * Translate the PLSQL procedure translation row, into the row |
| * expected by the SQL procedure. |
| * This handles expanding and re-ordering parameters. |
| */ |
| @Override |
| public void translate(AbstractRecord translationRow, AbstractRecord modifyRow, AbstractSession session) { |
| // re-order elements in translationRow to conform to re-ordered indices |
| AbstractRecord copyOfTranslationRow = translationRow.clone(); |
| int len = copyOfTranslationRow.size(); |
| List<DatabaseField> copyOfTranslationFields = copyOfTranslationRow.getFields(); |
| translationRow.clear(); |
| Vector<DatabaseField> translationRowFields = translationRow.getFields(); |
| translationRowFields.setSize(len); |
| Vector translationRowValues = translationRow.getValues(); |
| translationRowValues.setSize(len); |
| for (PLSQLargument arg : arguments) { |
| if (arg.direction == IN || arg.direction == INOUT) { |
| arg.databaseType.translate(arg, translationRow, |
| copyOfTranslationRow, copyOfTranslationFields, translationRowFields, |
| translationRowValues, this); |
| } |
| } |
| this.translationRow = translationRow; // save a copy for logging |
| super.translate(translationRow, modifyRow, session); |
| } |
| |
| /** |
| * Translate the SQL procedure output row, into the row |
| * expected by the PLSQL procedure. |
| * This handles re-ordering parameters. |
| */ |
| @Override |
| public AbstractRecord buildOutputRow(CallableStatement statement, DatabaseAccessor accessor, AbstractSession session) throws SQLException { |
| |
| AbstractRecord outputRow = super.buildOutputRow(statement, accessor, session); |
| if (!shouldBuildOutputRow) { |
| outputRow.put("", 1); // fake-out Oracle executeUpdate rowCount, always 1 |
| return outputRow; |
| } |
| // re-order elements in outputRow to conform to original indices |
| Vector<DatabaseField> outputRowFields = outputRow.getFields(); |
| Vector outputRowValues = outputRow.getValues(); |
| DatabaseRecord newOutputRow = new DatabaseRecord(); |
| List<PLSQLargument> outArguments = getArguments(arguments, OUT); |
| outArguments.addAll(getArguments(arguments, INOUT)); |
| Collections.sort(outArguments, new Comparator<PLSQLargument>() { |
| @Override |
| public int compare(PLSQLargument o1, PLSQLargument o2) { |
| return o1.originalIndex - o2.originalIndex; |
| } |
| }); |
| for (PLSQLargument outArg : outArguments) { |
| outArg.databaseType.buildOutputRow(outArg, outputRow, newOutputRow, outputRowFields, outputRowValues); |
| } |
| return newOutputRow; |
| } |
| |
| /** |
| * INTERNAL: |
| * Build the log string for the call. |
| */ |
| @Override |
| public String getLogString(Accessor accessor) { |
| |
| StringBuilder sb = new StringBuilder(getSQLString()); |
| sb.append(Helper.cr()); |
| sb.append(INDENT); |
| sb.append("bind => ["); |
| List<PLSQLargument> specifiedArguments = this.arguments; |
| AbstractRecord row = getQuery().getTranslationRow(); |
| if ((row != null) && hasOptionalArguments()) { |
| for (PLSQLargument argument : this.arguments) { |
| DatabaseField queryArgument = new DatabaseField(argument.name); |
| if (this.optionalArguments.contains(queryArgument) && (row.get(queryArgument) == null)) { |
| if (specifiedArguments == this.arguments) { |
| specifiedArguments = new ArrayList<>(this.arguments); |
| } |
| specifiedArguments.remove(argument); |
| } |
| } |
| } |
| List<PLSQLargument> inArguments = getArguments(specifiedArguments, IN); |
| inArguments.addAll(getArguments(specifiedArguments, INOUT)); |
| Collections.sort(inArguments, new Comparator<PLSQLargument>() { |
| @Override |
| public int compare(PLSQLargument o1, PLSQLargument o2) { |
| return o1.inIndex - o2.inIndex; |
| } |
| }); |
| for (Iterator<PLSQLargument> i = inArguments.iterator(); i.hasNext();) { |
| PLSQLargument inArg = i.next(); |
| inArg.databaseType.logParameter(sb, IN, inArg, translationRow, |
| getQuery().getSession().getPlatform()); |
| if (i.hasNext()) { |
| sb.append(", "); |
| } |
| } |
| List<PLSQLargument> outArguments = getArguments(specifiedArguments, OUT); |
| outArguments.addAll(getArguments(specifiedArguments, INOUT)); |
| Collections.sort(outArguments, new Comparator<PLSQLargument>() { |
| @Override |
| public int compare(PLSQLargument o1, PLSQLargument o2) { |
| return o1.outIndex - o2.outIndex; |
| } |
| }); |
| if (!inArguments.isEmpty() && !outArguments.isEmpty()) { |
| sb.append(", "); |
| } |
| for (Iterator<PLSQLargument> i = outArguments.iterator(); i.hasNext();) { |
| PLSQLargument outArg = i.next(); |
| outArg.databaseType.logParameter(sb, OUT, outArg, translationRow, |
| getQuery().getSession().getPlatform()); |
| if (i.hasNext()) { |
| sb.append(", "); |
| } |
| } |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| /** |
| * INTERNAL |
| * |
| * @return list of arguments with the specified direction |
| */ |
| protected static List<PLSQLargument> getArguments(List<PLSQLargument> args, Integer direction) { |
| List<PLSQLargument> inArgs = new ArrayList<>(); |
| for (PLSQLargument arg : args) { |
| if (arg.direction == direction) { |
| inArgs.add(arg); |
| } |
| } |
| return inArgs; |
| } |
| |
| // Made static for performance reasons. |
| /** |
| * INTERNAL: |
| * Helper structure used to store the PLSQL type conversion routines. |
| */ |
| static final class TypeInfo { |
| String sql2PlName; |
| String sql2PlConv; |
| String pl2SqlName; |
| String pl2SqlConv; |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(NL); |
| sb.append(sql2PlName == null ? "" : sql2PlConv); |
| sb.append(pl2SqlName == null ? "" : pl2SqlConv); |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * Return the conversion function name, generate the function if missing. |
| */ |
| public String getSQL2PlName(ComplexDatabaseType type) { |
| if (typesInfo == null) { |
| return null; |
| } |
| TypeInfo info = typesInfo.get(type.getTypeName()); |
| if (info == null) { |
| info = generateNestedFunction(type); |
| } |
| return info.sql2PlName; |
| } |
| |
| @Override |
| public boolean isStoredPLSQLProcedureCall() { |
| return true; |
| } |
| |
| /** |
| * Return the conversion function name, generate the function if missing. |
| */ |
| public String getPl2SQLName(ComplexDatabaseType type) { |
| if (typesInfo == null) { |
| return null; |
| } |
| TypeInfo info = typesInfo.get(type.getTypeName()); |
| if (info == null) { |
| info = generateNestedFunction(type); |
| } |
| return info.pl2SqlName; |
| } |
| |
| @Override |
| public Object getOutputParameterValue(CallableStatement statement, int index, AbstractSession session) throws SQLException { |
| return session.getPlatform().getParameterValueFromDatabaseCall(statement, index + 1, session); |
| } |
| |
| /** |
| * Return the PLSQL arguments. |
| */ |
| public List<PLSQLargument> getArguments() { |
| return arguments; |
| } |
| |
| /** |
| * Set the PLSQL arguments. |
| */ |
| public void setArguments(List<PLSQLargument> arguments) { |
| this.arguments = arguments; |
| } |
| |
| // Made static final for performance reasons. |
| /** |
| * Class responsible for comparing PLSQLargument instances based on |
| * the inIndex property. |
| * |
| */ |
| static final class InArgComparer implements Comparator<PLSQLargument>, Serializable { |
| private static final long serialVersionUID = -4182293492217092689L; |
| @Override |
| public int compare(PLSQLargument arg0, PLSQLargument arg1) { |
| if (arg0.inIndex < arg1.inIndex) { |
| return -1; |
| } |
| if (arg0.inIndex > arg1.inIndex) { |
| return 1; |
| } |
| return 0; |
| } |
| } |
| |
| // Made static final for performance reasons. |
| /** |
| * Class responsible for comparing PLSQLargument instances based on |
| * the outIndex property. |
| * |
| */ |
| static final class OutArgComparer implements Comparator<PLSQLargument>, Serializable { |
| private static final long serialVersionUID = -4182293492217092689L; |
| @Override |
| public int compare(PLSQLargument arg0, PLSQLargument arg1) { |
| if (arg0.inIndex < arg1.outIndex) { |
| return -1; |
| } |
| if (arg0.inIndex > arg1.outIndex) { |
| return 1; |
| } |
| return 0; |
| } |
| } |
| } |