Initial Development #1
@ -98,4 +98,23 @@ object Parameters {
|
|||||||
throw DocumentException("Error creating query / binding parameters: ${ex.message}", ex)
|
throw DocumentException("Error creating query / binding parameters: ${ex.message}", ex)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create parameters for field names to be removed from a document
|
||||||
|
*
|
||||||
|
* @param names The names of the fields to be removed
|
||||||
|
* @param parameterName The parameter name to use for the query
|
||||||
|
* @return A list of parameters to use for building the query
|
||||||
|
*/
|
||||||
|
fun fieldNames(names: Collection<String>, parameterName: String = ":name") =
|
||||||
|
when (Configuration.dialect("generate field name parameters")) {
|
||||||
|
Dialect.POSTGRESQL -> listOf(Parameter(parameterName, ParameterType.STRING, if (names.size == 1) {
|
||||||
|
names.elementAt(0)
|
||||||
|
} else {
|
||||||
|
names.joinToString(",").let { "{$it}" }
|
||||||
|
}))
|
||||||
|
Dialect.SQLITE -> names.mapIndexed { index, name ->
|
||||||
|
Parameter("$parameterName$index", ParameterType.STRING, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ object Patch {
|
|||||||
* @param tableName The name of the table where the document is stored
|
* @param tableName The name of the table where the document is stored
|
||||||
* @return A query to patch JSON documents by JSON containment
|
* @return A query to patch JSON documents by JSON containment
|
||||||
*/
|
*/
|
||||||
fun <TKey> byContains(tableName: String) =
|
fun byContains(tableName: String) =
|
||||||
statementWhere(patch(tableName), Where.jsonContains())
|
statementWhere(patch(tableName), Where.jsonContains())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,6 +60,6 @@ object Patch {
|
|||||||
* @param tableName The name of the table where the document is stored
|
* @param tableName The name of the table where the document is stored
|
||||||
* @return A query to patch JSON documents by JSON path match
|
* @return A query to patch JSON documents by JSON path match
|
||||||
*/
|
*/
|
||||||
fun <TKey> byJsonPath(tableName: String) =
|
fun byJsonPath(tableName: String) =
|
||||||
statementWhere(patch(tableName), Where.jsonPathMatches())
|
statementWhere(patch(tableName), Where.jsonPathMatches())
|
||||||
}
|
}
|
||||||
|
74
src/main/kotlin/query/RemoveFields.kt
Normal file
74
src/main/kotlin/query/RemoveFields.kt
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
package solutions.bitbadger.documents.query
|
||||||
|
|
||||||
|
import solutions.bitbadger.documents.*
|
||||||
|
import solutions.bitbadger.documents.query.byFields as byFieldsBase
|
||||||
|
import solutions.bitbadger.documents.query.byId as byIdBase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions to create queries to remove fields from documents
|
||||||
|
*/
|
||||||
|
object RemoveFields {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a query to remove fields based on the given parameters
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table in which documents should have fields removed
|
||||||
|
* @param toRemove The parameters for the fields to be removed
|
||||||
|
* @return A query to remove fields from documents in the given table
|
||||||
|
*/
|
||||||
|
private fun removeFields(tableName: String, toRemove: List<Parameter<String>>) =
|
||||||
|
when (Configuration.dialect("generate field removal query")) {
|
||||||
|
Dialect.POSTGRESQL -> "UPDATE $tableName SET data = data - ${toRemove[0].name}::text[]"
|
||||||
|
Dialect.SQLITE -> toRemove.joinToString(", ") { it.name }.let {
|
||||||
|
"UPDATE $tableName SET data = json_remove(data, $it)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query to patch (partially update) a JSON document by its ID
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table where the document is stored
|
||||||
|
* @param toRemove The parameters for the fields to be removed
|
||||||
|
* @param docId The ID of the document to be updated (optional, used for type checking)
|
||||||
|
* @return A query to patch a JSON document by its ID
|
||||||
|
*/
|
||||||
|
fun <TKey> byId(tableName: String, toRemove: List<Parameter<String>>, docId: TKey? = null) =
|
||||||
|
byIdBase(removeFields(tableName, toRemove), docId)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query to patch (partially update) a JSON document using field match criteria
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table where the documents are stored
|
||||||
|
* @param toRemove The parameters for the fields to be removed
|
||||||
|
* @param fields The field criteria
|
||||||
|
* @param howMatched How the fields should be matched (optional, defaults to `ALL`)
|
||||||
|
* @return A query to patch JSON documents by field match criteria
|
||||||
|
*/
|
||||||
|
fun byFields(
|
||||||
|
tableName: String,
|
||||||
|
toRemove: List<Parameter<String>>,
|
||||||
|
fields: Collection<Field<*>>,
|
||||||
|
howMatched: FieldMatch? = null
|
||||||
|
) =
|
||||||
|
byFieldsBase(removeFields(tableName, toRemove), fields, howMatched)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query to patch (partially update) a JSON document by JSON containment (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table where the document is stored
|
||||||
|
* @param toRemove The parameters for the fields to be removed
|
||||||
|
* @return A query to patch JSON documents by JSON containment
|
||||||
|
*/
|
||||||
|
fun byContains(tableName: String, toRemove: List<Parameter<String>>) =
|
||||||
|
statementWhere(removeFields(tableName, toRemove), Where.jsonContains())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A query to patch (partially update) a JSON document by JSON path match (PostgreSQL only)
|
||||||
|
*
|
||||||
|
* @param tableName The name of the table where the document is stored
|
||||||
|
* @param toRemove The parameters for the fields to be removed
|
||||||
|
* @return A query to patch JSON documents by JSON path match
|
||||||
|
*/
|
||||||
|
fun byJsonPath(tableName: String, toRemove: List<Parameter<String>>) =
|
||||||
|
statementWhere(removeFields(tableName, toRemove), Where.jsonPathMatches())
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package solutions.bitbadger.documents
|
package solutions.bitbadger.documents
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -12,6 +13,14 @@ import kotlin.test.assertSame
|
|||||||
@DisplayName("Parameters")
|
@DisplayName("Parameters")
|
||||||
class ParametersTest {
|
class ParametersTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
fun cleanUp() {
|
||||||
|
Configuration.dialectValue = null
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("nameFields works with no changes")
|
@DisplayName("nameFields works with no changes")
|
||||||
fun nameFieldsNoChange() {
|
fun nameFieldsNoChange() {
|
||||||
@ -47,4 +56,54 @@ class ParametersTest {
|
|||||||
assertEquals("SELECT data, data_ext FROM tbl WHERE data = ? AND data_ext = ? AND more_data = ?",
|
assertEquals("SELECT data, data_ext FROM tbl WHERE data = ? AND data_ext = ? AND more_data = ?",
|
||||||
Parameters.replaceNamesInQuery(query, parameters), "Parameters not replaced correctly")
|
Parameters.replaceNamesInQuery(query, parameters), "Parameters not replaced correctly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("fieldNames generates a single parameter (PostgreSQL)")
|
||||||
|
fun fieldNamesSinglePostgres() {
|
||||||
|
Configuration.dialectValue = Dialect.POSTGRESQL
|
||||||
|
val nameParams = Parameters.fieldNames(listOf("test"))
|
||||||
|
assertEquals(1, nameParams.size, "There should be one name parameter")
|
||||||
|
assertEquals(":name", nameParams[0].name, "The parameter name is incorrect")
|
||||||
|
assertEquals(ParameterType.STRING, nameParams[0].type, "The parameter type is incorrect")
|
||||||
|
assertEquals("test", nameParams[0].value, "The parameter value is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("fieldNames generates multiple parameters (PostgreSQL)")
|
||||||
|
fun fieldNamesMultiplePostgres() {
|
||||||
|
Configuration.dialectValue = Dialect.POSTGRESQL
|
||||||
|
val nameParams = Parameters.fieldNames(listOf("test", "this", "today"))
|
||||||
|
assertEquals(1, nameParams.size, "There should be one name parameter")
|
||||||
|
assertEquals(":name", nameParams[0].name, "The parameter name is incorrect")
|
||||||
|
assertEquals(ParameterType.STRING, nameParams[0].type, "The parameter type is incorrect")
|
||||||
|
assertEquals("{test,this,today}", nameParams[0].value, "The parameter value is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("fieldNames generates a single parameter (SQLite)")
|
||||||
|
fun fieldNamesSingleSQLite() {
|
||||||
|
Configuration.dialectValue = Dialect.SQLITE
|
||||||
|
val nameParams = Parameters.fieldNames(listOf("test"))
|
||||||
|
assertEquals(1, nameParams.size, "There should be one name parameter")
|
||||||
|
assertEquals(":name0", nameParams[0].name, "The parameter name is incorrect")
|
||||||
|
assertEquals(ParameterType.STRING, nameParams[0].type, "The parameter type is incorrect")
|
||||||
|
assertEquals("test", nameParams[0].value, "The parameter value is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("fieldNames generates multiple parameters (SQLite)")
|
||||||
|
fun fieldNamesMultipleSQLite() {
|
||||||
|
Configuration.dialectValue = Dialect.SQLITE
|
||||||
|
val nameParams = Parameters.fieldNames(listOf("test", "this", "today"))
|
||||||
|
assertEquals(3, nameParams.size, "There should be one name parameter")
|
||||||
|
assertEquals(":name0", nameParams[0].name, "The first parameter name is incorrect")
|
||||||
|
assertEquals(ParameterType.STRING, nameParams[0].type, "The first parameter type is incorrect")
|
||||||
|
assertEquals("test", nameParams[0].value, "The first parameter value is incorrect")
|
||||||
|
assertEquals(":name1", nameParams[1].name, "The second parameter name is incorrect")
|
||||||
|
assertEquals(ParameterType.STRING, nameParams[1].type, "The second parameter type is incorrect")
|
||||||
|
assertEquals("this", nameParams[1].value, "The second parameter value is incorrect")
|
||||||
|
assertEquals(":name2", nameParams[2].name, "The third parameter name is incorrect")
|
||||||
|
assertEquals(ParameterType.STRING, nameParams[2].type, "The third parameter type is incorrect")
|
||||||
|
assertEquals("today", nameParams[2].value, "The third parameter value is incorrect")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
102
src/test/kotlin/query/DeleteTest.kt
Normal file
102
src/test/kotlin/query/DeleteTest.kt
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package solutions.bitbadger.documents.query
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import solutions.bitbadger.documents.Configuration
|
||||||
|
import solutions.bitbadger.documents.Dialect
|
||||||
|
import solutions.bitbadger.documents.DocumentException
|
||||||
|
import solutions.bitbadger.documents.Field
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the `Delete` object
|
||||||
|
*/
|
||||||
|
@DisplayName("Delete (Query)")
|
||||||
|
class DeleteTest {
|
||||||
|
|
||||||
|
/** Test table name */
|
||||||
|
private val tbl = "test_table"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the connection string (resets Dialect)
|
||||||
|
*/
|
||||||
|
@AfterEach
|
||||||
|
fun cleanUp() {
|
||||||
|
Configuration.dialectValue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byId generates correctly (PostgreSQL)")
|
||||||
|
fun byIdPostgres() {
|
||||||
|
Configuration.dialectValue = Dialect.POSTGRESQL
|
||||||
|
assertEquals(
|
||||||
|
"DELETE FROM $tbl WHERE data->>'id' = :id",
|
||||||
|
Delete.byId<String>(tbl), "Delete query not constructed correctly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byId generates correctly (SQLite)")
|
||||||
|
fun byIdSQLite() {
|
||||||
|
Configuration.dialectValue = Dialect.SQLITE
|
||||||
|
assertEquals(
|
||||||
|
"DELETE FROM $tbl WHERE data->>'id' = :id",
|
||||||
|
Delete.byId<String>(tbl), "Delete query not constructed correctly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byFields generates correctly (PostgreSQL)")
|
||||||
|
fun byFieldsPostgres() {
|
||||||
|
Configuration.dialectValue = Dialect.POSTGRESQL
|
||||||
|
assertEquals(
|
||||||
|
"DELETE FROM $tbl WHERE data->>'a' = :b", Delete.byFields(tbl, listOf(Field.equal("a", "", ":b"))),
|
||||||
|
"Delete query not constructed correctly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byFields generates correctly (SQLite)")
|
||||||
|
fun byFieldsSQLite() {
|
||||||
|
Configuration.dialectValue = Dialect.SQLITE
|
||||||
|
assertEquals(
|
||||||
|
"DELETE FROM $tbl WHERE data->>'a' = :b", Delete.byFields(tbl, listOf(Field.equal("a", "", ":b"))),
|
||||||
|
"Delete query not constructed correctly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byContains generates correctly (PostgreSQL)")
|
||||||
|
fun byContainsPostgres() {
|
||||||
|
Configuration.dialectValue = Dialect.POSTGRESQL
|
||||||
|
assertEquals(
|
||||||
|
"DELETE FROM $tbl WHERE data @> :criteria", Delete.byContains(tbl), "Delete query not constructed correctly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byContains fails (SQLite)")
|
||||||
|
fun byContainsSQLite() {
|
||||||
|
Configuration.dialectValue = Dialect.SQLITE
|
||||||
|
assertThrows<DocumentException> { Delete.byContains(tbl) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byJsonPath generates correctly (PostgreSQL)")
|
||||||
|
fun byJsonPathPostgres() {
|
||||||
|
Configuration.dialectValue = Dialect.POSTGRESQL
|
||||||
|
assertEquals(
|
||||||
|
"DELETE FROM $tbl WHERE jsonb_path_exists(data, :path::jsonpath)", Delete.byJsonPath(tbl),
|
||||||
|
"Delete query not constructed correctly"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("byJsonPath fails (SQLite)")
|
||||||
|
fun byJsonPathSQLite() {
|
||||||
|
Configuration.dialectValue = Dialect.SQLITE
|
||||||
|
assertThrows<DocumentException> { Delete.byJsonPath(tbl) }
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ import solutions.bitbadger.documents.Field
|
|||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unit tests for the `Exists` object
|
* Unit tests for the `Find` object
|
||||||
*/
|
*/
|
||||||
@DisplayName("Find (Query)")
|
@DisplayName("Find (Query)")
|
||||||
class FindTest {
|
class FindTest {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user