Initial Development #1

Merged
danieljsummers merged 88 commits from v1-rc into main 2025-04-16 01:29:20 +00:00
5 changed files with 157 additions and 10 deletions
Showing only changes of commit 97be70cfaf - Show all commits

View File

@ -1,8 +1,7 @@
package solutions.bitbadger.documents.common package solutions.bitbadger.documents.common
import solutions.bitbadger.documents.* import solutions.bitbadger.documents.*
import kotlin.test.assertEquals import kotlin.test.*
import kotlin.test.fail
/** /**
* Integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions * Integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions
@ -85,4 +84,44 @@ object Document {
Configuration.idStringLength = 16 Configuration.idStringLength = 16
} }
} }
fun saveMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.save(TEST_TABLE, JsonDocument("two", numValue = 44))
val doc = db.conn.findById<String, JsonDocument>(TEST_TABLE, "two")
assertNotNull(doc, "There should have been a document returned")
assertEquals("two", doc.id, "An incorrect document was returned")
assertEquals("", doc.value, "The \"value\" field was not updated")
assertEquals(44, doc.numValue, "The \"numValue\" field was not updated")
assertNull(doc.sub, "The \"sub\" field was not updated")
}
fun saveNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.save(TEST_TABLE, JsonDocument("test", sub = SubDocument("a", "b")))
assertNotNull(
db.conn.findById<String, JsonDocument>(TEST_TABLE, "test"),
"The test document should have been saved"
)
}
fun updateMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
db.conn.update(TEST_TABLE, "one", JsonDocument("one", "howdy", 8, SubDocument("y", "z")))
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("howdy", doc.value, "The \"value\" field was not updated")
assertEquals(8, doc.numValue, "The \"numValue\" field was not updated")
assertNotNull(doc.sub, "The sub-document should not be null")
assertEquals("y", doc.sub.foo, "The sub-document \"foo\" field was not updated")
assertEquals("z", doc.sub.bar, "The sub-document \"bar\" field was not updated")
}
fun updateNoMatch(db: ThrowawayDatabase) {
JsonDocument.load(db)
assertFalse { db.conn.existsById(TEST_TABLE, "two-hundred") }
db.conn.update(TEST_TABLE, "two-hundred", JsonDocument("two-hundred", numValue = 200))
assertFalse { db.conn.existsById(TEST_TABLE, "two-hundred") }
}
} }

View File

@ -34,4 +34,24 @@ class DocumentIT {
@DisplayName("insert succeeds with random string auto ID") @DisplayName("insert succeeds with random string auto ID")
fun insertStringAutoId() = fun insertStringAutoId() =
PgDB().use(Document::insertStringAutoId) PgDB().use(Document::insertStringAutoId)
@Test
@DisplayName("save updates an existing document")
fun saveMatch() =
PgDB().use(Document::saveMatch)
@Test
@DisplayName("save inserts a new document")
fun saveNoMatch() =
PgDB().use(Document::saveNoMatch)
@Test
@DisplayName("update replaces an existing document")
fun updateMatch() =
PgDB().use(Document::updateMatch)
@Test
@DisplayName("update succeeds when no document exists")
fun updateNoMatch() =
PgDB().use(Document::updateNoMatch)
} }

View File

@ -34,4 +34,24 @@ class DocumentIT {
@DisplayName("insert succeeds with random string auto ID") @DisplayName("insert succeeds with random string auto ID")
fun insertStringAutoId() = fun insertStringAutoId() =
SQLiteDB().use(Document::insertStringAutoId) SQLiteDB().use(Document::insertStringAutoId)
@Test
@DisplayName("save updates an existing document")
fun saveMatch() =
SQLiteDB().use(Document::saveMatch)
@Test
@DisplayName("save inserts a new document")
fun saveNoMatch() =
SQLiteDB().use(Document::saveNoMatch)
@Test
@DisplayName("update replaces an existing document")
fun updateMatch() =
SQLiteDB().use(Document::updateMatch)
@Test
@DisplayName("update succeeds when no document exists")
fun updateNoMatch() =
SQLiteDB().use(Document::updateNoMatch)
} }

View File

@ -93,6 +93,25 @@ fun Connection.ensureDocumentIndex(tableName: String, indexType: DocumentIndex)
inline fun <reified TDoc> Connection.insert(tableName: String, document: TDoc) = inline fun <reified TDoc> Connection.insert(tableName: String, document: TDoc) =
Document.insert(tableName, document, this) Document.insert(tableName, document, this)
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
*/
inline fun <reified TDoc> Connection.save(tableName: String, document: TDoc) =
Document.save(tableName, document, this)
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
*/
inline fun <TKey, reified TDoc> Connection.update(tableName: String, docId: TKey, document: TDoc) =
Document.update(tableName, docId, document, this)
// ~~~ DOCUMENT COUNT QUERIES ~~~ // ~~~ DOCUMENT COUNT QUERIES ~~~
/** /**

View File

@ -2,6 +2,8 @@ package solutions.bitbadger.documents
import java.sql.Connection import java.sql.Connection
import solutions.bitbadger.documents.query.Document import solutions.bitbadger.documents.query.Document
import solutions.bitbadger.documents.query.Where
import solutions.bitbadger.documents.query.statementWhere
/** /**
* Functions for manipulating documents * Functions for manipulating documents
@ -26,7 +28,8 @@ object Document {
when (dialect) { when (dialect) {
Dialect.POSTGRESQL -> Dialect.POSTGRESQL ->
when (strategy) { when (strategy) {
AutoId.NUMBER -> "' || (SELECT coalesce(max(data->>'$idField')::numeric, 0) + 1 FROM $tableName) || '" AutoId.NUMBER -> "' || (SELECT coalesce(max(data->>'$idField')::numeric, 0) + 1 " +
"FROM $tableName) || '"
AutoId.UUID -> "\"${AutoId.generateUUID()}\"" AutoId.UUID -> "\"${AutoId.generateUUID()}\""
AutoId.RANDOM_STRING -> "\"${AutoId.generateRandomString()}\"" AutoId.RANDOM_STRING -> "\"${AutoId.generateRandomString()}\""
else -> "\"' || (:data)->>'$idField' || '\"" else -> "\"' || (:data)->>'$idField' || '\""
@ -57,4 +60,50 @@ object Document {
*/ */
inline fun <reified TDoc> insert(tableName: String, document: TDoc) = inline fun <reified TDoc> insert(tableName: String, document: TDoc) =
Configuration.dbConn().use { insert(tableName, document, it) } Configuration.dbConn().use { insert(tableName, document, it) }
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
* @param conn The connection on which the query should be executed
*/
inline fun <reified TDoc> save(tableName: String, document: TDoc, conn: Connection) =
conn.customNonQuery(Document.save(tableName), listOf(Parameters.json(":data", document)))
/**
* Save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
*
* @param tableName The table in which the document should be saved (may include schema)
* @param document The document to be saved
*/
inline fun <reified TDoc> save(tableName: String, document: TDoc) =
Configuration.dbConn().use { save(tableName, document, it) }
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
* @param conn The connection on which the query should be executed
*/
inline fun <TKey, reified TDoc> update(tableName: String, docId: TKey, document: TDoc, conn: Connection) =
conn.customNonQuery(
statementWhere(Document.update(tableName), Where.byId(":id", docId)),
Parameters.addFields(
listOf(Field.equal(Configuration.idField, docId, ":id")),
mutableListOf(Parameters.json(":data", document))
)
)
/**
* Update (replace) a document by its ID
*
* @param tableName The table in which the document should be replaced (may include schema)
* @param docId The ID of the document to be replaced
* @param document The document to be replaced
*/
inline fun <TKey, reified TDoc> update(tableName: String, docId: TKey, document: TDoc) =
Configuration.dbConn().use { update(tableName, docId, document, it) }
} }