blob: 02809ede28e45c3c79989c013ccbd8ebd019d1be [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// Vikram Bhatia - added method for releasing temporary LOBs after conversion
package org.eclipse.persistence.platform.database.oracle;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Struct;
import java.util.Hashtable;
import org.eclipse.persistence.internal.databaseaccess.DatabaseCall;
import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition;
import org.eclipse.persistence.internal.databaseaccess.Platform;
import org.eclipse.persistence.internal.databaseaccess.SimpleAppendCallCustomParameter;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.platform.database.OraclePlatform;
import org.eclipse.persistence.queries.Call;
import oracle.jdbc.OracleBlob;
import oracle.jdbc.OracleClob;
/**
* <p><b>Purpose:</b>
* Supports certain new Oracle 8 data types, and usage of certain Oracle JDBC specific APIs.
* <p> Supports Oracle thin JDBC driver LOB &gt;4k binding workaround.
* <p> Creates BLOB and CLOB type for byte[] and char[] for table creation.
* <p> Supports object-relational data-type creation.
*/
public class Oracle8Platform extends OraclePlatform {
/** Locator is required for Oracle thin driver to write LOB value exceeds the limits */
protected boolean usesLocatorForLOBWrite = true;
/** The LOB value limits when the Locator is required for the writing */
protected int lobValueLimits = 0;
/**
* Default constructor.
*/
public Oracle8Platform() {
super();
}
/**
* INTERNAL:
*/
@Override
protected Hashtable<Class<?>, FieldTypeDefinition> buildFieldTypes() {
Hashtable<Class<?>, FieldTypeDefinition> fieldTypeMapping = super.buildFieldTypes();
fieldTypeMapping.put(Byte[].class, new FieldTypeDefinition("BLOB", false));
fieldTypeMapping.put(Character[].class, new FieldTypeDefinition("CLOB", false));
return fieldTypeMapping;
}
/**
* INTERNAL:
* Allow for conversion from the Oralce type to the Java type.
*/
@Override
public void copyInto(Platform platform) {
super.copyInto(platform);
if (!(platform instanceof Oracle8Platform)) {
return;
}
Oracle8Platform oracle8Platform = (Oracle8Platform)platform;
oracle8Platform.setShouldUseLocatorForLOBWrite(shouldUseLocatorForLOBWrite());
oracle8Platform.setLobValueLimits(getLobValueLimits());
}
/**
* INTERNAL:
* Return if the LOB value size is larger than the limit, i.e. 4k.
*/
protected boolean lobValueExceedsLimit(Object value) {
if (value == null) {
return false;
}
int limit = getLobValueLimits();
if (value instanceof byte[]) {//blob
return ((byte[])value).length >= limit;
} else if (value instanceof String) {//clob
return ((String)value).length() >= limit;
} else {
return false;
}
}
/**
* INTERNAL:
* This method is used to unwrap the oracle connection wrapped by
* the application server. TopLink needs this unwrapped connection for certain
* Oracle Specific support. (ie TIMESTAMPTZ, LOB)
* This is added as a workaround for bug 4565190
*/
@Override
public Connection getConnection(AbstractSession session, Connection connection) {
if (session.getServerPlatform() != null && (session.getLogin()).shouldUseExternalConnectionPooling()){
// This is added as a workaround for bug 4460996
return session.getServerPlatform().unwrapConnection(connection);
}
return connection;
}
/**
* INTERNAL
* Used by SQLCall.translate(..)
* Typically there is no field translation (and this is default implementation).
* However on different platforms (Oracle) there are cases such that the values for
* binding and appending may be different (BLOB, CLOB).
* In these special cases the method returns a wrapper object
* which knows whether it should be bound or appended and knows how to do that.
*/
@Override
public Object getCustomModifyValueForCall(Call call, Object value, DatabaseField field, boolean shouldBind) {
Class type = field.getType();
if (ClassConstants.BLOB.equals(type) || ClassConstants.CLOB.equals(type)) {
if(value == null) {
return null;
}
value = convertToDatabaseType(value);
if (shouldUseLocatorForLOBWrite()) {
if (lobValueExceedsLimit(value)) {
((DatabaseCall)call).addContext(field, value);
if (ClassConstants.BLOB.equals(type)) {
if (shouldBind) {
value = new byte[1];
} else {
value = new SimpleAppendCallCustomParameter("empty_blob()");
}
} else {
if (shouldBind) {
value = new String(" ");
} else {
value = new SimpleAppendCallCustomParameter("empty_clob()");
}
}
}
}
return value;
}
return super.getCustomModifyValueForCall(call, value, field, shouldBind);
}
/**
* INTERNAL
* Used by SQLCall.appendModify(..)
* If the field should be passed to customModifyInDatabaseCall, retun true,
* otherwise false.
* Methods shouldCustomModifyInDatabaseCall and customModifyInDatabaseCall should be
* kept in sync: shouldCustomModifyInDatabaseCall should return true if and only if the field
* is handled by customModifyInDatabaseCall.
*/
@Override
public boolean shouldUseCustomModifyForCall(DatabaseField field) {
if (shouldUseLocatorForLOBWrite()) {
Class type = field.getType();
if (ClassConstants.BLOB.equals(type) || ClassConstants.CLOB.equals(type)) {
return true;
}
}
return super.shouldUseCustomModifyForCall(field);
}
/**
* INTERNAL:
* Write LOB value - works on Oracle 10 and newer
*/
@Override
public void writeLOB(DatabaseField field, Object value, ResultSet resultSet, AbstractSession session) throws SQLException {
if (isBlob(field.getType())) {
//change for 338585 to use getName instead of getNameDelimited
Blob blob = (Blob) resultSet.getObject(field.getName());
blob.setBytes(1, (byte[]) value);
//impose the localization
session.log(SessionLog.FINEST, SessionLog.SQL, "write_BLOB", blob.length(), field.getName());
} else if (isClob(field.getType())) {
//change for 338585 to use getName instead of getNameDelimited
Clob clob = (Clob) resultSet.getObject(field.getName());
clob.setString(1, (String) value);
//impose the localization
session.log(SessionLog.FINEST, SessionLog.SQL, "write_CLOB", clob.length(), field.getName());
} else {
//do nothing for now, open to BFILE or NCLOB types
}
}
/**
* INTERNAL:
* Used in writeLOB method only to identify a BLOB
*/
protected boolean isBlob(Class type) {
return ClassConstants.BLOB.equals(type);
}
/**
* INTERNAL:
* Used in writeLOB method only to identify a CLOB
*/
protected boolean isClob(Class type) {
return ClassConstants.CLOB.equals(type);
}
/**
* INTERNAL:
* Indicates whether app. server should unwrap connection
* to use lob locator.
*/
@Override
public boolean isNativeConnectionRequiredForLobLocator() {
return true;
}
/**
* PUBLIC:
* Set if the locator is required for the LOB write. The default is true.
* For Oracle thin driver, the locator is recommended for large size
* ( &gt;4k for Oracle8, &gt;5.9K for Oracle9) BLOB/CLOB value write.
*/
public void setShouldUseLocatorForLOBWrite(boolean usesLocatorForLOBWrite) {
this.usesLocatorForLOBWrite = usesLocatorForLOBWrite;
}
/**
* PUBLIC:
* Return if the locator is required for the LOB write. The default is true.
* For Oracle thin driver, the locator is recommended for large size
* ( &gt;4k for Oracle8, &gt;5.9K for Oracle9) BLOB/CLOB value write.
*/
public boolean shouldUseLocatorForLOBWrite() {
return usesLocatorForLOBWrite;
}
/**
* PUBLIC:
* Return the BLOB/CLOB value limits on thin driver. The default value is 0.
* If usesLocatorForLOBWrite is true, locator will be used in case the
* lob's size is larger than lobValueLimit.
*/
public int getLobValueLimits() {
return lobValueLimits;
}
/**
* PUBLIC:
* Set the BLOB/CLOB value limits on thin driver. The default value is 0.
* If usesLocatorForLOBWrite is true, locator will be used in case the
* lob's size is larger than lobValueLimit.
*/
public void setLobValueLimits(int lobValueLimits) {
this.lobValueLimits = lobValueLimits;
}
/**
* INTERNAL:
* Platforms that support java.sql.Array may override this method.
* @return Array
*/
@Override
public Array createArray(String elementDataTypeName, Object[] elements, Connection connection) throws SQLException {
return new oracle.sql.ARRAY(new oracle.sql.ArrayDescriptor(elementDataTypeName, connection), connection, elements);
}
/**
* INTERNAL:
* Platforms that support java.sql.Struct may override this method.
* @return Struct
*/
@Override
public Struct createStruct(String structTypeName, Object[] attributes, Connection connection) throws SQLException {
return new oracle.sql.STRUCT(new oracle.sql.StructDescriptor(structTypeName, connection), connection, attributes);
}
/**
* INTERNAL:
* Overrides DatabasePlatform method.
* @return String
*/
@Override
public Object getRefValue(Ref ref,Connection connection) throws SQLException {
((oracle.sql.REF)ref).setPhysicalConnectionOf(connection);
return ((oracle.sql.REF)ref).getValue();
}
/**
* INTERNAL:
* Used by Oracle platforms during reading of ResultSet to free temporary LOBs.
*/
@Override
public void freeTemporaryObject(Object value) throws SQLException {
if (value instanceof OracleClob && ((OracleClob) value).isTemporary()) {
((OracleClob) value).free();
} else if (value instanceof OracleBlob && ((OracleBlob) value).isTemporary()) {
((OracleBlob) value).free();
}
}
}