Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/composite key #31

Merged
merged 25 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b5a49bb
Composite key support
Akshay-Sundarraj Mar 7, 2023
0486641
Merge branch 'main' into feature/composite-key
Akshay-Sundarraj Mar 7, 2023
72c3d2f
o. Add derive query tests for composite primary key.
Akshay-Sundarraj Mar 14, 2023
4f0f1fe
Fix javadoc warning
Akshay-Sundarraj Mar 14, 2023
c984a38
Update composite key test
Akshay-Sundarraj Mar 14, 2023
63420c9
o. Fix an issue in table DDL generation for composite key
Akshay-Sundarraj Mar 17, 2023
3c45350
o. Added tests for table creation for composite primary key
Akshay-Sundarraj Mar 24, 2023
6314f0c
Merge branch 'main' into feature/composite-key
Akshay-Sundarraj Mar 24, 2023
7a9c98e
composite key Ordering.
Akshay-Sundarraj Mar 30, 2023
b93c8d2
Unit test improvements
Akshay-Sundarraj Apr 2, 2023
eca62e3
o. Fix an issue when findAll(sort) has composite key member
Akshay-Sundarraj Apr 5, 2023
9513d40
o. Fix an issue in ReactiveRepo.save() which was not saving an entity…
Akshay-Sundarraj Apr 10, 2023
a74525d
Adding missed file
Akshay-Sundarraj Apr 10, 2023
8fa4d9e
o. Fix an issue when projecting only primary keys
Akshay-Sundarraj Apr 13, 2023
c176ff8
Fix some of the review comments
Akshay-Sundarraj Apr 19, 2023
0348f64
NosqlKey ordering changes:
Akshay-Sundarraj Apr 20, 2023
c48019d
o. Fix review comments
Akshay-Sundarraj Apr 21, 2023
f6488d3
o. Added a test to check for collision of keys when sorted
Akshay-Sundarraj Apr 21, 2023
8fef50e
o. Update javadoc for NosqlKey
Akshay-Sundarraj Apr 22, 2023
8966b16
Merge branch 'main' into feature/composite-key
Akshay-Sundarraj Apr 22, 2023
fcce407
Updated javadoc for NosqlKey and NosqlId classes.
cezarfx Apr 25, 2023
98d0b75
Small update shardKey javadoc.
cezarfx Apr 26, 2023
5c4c53c
o. Updated CHANGELOG.md
Akshay-Sundarraj May 2, 2023
5c51782
Merge branch 'feature/composite-key' of https://github.com/oracle/nos…
Akshay-Sundarraj May 2, 2023
feab6da
o. Modified composite key ordering to be unique across both shard and…
Akshay-Sundarraj May 4, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/main/java/com/oracle/nosql/spring/data/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class Constants {
public static final int NOTSET_TABLE_TIMEOUT_MS = 0;
public static final int NOTSET_TABLE_TTL = 0;

public static final boolean NOTSET_SHARD_KEY = true;
public static final int NOTSET_PRIMARY_KEY_ORDER = -1;

public static final String USER_AGENT = "NoSQL-SpringSDK";

private Constants() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import com.oracle.nosql.spring.data.core.mapping.NosqlKey;
import com.oracle.nosql.spring.data.core.mapping.NosqlPersistentProperty;
import oracle.nosql.driver.NoSQLException;
import oracle.nosql.driver.NoSQLHandle;
import oracle.nosql.driver.ops.DeleteRequest;
Expand All @@ -39,7 +41,6 @@
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -485,6 +486,24 @@ private <T> String convertProperty(
return property;
}

//field can be composite key
NosqlPersistentProperty pp = mappingNosqlConverter.getMappingContext().
getPersistentPropertyPath(property,
entityInformation.getJavaType()).getLeafProperty();

NosqlPersistentProperty parentPp =
mappingNosqlConverter.getMappingContext().
getPersistentPropertyPath(property,
entityInformation.getJavaType()).getBaseProperty();
if(pp != null) {
if (pp.isAnnotationPresent(NosqlKey.class)) {
return pp.getName();
}
if(parentPp != null && parentPp.isIdProperty()) {
return pp.getName();
}
}

return JSON_COLUMN + "." + property;
}

