Skip to content

Commit

Permalink
Global Unique Index
Browse files Browse the repository at this point in the history
  • Loading branch information
g-sg-v committed Sep 23, 2024
1 parent 8fb22de commit 898877d
Show file tree
Hide file tree
Showing 15 changed files with 99 additions and 17 deletions.
4 changes: 2 additions & 2 deletions WORKSPACE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ SLF4J_VERSION = "2.0.11"

SNAKEYAML_VERSION = "1.33"

YDB_PROTOAPI_VERSION = "1.6.0"
YDB_PROTOAPI_VERSION = "1.6.2"

YDB_SDK_VERSION = "2.1.12"
YDB_SDK_VERSION = "2.2.11"

maven_install(
name = "java_contribs_stable",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,9 @@
* List of annotated class fields representing index columns.
*/
String[] fields();

/**
* Is unique index type
*/
boolean unique() default false;
}
10 changes: 9 additions & 1 deletion databind/src/main/java/tech/ydb/yoj/databind/schema/Schema.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
Expand Down Expand Up @@ -147,7 +148,7 @@ name, getType(), fieldPath)
}
columns.add(field.getName());
}
outputIndexes.add(new Index(name, List.copyOf(columns)));
outputIndexes.add(new Index(name, List.copyOf(columns), index.unique()));
}
return outputIndexes;
}
Expand Down Expand Up @@ -773,13 +774,20 @@ public FieldValueType getFieldValueType() {
}

@Value
@AllArgsConstructor
public static class Index {
public Index (@NonNull String indexName, @NonNull List<String> fieldNames) {
this(indexName, fieldNames, false);
}

@NonNull
String indexName;

@With
@NonNull
List<String> fieldNames;

boolean unique;
}

@Value
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
<os-maven-plugin.version>1.7.1</os-maven-plugin.version>

<!-- YDB SDK 2.x -->
<ydb-sdk-v2.version>2.2.8</ydb-sdk-v2.version>
<ydb-sdk-v2.version>2.2.11</ydb-sdk-v2.version>
<ydb-proto-api.version>1.6.2</ydb-proto-api.version>

<!-- build-only dependencies (provided) -->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package tech.ydb.yoj.repository.test.inmemory;

import tech.ydb.yoj.databind.schema.Schema;
import tech.ydb.yoj.repository.db.Entity;
import tech.ydb.yoj.repository.db.EntityIdSchema;
import tech.ydb.yoj.repository.db.EntitySchema;
Expand All @@ -15,6 +16,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;

Expand Down Expand Up @@ -142,16 +144,42 @@ public synchronized void insert(long txId, long version, T entity) {
throw new EntityAlreadyExistsException("Entity " + entity.getId() + " already exists");
}

save(txId, entity);
save(txId, version, entity);
}

