Add patch functions
This commit is contained in:
parent
32f8db6196
commit
d0375d14fc
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)
|
||||
|
||||
// ~~~ 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 ~~~
|
||||
|
||||
/**
|
||||
|
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