Skip to content

Commit

Permalink
Oracle 23C platform fixes (eclipse-ee4j#1969)
Browse files Browse the repository at this point in the history
This one fixes following two issues related with Oracle23c:

1. when empty String ("") is inserted into table column like ...CLOBDATA CLOB NOT NULL...
    Solution is based on conversion into java.sql.Clob, because solution based on SimpleAppendCallCustomParameter("empty_clob()") and DatabasePlatform.appendParameter() leads into another test failures.
2. loss of precision if DB table is automatically created if entity using java.time.LocalDateTime, java.time.LocalTime

Signed-off-by: Radek Felcman <[email protected]>
  • Loading branch information
rfelcman authored Oct 18, 2023
1 parent ae7f32c commit 4b88873
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2023 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
Expand All @@ -16,6 +16,7 @@

import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.internal.core.helper.CoreConversionManager;
import org.eclipse.persistence.internal.sessions.AbstractSession;

public interface CorePlatform<CONVERSION_MANAGER extends CoreConversionManager> {

Expand All @@ -29,6 +30,18 @@ public interface CorePlatform<CONVERSION_MANAGER extends CoreConversionManager>
*/
<T> T convertObject(Object sourceObject, Class<T> javaClass);

/**
* Convert the object to the appropriate type by invoking the appropriate
* ConversionManager method.
* @param sourceObject the object that must be converted
* @param javaClass the class that the object must be converted to
* @param session current database session
* @exception ConversionException all exceptions will be thrown as this type.
* @return the newly converted object
*/
<T> T convertObject(Object sourceObject, Class<T> javaClass, AbstractSession session) throws ConversionException;


/**
* The platform hold its own instance of conversion manager to allow customization.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,20 @@ public <T> T convertObject(Object sourceObject, Class<T> javaClass) throws Conve
return getConversionManager().convertObject(sourceObject, javaClass);
}

/**
* Convert the object to the appropriate type by invoking the appropriate
* ConversionManager method.
* @param sourceObject the object that must be converted
* @param javaClass the class that the object must be converted to
* @param session current database session
* @exception ConversionException all exceptions will be thrown as this type.
* @return the newly converted object
*/
@Override
public <T> T convertObject(Object sourceObject, Class<T> javaClass, AbstractSession session) throws ConversionException {
return convertObject(sourceObject, javaClass);
}

/**
* Copy the state into the new platform.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.persistence.internal.descriptors.ClassNameConversionRequired;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectCollectionMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
Expand Down Expand Up @@ -209,7 +210,12 @@ public void setObjectClassName(String objectClassName) {
@Override
public Object convertObjectValueToDataValue(Object attributeValue, Session session) {
try {
return session.getDatasourcePlatform().convertObject(attributeValue, getDataClass());
if (session.isConnected()) {
//Should handle conversions where DB connection is needed like String -> java.sql.Clob
return session.getDatasourcePlatform().convertObject(attributeValue, getDataClass(), (AbstractSession)session);
} else {
return session.getDatasourcePlatform().convertObject(attributeValue, getDataClass());
}
} catch (ConversionException e) {
throw ConversionException.couldNotBeConverted(mapping, mapping.getDescriptor(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,62 @@
// Oracle - initial API and implementation
package org.eclipse.persistence.platform.database;

import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.sessions.AbstractSession;

import java.sql.Clob;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Hashtable;

import static org.eclipse.persistence.internal.helper.StringHelper.EMPTY_STRING;

public class Oracle23Platform extends Oracle21Platform {

public Oracle23Platform() {
super();
}

@Override
protected Hashtable<Class<?>, FieldTypeDefinition> buildFieldTypes() {
Hashtable<Class<?>, FieldTypeDefinition> fieldTypes = super.buildFieldTypes();
fieldTypes.put(java.time.LocalDateTime.class, new FieldTypeDefinition("TIMESTAMP", 9));
fieldTypes.put(java.time.LocalTime.class, new FieldTypeDefinition("TIMESTAMP", 9));
return fieldTypes;
}

/**
* INTERNAL:
* Check whether current platform is Oracle 23c or later.
* @return Always returns {@code true} for instances of Oracle 23c platform.
* @since 4.0.2
*/
@Override
public boolean isOracle23() {
return true;
}

/**
* INTERNAL:
* Allow for conversion from the Oracle type to the Java type. Used in cases when DB connection is needed like BLOB, CLOB.
*/
@Override
public <T> T convertObject(Object sourceObject, Class<T> javaClass, AbstractSession session) throws ConversionException, DatabaseException {
//Handle special case when empty String ("") is passed from the entity into CLOB type column
if (ClassConstants.CLOB.equals(javaClass) && sourceObject instanceof String && EMPTY_STRING.equals(sourceObject)) {
Connection connection = session.getAccessor().getConnection();
Clob clob = null;
try {
clob = connection.createClob();
clob.setString(1, (String)sourceObject);
} catch (SQLException e) {
throw ConversionException.couldNotBeConvertedToClass(sourceObject, ClassConstants.CLOB, e);
}
return (T) clob;
}
return super.convertObject(sourceObject, javaClass);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@
// Oracle - initial API and implementation
package org.eclipse.persistence.platform.database.oracle;

import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.exceptions.DatabaseException;
import org.eclipse.persistence.internal.databaseaccess.FieldTypeDefinition;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.sessions.AbstractSession;

import java.sql.Clob;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Hashtable;

import static org.eclipse.persistence.internal.helper.StringHelper.EMPTY_STRING;

/**
* <p><b>Purpose:</b>
* Supports certain new Oracle 23c data types, and usage of certain Oracle JDBC specific APIs.
Expand All @@ -29,7 +42,6 @@ public Oracle23Platform() {
super();
}


/**
* INTERNAL:
* Check whether current platform is Oracle 23c or later.
Expand All @@ -40,4 +52,33 @@ public Oracle23Platform() {
public boolean isOracle23() {
return true;
}

@Override
protected Hashtable<Class<?>, FieldTypeDefinition> buildFieldTypes() {
Hashtable<Class<?>, FieldTypeDefinition> fieldTypes = super.buildFieldTypes();
fieldTypes.put(java.time.LocalDateTime.class, new FieldTypeDefinition("TIMESTAMP", 9));
fieldTypes.put(java.time.LocalTime.class, new FieldTypeDefinition("TIMESTAMP", 9));
return fieldTypes;
}

/**
* INTERNAL:
* Allow for conversion from the Oracle type to the Java type. Used in cases when DB connection is needed like BLOB, CLOB.
*/
@Override
public <T> T convertObject(Object sourceObject, Class<T> javaClass, AbstractSession session) throws ConversionException, DatabaseException {
//Handle special case when empty String ("") is passed from the entity into CLOB type column
if (ClassConstants.CLOB.equals(javaClass) && sourceObject instanceof String && EMPTY_STRING.equals(sourceObject)) {
Connection connection = session.getAccessor().getConnection();
Clob clob = null;
try {
clob = connection.createClob();
clob.setString(1, (String)sourceObject);
} catch (SQLException e) {
throw ConversionException.couldNotBeConvertedToClass(sourceObject, ClassConstants.CLOB, e);
}
return (T) clob;
}
return super.convertObject(sourceObject, javaClass);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2023 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
Expand Down Expand Up @@ -157,12 +157,13 @@ public void testOracleWithoutLOBLocatorWithEmptyClob() throws Exception {
// After Oracle 11, the lob locator is disabled by default (requiring a Session Customizer to reenable it)
// So the test should fail because Eclipselink will try store a null value instead of an empty_blob()/empty_clob()
// and violate the NOT NULL constraint.
// In Oracle23Platform case when empty String ("") is stored into CLOB column is handled by java.sql.Clob
Set<String> notAllowedPlatforms = new HashSet<String>();
notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle8Platform");
notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle9Platform");
notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle10Platform");


notAllowedPlatforms.add("org.eclipse.persistence.platform.database.Oracle23Platform");
notAllowedPlatforms.add("org.eclipse.persistence.platform.database.oracle.Oracle23Platform");
if (!checkIsOracle() || notAllowedPlatforms.contains(getPlatform(emfNoSessionCustomizer).getClass().getName())) {
// Skip if not testing against Oracle
return;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2023 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
Expand Down Expand Up @@ -120,10 +120,14 @@ public void process(DatabaseMapping mapping, MappingAccessor accessor, MetadataC
// referenceClass type.
if (isValidClobType(referenceClass)) {
setFieldClassification(mapping, java.sql.Clob.class, isForMapKey);
setConverter(mapping, new TypeConversionConverter(mapping), isForMapKey);
TypeConversionConverter typeConversionConverter = new TypeConversionConverter(mapping);
typeConversionConverter.setDataClass(java.sql.Clob.class);
setConverter(mapping, typeConversionConverter, isForMapKey);
} else if (isValidBlobType(referenceClass)) {
setFieldClassification(mapping, java.sql.Blob.class, isForMapKey);
setConverter(mapping, new TypeConversionConverter(mapping), isForMapKey);
TypeConversionConverter typeConversionConverter = new TypeConversionConverter(mapping);
typeConversionConverter.setDataClass(java.sql.Blob.class);
setConverter(mapping, typeConversionConverter, isForMapKey);
} else if (referenceClass.extendsInterface(Serializable.class)) {
setFieldClassification(mapping, java.sql.Blob.class, isForMapKey);
setConverter(mapping, new SerializedObjectConverter(mapping), isForMapKey);
Expand Down

0 comments on commit 4b88873

Please sign in to comment.