public synchronized void save(long txId, T entity) {
public synchronized void save(long txId, long version, T entity) {
InMemoryEntityLine entityLine = entityLines.computeIfAbsent(entity.getId(), __ -> new InMemoryEntityLine());

validateUniqueness(txId, version, entity);
uncommited.computeIfAbsent(txId, __ -> new HashSet<>()).add(entity.getId());

entityLine.put(txId, Columns.fromEntity(schema, entity));
}

private void validateUniqueness(long txId, long version, T entity) {
List<Schema.Index> indexes = schema.getGlobalIndexes().stream()
.filter(Schema.Index::isUnique)
.toList();
for (Schema.Index index : indexes) {
Object[] entityIndexValues = buildIndexValues(index, entity);
for (InMemoryEntityLine line : entityLines.values()) {
Columns columns = line.get(txId, version);
if (columns != null && Objects.deepEquals(entityIndexValues, buildIndexValues(index, columns.toSchema(schema)))) {
throw new EntityAlreadyExistsException("Entity " + entity.getId() + " already exists");
}
}
}
}

private Object[] buildIndexValues(Schema.Index index, T entity) {
Object[] objects = new Object[index.getFieldNames().size()];
Map<String, Object> cells = schema.flatten(entity);
for (int i = 0; i < index.getFieldNames().size(); i++) {
objects[i] = cells.get(index.getFieldNames().get(i));
}
return objects;
}

public synchronized void delete(long txId, Entity.Id<T> id) {
InMemoryEntityLine entityLine = entityLines.get(id);
if (entityLine == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void insert(T entity) {

@Override
public void save(T entity) {
shard.save(txId, entity);
shard.save(txId, version, entity);
}

@Override
Expand Down
1 change: 1 addition & 0 deletions repository-test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ java_library(
"@java_contribs_stable//:com_fasterxml_jackson_core_jackson_databind",
"@java_contribs_stable//:com_fasterxml_jackson_datatype_jackson_datatype_jdk8",
"@java_contribs_stable//:com_fasterxml_jackson_datatype_jackson_datatype_jsr310",
"@java_contribs_stable//:com_google_code_findbugs_jsr305",
"@java_contribs_stable//:com_google_guava_guava",
"@java_contribs_stable//:javax_annotation_javax_annotation_api",
"@java_contribs_stable//:junit_junit",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import tech.ydb.yoj.repository.test.sample.model.TypeFreak.A;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak.B;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak.Embedded;
import tech.ydb.yoj.repository.test.sample.model.UniqueProject;
import tech.ydb.yoj.repository.test.sample.model.UpdateFeedEntry;
import tech.ydb.yoj.repository.test.sample.model.Version;
import tech.ydb.yoj.repository.test.sample.model.VersionedAliasedEntity;
Expand Down Expand Up @@ -101,6 +102,7 @@
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static tech.ydb.yoj.repository.db.EntityExpressions.newFilterBuilder;

Expand Down Expand Up @@ -1351,6 +1353,14 @@ private void findInKeysViewFilteredAndOrdered(Set<IndexedEntity.Key> keys, boole
);
}

@Test
public void testUniqueIndex() {
UniqueProject ue1 = new UniqueProject(new UniqueProject.Id("id1"), "valuableName");
db.tx(() -> db.table(UniqueProject.class).save(ue1));
UniqueProject ue2 = new UniqueProject(new UniqueProject.Id("id2"), "valuableName");
assertThrows(EntityAlreadyExistsException.class, () -> db.tx(() -> db.table(UniqueProject.class).insert(ue2)));
}

@Test
public void doubleTxIsOk() {
db.tx(this::findRange);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import tech.ydb.yoj.repository.test.sample.model.Supabubble2;
import tech.ydb.yoj.repository.test.sample.model.Team;
import tech.ydb.yoj.repository.test.sample.model.TypeFreak;
import tech.ydb.yoj.repository.test.sample.model.UniqueProject;
import tech.ydb.yoj.repository.test.sample.model.UpdateFeedEntry;
import tech.ydb.yoj.repository.test.sample.model.VersionedAliasedEntity;
import tech.ydb.yoj.repository.test.sample.model.VersionedEntity;
Expand All @@ -34,7 +35,7 @@ private TestEntities() {

@SuppressWarnings("rawtypes")
public static final List<Class<? extends Entity>> ALL = List.of(
Project.class, TypeFreak.class, Complex.class, Referring.class, Primitive.class,
Project.class, UniqueProject.class, TypeFreak.class, Complex.class, Referring.class, Primitive.class,
Book.class, Book.ByAuthor.class, Book.ByTitle.class,
LogEntry.class, Team.class,
BytePkEntity.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package tech.ydb.yoj.repository.test.sample.model;

import lombok.Value;
import lombok.With;
import tech.ydb.yoj.databind.schema.GlobalIndex;
import tech.ydb.yoj.repository.db.Entity;

@Value
@GlobalIndex(name = "unique_name", fields = {"name"}, unique = true)
public class UniqueProject implements Entity<UniqueProject> {
Id id;
@With
String name;

@Value
public static class Id implements Entity.Id<UniqueProject> {
String value;
}
}

1 change: 1 addition & 0 deletions repository-ydb-v2/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ java_library(
"@java_contribs_stable//:tech_ydb_ydb_auth_api",
"@java_contribs_stable//:tech_ydb_ydb_proto_api",
"@java_contribs_stable//:tech_ydb_ydb_sdk_bom",
"@java_contribs_stable//:tech_ydb_ydb_sdk_common",
"@java_contribs_stable//:tech_ydb_ydb_sdk_core",
"@java_contribs_stable//:tech_ydb_ydb_sdk_scheme",
"@java_contribs_stable//:tech_ydb_ydb_sdk_table",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import tech.ydb.scheme.description.ListDirectoryResult;
import tech.ydb.table.Session;
import tech.ydb.table.description.TableDescription;
import tech.ydb.table.description.TableIndex;
import tech.ydb.table.description.TableTtl;
import tech.ydb.table.settings.AlterTableSettings;
import tech.ydb.table.settings.Changefeed;
Expand Down Expand Up @@ -90,7 +91,13 @@ public void createTable(String name, List<EntitySchema.JavaField> columns, List<
});
List<String> primaryKeysNames = primaryKeys.stream().map(Schema.JavaField::getName).collect(toList());
builder.setPrimaryKeys(primaryKeysNames);
globalIndexes.forEach(index -> builder.addGlobalIndex(index.getIndexName(), index.getFieldNames()));
globalIndexes.forEach(index -> {
if (index.isUnique()) {
builder.addGlobalUniqueIndex(index.getIndexName(), index.getFieldNames());
} else {
builder.addGlobalIndex(index.getIndexName(), index.getFieldNames());
}
});

Session session = sessionManager.getSession();
try {
Expand Down Expand Up @@ -163,7 +170,7 @@ public Table describeTable(String name, List<EntitySchema.JavaField> columns, Li
})
.toList();
List<Index> ydbIndexes = indexes.stream()
.map(i -> new Index(i.getIndexName(), i.getFieldNames()))
.map(i -> new Index(i.getIndexName(), i.getFieldNames(), i.isUnique()))
.toList();
TtlModifier tableTtl = ttlModifier == null
? null
Expand Down Expand Up @@ -282,7 +289,7 @@ private Table describeTableInternal(String path) {
})
.toList(),
table.getIndexes().stream()
.map(i -> new Index(i.getName(), i.getColumns()))
.map(i -> new Index(i.getName(), i.getColumns(), i.getType() == TableIndex.Type.GLOBAL_UNIQUE))
.toList(),
table.getTableTtl() == null || table.getTableTtl().getTtlMode() == TableTtl.TtlMode.NOT_SET
? null
Expand Down Expand Up @@ -428,6 +435,7 @@ public static class Column {
public static class Index {
String name;
List<String> columns;
boolean unique;
}

@Value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ private static String indexes(YdbSchemaOperations.Table table) {
return "\n";
}
return ",\n" + indexes.stream()
.map(idx -> "\tINDEX `" + idx.getName() + "` GLOBAL ON (" + indexColumns(idx.getColumns()) + ")")
.map(idx -> "\tINDEX `" + idx.getName() + "` GLOBAL " + (idx.isUnique() ? "UNIQUE " : "") + "ON (" + indexColumns(idx.getColumns()) + ")")
.collect(Collectors.joining(",\n")) + "\n";
}

Expand Down Expand Up @@ -401,7 +401,7 @@ private void makeMigrationTableIndexInstructions(YdbSchemaOperations.Table from,
.collect(toMap(YdbSchemaOperations.Index::getName, Function.identity()));

Function<YdbSchemaOperations.Index, String> createIndex = i ->
String.format("ALTER TABLE `%s` ADD INDEX `%s` GLOBAL ON (%s);",
String.format("ALTER TABLE `%s` ADD INDEX `%s` GLOBAL " + (i.isUnique() ? "UNIQUE ": "") + "ON (%s);",
to.getName(), i.getName(), i.getColumns().stream().map(c -> "`" + c + "`").collect(joining(","))
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public void testGlobalIndexMultiIndex() {
var schema = ObjectSchema.of(GlobalIndexMultiIndex.class);
Assert.assertEquals(List.of(
new Schema.Index("idx1", List.of("id_id1", "id_3")),
new Schema.Index("idx2", List.of("id_2", "id_3"))),
new Schema.Index("idx2", List.of("id_2", "id_3"), true)),
schema.getGlobalIndexes());
}

Expand Down Expand Up @@ -366,7 +366,7 @@ public static class Id implements Entity.Id<GlobalIndexSimple> {
}

@GlobalIndex(name = "idx1", fields = {"id.id1", "id3"})
@GlobalIndex(name = "idx2", fields = {"id.id2", "id3"})
@GlobalIndex(name = "idx2", fields = {"id.id2", "id3"}, unique = true)
@AllArgsConstructor
public static class GlobalIndexMultiIndex implements Entity<GlobalIndexMultiIndex> {
Id id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public void testGlobalIndexMultiIndex() {
var schema = ObjectSchema.of(GlobalIndexMultiIndex.class);
Assert.assertEquals(List.of(
new Schema.Index("idx1", List.of("id_id1", "id_3")),
new Schema.Index("idx2", List.of("id_2", "id_3"))),
new Schema.Index("idx2", List.of("id_2", "id_3"), true)),
schema.getGlobalIndexes());
}

Expand Down Expand Up @@ -366,7 +366,7 @@ public static class Id implements Entity.Id<GlobalIndexSimple> {
}

@GlobalIndex(name = "idx1", fields = {"id.id1", "id3"})
@GlobalIndex(name = "idx2", fields = {"id.id2", "id3"})
@GlobalIndex(name = "idx2", fields = {"id.id2", "id3"}, unique = true)
@AllArgsConstructor
public static class GlobalIndexMultiIndex implements Entity<GlobalIndexMultiIndex> {
Id id;
Expand Down

0 comments on commit 898877d

Please sign in to comment.