From 3c653fb39048e18f4c321ab11c3170e5c09e5582 Mon Sep 17 00:00:00 2001 From: silvanheller Date: Sat, 7 May 2022 13:32:11 +0200 Subject: [PATCH 1/6] adding test showcasing how export fails for nan --- .../cottontail/cli/ExportImportCommandTest.kt | 34 +++++++++++++++---- .../vitrivr/cottontail/test/GrpcTestUtils.kt | 33 +++++++++++------- 2 files changed, 48 insertions(+), 19 deletions(-) diff --git a/cottontaildb-cli/src/test/kotlin/org/vitrivr/cottontail/cli/ExportImportCommandTest.kt b/cottontaildb-cli/src/test/kotlin/org/vitrivr/cottontail/cli/ExportImportCommandTest.kt index 693a0d8ef..8b2cc00f1 100644 --- a/cottontaildb-cli/src/test/kotlin/org/vitrivr/cottontail/cli/ExportImportCommandTest.kt +++ b/cottontaildb-cli/src/test/kotlin/org/vitrivr/cottontail/cli/ExportImportCommandTest.kt @@ -9,8 +9,10 @@ import org.vitrivr.cottontail.cli.entity.TruncateEntityCommand import org.vitrivr.cottontail.core.database.Name import org.vitrivr.cottontail.data.Format import org.vitrivr.cottontail.test.AbstractClientTest +import org.vitrivr.cottontail.test.GrpcTestUtils import org.vitrivr.cottontail.test.GrpcTestUtils.countElements import org.vitrivr.cottontail.test.TestConstants +import org.vitrivr.cottontail.test.TestConstants.TEST_ENTITY_NAME import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.deleteIfExists @@ -39,13 +41,7 @@ class ExportImportCommandTest : AbstractClientTest() { fun exportCreatesFile() { formats.forEach { format -> TestConstants.ALL_ENTITY_NAMES.forEach { name -> - val path = exportFolder() - path.toFile().mkdirs() - val exported = exportFile(format, name) - exported.deleteIfExists() - DumpEntityCommand.dumpEntity(name, path, format, this.client) - assert(exported.toFile().exists()) - assert(exported.toFile().totalSpace > 1) + exportEntity(format, name) } } } @@ -67,4 +63,28 @@ class ExportImportCommandTest : AbstractClientTest() { } } } + + @Test + fun exportNan() { + GrpcTestUtils.insertIntoTestEntity(client, double = Double.NaN) + GrpcTestUtils.insertIntoTestEntity(client) + formats.forEach { format -> + exportEntity(format, TEST_ENTITY_NAME) + val exportFile = exportFile(format, TEST_ENTITY_NAME) + val count = countElements(this.client, TEST_ENTITY_NAME) + TruncateEntityCommand.truncate(TEST_ENTITY_NAME, this.client, true) + ImportDataCommand.importData(TEST_ENTITY_NAME, exportFile, format, this.client, true) + assert(count == countElements(this.client, TEST_ENTITY_NAME)) + } + } + + private fun exportEntity(format: Format, fqn: Name.EntityName) { + val path = exportFolder() + path.toFile().mkdirs() + val exported = exportFile(format, fqn) + exported.deleteIfExists() + DumpEntityCommand.dumpEntity(fqn, path, format, this.client) + assert(exported.toFile().exists()) + assert(exported.toFile().totalSpace > 1) + } } \ No newline at end of file diff --git a/cottontaildb-dbms/src/testFixtures/kotlin/org/vitrivr/cottontail/test/GrpcTestUtils.kt b/cottontaildb-dbms/src/testFixtures/kotlin/org/vitrivr/cottontail/test/GrpcTestUtils.kt index 039f7eed4..aa752b105 100644 --- a/cottontaildb-dbms/src/testFixtures/kotlin/org/vitrivr/cottontail/test/GrpcTestUtils.kt +++ b/cottontaildb-dbms/src/testFixtures/kotlin/org/vitrivr/cottontail/test/GrpcTestUtils.kt @@ -5,9 +5,13 @@ import org.vitrivr.cottontail.client.SimpleClient import org.vitrivr.cottontail.client.language.basics.Type import org.vitrivr.cottontail.client.language.ddl.* import org.vitrivr.cottontail.client.language.dml.BatchInsert +import org.vitrivr.cottontail.client.language.dml.Insert import org.vitrivr.cottontail.client.language.dql.Query import org.vitrivr.cottontail.core.database.Name import org.vitrivr.cottontail.grpc.CottontailGrpc +import org.vitrivr.cottontail.test.TestConstants.DOUBLE_COLUMN_NAME +import org.vitrivr.cottontail.test.TestConstants.INT_COLUMN_NAME +import org.vitrivr.cottontail.test.TestConstants.STRING_COLUMN_NAME import kotlin.random.Random @@ -43,9 +47,9 @@ object GrpcTestUtils { */ fun createTestEntity(client: SimpleClient) { val create = CreateEntity(TestConstants.TEST_ENTITY_NAME.fqn) - .column(TestConstants.STRING_COLUMN_NAME, Type.STRING) - .column(TestConstants.INT_COLUMN_NAME, Type.INTEGER) - .column(TestConstants.DOUBLE_COLUMN_NAME, Type.DOUBLE) + .column(STRING_COLUMN_NAME, Type.STRING) + .column(INT_COLUMN_NAME, Type.INTEGER) + .column(DOUBLE_COLUMN_NAME, Type.DOUBLE) client.create(create) } @@ -56,9 +60,9 @@ object GrpcTestUtils { */ fun createTestVectorEntity(client: SimpleClient) { val create = CreateEntity(TestConstants.TEST_VECTOR_ENTITY_NAME.fqn) - .column(TestConstants.STRING_COLUMN_NAME, Type.STRING) - .column(TestConstants.INT_COLUMN_NAME, Type.INTEGER) - .column(TestConstants.TWOD_COLUMN_NAME, Type.FLOAT_VECTOR, 2) + .column(STRING_COLUMN_NAME, Type.STRING) + .column(INT_COLUMN_NAME, Type.INTEGER) + .column(TestConstants.TWOD_COLUMN_NAME, Type.FLOAT_VECTOR, 2) client.create(create) } @@ -68,7 +72,7 @@ object GrpcTestUtils { * @param client [SimpleClient] to use. */ fun populateTestEntity(client: SimpleClient) { - val batch = BatchInsert().into(TestConstants.TEST_ENTITY_NAME.fqn).columns(TestConstants.STRING_COLUMN_NAME, TestConstants.INT_COLUMN_NAME, TestConstants.DOUBLE_COLUMN_NAME) + val batch = BatchInsert().into(TestConstants.TEST_ENTITY_NAME.fqn).columns(STRING_COLUMN_NAME, INT_COLUMN_NAME, DOUBLE_COLUMN_NAME) val random = Random.Default repeat(TestConstants.TEST_COLLECTION_SIZE) { batch.append( @@ -80,11 +84,16 @@ object GrpcTestUtils { client.insert(batch) } + fun insertIntoTestEntity(client: SimpleClient, string: String = RandomStringUtils.randomAlphabetic(5), int: Int = Random.nextInt(0, 100), double: Double = Random.nextDouble(1.0)) { + val insert = Insert().into(TestConstants.TEST_ENTITY_NAME.fqn).values(Pair(STRING_COLUMN_NAME, string), Pair(INT_COLUMN_NAME, int), Pair(DOUBLE_COLUMN_NAME, double)) + client.insert(insert) + } + /** * Creates a Lucene index on the [TestConstants.TEST_ENTITY_NAME]. */ fun createLuceneIndexOnTestEntity(client: SimpleClient) { - client.create(CreateIndex(TestConstants.TEST_ENTITY_NAME.fqn, TestConstants.STRING_COLUMN_NAME, CottontailGrpc.IndexType.LUCENE)) + client.create(CreateIndex(TestConstants.TEST_ENTITY_NAME.fqn, STRING_COLUMN_NAME, CottontailGrpc.IndexType.LUCENE)) client.optimize(OptimizeEntity(TestConstants.TEST_ENTITY_NAME.fqn)) } @@ -95,16 +104,16 @@ object GrpcTestUtils { */ fun populateVectorEntity(client: SimpleClient) { val batch = BatchInsert().into(TestConstants.TEST_VECTOR_ENTITY_NAME.fqn) - .columns(TestConstants.STRING_COLUMN_NAME, TestConstants.INT_COLUMN_NAME, TestConstants.TWOD_COLUMN_NAME) + .columns(STRING_COLUMN_NAME, INT_COLUMN_NAME, TestConstants.TWOD_COLUMN_NAME) val random = Random.Default repeat(TestConstants.TEST_COLLECTION_SIZE) { val lat = random.nextFloat() + random.nextInt(0, 50) val lon = random.nextFloat() + random.nextInt(0, 50) val arr = floatArrayOf(lat, lon) batch.append( - RandomStringUtils.randomAlphabetic(5), - random.nextInt(0, 10), - arr + RandomStringUtils.randomAlphabetic(5), + random.nextInt(0, 10), + arr ) } client.insert(batch) From 20ef4d25d29d4ac5478a9a7f33c2f5bb84d07d70 Mon Sep 17 00:00:00 2001 From: silvanheller Date: Sat, 7 May 2022 14:43:04 +0200 Subject: [PATCH 2/6] adding test showcasing how deletes fail for BTREE_UQ indices --- .../cottontail/server/grpc/DMLServiceTest.kt | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt b/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt index 6001c4a51..5cd057d89 100644 --- a/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt +++ b/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt @@ -6,14 +6,17 @@ import org.junit.jupiter.api.* import org.vitrivr.cottontail.client.language.basics.Direction import org.vitrivr.cottontail.client.language.basics.Type import org.vitrivr.cottontail.client.language.basics.predicate.Expression +import org.vitrivr.cottontail.client.language.basics.predicate.Predicate import org.vitrivr.cottontail.client.language.ddl.CreateEntity import org.vitrivr.cottontail.client.language.ddl.CreateIndex import org.vitrivr.cottontail.client.language.ddl.OptimizeEntity import org.vitrivr.cottontail.client.language.dml.BatchInsert +import org.vitrivr.cottontail.client.language.dml.Delete import org.vitrivr.cottontail.client.language.dml.Insert import org.vitrivr.cottontail.client.language.dml.Update import org.vitrivr.cottontail.client.language.dql.Query import org.vitrivr.cottontail.grpc.CottontailGrpc +import org.vitrivr.cottontail.grpc.CottontailGrpc.AtomicBooleanPredicate import org.vitrivr.cottontail.test.AbstractClientTest import org.vitrivr.cottontail.test.GrpcTestUtils import org.vitrivr.cottontail.test.GrpcTestUtils.countElements @@ -66,8 +69,8 @@ class DMLServiceTest : AbstractClientTest() { val txId = this.client.begin() repeat(entries / batchSize) { i -> val batch = BatchInsert().into(TEST_ENTITY_NAME.fqn).columns(INT_COLUMN_NAME, STRING_COLUMN_NAME, DOUBLE_COLUMN_NAME) - repeat(batchSize) { - j -> batch.append(i*j, RandomStringUtils.randomAlphanumeric(stringLength), 1.0) + repeat(batchSize) { j -> + batch.append(i * j, RandomStringUtils.randomAlphanumeric(stringLength), 1.0) } this.client.insert(batch.txId(txId)) } @@ -93,7 +96,7 @@ class DMLServiceTest : AbstractClientTest() { val txId = this.client.begin() repeat(entries / batchSize) { i -> val batch = BatchInsert().into(TEST_ENTITY_NAME.fqn).columns(INT_COLUMN_NAME, STRING_COLUMN_NAME, DOUBLE_COLUMN_NAME).txId(txId) - repeat(batchSize) { j -> batch.append(i*j, RandomStringUtils.randomAlphanumeric(stringLength), 1.0) } + repeat(batchSize) { j -> batch.append(i * j, RandomStringUtils.randomAlphanumeric(stringLength), 1.0) } this.client.insert(batch) } @@ -235,7 +238,7 @@ class DMLServiceTest : AbstractClientTest() { val repeatBatchInsert = 100 val stringLength = 200 var txId = client.begin() - this.client.create( CreateEntity(entityName.fqn).column(STRING_COLUMN_NAME, Type.STRING).txId(txId)) + this.client.create(CreateEntity(entityName.fqn).column(STRING_COLUMN_NAME, Type.STRING).txId(txId)) this.client.create(CreateIndex(entityName.fqn, STRING_COLUMN_NAME, CottontailGrpc.IndexType.LUCENE).txId(txId)) this.client.commit(txId) @@ -257,4 +260,55 @@ class DMLServiceTest : AbstractClientTest() { } Assertions.assertEquals(repeatBatchInsert * batchCount, countElements(this.client, entityName)!!.toInt()) } + + @Test + fun testDuplicateEntryDelete() { + val entityName = TEST_SCHEMA.entity("test") + val entryString = "one" + var txId = client.begin() + //create entity with one column + this.client.create(CreateEntity(entityName.fqn).column(STRING_COLUMN_NAME, Type.STRING).txId(txId)) + this.client.commit(txId) + + //insert four times the same entry + repeat(4) { + txId = this.client.begin() + val insert = Insert().into(entityName.fqn).value(STRING_COLUMN_NAME, entryString).txId(txId) + this.client.insert(insert) + this.client.commit(txId) + } + + // after deletion, we expect there to be 0 entries left + txId = this.client.begin() + val delete = Delete().from(entityName.fqn).where(Expression(STRING_COLUMN_NAME, "=", entryString)).txId(txId) + this.client.delete(delete) + this.client.commit(txId) + Assertions.assertEquals(0, countElements(this.client, entityName)!!.toInt()) + } + + @Test + fun testDuplicateEntryUniqueIdDelete() { + val entityName = TEST_SCHEMA.entity("test") + val entryString = "one" + var txId = client.begin() + //create entity with one column which is supposed to be unique + this.client.create(CreateEntity(entityName.fqn).column(STRING_COLUMN_NAME, Type.STRING).txId(txId)) + this.client.create(CreateIndex(entityName.fqn, STRING_COLUMN_NAME, CottontailGrpc.IndexType.BTREE_UQ).txId(txId)) + this.client.commit(txId) + + // this should probably already fail + repeat(4) { + txId = this.client.begin() + val insert = Insert().into(entityName.fqn).value(STRING_COLUMN_NAME, entryString).txId(txId) + this.client.insert(insert) + this.client.commit(txId) + } + + // in the failed state, deleting the entries does not work (!) + txId = this.client.begin() + val delete = Delete().from(entityName.fqn).where(Expression(STRING_COLUMN_NAME, "=", entryString)).txId(txId) + this.client.delete(delete) + this.client.commit(txId) + Assertions.assertEquals(0, countElements(this.client, entityName)!!.toInt()) + } } \ No newline at end of file From 7af63fb38067ab783948213c3b53bab5bf8dbdd3 Mon Sep 17 00:00:00 2001 From: Ralph Gasser Date: Mon, 9 May 2022 10:21:35 +0200 Subject: [PATCH 3/6] Fixed issue using JsonReader/JsonWritter's isLenient flag. --- .../vitrivr/cottontail/cli/AbstractCottontailCommand.kt | 2 +- .../vitrivr/cottontail/cli/entity/DumpEntityCommand.kt | 2 +- .../vitrivr/cottontail/data/exporter/JsonDataExporter.kt | 9 ++------- .../vitrivr/cottontail/data/importer/JsonDataImporter.kt | 8 +++++++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt index 914e2e76d..0af9f216b 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt @@ -187,7 +187,7 @@ sealed class AbstractCottontailCommand(name: String, help: String, val expand: B } } println("Executing and exporting query took $duration.") - } catch (e: StatusException) { + } catch (e: Throwable) { print("A ${e::class.java.simpleName} occurred while executing and exporting query: ${e.message}.") } } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt index e7ee404da..137210c81 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt @@ -45,7 +45,7 @@ class DumpEntityCommand(client: SimpleClient) : AbstractCottontailCommand.Entity } dataExporter.close() } - println("Dumping ${entityName} took $duration.") + println("Dumping $entityName took $duration.") } catch (e: Throwable) { print("A ${e::class.java.simpleName} occurred while executing and exporting query: ${e.message}.") } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt index f1111af70..e1d797524 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt @@ -26,15 +26,10 @@ class JsonDataExporter(override val path: Path, val indent: String = "") : DataE private set /** The [JsonWriter] instance used to read the JSON file. */ - private val writer = JsonWriter( - Files.newBufferedWriter( - this.path, - StandardOpenOption.CREATE_NEW, - StandardOpenOption.WRITE - ) - ) + private val writer = JsonWriter(Files.newBufferedWriter(this.path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) init { + this.writer.isLenient = true this.writer.setIndent(this.indent) this.writer.beginArray() /* Starts writer the JSON array, which is the expected input. */ } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt index 47b05dc8e..871c95455 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt @@ -1,5 +1,6 @@ package org.vitrivr.cottontail.data.importer +import com.google.gson.GsonBuilder import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap @@ -24,9 +25,14 @@ import java.nio.file.Path class JsonDataImporter(override val path: Path, override val schema: List>) : DataImporter { /** The [JsonReader] instance used to read the JSON file. */ - private val reader = JsonReader(Files.newBufferedReader(this.path)) + private val reader = GsonBuilder() + .serializeNulls() + .serializeSpecialFloatingPointValues() + .create() + .newJsonReader(Files.newBufferedReader(this.path)) init { + this.reader.isLenient = true this.reader.beginArray() /* Starts reading the JSON array, which is the expected input. */ } From 4e827e16deff0d28179b5aac975f7ba3a1024e7c Mon Sep 17 00:00:00 2001 From: Ralph Gasser Date: Mon, 9 May 2022 10:24:57 +0200 Subject: [PATCH 4/6] Revert "Fixed issue using JsonReader/JsonWritter's isLenient flag." This reverts commit 7af63fb38067ab783948213c3b53bab5bf8dbdd3. --- .../vitrivr/cottontail/cli/AbstractCottontailCommand.kt | 2 +- .../vitrivr/cottontail/cli/entity/DumpEntityCommand.kt | 2 +- .../vitrivr/cottontail/data/exporter/JsonDataExporter.kt | 9 +++++++-- .../vitrivr/cottontail/data/importer/JsonDataImporter.kt | 8 +------- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt index 0af9f216b..914e2e76d 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt @@ -187,7 +187,7 @@ sealed class AbstractCottontailCommand(name: String, help: String, val expand: B } } println("Executing and exporting query took $duration.") - } catch (e: Throwable) { + } catch (e: StatusException) { print("A ${e::class.java.simpleName} occurred while executing and exporting query: ${e.message}.") } } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt index 137210c81..e7ee404da 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt @@ -45,7 +45,7 @@ class DumpEntityCommand(client: SimpleClient) : AbstractCottontailCommand.Entity } dataExporter.close() } - println("Dumping $entityName took $duration.") + println("Dumping ${entityName} took $duration.") } catch (e: Throwable) { print("A ${e::class.java.simpleName} occurred while executing and exporting query: ${e.message}.") } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt index e1d797524..f1111af70 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt @@ -26,10 +26,15 @@ class JsonDataExporter(override val path: Path, val indent: String = "") : DataE private set /** The [JsonWriter] instance used to read the JSON file. */ - private val writer = JsonWriter(Files.newBufferedWriter(this.path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) + private val writer = JsonWriter( + Files.newBufferedWriter( + this.path, + StandardOpenOption.CREATE_NEW, + StandardOpenOption.WRITE + ) + ) init { - this.writer.isLenient = true this.writer.setIndent(this.indent) this.writer.beginArray() /* Starts writer the JSON array, which is the expected input. */ } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt index 871c95455..47b05dc8e 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt @@ -1,6 +1,5 @@ package org.vitrivr.cottontail.data.importer -import com.google.gson.GsonBuilder import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap @@ -25,14 +24,9 @@ import java.nio.file.Path class JsonDataImporter(override val path: Path, override val schema: List>) : DataImporter { /** The [JsonReader] instance used to read the JSON file. */ - private val reader = GsonBuilder() - .serializeNulls() - .serializeSpecialFloatingPointValues() - .create() - .newJsonReader(Files.newBufferedReader(this.path)) + private val reader = JsonReader(Files.newBufferedReader(this.path)) init { - this.reader.isLenient = true this.reader.beginArray() /* Starts reading the JSON array, which is the expected input. */ } From 6eef7916f062a3b05a61ed13feaae7f6a73c7975 Mon Sep 17 00:00:00 2001 From: Ralph Gasser Date: Mon, 9 May 2022 10:25:33 +0200 Subject: [PATCH 5/6] Fixed issue using JsonReader/JsonWritter's isLenient flag. --- .../vitrivr/cottontail/cli/AbstractCottontailCommand.kt | 2 +- .../vitrivr/cottontail/cli/entity/DumpEntityCommand.kt | 2 +- .../vitrivr/cottontail/data/exporter/JsonDataExporter.kt | 9 ++------- .../vitrivr/cottontail/data/importer/JsonDataImporter.kt | 8 +++++++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt index 914e2e76d..0af9f216b 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/AbstractCottontailCommand.kt @@ -187,7 +187,7 @@ sealed class AbstractCottontailCommand(name: String, help: String, val expand: B } } println("Executing and exporting query took $duration.") - } catch (e: StatusException) { + } catch (e: Throwable) { print("A ${e::class.java.simpleName} occurred while executing and exporting query: ${e.message}.") } } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt index e7ee404da..137210c81 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/cli/entity/DumpEntityCommand.kt @@ -45,7 +45,7 @@ class DumpEntityCommand(client: SimpleClient) : AbstractCottontailCommand.Entity } dataExporter.close() } - println("Dumping ${entityName} took $duration.") + println("Dumping $entityName took $duration.") } catch (e: Throwable) { print("A ${e::class.java.simpleName} occurred while executing and exporting query: ${e.message}.") } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt index f1111af70..e1d797524 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/exporter/JsonDataExporter.kt @@ -26,15 +26,10 @@ class JsonDataExporter(override val path: Path, val indent: String = "") : DataE private set /** The [JsonWriter] instance used to read the JSON file. */ - private val writer = JsonWriter( - Files.newBufferedWriter( - this.path, - StandardOpenOption.CREATE_NEW, - StandardOpenOption.WRITE - ) - ) + private val writer = JsonWriter(Files.newBufferedWriter(this.path, StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE)) init { + this.writer.isLenient = true this.writer.setIndent(this.indent) this.writer.beginArray() /* Starts writer the JSON array, which is the expected input. */ } diff --git a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt index 47b05dc8e..871c95455 100644 --- a/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt +++ b/cottontaildb-cli/src/main/kotlin/org/vitrivr/cottontail/data/importer/JsonDataImporter.kt @@ -1,5 +1,6 @@ package org.vitrivr.cottontail.data.importer +import com.google.gson.GsonBuilder import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap @@ -24,9 +25,14 @@ import java.nio.file.Path class JsonDataImporter(override val path: Path, override val schema: List>) : DataImporter { /** The [JsonReader] instance used to read the JSON file. */ - private val reader = JsonReader(Files.newBufferedReader(this.path)) + private val reader = GsonBuilder() + .serializeNulls() + .serializeSpecialFloatingPointValues() + .create() + .newJsonReader(Files.newBufferedReader(this.path)) init { + this.reader.isLenient = true this.reader.beginArray() /* Starts reading the JSON array, which is the expected input. */ } From a56f4e71ba3c3a58b035439d13924e1e21707123 Mon Sep 17 00:00:00 2001 From: Ralph Gasser Date: Mon, 9 May 2022 11:23:15 +0200 Subject: [PATCH 6/6] Fixed issue with deletions in presence of UniqueHashIndexes. --- .../cottontail/dbms/entity/DefaultEntity.kt | 7 +-- .../dbms/exceptions/DatabaseException.kt | 45 +++++++++++-------- .../dbms/exceptions/TransactionException.kt | 12 ++++- .../transactions/TransactionManager.kt | 10 ++--- .../cottontail/dbms/index/hash/BTreeIndex.kt | 10 ++--- .../dbms/index/hash/UQBTreeIndex.kt | 15 +++---- .../grpc/services/TransactionalGrpcService.kt | 3 +- .../cottontail/server/grpc/DMLServiceTest.kt | 19 +++++--- 8 files changed, 70 insertions(+), 51 deletions(-) diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/entity/DefaultEntity.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/entity/DefaultEntity.kt index fe2c3301d..df1d8ced4 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/entity/DefaultEntity.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/entity/DefaultEntity.kt @@ -389,7 +389,8 @@ class DefaultEntity(override val name: Name.EntityName, override val parent: Def inserts[column.columnDef] = value /* Check if null value is allowed. */ - if (value == null && !column.columnDef.nullable) throw DatabaseException("Cannot insert NULL value into column ${column.columnDef}.") + if (value == null && !column.columnDef.nullable) + throw DatabaseException.ValidationException("Cannot INSERT a NULL value into column ${column.columnDef}.") (this.context.getTx(column) as ColumnTx).add(nextTupleId, value) } @@ -418,10 +419,10 @@ class DefaultEntity(override val name: Name.EntityName, override val parent: Def /* Execute UPDATE on column level. */ val updates = Object2ObjectArrayMap, Pair>(record.columns.size) for (def in record.columns) { - val column = this.columns[def.name] ?: throw DatabaseException("Record with tuple ID ${record.tupleId} cannot be updated for column $def, because column does not exist on entity.") + val column = this.columns[def.name] ?: throw DatabaseException.ColumnDoesNotExistException(def.name) val columnTx = (this.context.getTx(column) as ColumnTx<*>) val value = record[def] - if (value == null && !def.nullable) throw DatabaseException("Record with tuple ID ${record.tupleId} cannot be updated with NULL value for column $def, because column is not nullable.") + if (value == null && !def.nullable) throw DatabaseException.ValidationException("Record ${record.tupleId} cannot be updated with NULL value for column $def, because column is not nullable.") updates[def] = Pair((columnTx as ColumnTx).update(record.tupleId, value), value) /* Map: ColumnDef -> Pair[Old, New]. */ } diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/DatabaseException.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/DatabaseException.kt index 47c4056bb..45e2ef0e4 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/DatabaseException.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/DatabaseException.kt @@ -3,7 +3,10 @@ package org.vitrivr.cottontail.dbms.exceptions import org.vitrivr.cottontail.core.database.Name import org.vitrivr.cottontail.dbms.column.Column import org.vitrivr.cottontail.dbms.entity.Entity +import org.vitrivr.cottontail.dbms.general.DBO import org.vitrivr.cottontail.dbms.general.DBOVersion +import org.vitrivr.cottontail.dbms.index.Index +import org.vitrivr.cottontail.dbms.schema.Schema open class DatabaseException(message: String, cause: Throwable? = null) : Throwable(message, cause) { /** @@ -15,64 +18,64 @@ open class DatabaseException(message: String, cause: Throwable? = null) : Throwa class VersionMismatchException(val expected: DBOVersion, val found: DBOVersion) : DatabaseException("Version mismatch for DBO: Expected $expected but found $found.") /** - * Thrown when trying to create a [Schema][org.vitrivr.cottontail.dbms.schema.DefaultSchema] + * Thrown when trying to create a [Schema] * that does already exist. * - * @param schema [Name] of the [Schema][org.vitrivr.cottontail.dbms.schema.DefaultSchema]. + * @param schema [Name] of the [Schema]. */ class SchemaAlreadyExistsException(val schema: Name.SchemaName) : DatabaseException("Schema '$schema' does already exist!") /** - * Thrown when trying to access a [Schema][org.vitrivr.cottontail.dbms.schema.DefaultSchema] + * Thrown when trying to access a [Schema] * that does not exist. * - * @param schema [Name] of the [Schema][org.vitrivr.cottontail.dbms.schema.DefaultSchema]. + * @param schema [Name] of the [Schema]. */ class SchemaDoesNotExistException(val schema: Name.SchemaName) : DatabaseException("Schema '$schema' does not exist!") /** - * Thrown when trying to create an [Entity][org.vitrivr.cottontail.dbms.entity.DefaultEntity] + * Thrown when trying to create an [Entity] * that does already exist. * - * @param entity [Name] of the [Entity][org.vitrivr.cottontail.dbms.entity.DefaultEntity]. + * @param entity [Name] of the [Entity]. */ class EntityAlreadyExistsException(val entity: Name.EntityName) : DatabaseException("Entity '$entity' does already exist!") /** - * Thrown when trying to access an [Entity][org.vitrivr.cottontail.dbms.entity.DefaultEntity] + * Thrown when trying to access an [Entity] * that does not exist. * - * @param entity [Name] of the [Entity][org.vitrivr.cottontail.dbms.entity.DefaultEntity]. + * @param entity [Name] of the [Entity]. */ class EntityDoesNotExistException(val entity: Name.EntityName) : DatabaseException("Entity '$entity' does not exist!") /** - * Thrown whenever trying to create an [Index][org.vitrivr.cottontail.dbms.index.AbstractIndex] + * Thrown whenever trying to create an [Index] * that does already exist. * - * @param index The [Name] of the [Index][org.vitrivr.cottontail.dbms.index.AbstractIndex] + * @param index The [Name] of the [Index] */ class IndexAlreadyExistsException(val index: Name.IndexName) : DatabaseException("Index '$index' does already exist!") /** - * Thrown whenever trying to access an [Index][org.vitrivr.cottontail.dbms.index.AbstractIndex] + * Thrown whenever trying to access an [Index] * that does not exist. * - * @param index The [Name] of the [Index][org.vitrivr.cottontail.dbms.index.AbstractIndex] + * @param index The [Name] of the [Index] */ class IndexDoesNotExistException(val index: Name) : DatabaseException("Index '$index' does not exist!") /** - * Thrown whenever trying to create an [Index][[org.vitrivr.cottontail.dbms.index.AbstractIndex] that is not supported (yet). * + * Thrown whenever trying to create an [Index]that is not supported (yet). * * - * @param index The [Name] of the [Index][org.vitrivr.cottontail.dbms.index.AbstractIndex] + * @param index The [Name] of the [Index] */ - class IndexNotSupportedException(val index: Name.IndexName, val reason: String) : DatabaseException("Index '$index' could not be created: $reason") + class IndexNotSupportedException(val index: Name.IndexName, reason: String) : DatabaseException("Index '$index' could not be created: $reason") /** * Thrown upon creation of an [Entity] if the definition contains no column. * - * @param entity [Name] of the affected [Entity][org.vitrivr.cottontail.dbms.entity.DefaultEntity] + * @param entity [Name] of the affected [Entity] */ class NoColumnException(entity: Name.EntityName) : DatabaseException("Entity '$entity' could not be created because it does not contain a column.") @@ -85,8 +88,7 @@ open class DatabaseException(message: String, cause: Throwable? = null) : Throwa class DuplicateColumnException(entity: Name.EntityName, name: Name.ColumnName) : DatabaseException("Entity '$entity' could not be created because it contains duplicate column names '$name'.") /** - * Thrown whenever trying to access a [Column][org.vitrivr.cottontail.dbms.column.Column] - * that does not exist. + * Thrown whenever trying to access a [Column][org.vitrivr.cottontail.dbms.column.Column]that does not exist. * * @param column The [Name] of the [Column][org.vitrivr.cottontail.dbms.column.Column]. */ @@ -112,6 +114,13 @@ open class DatabaseException(message: String, cause: Throwable? = null) : Throwa * @param message Description of the issue. */ class ReservedValueException(message: String): DatabaseException(message) + + /** + * Write could not be executed because it failed a validation step. This is often caused by a user error, providing erroneous data. + * + * @param message Description of the validation error. + */ + class ValidationException(message: String) : TransactionException(message) } diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/TransactionException.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/TransactionException.kt index 09e94a2ae..8f0d48ee3 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/TransactionException.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/exceptions/TransactionException.kt @@ -20,12 +20,20 @@ sealed class TransactionException(message: String) : DatabaseException(message) class DBOClosed(tid: TransactionId, dbo: DBO) : TransactionException("Tx object for transaction $tid could not be created for DBO '${dbo.name}': Enclosing DBO was closed.") /** - * Write could not be executed because it failed a validation step. This is often caused by a user error, providing erroneous data. + * COMMIT could not be executed because. * * @param tid The [TransactionId] of the [Tx] in which this error occurred. * @param message Description of the validation error. */ - class Validation(tid: TransactionId, message: String) : TransactionException("Transaction $tid reported validation error: $message") + class Commit(tid: TransactionId, override val message: String?) : TransactionException("Transaction $tid could not be committed: $message") + + /** + * ROLLBACK could not be executed because. + * + * @param tid The [TransactionId] of the [Tx] in which this error occurred. + * @param message Description of the validation error. + */ + class Rollback(tid: TransactionId, override val message: String?) : TransactionException("Transaction $tid could not be rolled back: $message") /** * Thrown if a [Transaction] could not be committed, because it is in conflict with another [Transaction]. diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/execution/transactions/TransactionManager.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/execution/transactions/TransactionManager.kt index 273d62188..f2813f285 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/execution/transactions/TransactionManager.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/execution/transactions/TransactionManager.kt @@ -203,9 +203,8 @@ class TransactionManager(val executionManager: ExecutionManager, transactionTabl */ override fun commit() = runBlocking { this@TransactionImpl.mutex.withLock { /* Synchronise with ongoing COMMITS, ROLLBACKS or queries that are being scheduled. */ - check(this@TransactionImpl.state.canCommit) { - "Unable to commit transaction ${this@TransactionImpl.txId} because it is in wrong state (s = ${this@TransactionImpl.state})." - } + if (!this@TransactionImpl.state.canCommit) + throw TransactionException.Commit(this@TransactionImpl.txId, "Unable to COMMIT because transaction is in wrong state (s = ${this@TransactionImpl.state}).") this@TransactionImpl.state = TransactionStatus.FINALIZING var commit = false try { @@ -233,9 +232,8 @@ class TransactionManager(val executionManager: ExecutionManager, transactionTabl */ override fun rollback() = runBlocking { this@TransactionImpl.mutex.withLock { - check(this@TransactionImpl.state.canRollback) { - "Unable to rollback transaction ${this@TransactionImpl.txId} because it is in wrong state (s = ${this@TransactionImpl.state})." - } + if (!this@TransactionImpl.state.canRollback) + throw TransactionException.Rollback(this@TransactionImpl.txId, "Unable to ROLLBACK because transaction is in wrong state (s = ${this@TransactionImpl.state}).") this@TransactionImpl.performRollback() } } diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/BTreeIndex.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/BTreeIndex.kt index 53938bf8c..b8f242c87 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/BTreeIndex.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/BTreeIndex.kt @@ -24,7 +24,6 @@ import org.vitrivr.cottontail.dbms.catalogue.storeName import org.vitrivr.cottontail.dbms.entity.DefaultEntity import org.vitrivr.cottontail.dbms.entity.EntityTx import org.vitrivr.cottontail.dbms.exceptions.DatabaseException -import org.vitrivr.cottontail.dbms.exceptions.TransactionException import org.vitrivr.cottontail.dbms.execution.transactions.TransactionContext import org.vitrivr.cottontail.dbms.index.* import org.vitrivr.cottontail.dbms.index.lucene.LuceneIndex @@ -242,11 +241,10 @@ class BTreeIndex(name: Name.IndexName, parent: DefaultEntity) : AbstractIndex(na /* Iterate over entity and update index with entries. */ val cursor = entityTx.cursor(this.columns) cursor.forEach { record -> - val value = record[this.columns[0]] ?: throw TransactionException.Validation( - this.context.txId, - "A value cannot be null for instances of NonUniqueHashIndex ${this@BTreeIndex.name} but given value is (value = null, tupleId = ${record.tupleId})." - ) - this.addMapping(value, record.tupleId) + val value = record[this.columns[0]] + if (value != null) { + this.addMapping(value, record.tupleId) + } } /* Close cursor. */ diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/UQBTreeIndex.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/UQBTreeIndex.kt index b7c3b864c..7bccf9bd0 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/UQBTreeIndex.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/dbms/index/hash/UQBTreeIndex.kt @@ -23,7 +23,6 @@ import org.vitrivr.cottontail.dbms.catalogue.storeName import org.vitrivr.cottontail.dbms.entity.DefaultEntity import org.vitrivr.cottontail.dbms.entity.EntityTx import org.vitrivr.cottontail.dbms.exceptions.DatabaseException -import org.vitrivr.cottontail.dbms.exceptions.TransactionException import org.vitrivr.cottontail.dbms.execution.transactions.TransactionContext import org.vitrivr.cottontail.dbms.index.* import org.vitrivr.cottontail.dbms.index.lucene.LuceneIndex @@ -143,13 +142,11 @@ class UQBTreeIndex(name: Name.IndexName, parent: DefaultEntity) : AbstractIndex( * * This is an internal function and can be used safely with values o */ - private fun addMapping(key: Value, tupleId: TupleId): Boolean { + private fun addMapping(key: Value, tupleId: TupleId) { val keyRaw = this.binding.valueToEntry(key) val tupleIdRaw = LongBinding.longToCompressedEntry(tupleId) - return if (this.dataStore.get(this.context.xodusTx, keyRaw) == null) { - this.dataStore.put(this.context.xodusTx, keyRaw, tupleIdRaw) - } else { - false + if (!this.dataStore.add(this.context.xodusTx, keyRaw, tupleIdRaw)) { + throw DatabaseException.ValidationException("Mapping of $key to tuple $tupleId could be added to UniqueHashIndex, because value must be unique.") } } @@ -228,9 +225,9 @@ class UQBTreeIndex(name: Name.IndexName, parent: DefaultEntity) : AbstractIndex( /* Iterate over entity and update index with entries. */ val cursor = entityTx.cursor(this.columns) cursor.forEach { record -> - val value = record[this.columns[0]] ?: throw TransactionException.Validation(this.context.txId, "Value cannot be null for UniqueHashIndex ${this@UQBTreeIndex.name} given value is (value = null, tupleId = ${record.tupleId}).") - if (!this.addMapping(value, record.tupleId)) { - throw TransactionException.Validation(this.context.txId, "Value must be unique for UniqueHashIndex ${this@UQBTreeIndex.name} but is not (value = $value, tupleId = ${record.tupleId}).") + val value = record[this.columns[0]] + if (value != null) { + this.addMapping(value, record.tupleId) } } diff --git a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/server/grpc/services/TransactionalGrpcService.kt b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/server/grpc/services/TransactionalGrpcService.kt index a67522e2e..324df052e 100644 --- a/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/server/grpc/services/TransactionalGrpcService.kt +++ b/cottontaildb-dbms/src/main/kotlin/org/vitrivr/cottontail/server/grpc/services/TransactionalGrpcService.kt @@ -191,7 +191,8 @@ internal interface TransactionalGrpcService { is DatabaseException.EntityAlreadyExistsException, is DatabaseException.IndexAlreadyExistsException -> Status.ALREADY_EXISTS.withCause(e) is DatabaseException.NoColumnException, - is DatabaseException.DuplicateColumnException -> Status.INVALID_ARGUMENT.withCause(e) + is DatabaseException.DuplicateColumnException, + is DatabaseException.ValidationException -> Status.INVALID_ARGUMENT.withCause(e) is DeadlockException, is TransactionException.InConflict -> Status.ABORTED.withCause(e) is ExecutionException, diff --git a/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt b/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt index 5cd057d89..1c9a07587 100644 --- a/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt +++ b/cottontaildb-dbms/src/test/kotlin/org/vitrivr/cottontail/server/grpc/DMLServiceTest.kt @@ -1,12 +1,13 @@ package org.vitrivr.cottontail.server.grpc +import io.grpc.Status +import io.grpc.StatusRuntimeException import org.apache.commons.lang3.RandomStringUtils import org.apache.commons.math3.random.JDKRandomGenerator import org.junit.jupiter.api.* import org.vitrivr.cottontail.client.language.basics.Direction import org.vitrivr.cottontail.client.language.basics.Type import org.vitrivr.cottontail.client.language.basics.predicate.Expression -import org.vitrivr.cottontail.client.language.basics.predicate.Predicate import org.vitrivr.cottontail.client.language.ddl.CreateEntity import org.vitrivr.cottontail.client.language.ddl.CreateIndex import org.vitrivr.cottontail.client.language.ddl.OptimizeEntity @@ -16,7 +17,6 @@ import org.vitrivr.cottontail.client.language.dml.Insert import org.vitrivr.cottontail.client.language.dml.Update import org.vitrivr.cottontail.client.language.dql.Query import org.vitrivr.cottontail.grpc.CottontailGrpc -import org.vitrivr.cottontail.grpc.CottontailGrpc.AtomicBooleanPredicate import org.vitrivr.cottontail.test.AbstractClientTest import org.vitrivr.cottontail.test.GrpcTestUtils import org.vitrivr.cottontail.test.GrpcTestUtils.countElements @@ -297,11 +297,18 @@ class DMLServiceTest : AbstractClientTest() { this.client.commit(txId) // this should probably already fail + repeat(4) { - txId = this.client.begin() - val insert = Insert().into(entityName.fqn).value(STRING_COLUMN_NAME, entryString).txId(txId) - this.client.insert(insert) - this.client.commit(txId) + try { + txId = this.client.begin() + val insert = Insert().into(entityName.fqn).value(STRING_COLUMN_NAME, entryString).txId(txId) + this.client.insert(insert) + this.client.commit(txId) + } catch (e: StatusRuntimeException){ + Assertions.assertEquals(Status.Code.INVALID_ARGUMENT, e.status.code) /* Validation exception has INVALID_ARGUMENT code. */ + Assertions.assertThrows(StatusRuntimeException::class.java) { this.client.commit(txId) } /* COMMIT should not longer be possible. */ + this.client.rollback(txId) + } } // in the failed state, deleting the entries does not work (!)