Initial Development #1
87
src/integration-test/kotlin/common/Patch.kt
Normal file
87
src/integration-test/kotlin/common/Patch.kt
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package solutions.bitbadger.documents.common
|
||||||
|
|
||||||
|
import solutions.bitbadger.documents.*
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Integration tests for the `Find` object
|
||||||
|
*/
|
||||||
|
object Patch {
|
||||||
|
|
||||||
|
fun byIdMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
db.conn.patchById(TEST_TABLE, "one", mapOf("numValue" to 44))
|
||||||
|
val doc = db.conn.findById<String, JsonDocument>(TEST_TABLE, "one")
|
||||||
|
assertNotNull(doc, "There should have been a document returned")
|
||||||
|
assertEquals("one", doc.id, "An incorrect document was returned")
|
||||||
|
assertEquals(44, doc.numValue, "The document was not patched")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byIdNoMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
assertFalse("Document with ID \"forty-seven\" should not exist") {
|
||||||
|
db.conn.existsById(TEST_TABLE, "forty-seven")
|
||||||
|
}
|
||||||
|
db.conn.patchById(TEST_TABLE, "forty-seven", mapOf("foo" to "green")) // no exception = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byFieldsMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
db.conn.patchByFields(TEST_TABLE, listOf(Field.equal("value", "purple")), mapOf("numValue" to 77))
|
||||||
|
assertEquals(
|
||||||
|
2,
|
||||||
|
db.conn.countByFields(TEST_TABLE, listOf(Field.equal("numValue", 77))),
|
||||||
|
"There should have been 2 documents with numeric value 77"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byFieldsNoMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
val fields = listOf(Field.equal("value", "burgundy"))
|
||||||
|
assertFalse("There should be no documents with value of \"burgundy\"") {
|
||||||
|
db.conn.existsByFields(TEST_TABLE, fields)
|
||||||
|
}
|
||||||
|
db.conn.patchByFields(TEST_TABLE, fields, mapOf("foo" to "green")) // no exception = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byContainsMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
val contains = mapOf("value" to "another")
|
||||||
|
db.conn.patchByContains(TEST_TABLE, contains, mapOf("numValue" to 12))
|
||||||
|
val doc = db.conn.findFirstByContains<JsonDocument, Map<String, String>>(TEST_TABLE, contains)
|
||||||
|
assertNotNull(doc, "There should have been a document returned")
|
||||||
|
assertEquals("two", doc.id, "The incorrect document was returned")
|
||||||
|
assertEquals(12, doc.numValue, "The document was not updated")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byContainsNoMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
val contains = mapOf("value" to "updated")
|
||||||
|
assertFalse("There should be no matching documents") { db.conn.existsByContains(TEST_TABLE, contains) }
|
||||||
|
db.conn.patchByContains(TEST_TABLE, contains, mapOf("sub.foo" to "green")) // no exception = pass
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byJsonPathMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
val path = "$.numValue ? (@ > 10)"
|
||||||
|
db.conn.patchByJsonPath(TEST_TABLE, path, mapOf("value" to "blue"))
|
||||||
|
val docs = db.conn.findByJsonPath<JsonDocument>(TEST_TABLE, path)
|
||||||
|
assertEquals(2, docs.size, "There should have been two documents returned")
|
||||||
|
docs.forEach {
|
||||||
|
assertTrue(listOf("four", "five").contains(it.id), "An incorrect document was returned (${it.id})")
|
||||||
|
assertEquals("blue", it.value, "The value for ID ${it.id} was incorrect")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun byJsonPathNoMatch(db: ThrowawayDatabase) {
|
||||||
|
JsonDocument.load(db)
|
||||||
|
val path = "$.numValue ? (@ > 100)"
|
||||||
|
assertFalse("There should be no documents with numeric values over 100") {
|
||||||
|
db.conn.existsByJsonPath(TEST_TABLE, path)
|
||||||
|
}
|
||||||
|
db.conn.patchByJsonPath(TEST_TABLE, path, mapOf("value" to "blue")) // no exception = pass
|
||||||
|
}
|
||||||
|
}
|
52
src/integration-test/kotlin/postgresql/PatchIT.kt
Normal file
52
src/integration-test/kotlin/postgresql/PatchIT.kt
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package solutions.bitbadger.documents.postgresql
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import solutions.bitbadger.documents.common.Patch
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PostgreSQL integration tests for the `Patch` object / `patchBy*` connection extension functions
|
||||||
|
*/
|
||||||
|
@DisplayName("PostgreSQL - Patch")
|
||||||
|
class PatchIT {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byId patches an existing document")
|
||||||
|
fun byIdMatch() =
|
||||||
|
PgDB().use(Patch::byIdMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byId succeeds for a non-existent document")
|
||||||
|
fun byIdNoMatch() =
|
||||||
|
PgDB().use(Patch::byIdNoMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byFields patches matching document")
|
||||||
|
fun byFieldsMatch() =
|
||||||
|
PgDB().use(Patch::byFieldsMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byFields succeeds when no documents match")
|
||||||
|
fun byFieldsNoMatch() =
|
||||||
|
PgDB().use(Patch::byFieldsNoMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byContains patches matching document")
|
||||||
|
fun byContainsMatch() =
|
||||||
|
PgDB().use(Patch::byContainsMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byContains succeeds when no documents match")
|
||||||
|
fun byContainsNoMatch() =
|
||||||
|
PgDB().use(Patch::byContainsNoMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byJsonPath patches matching document")
|
||||||
|
fun byJsonPathMatch() =
|
||||||
|
PgDB().use(Patch::byJsonPathMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byJsonPath succeeds when no documents match")
|
||||||
|
fun byJsonPathNoMatch() =
|
||||||
|
PgDB().use(Patch::byJsonPathNoMatch)
|
||||||
|
}
|
46
src/integration-test/kotlin/sqlite/PatchIT.kt
Normal file
46
src/integration-test/kotlin/sqlite/PatchIT.kt
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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.Patch
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite integration tests for the `Patch` object / `patchBy*` connection extension functions
|
||||||
|
*/
|
||||||
|
@DisplayName("SQLite - Patch")
|
||||||
|
class PatchIT {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byId patches an existing document")
|
||||||
|
fun byIdMatch() =
|
||||||
|
SQLiteDB().use(Patch::byIdMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byId succeeds for a non-existent document")
|
||||||
|
fun byIdNoMatch() =
|
||||||
|
SQLiteDB().use(Patch::byIdNoMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byFields patches matching document")
|
||||||
|
fun byFieldsMatch() =
|
||||||
|
SQLiteDB().use(Patch::byFieldsMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byFields succeeds when no documents match")
|
||||||
|
fun byFieldsNoMatch() =
|
||||||
|
SQLiteDB().use(Patch::byFieldsNoMatch)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byContains fails")
|
||||||
|
fun byContainsFails() {
|
||||||
|
assertThrows<DocumentException> { SQLiteDB().use(Patch::byContainsMatch) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byJsonPath fails")
|
||||||
|
fun byJsonPathFails() {
|
||||||
|
assertThrows<DocumentException> { SQLiteDB().use(Patch::byJsonPathMatch) }
|
||||||
|
}
|
||||||
|
}
|
@ -302,6 +302,60 @@ inline fun <reified TDoc> Connection.findFirstByJsonPath(
|
|||||||
) =
|
) =
|
||||||
Find.firstByJsonPath<TDoc>(tableName, path, orderBy, this)
|
Find.firstByJsonPath<TDoc>(tableName, path, orderBy, this)
|
||||||
|
|
||||||
|
// ~~~ DOCUMENT PATCH (PARTIAL UPDATE) QUERIES ~~~
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch a document by its ID
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which a document should be patched
|
||||||
|
* @param docId The ID of the document to be patched
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
*/
|
||||||
|
inline fun <TKey, reified TPatch> Connection.patchById(tableName: String, docId: TKey, patch: TPatch) =
|
||||||
|
Patch.byId(tableName, docId, patch, this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a field comparison
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param fields The fields which should be compared
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @param howMatched How the fields should be matched
|
||||||
|
*/
|
||||||
|
inline fun <reified TPatch> Connection.patchByFields(
|
||||||
|
tableName: String,
|
||||||
|
fields: Collection<Field<*>>,
|
||||||
|
patch: TPatch,
|
||||||
|
howMatched: FieldMatch? = null
|
||||||
|
) =
|
||||||
|
Patch.byFields(tableName, fields, patch, howMatched, this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a JSON containment query (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param criteria The object against which JSON containment should be checked
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @throws DocumentException If called on a SQLite connection
|
||||||
|
*/
|
||||||
|
inline fun <reified TContains, reified TPatch> Connection.patchByContains(
|
||||||
|
tableName: String,
|
||||||
|
criteria: TContains,
|
||||||
|
patch: TPatch
|
||||||
|
) =
|
||||||
|
Patch.byContains(tableName, criteria, patch, this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a JSON Path match query (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param path The JSON path comparison to match
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @throws DocumentException If called on a SQLite connection
|
||||||
|
*/
|
||||||
|
inline fun <reified TPatch> Connection.patchByJsonPath(tableName: String, path: String, patch: TPatch) =
|
||||||
|
Patch.byJsonPath(tableName, path, patch, this)
|
||||||
|
|
||||||
// ~~~ DOCUMENT DELETION QUERIES ~~~
|
// ~~~ DOCUMENT DELETION QUERIES ~~~
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
135
src/main/kotlin/Patch.kt
Normal file
135
src/main/kotlin/Patch.kt
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package solutions.bitbadger.documents
|
||||||
|
|
||||||
|
import solutions.bitbadger.documents.query.Patch
|
||||||
|
import java.sql.Connection
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions to patch (partially update) documents
|
||||||
|
*/
|
||||||
|
object Patch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch a document by its ID
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which a document should be patched
|
||||||
|
* @param docId The ID of the document to be patched
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @param conn The connection on which the update should be executed
|
||||||
|
*/
|
||||||
|
inline fun <TKey, reified TPatch> byId(tableName: String, docId: TKey, patch: TPatch, conn: Connection) =
|
||||||
|
conn.customNonQuery(
|
||||||
|
Patch.byId(tableName, docId),
|
||||||
|
Parameters.addFields(
|
||||||
|
listOf(Field.equal(Configuration.idField, docId, ":id")),
|
||||||
|
mutableListOf(Parameters.json(":data", patch))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch a document by its ID
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which a document should be patched
|
||||||
|
* @param docId The ID of the document to be patched
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
*/
|
||||||
|
inline fun <TKey, reified TPatch> byId(tableName: String, docId: TKey, patch: TPatch) =
|
||||||
|
Configuration.dbConn().use { byId(tableName, docId, patch, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a field comparison
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param fields The fields which should be compared
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @param howMatched How the fields should be matched
|
||||||
|
* @param conn The connection on which the update should be executed
|
||||||
|
*/
|
||||||
|
inline fun <reified TPatch> byFields(
|
||||||
|
tableName: String,
|
||||||
|
fields: Collection<Field<*>>,
|
||||||
|
patch: TPatch,
|
||||||
|
howMatched: FieldMatch? = null,
|
||||||
|
conn: Connection
|
||||||
|
) {
|
||||||
|
val named = Parameters.nameFields(fields)
|
||||||
|
conn.customNonQuery(
|
||||||
|
Patch.byFields(tableName, named, howMatched), Parameters.addFields(
|
||||||
|
named,
|
||||||
|
mutableListOf(Parameters.json(":data", patch))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a field comparison
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param fields The fields which should be compared
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @param howMatched How the fields should be matched
|
||||||
|
*/
|
||||||
|
inline fun <reified TPatch> byFields(
|
||||||
|
tableName: String,
|
||||||
|
fields: Collection<Field<*>>,
|
||||||
|
patch: TPatch,
|
||||||
|
howMatched: FieldMatch? = null
|
||||||
|
) =
|
||||||
|
Configuration.dbConn().use { byFields(tableName, fields, patch, howMatched, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a JSON containment query (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param criteria The object against which JSON containment should be checked
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @param conn The connection on which the update should be executed
|
||||||
|
* @throws DocumentException If called on a SQLite connection
|
||||||
|
*/
|
||||||
|
inline fun <reified TContains, reified TPatch> byContains(
|
||||||
|
tableName: String,
|
||||||
|
criteria: TContains,
|
||||||
|
patch: TPatch,
|
||||||
|
conn: Connection
|
||||||
|
) =
|
||||||
|
conn.customNonQuery(
|
||||||
|
Patch.byContains(tableName),
|
||||||
|
listOf(Parameters.json(":criteria", criteria), Parameters.json(":data", patch))
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a JSON containment query (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param criteria The object against which JSON containment should be checked
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @throws DocumentException If called on a SQLite connection
|
||||||
|
*/
|
||||||
|
inline fun <reified TContains, reified TPatch> byContains(tableName: String, criteria: TContains, patch: TPatch) =
|
||||||
|
Configuration.dbConn().use { byContains(tableName, criteria, patch, it) }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a JSON Path match query (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param path The JSON path comparison to match
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @param conn The connection on which the update should be executed
|
||||||
|
* @throws DocumentException If called on a SQLite connection
|
||||||
|
*/
|
||||||
|
inline fun <reified TPatch> byJsonPath(tableName: String, path: String, patch: TPatch, conn: Connection) =
|
||||||
|
conn.customNonQuery(
|
||||||
|
Patch.byJsonPath(tableName),
|
||||||
|
listOf(Parameter(":path", ParameterType.STRING, path), Parameters.json(":data", patch))
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patch documents using a JSON Path match query (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should be patched
|
||||||
|
* @param path The JSON path comparison to match
|
||||||
|
* @param patch The object whose properties should be replaced in the document
|
||||||
|
* @throws DocumentException If called on a SQLite connection
|
||||||
|
*/
|
||||||
|
inline fun <reified TPatch> byJsonPath(tableName: String, path: String, patch: TPatch) =
|
||||||
|
Configuration.dbConn().use { byJsonPath(tableName, path, patch, it) }
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user