| /* |
| * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 1998, 2021 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 |
| // 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 |
| // 02/14/2018-2.7 Will Dazey |
| // - 529602: Added support for CLOBs in DELETE statements for Oracle |
| 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.List; |
| import java.util.Vector; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.persistence.exceptions.DatabaseException; |
| import org.eclipse.persistence.exceptions.ValidationException; |
| import org.eclipse.persistence.expressions.Expression; |
| import org.eclipse.persistence.expressions.ExpressionOperator; |
| import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; |
| import org.eclipse.persistence.internal.databaseaccess.DatasourceCall; |
| 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.localization.ExceptionLocalization; |
| import org.eclipse.persistence.internal.sessions.AbstractSession; |
| import org.eclipse.persistence.internal.sessions.DatabaseSessionImpl; |
| import org.eclipse.persistence.queries.DataModifyQuery; |
| import org.eclipse.persistence.queries.DataReadQuery; |
| 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.StoredProcedureCall; |
| import org.eclipse.persistence.queries.ValueReadQuery; |
| import org.eclipse.persistence.tools.schemaframework.TableDefinition; |
| |
| /** |
| * <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.cursorCode = -10; |
| 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<Class<?>, FieldTypeDefinition> buildFieldTypes() { |
| Hashtable<Class<?>, FieldTypeDefinition> 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 |
| */ |
| 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 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; |
| } |
| |
| @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); |
| List<String> v = new ArrayList<>(3); |
| v.add("REGEXP_LIKE("); |
| v.add(", "); |
| v.add(")"); |
| result.printsAs(v); |
| result.bePrefix(); |
| result.setNodeClass(ClassConstants.FunctionExpression_Class); |
| v = new ArrayList<>(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); |
| List<String> v = new ArrayList<>(2); |
| v.add("LOG(10,"); |
| v.add(")"); |
| 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<Class<? extends Number>, ? super Number> maximumNumericValues() { |
| Hashtable<Class<? extends Number>, ? super Number> values = new Hashtable<>(); |
| values.put(Integer.class, Integer.MAX_VALUE); |
| values.put(Long.class, Long.MAX_VALUE); |
| values.put(Double.class, 9.9999E125); |
| values.put(Short.class, Short.MAX_VALUE); |
| values.put(Byte.class, Byte.MAX_VALUE); |
| values.put(Float.class, 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<Class<? extends Number>, ? super Number> minimumNumericValues() { |
| Hashtable<Class<? extends Number>, ? super Number> values = new Hashtable<>(); |
| values.put(Integer.class, Integer.MIN_VALUE); |
| values.put(Long.class, Long.MIN_VALUE); |
| values.put(Double.class, -1E-129); |
| values.put(Short.class, Short.MIN_VALUE); |
| values.put(Byte.class, Byte.MIN_VALUE); |
| values.put(Float.class, 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); |
| List<String> v = new ArrayList<>(2); |
| v.add(" (+) = "); |
| 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); |
| List<String> v = new ArrayList<>(2); |
| v.add("INSTR("); |
| v.add(", "); |
| v.add(")"); |
| 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); |
| List<String> v = new ArrayList<>(2); |
| v.add("INSTR("); |
| v.add(", "); |
| v.add(", "); |
| v.add(")"); |
| 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 false; |
| } |
| |
| @Override |
| public String getProcedureArgument(String name, Object parameter, Integer parameterType, |
| StoredProcedureCall call, AbstractSession session) { |
| if(name != null && DatasourceCall.IN.equals(parameterType) && !call.usesBinding(session)) { |
| return name + "=>" + "?"; |
| } |
| return "?"; |
| } |
| |
| /** |
| * 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; |
| } |
| |
| @Override |
| public boolean supportsWaitForUpdate() { |
| 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 |
| && Pattern.compile("Oracle", Pattern.CASE_INSENSITIVE).matcher(this.driverName).find(); |
| } |
| 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) { |
| List newParameterList = new ArrayList(call.getParameters()); |
| newParameterList.addAll(call.getParameters()); |
| call.setParameters(newParameterList); |
| List<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; |
| } |
| |
| @Override |
| public Expression createExpressionFor(DatabaseField field, Expression builder) { |
| if (field.getType() == java.sql.Clob.class || |
| field.getType() == java.sql.Blob.class) { |
| Expression subExp1 = builder.getField(field); |
| Expression subExp2 = builder.getParameter(field); |
| subExp1 = subExp1.getFunction("dbms_lob.compare", subExp2); |
| return subExp1.equal(0); |
| } |
| return super.createExpressionFor(field, builder); |
| } |
| |
| // Value of shouldCheckResultTableExistsQuery must be true. |
| /** |
| * INTERNAL: |
| * Returns query to check whether given table exists. |
| * Query execution returns a row when table exists or empty result otherwise. |
| * @param table database table meta-data |
| * @return query to check whether given table exists |
| */ |
| @Override |
| protected DataReadQuery getTableExistsQuery(final TableDefinition table) { |
| final DataReadQuery query = new DataReadQuery( |
| "SELECT table_name FROM user_tables WHERE table_name='" + table.getFullName() + "'"); |
| query.setMaxRows(1); |
| return query; |
| } |
| |
| /** |
| * INTERNAL: |
| * Executes and evaluates query to check whether given table exists. |
| * Returned value depends on returned result set being empty or not. |
| * @param session current database session |
| * @param table database table meta-data |
| * @param suppressLogging whether to suppress logging during query execution |
| * @return value of {@code true} if given table exists or {@code false} otherwise |
| */ |
| @Override |
| public boolean checkTableExists(final DatabaseSessionImpl session, |
| final TableDefinition table, final boolean suppressLogging) { |
| try { |
| session.setLoggingOff(suppressLogging); |
| final Vector result = (Vector)session.executeQuery(getTableExistsQuery(table)); |
| return !result.isEmpty(); |
| } catch (Exception notFound) { |
| return false; |
| } |
| } |
| |
| @Override |
| public int getINClauseLimit() { |
| return 1000; |
| } |
| } |