Skip to content

Commit

Permalink
Oracle NoSQL Database - NativeQueries and more complex JPA queries su…
Browse files Browse the repository at this point in the history
…pport (#1793)

This change brings into EclipseLink and Oracle NoSQL Database platform support for:

    JPA @NamedNativeQuery
    support for more complex Jakarta Persistence queries like SELECT t FROM DataTypesEntity t WHERE t.id = :id AND t.fieldString = :name

Signed-off-by: Radek Felcman <radek.felcman@oracle.com>
  • Loading branch information
rfelcman committed Jan 27, 2023
1 parent 9bebcd2 commit 1e31560
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright (c) 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
* 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
package org.eclipse.persistence.testing.models.jpa.nosql;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.NamedNativeQuery;
import jakarta.persistence.Table;

import org.eclipse.persistence.nosql.annotations.DataFormatType;
import org.eclipse.persistence.nosql.annotations.NoSql;

@Entity
@Table(name = "TEST_TAB")
@NamedNativeQuery(
name="NativeQueryEntity.findByIdSQLNativeQueryEntity",
query="DECLARE $id INTEGER; SELECT $tab.id, $tab.col_json_object.map[$element.m1 = 5] as component FROM TEST_TAB $tab WHERE id = $id",
resultClass= org.eclipse.persistence.testing.models.jpa.nosql.NativeQueryEntity.class
)
//@NoSql -> it's not possible to use default (dataFormat= DataFormatType.XML) as QueryStringInteraction which is behind @NamedNativeQuery extends MappedInteraction not XMLInteraction
@NoSql(dataFormat= DataFormatType.MAPPED)
public class NativeQueryEntity {
@Id
private long id;
@Column(name = "component", columnDefinition = "JSON")
private String component;

public NativeQueryEntity() {
}

public NativeQueryEntity(long id) {
this.id = id;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getComponent() {
return component;
}

public void setComponent(String component) {
this.component = component;
}

@Override
public String toString() {
return "NativeQueryEntity{" +
"id=" + id +
", component='" + component + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import jakarta.persistence.Persistence;
import jakarta.persistence.Query;
import jakarta.resource.cci.Connection;

import oracle.nosql.driver.NoSQLHandle;
import oracle.nosql.driver.ops.GetRequest;
import oracle.nosql.driver.ops.GetResult;
Expand All @@ -31,10 +32,14 @@
import oracle.nosql.driver.values.ArrayValue;
import oracle.nosql.driver.values.JsonNullValue;
import oracle.nosql.driver.values.MapValue;

import org.eclipse.persistence.internal.nosql.adapters.sdk.OracleNoSQLConnection;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.logging.SessionLog;

import org.eclipse.persistence.testing.models.jpa.nosql.DataTypesEntity;
import org.eclipse.persistence.testing.models.jpa.nosql.NativeQueryEntity;

import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
Expand Down Expand Up @@ -90,19 +95,8 @@ public void testJPAFind() {
try {
DataTypesEntity dataTypesEntity = em.find(DataTypesEntity.class, ID);
LOG.log(SessionLog.INFO, String.format("Entity by em.find(...):\t%s", dataTypesEntity));

assertEquals(ID, dataTypesEntity.getId());
assertArrayEquals(FIELD_BINARY, dataTypesEntity.getFieldBinary());
assertEquals(FIELD_BOOLEAN, dataTypesEntity.isFieldBoolean());
assertEquals(FIELD_JSON_STRING, dataTypesEntity.getFieldJsonString());
JsonObject jsonObjectFromStringField = Json.createReader(new StringReader(dataTypesEntity.getFieldJsonString())).readObject();
assertNotNull(jsonObjectFromStringField);
assertEquals(FIELD_JSON_OBJECT.toJson(), dataTypesEntity.getFieldJsonObject());
JsonObject jsonObjectFromObjectField = Json.createReader(new StringReader(dataTypesEntity.getFieldJsonObject())).readObject();
assertNotNull(jsonObjectFromObjectField);
assertNull(dataTypesEntity.getFieldNull());
assertEquals(FIELD_STRING, dataTypesEntity.getFieldString());
assertEquals(FIELD_TIMESTAMP, dataTypesEntity.getFieldTimestamp());
assertNotNull(dataTypesEntity);
assertDataTypesEntity(dataTypesEntity, ID);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
Expand Down Expand Up @@ -141,11 +135,52 @@ public void testJPAQueryFindById() {
try {
Query query = em.createNamedQuery("DataTypesEntity.findById", DataTypesEntity.class);
query.setParameter("id", ID);
List<DataTypesEntity> testEntities = query.getResultList();
assertTrue(testEntities.size() > 0);
for (DataTypesEntity dataTypesEntity : testEntities) {
LOG.log(SessionLog.INFO, String.format("Entity by DataTypesEntity.findById query:\t%s", dataTypesEntity));
DataTypesEntity dataTypesEntity = (DataTypesEntity)query.getSingleResult();
assertNotNull(dataTypesEntity);
assertDataTypesEntity(dataTypesEntity, ID);
LOG.log(SessionLog.INFO, String.format("Entity by DataTypesEntity.findById query:\t%s", dataTypesEntity));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
} finally {
if (em != null) {
em.close();
}
}
}

@Test
public void testJPAQueryFindByIdAndName() {
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(PU_UNIT_NAME);
EntityManager em = entityManagerFactory.createEntityManager();
try {
Query query = em.createNamedQuery("DataTypesEntity.findByIdAndName", DataTypesEntity.class);
query.setParameter("id", ID);
query.setParameter("name", FIELD_STRING);
DataTypesEntity dataTypesEntity = (DataTypesEntity)query.getSingleResult();
assertNotNull(dataTypesEntity);
assertDataTypesEntity(dataTypesEntity, ID);
LOG.log(SessionLog.INFO, String.format("Entity by DataTypesEntity.findByIdAndName query:\t%s", dataTypesEntity));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
} finally {
if (em != null) {
em.close();
}
}
}

@Test
public void testJPANativeQueryFindById() {
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(PU_UNIT_NAME);
EntityManager em = entityManagerFactory.createEntityManager();
try {
Query query = em.createNamedQuery("NativeQueryEntity.findByIdSQLNativeQueryEntity", NativeQueryEntity.class);
query.setParameter("id", ID);
NativeQueryEntity nativeQueryEntity = (NativeQueryEntity)query.getSingleResult();
assertNativeQueryEntity(nativeQueryEntity, ID);
LOG.log(SessionLog.INFO, String.format("Entity by NativeQueryEntity.findByIdSQLNativeQueryEntity query:\t%s", nativeQueryEntity));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException();
Expand Down Expand Up @@ -296,4 +331,28 @@ private static void updateRow(NoSQLHandle handle) {
LOG.log(SessionLog.INFO,"Update failed");
}
}

private void assertDataTypesEntity(DataTypesEntity dataTypesEntity, long id) {
if (dataTypesEntity.getId() == id) {
assertEquals(ID, dataTypesEntity.getId());
assertArrayEquals(FIELD_BINARY, dataTypesEntity.getFieldBinary());
assertEquals(FIELD_BOOLEAN, dataTypesEntity.isFieldBoolean());
assertEquals(FIELD_JSON_STRING, dataTypesEntity.getFieldJsonString());
JsonObject jsonObjectFromStringField = Json.createReader(new StringReader(dataTypesEntity.getFieldJsonString())).readObject();
assertNotNull(jsonObjectFromStringField);
assertEquals(FIELD_JSON_OBJECT.toJson(), dataTypesEntity.getFieldJsonObject());
JsonObject jsonObjectFromObjectField = Json.createReader(new StringReader(dataTypesEntity.getFieldJsonObject())).readObject();
assertNotNull(jsonObjectFromObjectField);
assertNull(dataTypesEntity.getFieldNull());
assertEquals(FIELD_STRING, dataTypesEntity.getFieldString());
assertEquals(FIELD_TIMESTAMP, dataTypesEntity.getFieldTimestamp());
}
}

private void assertNativeQueryEntity(NativeQueryEntity nativeQueryEntity, long id) {
if (nativeQueryEntity.getId() == id) {
assertEquals(ID, nativeQueryEntity.getId());
assertEquals("{\"m1\":5}", nativeQueryEntity.getComponent());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!--
Copyright (c) 2018, 2022 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2018, 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 @@ -60,6 +60,7 @@
<persistence-unit name="nosql-datatypes-sdk" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<class>org.eclipse.persistence.testing.models.jpa.nosql.DataTypesEntity</class>
<class>org.eclipse.persistence.testing.models.jpa.nosql.NativeQueryEntity</class>
<properties>
<property name="eclipselink.nosql.property.nosql.service" value="@nosql.sdk.service@"/>
<property name="eclipselink.nosql.property.nosql.endpoint" value="@nosql.sdk.endpoint@"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
import oracle.nosql.driver.values.NullValue;
import oracle.nosql.driver.values.NumberValue;
import oracle.nosql.driver.values.StringValue;

import oracle.nosql.driver.values.TimestampValue;

import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.eis.EISException;
import org.eclipse.persistence.eis.mappings.EISCompositeCollectionMapping;
Expand All @@ -64,9 +64,9 @@
import org.eclipse.persistence.mappings.converters.Converter;
import org.eclipse.persistence.nosql.adapters.sdk.OracleNoSQLPlatform;
import org.eclipse.persistence.oxm.record.DOMRecord;

import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.factories.SessionManager;

import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
Expand Down Expand Up @@ -222,22 +222,24 @@ public jakarta.resource.cci.Record execute(InteractionSpec spec, jakarta.resourc
return output;
} else if (operation == OracleNoSQLOperation.ITERATOR_QUERY) {
OracleNoSQLRecord output = new OracleNoSQLRecord();
Map<String, String> inputRecord = null;
OracleNoSQLRecord inputRecord = null;
for (Map.Entry<?, ?> entry : (Set<Map.Entry<?, ?>>) input.entrySet()) {
DOMRecord domRecord = createDOMRecord((byte[]) entry.getValue());
inputRecord = createMapFromDOMRecord(createDOMRecord((byte[]) entry.getValue()));
System.out.println(inputRecord);
if (entry.getValue() instanceof byte[]) {
inputRecord = createMapFromDOMRecord(createDOMRecord((byte[]) entry.getValue()));
} else {
inputRecord = (OracleNoSQLRecord)entry.getValue();
}
}
//Handle Find queries like SELECT * FROM TEST_TABLE WHERE NAME = :name
String sqlString = inputRecord.get(OracleNoSQLPlatform.QUERY);
//Handle JPQL queries like SELECT * FROM TEST_TABLE WHERE NAME = :name
String sqlString = (String)inputRecord.get(OracleNoSQLPlatform.QUERY);
PrepareRequest prepareRequest = new PrepareRequest().setStatement(sqlString);
if (noSqlSpec.getTimeout() > 0) {
prepareRequest.setTimeout(noSqlSpec.getTimeout());
}
PrepareResult prepareResult = this.connection.getNoSQLHandle().prepare(prepareRequest);
PreparedStatement preparedStatement = prepareResult.getPreparedStatement();
if (inputRecord.get(OracleNoSQLPlatform.QUERY_ARGUMENTS) != null) {
StringTokenizer st = new StringTokenizer(inputRecord.get(OracleNoSQLPlatform.QUERY_ARGUMENTS), ";");
StringTokenizer st = new StringTokenizer((String)inputRecord.get(OracleNoSQLPlatform.QUERY_ARGUMENTS), ";");
while (st.hasMoreTokens()) {
String argumentName = st.nextToken();
preparedStatement.setVariable("$" + argumentName, OracleNoSQLPlatform.getFieldValue(argumentName + (String) inputRecord.get(OracleNoSQLPlatform.QUERY_ARGUMENT_TYPE_SUFFIX), (String) inputRecord.get(argumentName + OracleNoSQLPlatform.QUERY_ARGUMENT_VALUE_SUFFIX), false));
Expand All @@ -261,6 +263,40 @@ public jakarta.resource.cci.Record execute(InteractionSpec spec, jakarta.resourc
return null;
}
return output;
} else if (operation == OracleNoSQLOperation.NATIVE_QUERY) {
OracleNoSQLRecord output = new OracleNoSQLRecord();
//Handle Native queries like "DECLARE $id INTEGER; SELECT $tab.id, $tab.col_json_object.map[$element.m1 = 5] as component FROM $tab WHERE id = $id"
String sqlString = (String)input.get(OracleNoSQLPlatform.QUERY);
PrepareRequest prepareRequest = new PrepareRequest().setStatement(sqlString);
if (noSqlSpec.getTimeout() > 0) {
prepareRequest.setTimeout(noSqlSpec.getTimeout());
}
PrepareResult prepareResult = this.connection.getNoSQLHandle().prepare(prepareRequest);
PreparedStatement preparedStatement = prepareResult.getPreparedStatement();
if (input.get(OracleNoSQLPlatform.QUERY_ARGUMENTS) != null) {
OracleNoSQLRecord arguments = (OracleNoSQLRecord)input.get(OracleNoSQLPlatform.QUERY_ARGUMENTS);
for (Map.Entry<DatabaseField, Object> entry : (Set<Map.Entry<DatabaseField, Object>>) arguments.entrySet()) {
preparedStatement.setVariable("$" + entry.getKey(), OracleNoSQLPlatform.getFieldValue(entry.getValue().getClass().getName(), entry.getValue(), false));
}
}
QueryRequest queryRequest = new QueryRequest().setPreparedStatement(preparedStatement);
do {
QueryResult queryResult = this.connection.getNoSQLHandle().query(queryRequest);
List<MapValue> results = queryResult.getResults();
int rowId = 1;
OracleNoSQLRecord outputRow;
for (MapValue values : results) {
outputRow = new OracleNoSQLRecord();
for (Map.Entry<String, FieldValue> outputEntry : values.entrySet()) {
outputRow.put(outputEntry.getKey(), unboxFieldValue(outputEntry.getValue(), spec, isDBFieldJSONType(noSqlSpec.getTableName(), outputEntry.getKey(), fieldNameMapping)));
}
output.put(rowId++, outputRow);
}
} while (!queryRequest.isDone());
if (output.isEmpty()) {
return null;
}
return output;
} else {
throw new ResourceException("Invalid NoSQL operation:" + operation);
}
Expand Down Expand Up @@ -507,8 +543,8 @@ private MapValue createMapValue(Map recordEntry, ClassDescriptor descriptor) {
return mapValue;
}

private Map<String, String> createMapFromDOMRecord(DOMRecord domRecord) {
Map<String, String> map = new HashMap<>();
private OracleNoSQLRecord createMapFromDOMRecord(DOMRecord domRecord) {
OracleNoSQLRecord map = new OracleNoSQLRecord();
for (Map.Entry<DatabaseField, Element> entry : (Set<Map.Entry<DatabaseField, Element>>) domRecord.entrySet()) {
String key = entry.getKey().getName();
String value = entry.getValue().getFirstChild().getNodeValue();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2022, 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 @@ -21,5 +21,5 @@
* @since EclipseLink 4.0
*/
public enum OracleNoSQLOperation {
GET, PUT, PUT_IF_ABSENT, PUT_IF_PRESENT, PUT_IF_VERSION, DELETE, DELETE_IF_VERSION, ITERATOR, ITERATOR_QUERY
GET, PUT, PUT_IF_ABSENT, PUT_IF_PRESENT, PUT_IF_VERSION, DELETE, DELETE_IF_VERSION, ITERATOR, ITERATOR_QUERY, NATIVE_QUERY
}

0 comments on commit 1e31560

Please sign in to comment.