/******************************************************************************* * Copyright (c) 1998, 2016 Oracle and/or its affiliates, IBM Corporation. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink * Markus Karg - bug fix for log operator * 09/09/2011-2.3.1 Guy Pelletier * - 356197: Add new VPD type to MultitenantType * 09/14/2011-2.3.1 Guy Pelletier * - 357533: Allow DDL queries to execute even when Multitenant entities are part of the PU * 02/04/2013-2.5 Guy Pelletier * - 389090: JPA 2.1 DDL Generation Support * 02/19/2015 - Rick Curtis * - 458877 : Add national character support * 02/23/2015-2.6 Dalia Abo Sheasha * - 460607: Change DatabasePlatform StoredProcedureTerminationToken to be configurable *****************************************************************************/ package org.eclipse.persistence.platform.database; import java.io.CharArrayWriter; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; import java.util.Calendar; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import org.eclipse.persistence.exceptions.DatabaseException; import org.eclipse.persistence.exceptions.ValidationException; import org.eclipse.persistence.expressions.ExpressionOperator; import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition; import org.eclipse.persistence.internal.expressions.ExpressionSQLPrinter; import org.eclipse.persistence.internal.expressions.FunctionExpression; import org.eclipse.persistence.internal.expressions.RelationExpression; import org.eclipse.persistence.internal.expressions.SQLSelectStatement; import org.eclipse.persistence.internal.helper.ClassConstants; import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.helper.Helper; import org.eclipse.persistence.internal.helper.NonSynchronizedVector; import org.eclipse.persistence.internal.localization.ExceptionLocalization; import org.eclipse.persistence.internal.sessions.AbstractSession; import org.eclipse.persistence.queries.DataModifyQuery; import org.eclipse.persistence.queries.DatabaseQuery; import org.eclipse.persistence.queries.ObjectBuildingQuery; import org.eclipse.persistence.queries.ReadQuery; import org.eclipse.persistence.queries.SQLCall; import org.eclipse.persistence.queries.ValueReadQuery; /** * <p><b>Purpose</b>: Provides Oracle specific behavior. * <p><b>Responsibilities</b>:<ul> * <li> Native SQL for byte[], Date, Time, {@literal &} Timestamp. * <li> Native sequencing named sequences. * <li> Native SQL/ROWNUM support for MaxRows and FirstResult filtering. * </ul> * * @since TOPLink/Java 1.0 */ public class OraclePlatform extends org.eclipse.persistence.platform.database.DatabasePlatform { protected static DataModifyQuery vpdSetIdentifierQuery; protected static DataModifyQuery vpdClearIdentifierQuery; /** * Whether a FOR UPDATE clause should be printed at the end of the query */ protected boolean shouldPrintForUpdateClause; /** * Advanced attribute indicating whether identity is supported, * see comment to setSupportsIdentity method. */ protected boolean supportsIdentity; public OraclePlatform(){ super(); this.pingSQL = "SELECT 1 FROM DUAL"; this.storedProcedureTerminationToken = ""; this.shouldPrintForUpdateClause = true; } @Override public void initializeConnectionData(Connection connection) throws SQLException { DatabaseMetaData dmd = connection.getMetaData(); // Tested with 11.1.0.6 this.driverSupportsNationalCharacterVarying = Helper.compareVersions(dmd.getDriverVersion(), "11.1") >= 0; } /* * Used for stored procedure definitions. */ @Override public boolean allowsSizeInProcedureArguments() { return false; } /** * INTERNAL: * If using native SQL then print a byte[] literally as a hex string otherwise use ODBC format * as provided in DatabasePlatform. */ @Override protected void appendByteArray(byte[] bytes, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write('\''); Helper.writeHexString(bytes, writer); writer.write('\''); } else { super.appendByteArray(bytes, writer); } } /** * INTERNAL: * Appends an Oracle specific date if usesNativeSQL is true otherwise use the ODBC format. * Native FORMAT: to_date('1997-11-06','yyyy-mm-dd') */ @Override protected void appendDate(java.sql.Date date, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write("to_date('"); writer.write(Helper.printDate(date)); writer.write("','yyyy-mm-dd')"); } else { super.appendDate(date, writer); } } /** * INTERNAL: * Appends an Oracle specific time if usesNativeSQL is true otherwise use the ODBC format. * Native FORMAT: to_date(#####, 'sssss'). */ @Override protected void appendTime(java.sql.Time time, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write("to_date('"); writer.write(Helper.printTime(time)); writer.write("','hh24:mi:ss')"); } else { super.appendTime(time, writer); } } /** * INTERNAL: * Appends an Oracle specific Timestamp, if usesNativeSQL is true otherwise use the ODBC format. * Native Format: to_date ('1997-11-06 10:35:45.0' , 'yyyy-mm-dd hh:mm:ss.n') */ @Override protected void appendTimestamp(java.sql.Timestamp timestamp, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write("to_date('"); writer.write(Helper.printTimestampWithoutNanos(timestamp)); writer.write("','yyyy-mm-dd hh24:mi:ss')"); } else { super.appendTimestamp(timestamp, writer); } } /** * INTERNAL: * Appends an Oracle specific Timestamp, if usesNativeSQL is true otherwise use the ODBC format. * Native Format: to_date ('1997-11-06 10:35:45.0' , 'yyyy-mm-dd hh:mm:ss.n') */ @Override protected void appendCalendar(Calendar calendar, Writer writer) throws IOException { if (usesNativeSQL()) { writer.write("to_date('"); writer.write(Helper.printCalendarWithoutNanos(calendar)); writer.write("','yyyy-mm-dd hh24:mi:ss')"); } else { super.appendCalendar(calendar, writer); } } /** * INTERNAL: * Build operator. */ public ExpressionOperator atan2Operator() { return ExpressionOperator.simpleTwoArgumentFunction(ExpressionOperator.Atan2, "ATAN2"); } /** * INTERNAL: */ @Override protected Hashtable buildFieldTypes() { Hashtable fieldTypeMapping; fieldTypeMapping = new Hashtable(); fieldTypeMapping.put(Boolean.class, new FieldTypeDefinition("NUMBER(1) default 0", false)); fieldTypeMapping.put(Integer.class, new FieldTypeDefinition("NUMBER", 10)); fieldTypeMapping.put(Long.class, new FieldTypeDefinition("NUMBER", 19)); fieldTypeMapping.put(Float.class, new FieldTypeDefinition("NUMBER", 19, 4)); fieldTypeMapping.put(Double.class, new FieldTypeDefinition("NUMBER", 19, 4)); fieldTypeMapping.put(Short.class, new FieldTypeDefinition("NUMBER", 5)); fieldTypeMapping.put(Byte.class, new FieldTypeDefinition("NUMBER", 3)); fieldTypeMapping.put(java.math.BigInteger.class, new FieldTypeDefinition("NUMBER", 38)); fieldTypeMapping.put(java.math.BigDecimal.class, new FieldTypeDefinition("NUMBER", 38).setLimits(38, -38, 38)); fieldTypeMapping.put(Number.class, new FieldTypeDefinition("NUMBER", 38).setLimits(38, -38, 38)); if(getUseNationalCharacterVaryingTypeForString()){ fieldTypeMapping.put(String.class, new FieldTypeDefinition("NVARCHAR2", DEFAULT_VARCHAR_SIZE)); }else { fieldTypeMapping.put(String.class, new FieldTypeDefinition("VARCHAR2", DEFAULT_VARCHAR_SIZE)); } fieldTypeMapping.put(Character.class, new FieldTypeDefinition("CHAR", 1)); fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("BLOB", false)); fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("CLOB", false)); fieldTypeMapping.put(byte[].class, new FieldTypeDefinition("BLOB", false)); fieldTypeMapping.put(char[].class, new FieldTypeDefinition("CLOB", false)); fieldTypeMapping.put(java.sql.Blob.class, new FieldTypeDefinition("BLOB", false)); fieldTypeMapping.put(java.sql.Clob.class, new FieldTypeDefinition("CLOB", false)); fieldTypeMapping.put(java.sql.Date.class, new FieldTypeDefinition("DATE", false)); fieldTypeMapping.put(java.sql.Time.class, new FieldTypeDefinition("TIMESTAMP", false)); fieldTypeMapping.put(java.sql.Timestamp.class, new FieldTypeDefinition("TIMESTAMP", false)); //bug 5871089 the default generator requires definitions based on all java types fieldTypeMapping.put(java.util.Calendar.class, new FieldTypeDefinition("TIMESTAMP")); fieldTypeMapping.put(java.util.Date.class, new FieldTypeDefinition("TIMESTAMP")); return fieldTypeMapping; } /** * Build the hint string used for first rows. * * Allows it to be overridden * @param max * @return */ protected String buildFirstRowsHint(int max){ return HINT_START + HINT_END; } /** * INTERNAL: * Returns null unless the platform supports call with returning */ @Override public DatabaseCall buildCallWithReturning(SQLCall sqlCall, Vector returnFields) { SQLCall call = new SQLCall(); call.setParameters(sqlCall.getParameters()); call.setParameterTypes(sqlCall.getParameterTypes()); Writer writer = new CharArrayWriter(200); try { writer.write("BEGIN "); writer.write(sqlCall.getSQLString()); writer.write(" RETURNING "); for (int i = 0; i < returnFields.size(); i++) { DatabaseField field = (DatabaseField)returnFields.elementAt(i); writer.write(field.getNameDelimited(this)); if ((i + 1) < returnFields.size()) { writer.write(", "); } } writer.write(" INTO "); for (int i = 0; i < returnFields.size(); i++) { DatabaseField field = (DatabaseField)returnFields.elementAt(i); call.appendOut(writer, field); if ((i + 1) < returnFields.size()) { writer.write(", "); } } writer.write("; END;"); call.setQueryString(writer.toString()); } catch (IOException exception) { throw ValidationException.fileError(exception); } return call; } /** * INTERNAL: * Indicates whether the platform can build call with returning. * In case this method returns true, buildCallWithReturning method * may be called. */ @Override public boolean canBuildCallWithReturning() { return true; } /** * INTERNAL: * Clears both implicit and explicit caches of OracleConnection on Oracle9Platform, noop here. */ public void clearOracleConnectionCache(Connection conn) { } /** * INTERNAL: * Used for stored function calls. */ @Override public String getAssignmentString() { return ":= "; } /** * INTERNAL: * DECLARE stanza header for Anonymous PL/SQL block */ public String getDeclareBeginString() { return "DECLARE "; } /** * Used for batch writing and sp defs. */ @Override public String getBatchBeginString() { return "BEGIN "; } /** * Used for batch writing and sp defs. */ @Override public String getBatchEndString() { return "END;"; } /** * Used for batch writing for row count return. */ @Override public String getBatchRowCountDeclareString() { return "DECLARE EL_COUNTER NUMBER := 0; "; } /** * Oracle does not return the row count from PLSQL anon blocks, * so an output parameter is required for this. */ @Override public boolean isRowCountOutputParameterRequired() { return true; } /** * Used for batch writing for row count return. */ @Override public String getBatchRowCountReturnString() { return "? := EL_COUNTER; "; } /** * Return the drop schema definition. Subclasses should override as needed. */ @Override public String getDropDatabaseSchemaString(String schema) { return "DROP SCHEMA " + schema + " RESTRICT"; } /** * Used for batch writing for row count return. */ @Override public String getBatchRowCountAssignString() { return "EL_COUNTER := EL_COUNTER + SQL%ROWCOUNT; "; } /** * INTERNAL: * returns the maximum number of characters that can be used in a field * name on this platform. */ @Override public int getMaxFieldNameSize() { return 30; } /** * Return the catalog information through using the native SQL catalog selects. * This is required because many JDBC driver do not support meta-data. * Willcards can be passed as arguments. */ public Vector getNativeTableInfo(String table, String creator, AbstractSession session) { String query = "SELECT * FROM ALL_TABLES WHERE OWNER NOT IN ('SYS', 'SYSTEM')"; if (table != null) { if (table.indexOf('%') != -1) { query = query + " AND TABLE_NAME LIKE " + table; } else { query = query + " AND TABLE_NAME = " + table; } } if (creator != null) { if (creator.indexOf('%') != -1) { query = query + " AND OWNER LIKE " + creator; } else { query = query + " AND OWNER = " + creator; } } return session.executeSelectingCall(new SQLCall(query)); } /** * Used for sp calls. */ @Override public String getProcedureArgumentSetter() { return "=>"; } /** * Used for sp calls. */ @Override public String getProcedureCallHeader() { return useJDBCStoredProcedureSyntax() ? "{CALL " : "BEGIN "; } /** * Used for sp calls. */ @Override public String getProcedureCallTail() { return useJDBCStoredProcedureSyntax() ? "}" : "; END;"; } /** * Allows DROP TABLE to cascade dropping of any dependent constraints if the database supports this option. */ @Override public String getDropCascadeString() { return " CASCADE CONSTRAINTS"; } @Override public String getSelectForUpdateString() { return " FOR UPDATE"; } @Override public String getSelectForUpdateWaitString(Integer waitTimeout) { return " FOR UPDATE WAIT " + waitTimeout.intValue(); } @Override public String getStoredProcedureParameterPrefix() { return "P_"; } /** * PUBLIC: * The query to select the current system change number * from Oracle. * In order to execute this query a database administrator may need * to grant execute permission on pl/sql package DBMS_FLASHBACK. */ public ValueReadQuery getSystemChangeNumberQuery() { ValueReadQuery sCNQuery = new ValueReadQuery(); sCNQuery.setSQLString("SELECT DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER FROM DUAL"); return sCNQuery; } /** * PUBLIC: * This method returns the query to select the timestamp * from the server for Oracle. */ @Override public ValueReadQuery getTimestampQuery() { if (timestampQuery == null) { timestampQuery = new ValueReadQuery(); timestampQuery.setSQLString("SELECT SYSDATE FROM DUAL"); timestampQuery.setAllowNativeSQLQuery(true); } return timestampQuery; } /** * INTERNAL: * Return an Oracle defined VPD clear identifier query. */ @Override public DatabaseQuery getVPDClearIdentifierQuery(String vpdIdentifier) { if (vpdClearIdentifierQuery == null) { vpdClearIdentifierQuery = new DataModifyQuery("CALL DBMS_SESSION.CLEAR_IDENTIFIER()"); } return vpdClearIdentifierQuery; } /** * INTERNAL: * Return an Oracle defined VPD identifier function. Used for DDL generation. */ @Override public String getVPDCreationFunctionString(String tableName, String tenantFieldName) { String functionName = tableName + "_ident_func"; return "CREATE OR REPLACE FUNCTION " + functionName + " (p_schema in VARCHAR2 default NULL, p_object in VARCHAR2 default NULL) RETURN VARCHAR2 AS BEGIN return '" + tenantFieldName + " = sys_context(''userenv'', ''client_identifier'')'; END;"; } /** * INTERNAL: * Return an Oracle defined VPD identifier policy. Used for DDL generation. */ @Override public String getVPDCreationPolicyString(String tableName, AbstractSession session) { String functionName = tableName + "_ident_func"; String schemaName = session.getDatasourceLogin().getUserName(); String policyName = tableName + "_todo_list_policy"; return "\nCALL DBMS_RLS.ADD_POLICY ('" + schemaName + "', '" + tableName + "', '" + policyName + "', '" + schemaName + "', '" + functionName +"', 'select, update, delete')\n"; } /** * INTERNAL: * Return an Oracle defined VPD identifier policy deletion. Used for DDL generation. */ @Override public String getVPDDeletionString(String tableName, AbstractSession session) { String schemaName = session.getDatasourceLogin().getUserName(); String policyName = tableName + "_todo_list_policy"; return "\nCALL DBMS_RLS.DROP_POLICY ('" + schemaName + "', '" + tableName + "', '" + policyName + "')"; } /** * INTERNAL: * Return an Oracle defined VPD set identifier query. */ @Override public DatabaseQuery getVPDSetIdentifierQuery(String vpdIdentifier) { if (vpdSetIdentifierQuery == null) { vpdSetIdentifierQuery = new DataModifyQuery("CALL DBMS_SESSION.SET_IDENTIFIER(#" + vpdIdentifier + ")"); } return vpdSetIdentifierQuery; } /** * INTERNAL: * Get a timestamp value from a result set. * Overrides the default behavior to specifically return a timestamp. Added * to overcome an issue with the oracle 9.0.1.4 JDBC driver. */ @Override public Object getObjectFromResultSet(ResultSet resultSet, int columnNumber, int type, AbstractSession session) throws java.sql.SQLException { //Bug#3381652 10G Drivers return sql.Date instead of timestamp on DATE field if ((type == Types.TIMESTAMP) || (type == Types.DATE)) { return resultSet.getTimestamp(columnNumber); } else { return super.getObjectFromResultSet(resultSet, columnNumber, type, session); } } /** * Initialize any platform-specific operators */ @Override protected void initializePlatformOperators() { super.initializePlatformOperators(); addOperator(operatorOuterJoin()); addOperator(logOperator()); addOperator(ExpressionOperator.simpleTwoArgumentFunction(ExpressionOperator.Concat, "CONCAT")); addOperator(todayOperator()); addOperator(currentDateOperator()); addOperator(currentTimeOperator()); addOperator(ExpressionOperator.truncateDate()); addOperator(ExpressionOperator.newTime()); addOperator(ExpressionOperator.ifNull()); addOperator(atan2Operator()); addOperator(ExpressionOperator.oracleDateName()); addOperator(operatorLocate()); addOperator(operatorLocate2()); addOperator(regexpOperator()); addOperator(exceptOperator()); } /** * INTERNAL: * Create the EXCEPT operator, MINUS in Oracle. */ public static ExpressionOperator exceptOperator() { ExpressionOperator exOperator = new ExpressionOperator(); exOperator.setType(ExpressionOperator.FunctionOperator); exOperator.setSelector(ExpressionOperator.Except); exOperator.printsAs("MINUS "); exOperator.bePostfix(); exOperator.setNodeClass(ClassConstants.FunctionExpression_Class); return exOperator; } /** * INTERNAL: * Create the REGEXP_LIKE operator. */ public static ExpressionOperator regexpOperator() { ExpressionOperator result = new ExpressionOperator(); result.setSelector(ExpressionOperator.Regexp); result.setType(ExpressionOperator.FunctionOperator); Vector v = NonSynchronizedVector.newInstance(3); v.add("REGEXP_LIKE("); v.add(", "); v.add(")"); result.printsAs(v); result.bePrefix(); result.setNodeClass(ClassConstants.FunctionExpression_Class); v = NonSynchronizedVector.newInstance(2); v.add(".regexp("); v.add(")"); result.printsJavaAs(v); return result; } /** * INTERNAL: * Used by derived platforms (Oracle8Platform and higher) * to indicate whether app. server should unwrap connection * to use lob locator. */ public boolean isNativeConnectionRequiredForLobLocator() { return false; } @Override public boolean isOracle() { return true; } /** * Create the log operator for this platform */ protected ExpressionOperator logOperator() { ExpressionOperator result = new ExpressionOperator(); result.setSelector(ExpressionOperator.Log); Vector v = NonSynchronizedVector.newInstance(2); v.addElement("LOG(10,"); v.addElement(")"); result.printsAs(v); result.bePrefix(); result.setNodeClass(FunctionExpression.class); return result; } /** * Builds a table of maximum numeric values keyed on java class. This is used for type testing but * might also be useful to end users attempting to sanitize values. * <p><b>NOTE</b>: BigInteger {@literal &} BigDecimal maximums are dependent upon their precision {@literal &} Scale */ @Override public Hashtable maximumNumericValues() { Hashtable values = new Hashtable(); values.put(Integer.class, Integer.valueOf(Integer.MAX_VALUE)); values.put(Long.class, Long.valueOf(Long.MAX_VALUE)); values.put(Double.class, Double.valueOf(9.9999E125)); values.put(Short.class, Short.valueOf(Short.MAX_VALUE)); values.put(Byte.class, Byte.valueOf(Byte.MAX_VALUE)); values.put(Float.class, Float.valueOf(Float.MAX_VALUE)); values.put(java.math.BigInteger.class, new java.math.BigInteger("0")); values.put(java.math.BigDecimal.class, new java.math.BigDecimal(new java.math.BigInteger("0"), 38)); return values; } /** * Builds a table of minimum numeric values keyed on java class. This is used for type testing but * might also be useful to end users attempting to sanitize values. * <p><b>NOTE</b>: BigInteger {@literal &} BigDecimal minimums are dependent upon their precision {@literal &} Scale */ @Override public Hashtable minimumNumericValues() { Hashtable values = new Hashtable(); values.put(Integer.class, Integer.valueOf(Integer.MIN_VALUE)); values.put(Long.class, Long.valueOf(Long.MIN_VALUE)); values.put(Double.class, Double.valueOf(-1E-129)); values.put(Short.class, Short.valueOf(Short.MIN_VALUE)); values.put(Byte.class, Byte.valueOf(Byte.MIN_VALUE)); values.put(Float.class, Float.valueOf(Float.MIN_VALUE)); values.put(java.math.BigInteger.class, new java.math.BigInteger("0")); values.put(java.math.BigDecimal.class, new java.math.BigDecimal(new java.math.BigInteger("0"), 38)); return values; } /** * INTERNAL: * Produce a DataReadQuery which updates(!) the sequence number in the db * and returns it. Currently implemented on Oracle only. * @param qualifiedSeqName known by Oracle to be a defined sequence */ @Override public ValueReadQuery buildSelectQueryForSequenceObject(String qualifiedSeqName, Integer size) { return new ValueReadQuery("SELECT " + qualifiedSeqName + ".NEXTVAL FROM DUAL"); } /** * INTERNAL: * Though Oracle doesn't support identity it could be imitated, * see comment to setSupportsIdentity method. * @param qualifiedSeqName known by Oracle to be a defined sequence */ @Override public ValueReadQuery buildSelectQueryForIdentity(String qualifiedSeqName, Integer size) { return new ValueReadQuery("SELECT " + qualifiedSeqName + ".CURRVAL FROM DUAL"); } /** * Create the outer join operator for this platform */ protected ExpressionOperator operatorOuterJoin() { ExpressionOperator result = new ExpressionOperator(); result.setSelector(ExpressionOperator.EqualOuterJoin); Vector v = NonSynchronizedVector.newInstance(2); v.addElement(" (+) = "); result.printsAs(v); result.bePostfix(); result.setNodeClass(RelationExpression.class); return result; } /** * INTERNAL: * Override the default locate operator */ protected ExpressionOperator operatorLocate() { ExpressionOperator result = new ExpressionOperator(); result.setSelector(ExpressionOperator.Locate); Vector v = NonSynchronizedVector.newInstance(2); v.addElement("INSTR("); v.addElement(", "); v.addElement(")"); result.printsAs(v); result.bePrefix(); result.setNodeClass(RelationExpression.class); return result; } /** * INTERNAL: * Override the default locate operator */ protected ExpressionOperator operatorLocate2() { ExpressionOperator result = new ExpressionOperator(); result.setSelector(ExpressionOperator.Locate2); Vector v = NonSynchronizedVector.newInstance(2); v.addElement("INSTR("); v.addElement(", "); v.addElement(", "); v.addElement(")"); result.printsAs(v); result.bePrefix(); result.setNodeClass(RelationExpression.class); return result; } /** * INTERNAL: * Append the receiver's field 'NULL' constraint clause to a writer. */ @Override public void printFieldNullClause(Writer writer) throws ValidationException { try { writer.write(" NULL"); } catch (IOException ioException) { throw ValidationException.fileError(ioException); } } /** * Return the current date and time from the server. */ public String serverTimestampString() { return "SYSDATE"; } /** * INTERNAL: * Should the variable name of a stored procedure call be printed as part of the procedure call * e.g. EXECUTE PROCEDURE MyStoredProc(myvariable = ?) */ @Override public boolean shouldPrintStoredProcedureArgumentNameInCall() { return ! useJDBCStoredProcedureSyntax(); } /** * JDBC defines and outer join syntax, many drivers do not support this. So we normally avoid it. */ @Override public boolean shouldUseJDBCOuterJoinSyntax() { return false; } /** * Some db allow VARCHAR db field to be used in arithmetic operations automatically converting them to numeric: * UPDATE OL_PHONE SET PHONE_ORDER_VARCHAR = (PHONE_ORDER_VARCHAR + 1) WHERE ... * SELECT ... WHERE ... t0.MANAGED_ORDER_VARCHAR BETWEEN 1 AND 4 ... */ @Override public boolean supportsAutoConversionToNumericForArithmeticOperations() { return true; } /** * INTERNAL: * Indicates whether the platform supports sequence objects. * This method is to be used *ONLY* by sequencing classes */ @Override public boolean supportsSequenceObjects() { return true; } /** * INTERNAL: * Indicates whether the platform supports identity. * This method is to be used *ONLY* by sequencing classes */ @Override public boolean supportsIdentity() { return supportsIdentity; } /** * ADVANCED: * Oracle db doesn't support identity. * However it's possible to get identity-like behavior * using sequence in an insert trigger - that's the only * case when supportsIdentity should be set to true: * in this case all the sequences that have shouldAcquireValueAfterInsert * set to true will keep this setting (it would've been reversed in case * identity is not supported). * Note that with supportsIdentity==true attempt to create tables that have * identity fields will fail - Oracle doesn't support identity. * Therefore if there's table creation reqiured it should be done * with supportsIdentity==false, then set the flag to true and reset sequencing * (or logout and login the session). */ public void setSupportsIdentity(boolean supportsIdentity) { this.supportsIdentity = supportsIdentity; } /** * INTERNAL: * Return if database stored functions are supported. */ @Override public boolean supportsStoredFunctions() { return true; } /** * Oracle db supports VPD. */ @Override public boolean supportsVPD() { return true; } /** * Returns true if the database supports SQL syntax not to wait on a SELECT..FOR UPADTE * (i.e. In Oracle adding NOWAIT to the end will accomplish this) */ public boolean supportsSelectForUpdateNoWait() { return true; } /** * Create the sysdate operator for this platform */ protected ExpressionOperator todayOperator() { return ExpressionOperator.simpleFunctionNoParentheses(ExpressionOperator.Today, "SYSDATE"); } protected ExpressionOperator currentDateOperator() { return ExpressionOperator.simpleFunctionNoParentheses(ExpressionOperator.CurrentDate, "TO_DATE(CURRENT_DATE)"); } protected ExpressionOperator currentTimeOperator() { return ExpressionOperator.simpleFunctionNoParentheses(ExpressionOperator.CurrentTime, "SYSDATE"); } /** * INTERNAL: * Indicates whether this Oracle platform can unwrap Oracle connection. */ public boolean canUnwrapOracleConnection() { return false; } /** * INTERNAL: * If can unwrap returns unwrapped Oracle connection, otherwise original connection. */ public Connection unwrapOracleConnection(Connection connection) { return connection; } /** * Return true if JDBC syntax should be used for stored procedure calls. */ public boolean useJDBCStoredProcedureSyntax() { if (useJDBCStoredProcedureSyntax == null) { useJDBCStoredProcedureSyntax = this.driverName != null && this.driverName.equals("Oracle"); } return useJDBCStoredProcedureSyntax; } //Oracle Rownum support protected String SELECT = "SELECT * FROM (SELECT "; protected String HINT_START = "/*+ FIRST_ROWS"; protected String HINT_END = " */ "; protected String FROM = "a.*, ROWNUM rnum FROM ("; protected String END_FROM = ") a "; protected String MAX_ROW = "WHERE ROWNUM <= "; protected String MIN_ROW = ") WHERE rnum > "; // Bug #453208 protected String LOCK_START_PREFIX = " AND ("; protected String LOCK_START_SUFFIX = ") IN ("; protected String LOCK_END = ") FOR UPDATE"; protected String SELECT_ID_PREFIX = "SELECT "; protected String SELECT_ID_SUFFIX = " FROM (SELECT "; protected String FROM_ID = ", ROWNUM rnum FROM ("; protected String END_FROM_ID = ") "; protected String ORDER_BY_ID = " ORDER BY "; /** * INTERNAL: * Print the SQL representation of the statement on a stream, storing the fields * in the DatabaseCall. This implementation works MaxRows and FirstResult into the SQL using * Oracle's ROWNUM to filter values if shouldUseRownumFiltering is true. */ @Override public void printSQLSelectStatement(DatabaseCall call, ExpressionSQLPrinter printer, SQLSelectStatement statement) { int max = 0; int firstRow = 0; ReadQuery query = statement.getQuery(); if (query != null) { max = query.getMaxRows(); firstRow = query.getFirstResult(); } if (!(this.shouldUseRownumFiltering()) || (!(max > 0) && !(firstRow > 0))) { super.printSQLSelectStatement(call, printer, statement); return; } else { statement.setUseUniqueFieldAliases(true); // Bug #453208 - Pessimistic locking with query row limits does not work on Oracle DB. if (query.isObjectBuildingQuery() && (((ObjectBuildingQuery) query).getLockMode() == ObjectBuildingQuery.LOCK || ((ObjectBuildingQuery) query).getLockMode() == ObjectBuildingQuery.LOCK_NOWAIT)) { if (query.isReadAllQuery() || query.isReadObjectQuery()) { // Workaround can exist for this specific case Vector fields = new Vector(); statement.enableFieldAliasesCaching(); String queryString = printOmittingForUpdate(statement, printer, fields); duplicateCallParameters(call); call.setFields(fields); /* Prints a query similar to the below: * * SELECT t1.EMP_ID AS a1, ... * FROM CMP3_EMPLOYEE t1 * WHERE ... * AND (t1.EMP_ID) IN ( * SELECT a1 FROM ( * SELECT a1, ROWNUM rnum FROM ( * SELECT t1.EMP_ID AS a1, ... * FROM CMP3_EMPLOYEE t1 * WHERE ...) * WHERE ROWNUM <= ?) * WHERE rnum > ?) * FOR UPDATE; */ printer.printString(queryString); printLockStartWithPrimaryKeyFields(statement, printer); String primaryKeyFields = getPrimaryKeyAliases(statement); printer.printString(SELECT_ID_PREFIX); printer.printString(primaryKeyFields); printer.printString(SELECT_ID_SUFFIX); printer.printString(buildFirstRowsHint(max)); printer.printString(primaryKeyFields); printer.printString(FROM_ID); printer.printString(queryString); printer.printString(ORDER_BY_ID); printer.printString(primaryKeyFields); printer.printString(END_FROM_ID); printer.printString(MAX_ROW); printer.printParameter(DatabaseCall.MAXROW_FIELD); printer.printString(MIN_ROW); printer.printParameter(DatabaseCall.FIRSTRESULT_FIELD); printer.printString(LOCK_END); } else { throw new UnsupportedOperationException(ExceptionLocalization.buildMessage("ora_pessimistic_locking_with_rownum")); } } else { if (max > 0) { printer.printString(SELECT); printer.printString(buildFirstRowsHint(max)); printer.printString(FROM); call.setFields(statement.printSQL(printer)); printer.printString(END_FROM); printer.printString(MAX_ROW); printer.printParameter(DatabaseCall.MAXROW_FIELD); printer.printString(MIN_ROW); printer.printParameter(DatabaseCall.FIRSTRESULT_FIELD); } else {// firstRow>0 printer.printString(SELECT); printer.printString(FROM); call.setFields(statement.printSQL(printer)); printer.printString(END_FROM); printer.printString(MIN_ROW); printer.printParameter(DatabaseCall.FIRSTRESULT_FIELD); } } } call.setIgnoreFirstRowSetting(true); call.setIgnoreMaxResultsSetting(true); } @SuppressWarnings("unchecked") // Bug #453208 - Duplicate call parameters since the query is performed twice private void duplicateCallParameters(DatabaseCall call) { ArrayList newParameterList = new ArrayList(call.getParameters()); newParameterList.addAll(call.getParameters()); call.setParameters(newParameterList); ArrayList<Integer> newParameterTypesList = new ArrayList(call.getParameterTypes()); newParameterTypesList.addAll(call.getParameterTypes()); call.setParameterTypes(newParameterTypesList); } @SuppressWarnings("unchecked") private String printOmittingForUpdate(SQLSelectStatement statement, ExpressionSQLPrinter printer, Vector fields) { boolean originalShouldPrintForUpdate = this.shouldPrintForUpdateClause; Writer originalWriter = printer.getWriter(); this.shouldPrintForUpdateClause = false; printer.setWriter(new StringWriter()); fields.addAll(statement.printSQL(printer)); String query = printer.getWriter().toString(); this.shouldPrintForUpdateClause = originalShouldPrintForUpdate; printer.setWriter(originalWriter); return query; } private void printLockStartWithPrimaryKeyFields(SQLSelectStatement statement, ExpressionSQLPrinter printer) { printer.printString(LOCK_START_PREFIX); Iterator<DatabaseField> iterator = statement.getQuery().getDescriptor().getPrimaryKeyFields().iterator(); while (iterator.hasNext()) { DatabaseField field = iterator.next(); DatabaseTable alias = statement.getExpressionBuilder().aliasForTable(field.getTable()); printer.printField(field, alias); if(iterator.hasNext()) { printer.printString(","); } } printer.printString(LOCK_START_SUFFIX); } private String getPrimaryKeyAliases(SQLSelectStatement statement) { StringBuilder builder = new StringBuilder(); Iterator<DatabaseField> iterator = statement.getQuery().getDescriptor().getPrimaryKeyFields().iterator(); while (iterator.hasNext()) { builder.append(statement.getAliasFor(iterator.next())); if(iterator.hasNext()) { builder.append(','); } } return builder.toString(); } /** * INTERNAL: * Override this method if the platform supports sequence objects * and it's possible to alter sequence object's increment in the database. */ @Override public boolean isAlterSequenceObjectSupported() { return true; } /** * INTERNAL: * Indicates whether SELECT DISTINCT ... FOR UPDATE is allowed by the platform (Oracle doesn't allow this). */ @Override public boolean isForUpdateCompatibleWithDistinct() { return false; } /** * INTERNAL: * Indicates whether SELECT DISTINCT lob FROM ... (where lob is BLOB or CLOB) is allowed by the platform (Oracle doesn't allow this). */ @Override public boolean isLobCompatibleWithDistinct() { return false; } /** * Return true if the given exception occurred as a result of a lock * time out exception (WAIT clause). */ @Override public boolean isLockTimeoutException(DatabaseException e) { return (e.getInternalException() instanceof java.sql.SQLException && ((java.sql.SQLException) e.getInternalException()).getErrorCode() == 30006); } /** * INTERNAL: * A call to this method will perform a platform based check on the connection and exception * error code to determine if the connection is still valid or if a communication error has occurred. * If a communication error has occurred then the query may be retried. * If this platform is unable to determine if the error was communication based it will return * false forcing the error to be thrown to the user. */ @Override public boolean wasFailureCommunicationBased(SQLException exception, Connection connection, AbstractSession sessionForProfile){ if (exception != null){ if (exception.getErrorCode() == 17410){ return true; } if (exception.getErrorCode() == 17002){ return true; } if (exception.getErrorCode() == 2399){ return true; } if (exception.getErrorCode() == 2396){ return true; } } return super.wasFailureCommunicationBased(exception, connection, sessionForProfile); } @Override public boolean shouldPrintForUpdateClause() { return shouldPrintForUpdateClause; } }