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 22 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/).

## [1.6.0]
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
### Added
- Added support for composite primary keys.

## [1.5.0]
### Added
- Added support for java.util.Map and similar types as mapping types.
Expand Down
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.Assert;


public abstract class NosqlTemplateBase
implements ApplicationContextAware {

Expand Down Expand Up @@ -116,35 +115,59 @@ protected TableResult doTableRequest(NosqlEntityInformation<?, ?> entityInformat
protected boolean doCreateTableIfNotExists(
NosqlEntityInformation<?, ?> entityInformation) {

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

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() + ")";
}
Map<String, FieldValue.Type> shardKeys =
entityInformation.getShardKeys();

String autogen = "";
if (entityInformation.isAutoGeneratedId() ) {
if (entityInformation.getIdNosqlType() == FieldValue.Type.STRING) {
autogen = TEMPLATE_GENERATED_UUID;
} else {
autogen = TEMPLATE_GENERATED_ALWAYS;
Map<String, FieldValue.Type> nonShardKeys =
entityInformation.getNonShardKeys();

StringBuilder tableBuilder = new StringBuilder();
tableBuilder.append("CREATE TABLE IF NOT EXISTS ");
tableBuilder.append(tableName).append("("); //create open (

shardKeys.forEach((key, type) -> {
String keyType = type.name();
if (keyType.equals(FieldValue.Type.TIMESTAMP.toString())) {
keyType += "(" + nosqlDbFactory.getTimestampPrecision() + ")";
}
String autogen = getAutoGenType(entityInformation);
tableBuilder.append(key).append(" ").append(keyType)
.append(" ").append(autogen).append(",");
});

nonShardKeys.forEach((key, type) -> {
String keyType = type.name();
if (keyType.equals(FieldValue.Type.TIMESTAMP.toString())) {
keyType += "(" + nosqlDbFactory.getTimestampPrecision() + ")";
}
String autogen = getAutoGenType(entityInformation);
tableBuilder.append(key).append(" ").append(keyType)
.append(" ").append(autogen).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(",", shardKeys.keySet()));
tableBuilder.append(")");

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

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

String tableName = entityInformation.getTableName();
String sql = String.format(TEMPLATE_CREATE_TABLE,
tableName,
idColName, idColType, autogen, idColName, ttl);
sql = tableBuilder.toString();

TableRequest tableReq = new TableRequest().setStatement(sql)
.setTableLimits(entityInformation.getTableLimits(nosqlDbFactory));
Expand Down Expand Up @@ -266,7 +289,6 @@ protected void doUpdate(NosqlEntityInformation<?, ?> entityInformation,
idColumnName);

Map<String, FieldValue> params = new HashMap<>();
//todo implement composite keys
params.put("$id", row.get(idColumnName));
params.put("$json", row.get(JSON_COLUMN));

Expand Down Expand Up @@ -328,7 +350,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 Expand Up @@ -387,4 +410,12 @@ private PreparedStatement getPreparedStatement(
private Iterable<MapValue> doQuery(QueryRequest qReq) {
return new IterableUtil.IterableImpl(nosqlClient, qReq);
}

private String getAutoGenType(NosqlEntityInformation<?, ?> entityInformation) {
if (entityInformation.isAutoGeneratedId()) {
return (entityInformation.getIdNosqlType() == FieldValue.Type.STRING) ?
TEMPLATE_GENERATED_UUID : TEMPLATE_GENERATED_ALWAYS;
}
return "";
}
}
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,26 +576,38 @@ 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;
MapValue jsonValue = null;
if (nosqlValue.asMap().get(NosqlTemplateBase.JSON_COLUMN) !=
null) {
jsonValue = nosqlValue.asMap().
get(NosqlTemplateBase.JSON_COLUMN).asMap();

NosqlPersistentEntity<E> clsEntity =
updateEntity(entity, getInstanceClass(jsonValue));
entityObj = getNewInstance(clsEntity, nosqlValue.asMap(),
jsonValue);
get(NosqlTemplateBase.JSON_COLUMN).asMap();
}
NosqlPersistentEntity<E> clsEntity =
updateEntity(entity, getInstanceClass(jsonValue));
entityObj = getNewInstance(clsEntity, nosqlValue.asMap(),
jsonValue);

if (idFieldValue != null) {
setId(entityObj, idFieldValue);
}
setPojoProperties(clsEntity, entityObj, jsonValue);
if (idFieldValue != null) {
setId(entityObj, idFieldValue);
}
setPojoProperties(clsEntity, entityObj, jsonValue);

} else {
MapValue mapValue = nosqlValue.asMap();
String instClsStr = getInstanceClass(mapValue);
Expand Down Expand Up @@ -811,7 +831,7 @@ private <E> List<Object> convertArrayValueToCollection(FieldValue nosqlValue,

private <R> R getNewInstance(NosqlPersistentEntity<R> entity,
MapValue rootFieldValue,
@NonNull MapValue jsonValue) {
@Nullable MapValue jsonValue) {

EntityInstantiator instantiator =
instantiators.getInstantiatorFor(entity);
Expand All @@ -828,14 +848,16 @@ public <T> T getParameterValue(
NosqlPersistentProperty prop =
entity.getPersistentProperty(paramName);

FieldValue value;
if (rootFieldValue == null) {
FieldValue value = null;
if (rootFieldValue == null && jsonValue != null) {
value = jsonValue.get(paramName);
} else {
if (prop.isIdProperty()) {
value = rootFieldValue.get(paramName);
} else {
value = jsonValue.get(paramName);
if (jsonValue != null) {
value = jsonValue.get(paramName);
}
if (value == null) {
// if field is not marked id and it's not in
// kv_json_ it may be an unmarked id field
Expand Down Expand Up @@ -1115,10 +1137,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,15 @@ 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
}

@Override
public boolean isNosqlKey() {
return isAnnotationPresent(NosqlKey.class);
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@

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 {@code PRIMARY KEY} of the corresponding Nosql
* table. Only one field of the entity can be annotated with this annotation.
*
* If using a composite primary key, the fields comprising the composite key
* must have distinct names when viewed in lower case, otherwise an error is
* raised.
*/
@Id
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE })
Akshay-Sundarraj marked this conversation as resolved.
Show resolved Hide resolved
public @interface NosqlId {
/**
* Specifies if values for the field are automatically generated. Valid only
* for int, Integer, long, Long, BigInteger, BigDecimal,
*/
boolean generated() default false;

//todo: will be supported in a future version
//boolean shardKey() default false;
}
Loading