Expand Down Expand Up @@ -582,4 +601,8 @@ public <S, T> Iterable<T> find(

return IterableUtil.getIterableFromStream(resStream);
}

public NoSQLHandle getNosqlClient() {
return nosqlClient;
}
}
162 changes: 137 additions & 25 deletions src/main/java/com/oracle/nosql/spring/data/core/NosqlTemplateBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
*/
package com.oracle.nosql.spring.data.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

import oracle.nosql.driver.NoSQLException;
import oracle.nosql.driver.NoSQLHandle;
Expand All @@ -31,6 +36,9 @@

import com.oracle.nosql.spring.data.NosqlDbFactory;
import com.oracle.nosql.spring.data.core.convert.MappingNosqlConverter;
import com.oracle.nosql.spring.data.core.mapping.NosqlKey;
import com.oracle.nosql.spring.data.core.mapping.NosqlPersistentEntity;
import com.oracle.nosql.spring.data.core.mapping.NosqlPersistentProperty;
import com.oracle.nosql.spring.data.core.query.NosqlQuery;
import com.oracle.nosql.spring.data.repository.support.NosqlEntityInformation;

Expand All @@ -41,6 +49,8 @@
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;

import static com.oracle.nosql.spring.data.Constants.NOTSET_PRIMARY_KEY_ORDER;


