diff --git a/src/integration-test/kotlin/common/Find.kt b/src/integration-test/kotlin/common/Find.kt new file mode 100644 index 0000000..d0de8ea --- /dev/null +++ b/src/integration-test/kotlin/common/Find.kt @@ -0,0 +1,288 @@ +package solutions.bitbadger.documents.common + +import solutions.bitbadger.documents.* +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue + +/** + * Integration tests for the `Find` object + */ +object Find { + + fun allDefault(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals(5, db.conn.findAll(TEST_TABLE).size, "There should have been 5 documents returned") + } + + fun allAscending(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findAll(TEST_TABLE, listOf(Field.named("id"))) + assertEquals(5, docs.size, "There should have been 5 documents returned") + assertEquals( + "five|four|one|three|two", + docs.joinToString("|") { it.id }, + "The documents were not ordered correctly" + ) + } + + fun allDescending(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findAll(TEST_TABLE, listOf(Field.named("id DESC"))) + assertEquals(5, docs.size, "There should have been 5 documents returned") + assertEquals( + "two|three|one|four|five", + docs.joinToString("|") { it.id }, + "The documents were not ordered correctly" + ) + } + + fun allNumOrder(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findAll( + TEST_TABLE, + listOf(Field.named("sub.foo NULLS LAST"), Field.named("n:numValue")) + ) + assertEquals(5, docs.size, "There should have been 5 documents returned") + assertEquals( + "two|four|one|three|five", + docs.joinToString("|") { it.id }, + "The documents were not ordered correctly" + ) + } + + fun allEmpty(db: ThrowawayDatabase) = + assertEquals(0, db.conn.findAll(TEST_TABLE).size, "There should have been no documents returned") + + fun byIdString(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findById(TEST_TABLE, "two") + assertNotNull(doc, "The document should have been returned") + assertEquals("two", doc.id, "An incorrect document was returned") + } + + fun byIdNumber(db: ThrowawayDatabase) { + Configuration.idField = "key" + try { + db.conn.insert(TEST_TABLE, NumIdDocument(18, "howdy")) + val doc = db.conn.findById(TEST_TABLE, 18) + assertNotNull(doc, "The document should have been returned") + } finally { + Configuration.idField = "id" + } + } + + fun byIdNotFound(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertNull( + db.conn.findById(TEST_TABLE, "x"), + "There should have been no document returned" + ) + } + + fun byFieldsMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByFields( + TEST_TABLE, + listOf(Field.any("value", listOf("blue", "purple")), Field.exists("sub")), + FieldMatch.ALL + ) + assertEquals(1, docs.size, "There should have been a document returned") + assertEquals("four", docs[0].id, "The incorrect document was returned") + } + + fun byFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByFields( + TEST_TABLE, + listOf(Field.equal("value", "purple")), + orderBy = listOf(Field.named("id")) + ) + assertEquals(2, docs.size, "There should have been 2 documents returned") + assertEquals("five|four", docs.joinToString("|") { it.id }, "The documents were not ordered correctly") + } + + fun byFieldsMatchNumIn(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByFields(TEST_TABLE, listOf(Field.any("numValue", listOf(2, 4, 6, 8)))) + assertEquals(1, docs.size, "There should have been a document returned") + assertEquals("three", docs[0].id, "The incorrect document was returned") + } + + fun byFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + 0, + db.conn.findByFields(TEST_TABLE, listOf(Field.greater("numValue", 100))).size, + "There should have been no documents returned" + ) + } + + fun byFieldsMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + val docs = + db.conn.findByFields(TEST_TABLE, listOf(Field.inArray("values", TEST_TABLE, listOf("c")))) + assertEquals(2, docs.size, "There should have been two documents returned") + assertTrue(listOf("first", "second").contains(docs[0].id), "An incorrect document was returned (${docs[0].id}") + assertTrue(listOf("first", "second").contains(docs[1].id), "An incorrect document was returned (${docs[1].id}") + } + + fun byFieldsNoMatchInArray(db: ThrowawayDatabase) { + ArrayDocument.testDocuments.forEach { db.conn.insert(TEST_TABLE, it) } + assertEquals( + 0, + db.conn.findByFields( + TEST_TABLE, + listOf(Field.inArray("values", TEST_TABLE, listOf("j"))) + ).size, + "There should have been no documents returned" + ) + } + + fun byContainsMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByContains>(TEST_TABLE, mapOf("value" to "purple")) + assertEquals(2, docs.size, "There should have been 2 documents returned") + assertTrue(listOf("four", "five").contains(docs[0].id), "An incorrect document was returned (${docs[0].id}") + assertTrue(listOf("four", "five").contains(docs[1].id), "An incorrect document was returned (${docs[1].id}") + } + + fun byContainsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByContains>>( + TEST_TABLE, + mapOf("sub" to mapOf("foo" to "green")), + listOf(Field.named("value")) + ) + assertEquals(2, docs.size, "There should have been 2 documents returned") + assertEquals("two|four", docs.joinToString("|") { it.id }, "The documents were not ordered correctly") + } + + fun byContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + 0, + db.conn.findByContains>(TEST_TABLE, mapOf("value" to "indigo")).size, + "There should have been no documents returned" + ) + } + + fun byJsonPathMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + assertEquals(2, docs.size, "There should have been 2 documents returned") + assertTrue(listOf("four", "five").contains(docs[0].id), "An incorrect document was returned (${docs[0].id}") + assertTrue(listOf("four", "five").contains(docs[1].id), "An incorrect document was returned (${docs[1].id}") + } + + fun byJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val docs = db.conn.findByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)", listOf(Field.named("id"))) + assertEquals(2, docs.size, "There should have been 2 documents returned") + assertEquals("five|four", docs.joinToString("|") { it.id }, "The documents were not ordered correctly") + } + + fun byJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertEquals( + 0, + db.conn.findByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)").size, + "There should have been no documents returned" + ) + } + + fun firstByFieldsMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("value", "another"))) + assertNotNull(doc, "There should have been a document returned") + assertEquals("two", doc.id, "The incorrect document was returned") + } + + fun firstByFieldsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green"))) + assertNotNull(doc, "There should have been a document returned") + assertTrue(listOf("two", "four").contains(doc.id), "An incorrect document was returned (${doc.id}") + } + + fun firstByFieldsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("sub.foo", "green")), orderBy = listOf(Field.named("n:numValue DESC"))) + assertNotNull(doc, "There should have been a document returned") + assertEquals("four", doc.id, "An incorrect document was returned") + } + + fun firstByFieldsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertNull( + db.conn.findFirstByFields(TEST_TABLE, listOf(Field.equal("value", "absent"))), + "There should have been no document returned" + ) + } + + fun firstByContainsMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByContains>(TEST_TABLE, mapOf("value" to "FIRST!")) + assertNotNull(doc, "There should have been a document returned") + assertEquals("one", doc.id, "An incorrect document was returned") + } + + fun firstByContainsMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByContains>(TEST_TABLE, mapOf("value" to "purple")) + assertNotNull(doc, "There should have been a document returned") + assertTrue(listOf("four", "five").contains(doc.id), "An incorrect document was returned (${doc.id}") + } + + fun firstByContainsMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByContains>( + TEST_TABLE, + mapOf("value" to "purple"), + listOf(Field.named("sub.bar NULLS FIRST")) + ) + assertNotNull(doc, "There should have been a document returned") + assertEquals("five", doc.id, "An incorrect document was returned") + } + + fun firstByContainsNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertNull( + db.conn.findFirstByContains>( + TEST_TABLE, + mapOf("value" to "indigo") + ), "There should have been no document returned" + ) + } + + fun firstByJsonPathMatchOne(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ == 10)") + assertNotNull(doc, "There should have been a document returned") + assertEquals("two", doc.id, "An incorrect document was returned") + } + + fun firstByJsonPathMatchMany(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 10)") + assertNotNull(doc, "There should have been a document returned") + assertTrue(listOf("four", "five").contains(doc.id), "An incorrect document was returned (${doc.id}") + } + + fun firstByJsonPathMatchOrdered(db: ThrowawayDatabase) { + JsonDocument.load(db) + val doc = db.conn.findFirstByJsonPath( + TEST_TABLE, + "$.numValue ? (@ > 10)", + listOf(Field.named("id DESC")) + ) + assertNotNull(doc, "There should have been a document returned") + assertEquals("four", doc.id, "An incorrect document was returned") + } + + fun firstByJsonPathNoMatch(db: ThrowawayDatabase) { + JsonDocument.load(db) + assertNull(db.conn.findFirstByJsonPath(TEST_TABLE, "$.numValue ? (@ > 100)"), "There should have been no document returned") + } +} diff --git a/src/integration-test/kotlin/postgresql/FindIT.kt b/src/integration-test/kotlin/postgresql/FindIT.kt new file mode 100644 index 0000000..ed72e80 --- /dev/null +++ b/src/integration-test/kotlin/postgresql/FindIT.kt @@ -0,0 +1,172 @@ +package solutions.bitbadger.documents.postgresql + +import org.junit.jupiter.api.DisplayName +import solutions.bitbadger.documents.common.Find +import kotlin.test.Test + +/** + * PostgreSQL integration tests for the `Find` object / `find*` connection extension functions + */ +@DisplayName("PostgreSQL - Find") +class FindIT { + + @Test + @DisplayName("all retrieves all documents") + fun allDefault() = + PgDB().use(Find::allDefault) + + @Test + @DisplayName("all sorts data ascending") + fun allAscending() = + PgDB().use(Find::allAscending) + + @Test + @DisplayName("all sorts data descending") + fun allDescending() = + PgDB().use(Find::allDescending) + + @Test + @DisplayName("all sorts data numerically") + fun allNumOrder() = + PgDB().use(Find::allNumOrder) + + @Test + @DisplayName("all succeeds with an empty table") + fun allEmpty() = + PgDB().use(Find::allEmpty) + + @Test + @DisplayName("byId retrieves a document via a string ID") + fun byIdString() = + PgDB().use(Find::byIdString) + + @Test + @DisplayName("byId retrieves a document via a numeric ID") + fun byIdNumber() = + PgDB().use(Find::byIdNumber) + + @Test + @DisplayName("byId returns null when a matching ID is not found") + fun byIdNotFound() = + PgDB().use(Find::byIdNotFound) + + @Test + @DisplayName("byFields retrieves matching documents") + fun byFieldsMatch() = + PgDB().use(Find::byFieldsMatch) + + @Test + @DisplayName("byFields retrieves ordered matching documents") + fun byFieldsMatchOrdered() = + PgDB().use(Find::byFieldsMatchOrdered) + + @Test + @DisplayName("byFields retrieves matching documents with a numeric IN clause") + fun byFieldsMatchNumIn() = + PgDB().use(Find::byFieldsMatchNumIn) + + @Test + @DisplayName("byFields succeeds when no documents match") + fun byFieldsNoMatch() = + PgDB().use(Find::byFieldsNoMatch) + + @Test + @DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison") + fun byFieldsMatchInArray() = + PgDB().use(Find::byFieldsMatchInArray) + + @Test + @DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison") + fun byFieldsNoMatchInArray() = + PgDB().use(Find::byFieldsNoMatchInArray) + + @Test + @DisplayName("byContains retrieves matching documents") + fun byContainsMatch() = + PgDB().use(Find::byContainsMatch) + + @Test + @DisplayName("byContains retrieves ordered matching documents") + fun byContainsMatchOrdered() = + PgDB().use(Find::byContainsMatchOrdered) + + @Test + @DisplayName("byContains succeeds when no documents match") + fun byContainsNoMatch() = + PgDB().use(Find::byContainsNoMatch) + + @Test + @DisplayName("byJsonPath retrieves matching documents") + fun byJsonPathMatch() = + PgDB().use(Find::byJsonPathMatch) + + @Test + @DisplayName("byJsonPath retrieves ordered matching documents") + fun byJsonPathMatchOrdered() = + PgDB().use(Find::byJsonPathMatchOrdered) + + @Test + @DisplayName("byJsonPath succeeds when no documents match") + fun byJsonPathNoMatch() = + PgDB().use(Find::byJsonPathNoMatch) + + @Test + @DisplayName("firstByFields retrieves a matching document") + fun firstByFieldsMatchOne() = + PgDB().use(Find::firstByFieldsMatchOne) + + @Test + @DisplayName("firstByFields retrieves a matching document among many") + fun firstByFieldsMatchMany() = + PgDB().use(Find::firstByFieldsMatchMany) + + @Test + @DisplayName("firstByFields retrieves a matching document among many (ordered)") + fun firstByFieldsMatchOrdered() = + PgDB().use(Find::firstByFieldsMatchOrdered) + + @Test + @DisplayName("firstByFields returns null when no document matches") + fun firstByFieldsNoMatch() = + PgDB().use(Find::firstByFieldsNoMatch) + + @Test + @DisplayName("firstByContains retrieves a matching document") + fun firstByContainsMatchOne() = + PgDB().use(Find::firstByContainsMatchOne) + + @Test + @DisplayName("firstByContains retrieves a matching document among many") + fun firstByContainsMatchMany() = + PgDB().use(Find::firstByContainsMatchMany) + + @Test + @DisplayName("firstByContains retrieves a matching document among many (ordered)") + fun firstByContainsMatchOrdered() = + PgDB().use(Find::firstByContainsMatchOrdered) + + @Test + @DisplayName("firstByContains returns null when no document matches") + fun firstByContainsNoMatch() = + PgDB().use(Find::firstByContainsNoMatch) + + @Test + @DisplayName("firstByJsonPath retrieves a matching document") + fun firstByJsonPathMatchOne() = + PgDB().use(Find::firstByJsonPathMatchOne) + + @Test + @DisplayName("firstByJsonPath retrieves a matching document among many") + fun firstByJsonPathMatchMany() = + PgDB().use(Find::firstByJsonPathMatchMany) + + @Test + @DisplayName("firstByJsonPath retrieves a matching document among many (ordered)") + fun firstByJsonPathMatchOrdered() = + PgDB().use(Find::firstByJsonPathMatchOrdered) + + @Test + @DisplayName("firstByJsonPath returns null when no document matches") + fun firstByJsonPathNoMatch() = + PgDB().use(Find::firstByJsonPathNoMatch) +} diff --git a/src/integration-test/kotlin/sqlite/FindIT.kt b/src/integration-test/kotlin/sqlite/FindIT.kt new file mode 100644 index 0000000..fbb40b1 --- /dev/null +++ b/src/integration-test/kotlin/sqlite/FindIT.kt @@ -0,0 +1,128 @@ +package solutions.bitbadger.documents.sqlite + +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.assertThrows +import solutions.bitbadger.documents.DocumentException +import solutions.bitbadger.documents.common.Find +import kotlin.test.Test + +/** + * SQLite integration tests for the `Find` object / `find*` connection extension functions + */ +@DisplayName("SQLite - Find") +class FindIT { + + @Test + @DisplayName("all retrieves all documents") + fun allDefault() = + SQLiteDB().use(Find::allDefault) + + @Test + @DisplayName("all sorts data ascending") + fun allAscending() = + SQLiteDB().use(Find::allAscending) + + @Test + @DisplayName("all sorts data descending") + fun allDescending() = + SQLiteDB().use(Find::allDescending) + + @Test + @DisplayName("all sorts data numerically") + fun allNumOrder() = + SQLiteDB().use(Find::allNumOrder) + + @Test + @DisplayName("all succeeds with an empty table") + fun allEmpty() = + SQLiteDB().use(Find::allEmpty) + + @Test + @DisplayName("byId retrieves a document via a string ID") + fun byIdString() = + SQLiteDB().use(Find::byIdString) + + @Test + @DisplayName("byId retrieves a document via a numeric ID") + fun byIdNumber() = + SQLiteDB().use(Find::byIdNumber) + + @Test + @DisplayName("byId returns null when a matching ID is not found") + fun byIdNotFound() = + SQLiteDB().use(Find::byIdNotFound) + + @Test + @DisplayName("byFields retrieves matching documents") + fun byFieldsMatch() = + SQLiteDB().use(Find::byFieldsMatch) + + @Test + @DisplayName("byFields retrieves ordered matching documents") + fun byFieldsMatchOrdered() = + SQLiteDB().use(Find::byFieldsMatchOrdered) + + @Test + @DisplayName("byFields retrieves matching documents with a numeric IN clause") + fun byFieldsMatchNumIn() = + SQLiteDB().use(Find::byFieldsMatchNumIn) + + @Test + @DisplayName("byFields succeeds when no documents match") + fun byFieldsNoMatch() = + SQLiteDB().use(Find::byFieldsNoMatch) + + @Test + @DisplayName("byFields retrieves matching documents with an IN_ARRAY comparison") + fun byFieldsMatchInArray() = + SQLiteDB().use(Find::byFieldsMatchInArray) + + @Test + @DisplayName("byFields succeeds when no documents match an IN_ARRAY comparison") + fun byFieldsNoMatchInArray() = + SQLiteDB().use(Find::byFieldsNoMatchInArray) + + @Test + @DisplayName("byContains fails") + fun byContainsFails() { + assertThrows { SQLiteDB().use(Find::byContainsMatch) } + } + + @Test + @DisplayName("byJsonPath fails") + fun byJsonPathFails() { + assertThrows { SQLiteDB().use(Find::byJsonPathMatch) } + } + + @Test + @DisplayName("firstByFields retrieves a matching document") + fun firstByFieldsMatchOne() = + SQLiteDB().use(Find::firstByFieldsMatchOne) + + @Test + @DisplayName("firstByFields retrieves a matching document among many") + fun firstByFieldsMatchMany() = + SQLiteDB().use(Find::firstByFieldsMatchMany) + + @Test + @DisplayName("firstByFields retrieves a matching document among many (ordered)") + fun firstByFieldsMatchOrdered() = + SQLiteDB().use(Find::firstByFieldsMatchOrdered) + + @Test + @DisplayName("firstByFields returns null when no document matches") + fun firstByFieldsNoMatch() = + SQLiteDB().use(Find::firstByFieldsNoMatch) + + @Test + @DisplayName("firstByContains fails") + fun firstByContainsFails() { + assertThrows { SQLiteDB().use(Find::firstByContainsMatchOne) } + } + + @Test + @DisplayName("firstByJsonPath fails") + fun firstByJsonPathFails() { + assertThrows { SQLiteDB().use(Find::firstByJsonPathMatchOne) } + } +} diff --git a/src/main/kotlin/Op.kt b/src/main/kotlin/Op.kt index 0566f06..61723c9 100644 --- a/src/main/kotlin/Op.kt +++ b/src/main/kotlin/Op.kt @@ -21,7 +21,7 @@ enum class Op(val sql: String) { /** Compare existence in a list of values */ IN("IN"), /** Compare overlap between an array and a list of values */ - IN_ARRAY("?|"), + IN_ARRAY("??|"), /** Compare existence */ EXISTS("IS NOT NULL"), /** Compare nonexistence */ diff --git a/src/main/kotlin/Parameters.kt b/src/main/kotlin/Parameters.kt index 11ff18b..3467ea2 100644 --- a/src/main/kotlin/Parameters.kt +++ b/src/main/kotlin/Parameters.kt @@ -86,6 +86,7 @@ object Parameters { return try { replaceNamesInQuery(query, parameters) + //.also(::println) .let { conn.prepareStatement(it) } .also { stmt -> replacements.sortedBy { it.first } diff --git a/src/test/kotlin/FieldTest.kt b/src/test/kotlin/FieldTest.kt index 56e54f9..5356893 100644 --- a/src/test/kotlin/FieldTest.kt +++ b/src/test/kotlin/FieldTest.kt @@ -233,7 +233,7 @@ class FieldTest { @DisplayName("toWhere generates for inArray (PostgreSQL)") fun toWhereInArrayPostgres() { Configuration.connectionString = ":postgresql:" - assertEquals("data->'even' ?| ARRAY[:it_0, :it_1, :it_2, :it_3]", + assertEquals("data->'even' ??| ARRAY[:it_0, :it_1, :it_2, :it_3]", Field.inArray("even", "tbl", listOf(2, 4, 6, 8), ":it").toWhere(), "Field WHERE clause not generated correctly") } diff --git a/src/test/kotlin/OpTest.kt b/src/test/kotlin/OpTest.kt index f34099b..f30797a 100644 --- a/src/test/kotlin/OpTest.kt +++ b/src/test/kotlin/OpTest.kt @@ -61,7 +61,7 @@ class OpTest { @Test @DisplayName("IN_ARRAY uses proper SQL") fun inArraySQL() { - assertEquals("?|", Op.IN_ARRAY.sql, "The SQL for in-array is incorrect") + assertEquals("??|", Op.IN_ARRAY.sql, "The SQL for in-array is incorrect") } @Test