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

Global Unique Index #88

Merged
merged 7 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 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,14 @@
* List of annotated class fields representing index columns.
*/
String[] fields();

/**
* Index type
*/
Type type() default Type.GLOBAL;

enum Type {
GLOBAL,
UNIQUE
}
}
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.type() == GlobalIndex.Type.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 Down Expand Up @@ -142,16 +143,39 @@ 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) {
Map<String, Object> entityIndexValues = buildIndexValues(index, entity);
for (InMemoryEntityLine line : entityLines.values()) {
Columns columns = line.get(txId, version);
if (columns != null && entityIndexValues.equals(buildIndexValues(index, columns.toSchema(schema)))) {
throw new EntityAlreadyExistsException("Entity " + entity.getId() + " already exists");
}
}
}
}

private Map<String, Object> buildIndexValues(Schema.Index index, T entity) {
Map<String, Object> cells = new HashMap<>(schema.flatten(entity));
cells.keySet().retainAll(index.getFieldNames());
return cells;
}

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"}, type = GlobalIndex.Type.UNIQUE)
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"}, type = GlobalIndex.Type.UNIQUE)
@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"}, type = GlobalIndex.Type.UNIQUE)
@AllArgsConstructor
public static class GlobalIndexMultiIndex implements Entity<GlobalIndexMultiIndex> {
Id id;
Expand Down