public abstract class NosqlTemplateBase
implements ApplicationContextAware {
Expand Down Expand Up @@ -116,36 +126,137 @@ protected TableResult doTableRequest(NosqlEntityInformation<?, ?> entityInformat
protected boolean doCreateTableIfNotExists(
NosqlEntityInformation<?, ?> entityInformation) {

String idColName = entityInformation.getIdField().getName();
String tableName = entityInformation.getTableName();
String sql;

/*If composite key, sort composite key class to create table.
sorting is based on following rules.
Sort by shard key=true, then by order then by name
*/
if (NosqlEntityInformation.isCompositeKeyType(entityInformation.getIdField().getType())) {
Map<Integer, SortedSet<String>> shardKeys = new TreeMap<>();
Map<Integer, SortedSet<String>> otherKeys = new TreeMap<>();

NosqlPersistentEntity<?> compositeKeyEntity =
mappingNosqlConverter.getMappingContext().
getRequiredPersistentEntity(entityInformation.getIdType());
for (NosqlPersistentProperty idProperty : compositeKeyEntity) {
if (idProperty.isWritable()) {
if (idProperty.isAnnotationPresent(NosqlKey.class)) {
NosqlKey noSqlKey =
idProperty.findAnnotation(NosqlKey.class);
int order = noSqlKey.order();
if (noSqlKey.shardKey()) {
SortedSet<String> ss = shardKeys.getOrDefault(order,
new TreeSet<>());
ss.add(idProperty.getName());
shardKeys.put(order, ss);
} else {
SortedSet<String> ss = otherKeys.getOrDefault(order,
new TreeSet<>());
ss.add(idProperty.getName());
otherKeys.put(order, ss);
}
} else {
SortedSet<String> ss =
shardKeys.getOrDefault(NOTSET_PRIMARY_KEY_ORDER,
new TreeSet<>());
ss.add(idProperty.getName());
shardKeys.put(NOTSET_PRIMARY_KEY_ORDER, ss);
}
}
}
List<String> sortedShardKeys = new ArrayList<>();
List<String> sortedOtherKeys = new ArrayList<>();

shardKeys.forEach((order, keys) -> {
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
sortedShardKeys.addAll(keys);
});

String idColType = entityInformation.getIdNosqlType().toString();
if (entityInformation.getIdNosqlType() == FieldValue.Type.TIMESTAMP) {
// For example: CREATE TABLE IF NOT EXISTS SensorIdTimestamp
// (time TIMESTAMP(3) , kv_json_ JSON, PRIMARY KEY( time ))
idColType += "(" + nosqlDbFactory.getTimestampPrecision() + ")";
}
otherKeys.forEach((order, keys) -> {
sortedOtherKeys.addAll(keys);
});

String autogen = "";
if (entityInformation.isAutoGeneratedId() ) {
if (entityInformation.getIdNosqlType() == FieldValue.Type.STRING) {
autogen = TEMPLATE_GENERATED_UUID;
} else {
autogen = TEMPLATE_GENERATED_ALWAYS;
if (sortedShardKeys.isEmpty()) {
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException("At least one of the " +
"@NoSqlKey must be shard key in class " +
compositeKeyEntity.getName());
}
}

String ttl = "";
if (entityInformation.getTtl() != null &&
entityInformation.getTtl().getValue() != 0) {
ttl = String.format(TEMPLATE_TTL_CREATE,
entityInformation.getTtl().toString());
}
StringBuilder tableBuilder = new StringBuilder();
tableBuilder.append("CREATE TABLE IF NOT EXISTS ");
tableBuilder.append(tableName).append("("); //create open (

sortedShardKeys.forEach(keyName -> {
NosqlPersistentProperty shardKeyProp =
compositeKeyEntity.getRequiredPersistentProperty(keyName);
String keyType = NosqlEntityInformation.findIdNosqlType(
shardKeyProp.getType()).toString();
tableBuilder.append(keyName).append(" ").append(keyType).
append(",");
});
sortedOtherKeys.forEach(keyName -> {
NosqlPersistentProperty shardKeyProp =
compositeKeyEntity.getRequiredPersistentProperty(keyName);
String keyType = NosqlEntityInformation.findIdNosqlType(
shardKeyProp.getType()).toString();
if (keyType.equals(FieldValue.Type.TIMESTAMP.toString())) {
keyType += "(" + nosqlDbFactory.getTimestampPrecision() + ")";
}
tableBuilder.append(keyName).append(" ").append(keyType).
append(",");
});
tableBuilder.append(JSON_COLUMN).append(" ").append("JSON").append(",");

tableBuilder.append("PRIMARY KEY").append("("); //primary key open (
tableBuilder.append("SHARD").append("(");
tableBuilder.append(String.join(",", sortedShardKeys));
tableBuilder.append(")");

if (!sortedOtherKeys.isEmpty()) {
tableBuilder.append(",");
tableBuilder.append(String.join(",", sortedOtherKeys));
}
tableBuilder.append(")"); //primary key close )
tableBuilder.append(")"); //create close )

//ttl
if (entityInformation.getTtl() != null &&
entityInformation.getTtl().getValue() != 0) {
tableBuilder.append(String.format(TEMPLATE_TTL_CREATE,
entityInformation.getTtl().toString()));
}
sql = tableBuilder.toString();
} else {
String idColName = entityInformation.getIdField().getName();
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved

String tableName = entityInformation.getTableName();
String sql = String.format(TEMPLATE_CREATE_TABLE,
tableName,
idColName, idColType, autogen, idColName, ttl);
String idColType = entityInformation.getIdNosqlType().toString();
if (entityInformation.getIdNosqlType() == FieldValue.Type.TIMESTAMP) {
// For example: CREATE TABLE IF NOT EXISTS SensorIdTimestamp
// (time TIMESTAMP(3) , kv_json_ JSON, PRIMARY KEY( time ))
idColType += "(" + nosqlDbFactory.getTimestampPrecision() + ")";
}

String autogen = "";
if (entityInformation.isAutoGeneratedId()) {
if (entityInformation.getIdNosqlType() == FieldValue.Type.STRING) {
autogen = TEMPLATE_GENERATED_UUID;
} else {
autogen = TEMPLATE_GENERATED_ALWAYS;
}
}

String ttl = "";
if (entityInformation.getTtl() != null &&
entityInformation.getTtl().getValue() != 0) {
ttl = String.format(TEMPLATE_TTL_CREATE,
entityInformation.getTtl().toString());
}

sql = String.format(TEMPLATE_CREATE_TABLE,
tableName,
idColName, idColType, autogen, idColName, ttl);
}
TableRequest tableReq = new TableRequest().setStatement(sql)
.setTableLimits(entityInformation.getTableLimits(nosqlDbFactory));

Expand Down Expand Up @@ -328,7 +439,8 @@ protected <T> Iterable<MapValue> doExecuteMapValueQuery(NosqlQuery query,

final Map<String, Object> params = new LinkedHashMap<>();
String sql = query.generateSql(entityInformation.getTableName(), params,
idPropertyName);
idPropertyName, mappingNosqlConverter.
getMappingContext().getPersistentEntity(entityClass));

PreparedStatement pStmt = getPreparedStatement(entityInformation, sql);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import com.oracle.nosql.spring.data.core.mapping.BasicNosqlPersistentProperty;
import com.oracle.nosql.spring.data.core.mapping.NosqlPersistentEntity;
import com.oracle.nosql.spring.data.core.mapping.NosqlPersistentProperty;
import com.oracle.nosql.spring.data.repository.support.NosqlEntityInformation;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -211,10 +212,17 @@ public <T> MapValue convertObjToRow(T objectToSave, boolean skipSetId) {
row.put(NosqlTemplateBase.JSON_COLUMN, valueMap);

if (!skipSetId && idProperty != null) {
//todo implement composite key
row.put(idProperty.getName(),
convertObjToFieldValue(accessor.getProperty(idProperty),
idProperty, false));
if (idProperty.isCompositeKey()) {
MapValue ids = convertObjToFieldValue(
accessor.getProperty(idProperty),
idProperty,
false).asMap();
ids.getMap().forEach(row::put);
} else {
row.put(idProperty.getName(),
convertObjToFieldValue(accessor.getProperty(idProperty),
idProperty, false));
}
}

for (NosqlPersistentProperty prop : persistentEntity) {
Expand Down Expand Up @@ -568,8 +576,20 @@ private <E> E convertFieldValueToObj(Class<?> type,
FieldValue idFieldValue = null;

if (entity.getIdProperty() != null) {
idFieldValue = nosqlValue.asMap()
.get(entity.getIdProperty().getName());
NosqlPersistentProperty idProperty = entity.getIdProperty();

if (idProperty.isCompositeKey()) {
idFieldValue = new MapValue();
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
NosqlPersistentEntity<?> idEntity =
mappingContext.getPersistentEntity(idProperty.getType());
for (NosqlPersistentProperty p : idEntity) {
idFieldValue.asMap().put(p.getName(),
nosqlValue.asMap().get(p.getName()));
}
} else {
idFieldValue = nosqlValue.asMap()
.get(entity.getIdProperty().getName());
}
}

MapValue jsonValue;
Expand Down Expand Up @@ -1115,10 +1135,15 @@ public <ID> MapValue convertIdToPrimaryKey(String idColumnName, ID id) {
}

MapValue row = new MapValue();

row.put(idColumnName, convertObjToFieldValue(id, null, false));
//todo: add support for composite key

if (NosqlEntityInformation.isCompositeKeyType(id.getClass())) {
/*composite key. Here convertObjToFieldValue adds #class that is
why convertObjToRow is used*/
MapValue compositeKey = convertObjToRow(id, false);
compositeKey.get(NosqlTemplateBase.JSON_COLUMN).asMap().
getMap().forEach(row::put);
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
} else {
row.put(idColumnName, convertObjToFieldValue(id, null, false));
}
return row;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import oracle.nosql.driver.values.FieldValue;

import com.oracle.nosql.spring.data.Constants;
import com.oracle.nosql.spring.data.repository.support.NosqlEntityInformation;

import org.springframework.data.geo.Point;
import org.springframework.data.geo.Polygon;
Expand Down Expand Up @@ -170,4 +171,10 @@ public static TypeCode getCodeForSerialization(Class<?> cls) {
return TypeCode.POJO;
}
}

@Override
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
public boolean isCompositeKey() {
return isIdProperty() &&
NosqlEntityInformation.isCompositeKeyType(getType());
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@

import org.springframework.data.annotation.Id;

/**
* Identifies the primary key field of the entity. Primary key can be of a
* basic type or of a type that represents a composite primary key. This
* field corresponds to the {@literal PRIMARY KEY} of the corresponding Nosql
* table. Only one field of the entity can be annotated with this annotation.
*/

@Id
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Loading