Initial Development #1
							
								
								
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								pom.xml
									
									
									
									
									
								
							| @ -107,6 +107,12 @@ | |||||||
|             <artifactId>kotlinx-serialization-json</artifactId> |             <artifactId>kotlinx-serialization-json</artifactId> | ||||||
|             <version>${serialization.version}</version> |             <version>${serialization.version}</version> | ||||||
|         </dependency> |         </dependency> | ||||||
|  |         <dependency> | ||||||
|  |             <groupId>org.xerial</groupId> | ||||||
|  |             <artifactId>sqlite-jdbc</artifactId> | ||||||
|  |             <version>3.46.1.2</version> | ||||||
|  |             <scope>integration-test</scope> | ||||||
|  |         </dependency> | ||||||
|     </dependencies> |     </dependencies> | ||||||
| 
 | 
 | ||||||
| </project> | </project> | ||||||
| @ -1,43 +1,103 @@ | |||||||
| package solutions.bitbadger.documents | package solutions.bitbadger.documents | ||||||
| 
 | 
 | ||||||
| import kotlinx.serialization.Serializable |  | ||||||
| import org.junit.jupiter.api.AfterEach |  | ||||||
| import org.junit.jupiter.api.BeforeEach |  | ||||||
| import org.junit.jupiter.api.DisplayName | import org.junit.jupiter.api.DisplayName | ||||||
| import org.junit.jupiter.api.Test | import solutions.bitbadger.documents.query.Count | ||||||
| import solutions.bitbadger.documents.query.Definition |  | ||||||
| import solutions.bitbadger.documents.query.Find | import solutions.bitbadger.documents.query.Find | ||||||
|  | import kotlin.test.Test | ||||||
| import kotlin.test.assertEquals | import kotlin.test.assertEquals | ||||||
|  | import kotlin.test.assertNotNull | ||||||
|  | import kotlin.test.assertNull | ||||||
| 
 | 
 | ||||||
| 
 | /** | ||||||
| class CustomSQLiteIT { |  * SQLite integration tests for the `Custom` object / `custom*` connection extension functions | ||||||
| 
 |  | ||||||
|     private val tbl = "test_table"; |  | ||||||
| 
 |  | ||||||
|     @BeforeEach |  | ||||||
|     fun setUp() { |  | ||||||
|         Configuration.connectionString = "jdbc:sqlite:memory" |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * Reset the dialect |  | ||||||
|  */ |  */ | ||||||
|     @AfterEach | @DisplayName("SQLite - Custom") | ||||||
|     fun cleanUp() { | class CustomSQLiteIT { | ||||||
|         Configuration.dialectValue = null |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     @Test |     @Test | ||||||
|     @DisplayName("list succeeds with empty list") |     @DisplayName("list succeeds with empty list") | ||||||
|     fun listEmpty() { |     fun listEmpty() = | ||||||
|         Configuration.dbConn().use { conn -> |         SQLiteDB().use { db -> | ||||||
|             conn.customNonQuery(Definition.ensureTable(tbl), listOf()) |             JsonDocument.load(db.conn, SQLiteDB.tableName) | ||||||
|             conn.customNonQuery(Definition.ensureKey(tbl, Dialect.SQLITE), listOf()) |             db.conn.customNonQuery("DELETE FROM ${SQLiteDB.tableName}") | ||||||
|             val result = conn.customList<TestDocument>(Find.all(tbl), listOf(), Results::fromData) |             val result = db.conn.customList<JsonDocument>(Find.all(SQLiteDB.tableName), mapFunc = Results::fromData) | ||||||
|             assertEquals(0, result.size, "There should have been no results") |             assertEquals(0, result.size, "There should have been no results") | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("list succeeds with a non-empty list") | ||||||
|  |     fun listAll() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             JsonDocument.load(db.conn, SQLiteDB.tableName) | ||||||
|  |             val result = db.conn.customList<JsonDocument>(Find.all(SQLiteDB.tableName), mapFunc = Results::fromData) | ||||||
|  |             assertEquals(5, result.size, "There should have been 5 results") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("single succeeds when document not found") | ||||||
|  |     fun singleNone() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             assertNull( | ||||||
|  |                 db.conn.customSingle(Find.all(SQLiteDB.tableName), mapFunc = Results::fromData), | ||||||
|  |                 "There should not have been a document returned" | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("single succeeds when a document is found") | ||||||
|  |     fun singleOne() { | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             JsonDocument.load(db.conn, SQLiteDB.tableName) | ||||||
|  |             assertNotNull( | ||||||
|  |                 db.conn.customSingle<JsonDocument>(Find.all(SQLiteDB.tableName), mapFunc = Results::fromData), | ||||||
|  |                 "There should not have been a document returned" | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("nonQuery makes changes") | ||||||
|  |     fun nonQueryChanges() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             JsonDocument.load(db.conn, SQLiteDB.tableName) | ||||||
|  |             assertEquals( | ||||||
|  |                 5L, db.conn.customScalar(Count.all(SQLiteDB.tableName), mapFunc = Results::toCount), | ||||||
|  |                 "There should have been 5 documents in the table" | ||||||
|  |             ) | ||||||
|  |             db.conn.customNonQuery("DELETE FROM ${SQLiteDB.tableName}") | ||||||
|  |             assertEquals( | ||||||
|  |                 0L, db.conn.customScalar(Count.all(SQLiteDB.tableName), mapFunc = Results::toCount), | ||||||
|  |                 "There should have been no documents in the table" | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("nonQuery makes no changes when where clause matches nothing") | ||||||
|  |     fun nonQueryNoChanges() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             JsonDocument.load(db.conn, SQLiteDB.tableName) | ||||||
|  |             assertEquals( | ||||||
|  |                 5L, db.conn.customScalar(Count.all(SQLiteDB.tableName), mapFunc = Results::toCount), | ||||||
|  |                 "There should have been 5 documents in the table" | ||||||
|  |             ) | ||||||
|  |             db.conn.customNonQuery( | ||||||
|  |                 "DELETE FROM ${SQLiteDB.tableName} WHERE data->>'id' = :id", | ||||||
|  |                 listOf(Parameter(":id", ParameterType.STRING, "eighty-two")) | ||||||
|  |             ) | ||||||
|  |             assertEquals( | ||||||
|  |                 5L, db.conn.customScalar(Count.all(SQLiteDB.tableName), mapFunc = Results::toCount), | ||||||
|  |                 "There should still have been 5 documents in the table" | ||||||
|  |             ) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("scalar succeeds") | ||||||
|  |     fun scalar() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             assertEquals( | ||||||
|  |                 3L, | ||||||
|  |                 db.conn.customScalar("SELECT 3 AS it FROM ${SQLiteDB.catalog} LIMIT 1", mapFunc = Results::toCount), | ||||||
|  |                 "The number 3 should have been returned" | ||||||
|  |             ) | ||||||
|         } |         } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| @Serializable |  | ||||||
| data class TestDocument(val id: String) |  | ||||||
|  | |||||||
							
								
								
									
										45
									
								
								src/integration-test/kotlin/DefinitionSQLiteIT.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/integration-test/kotlin/DefinitionSQLiteIT.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.DisplayName | ||||||
|  | import java.sql.Connection | ||||||
|  | import kotlin.test.Test | ||||||
|  | import kotlin.test.assertFalse | ||||||
|  | import kotlin.test.assertTrue | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SQLite integration tests for the `Definition` object / `ensure*` connection extension functions | ||||||
|  |  */ | ||||||
|  | @DisplayName("SQLite - Definition") | ||||||
|  | class DefinitionSQLiteIT { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Determine if a database item exists | ||||||
|  |      * | ||||||
|  |      * @param item The items whose existence should be checked | ||||||
|  |      * @param conn The current database connection | ||||||
|  |      * @return True if the item exists in the given database, false if not | ||||||
|  |      */ | ||||||
|  |     private fun itExists(item: String, conn: Connection) = | ||||||
|  |         conn.customScalar("SELECT EXISTS (SELECT 1 FROM ${SQLiteDB.catalog} WHERE name = :name) AS it", | ||||||
|  |             listOf(Parameter(":name", ParameterType.STRING, item)), Results::toExists) | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("ensureTable creates table and index") | ||||||
|  |     fun ensureTable() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             assertFalse(itExists("ensured", db.conn), "The 'ensured' table should not exist") | ||||||
|  |             assertFalse(itExists("idx_ensured_key", db.conn), "The PK index for the 'ensured' table should not exist") | ||||||
|  |             db.conn.ensureTable("ensured") | ||||||
|  |             assertTrue(itExists("ensured", db.conn), "The 'ensured' table should exist") | ||||||
|  |             assertTrue(itExists("idx_ensured_key", db.conn), "The PK index for the 'ensured' table should now exist") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("ensureFieldIndex creates an index") | ||||||
|  |     fun ensureFieldIndex() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             assertFalse(itExists("idx_${SQLiteDB.tableName}_test", db.conn), "The test index should not exist") | ||||||
|  |             db.conn.ensureFieldIndex(SQLiteDB.tableName, "test", listOf("id", "category")) | ||||||
|  |             assertTrue(itExists("idx_${SQLiteDB.tableName}_test", db.conn), "The test index should now exist") | ||||||
|  |         } | ||||||
|  | } | ||||||
							
								
								
									
										66
									
								
								src/integration-test/kotlin/DocumentSQLiteIT.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/integration-test/kotlin/DocumentSQLiteIT.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import org.junit.jupiter.api.DisplayName | ||||||
|  | import kotlin.test.Test | ||||||
|  | import kotlin.test.assertEquals | ||||||
|  | import kotlin.test.fail | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * SQLite integration tests for the `Document` object / `insert`, `save`, `update` connection extension functions | ||||||
|  |  */ | ||||||
|  | @DisplayName("SQLite - Document") | ||||||
|  | class DocumentSQLiteIT { | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("insert works with default values") | ||||||
|  |     fun insertDefault() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             assertEquals(0L, db.conn.countAll(SQLiteDB.tableName), "There should be no documents in the table") | ||||||
|  |             val doc = JsonDocument("turkey", "", 0, SubDocument("gobble", "gobble")) | ||||||
|  |             db.conn.insert(SQLiteDB.tableName, doc) | ||||||
|  |             val after = db.conn.findAll<JsonDocument>(SQLiteDB.tableName) | ||||||
|  |             assertEquals(1, after.size, "There should be one document in the table") | ||||||
|  |             assertEquals(doc, after[0], "The document should be what was inserted") | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("insert fails with duplicate key") | ||||||
|  |     fun insertDupe() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             db.conn.insert(SQLiteDB.tableName, JsonDocument("a", "", 0, null)) | ||||||
|  |             try { | ||||||
|  |                 db.conn.insert(SQLiteDB.tableName, JsonDocument("a", "b", 22, null)) | ||||||
|  |                 fail("Inserting a document with a duplicate key should have thrown an exception") | ||||||
|  |             } catch (_: Exception) { | ||||||
|  |                 // yay | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     @Test | ||||||
|  |     @DisplayName("insert succeeds with numeric auto IDs") | ||||||
|  |     fun insertNumAutoId() = | ||||||
|  |         SQLiteDB().use { db -> | ||||||
|  |             try { | ||||||
|  |                 Configuration.autoIdStrategy = AutoId.NUMBER | ||||||
|  |                 Configuration.idField = "key" | ||||||
|  |                 assertEquals(0L, db.conn.countAll(SQLiteDB.tableName), "There should be no documents in the table") | ||||||
|  | 
 | ||||||
|  |                 db.conn.insert(SQLiteDB.tableName, NumIdDocument(0, "one")) | ||||||
|  |                 db.conn.insert(SQLiteDB.tableName, NumIdDocument(0, "two")) | ||||||
|  |                 db.conn.insert(SQLiteDB.tableName, NumIdDocument(77, "three")) | ||||||
|  |                 db.conn.insert(SQLiteDB.tableName, NumIdDocument(0, "four")) | ||||||
|  | 
 | ||||||
|  |                 val after = db.conn.findAll<NumIdDocument>(SQLiteDB.tableName, listOf(Field.named("key"))) | ||||||
|  |                 assertEquals(4, after.size, "There should have been 4 documents returned") | ||||||
|  |                 assertEquals( | ||||||
|  |                     "1|2|77|78", after.joinToString("|") { it.key.toString() }, | ||||||
|  |                     "The IDs were not generated correctly" | ||||||
|  |                 ) | ||||||
|  |             } finally { | ||||||
|  |                 Configuration.autoIdStrategy = AutoId.DISABLED | ||||||
|  |                 Configuration.idField = "id" | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     // TODO: UUID, Random String | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								src/integration-test/kotlin/SQLiteDB.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/integration-test/kotlin/SQLiteDB.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import java.io.File | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * A wrapper for a throwaway SQLite database | ||||||
|  |  */ | ||||||
|  | class SQLiteDB : AutoCloseable { | ||||||
|  | 
 | ||||||
|  |     private var dbName = ""; | ||||||
|  | 
 | ||||||
|  |     init { | ||||||
|  |         dbName = "test-db-${AutoId.generateRandomString(8)}.db" | ||||||
|  |         Configuration.connectionString = "jdbc:sqlite:$dbName" | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     val conn = Configuration.dbConn() | ||||||
|  | 
 | ||||||
|  |     init { | ||||||
|  |         conn.ensureTable(tableName) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     override fun close() { | ||||||
|  |         conn.close() | ||||||
|  |         File(dbName).delete() | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     companion object { | ||||||
|  | 
 | ||||||
|  |         /** The catalog table for SQLite's schema */ | ||||||
|  |         val catalog = "sqlite_master" | ||||||
|  | 
 | ||||||
|  |         /** The table used for test documents */ | ||||||
|  |         val tableName = "test_table" | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								src/integration-test/kotlin/Types.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/integration-test/kotlin/Types.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,40 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import kotlinx.serialization.Serializable | ||||||
|  | import java.sql.Connection | ||||||
|  | 
 | ||||||
|  | @Serializable | ||||||
|  | data class NumIdDocument(val key: Int, val text: String) | ||||||
|  | 
 | ||||||
|  | @Serializable | ||||||
|  | data class SubDocument(val foo: String, val bar: String) | ||||||
|  | 
 | ||||||
|  | @Serializable | ||||||
|  | data class ArrayDocument(val id: String, val values: List<String>) { | ||||||
|  |     companion object { | ||||||
|  |         /** A set of documents used for integration tests */ | ||||||
|  |         val testDocuments = listOf( | ||||||
|  |             ArrayDocument("first", listOf("a", "b", "c" )), | ||||||
|  |             ArrayDocument("second", listOf("c", "d", "e")), | ||||||
|  |             ArrayDocument("third", listOf("x", "y", "z"))) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @Serializable | ||||||
|  | data class JsonDocument(val id: String, val value: String, val numValue: Int, val sub: SubDocument?) { | ||||||
|  |     companion object { | ||||||
|  |         /** An empty JsonDocument */ | ||||||
|  |         val emptyDoc = JsonDocument("", "", 0, null) | ||||||
|  | 
 | ||||||
|  |         /** Documents to use for testing */ | ||||||
|  |         val testDocuments = listOf( | ||||||
|  |             JsonDocument("one",   "FIRST!",   0, null), | ||||||
|  |             JsonDocument("two",   "another", 10, SubDocument("green", "blue")), | ||||||
|  |             JsonDocument("three", "",         4, null), | ||||||
|  |             JsonDocument("four",  "purple",  17, SubDocument("green", "red")), | ||||||
|  |             JsonDocument("five",  "purple",  18, null)) | ||||||
|  | 
 | ||||||
|  |         fun load(conn: Connection, tableName: String = "test_table") = | ||||||
|  |             testDocuments.forEach { conn.insert(tableName, it) } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -3,6 +3,8 @@ package solutions.bitbadger.documents | |||||||
| import java.sql.Connection | import java.sql.Connection | ||||||
| import java.sql.ResultSet | import java.sql.ResultSet | ||||||
| 
 | 
 | ||||||
|  | // ~~~ CUSTOM QUERIES ~~~ | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Execute a query that returns a list of results |  * Execute a query that returns a list of results | ||||||
|  * |  * | ||||||
| @ -12,7 +14,7 @@ import java.sql.ResultSet | |||||||
|  * @return A list of results for the given query |  * @return A list of results for the given query | ||||||
|  */ |  */ | ||||||
| inline fun <reified TDoc> Connection.customList( | inline fun <reified TDoc> Connection.customList( | ||||||
|     query: String, parameters: Collection<Parameter<*>>, mapFunc: (ResultSet) -> TDoc |     query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc | ||||||
| ) = Custom.list(query, parameters, this, mapFunc) | ) = Custom.list(query, parameters, this, mapFunc) | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -24,7 +26,7 @@ inline fun <reified TDoc> Connection.customList( | |||||||
|  * @return The document if one matches the query, `null` otherwise |  * @return The document if one matches the query, `null` otherwise | ||||||
|  */ |  */ | ||||||
| inline fun <reified TDoc> Connection.customSingle( | inline fun <reified TDoc> Connection.customSingle( | ||||||
|     query: String, parameters: Collection<Parameter<*>>, mapFunc: (ResultSet) -> TDoc |     query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc | ||||||
| ) = Custom.single(query, parameters, this, mapFunc) | ) = Custom.single(query, parameters, this, mapFunc) | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -33,7 +35,7 @@ inline fun <reified TDoc> Connection.customSingle( | |||||||
|  * @param query The query to retrieve the results |  * @param query The query to retrieve the results | ||||||
|  * @param parameters Parameters to use for the query |  * @param parameters Parameters to use for the query | ||||||
|  */ |  */ | ||||||
| fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*>>) = | fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) = | ||||||
|     Custom.nonQuery(query, parameters, this) |     Custom.nonQuery(query, parameters, this) | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -46,6 +48,69 @@ fun Connection.customNonQuery(query: String, parameters: Collection<Parameter<*> | |||||||
|  */ |  */ | ||||||
| inline fun <reified T> Connection.customScalar( | inline fun <reified T> Connection.customScalar( | ||||||
|     query: String, |     query: String, | ||||||
|     parameters: Collection<Parameter<*>>, |     parameters: Collection<Parameter<*>> = listOf(), | ||||||
|     mapFunc: (ResultSet) -> T & Any |     mapFunc: (ResultSet) -> T & Any | ||||||
| ) = Custom.scalar(query, parameters, this, mapFunc) | ) = Custom.scalar(query, parameters, this, mapFunc) | ||||||
|  | 
 | ||||||
|  | // ~~~ DEFINITION QUERIES ~~~ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Create a document table if necessary | ||||||
|  |  * | ||||||
|  |  * @param tableName The table whose existence should be ensured (may include schema) | ||||||
|  |  */ | ||||||
|  | fun Connection.ensureTable(tableName: String) = | ||||||
|  |     Definition.ensureTable(tableName, this) | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Create an index on field(s) within documents in the specified table if necessary | ||||||
|  |  * | ||||||
|  |  * @param tableName The table to be indexed (may include schema) | ||||||
|  |  * @param indexName The name of the index to create | ||||||
|  |  * @param fields One or more fields to be indexed< | ||||||
|  |  */ | ||||||
|  | fun Connection.ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) = | ||||||
|  |     Definition.ensureFieldIndex(tableName, indexName, fields, this) | ||||||
|  | 
 | ||||||
|  | // ~~~ DOCUMENT MANIPULATION QUERIES ~~~ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Insert a new document | ||||||
|  |  * | ||||||
|  |  * @param tableName The table into which the document should be inserted (may include schema) | ||||||
|  |  * @param document The document to be inserted | ||||||
|  |  */ | ||||||
|  | inline fun <reified TDoc> Connection.insert(tableName: String, document: TDoc) = | ||||||
|  |     Document.insert(tableName, document, this) | ||||||
|  | 
 | ||||||
|  | // ~~~ DOCUMENT COUNT QUERIES ~~~ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Count all documents in the table | ||||||
|  |  * | ||||||
|  |  * @param tableName The name of the table in which documents should be counted | ||||||
|  |  * @return A count of the documents in the table | ||||||
|  |  */ | ||||||
|  | fun Connection.countAll(tableName: String) = | ||||||
|  |     Count.all(tableName, this) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | // ~~~ DOCUMENT RETRIEVAL QUERIES ~~~ | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Retrieve all documents in the given table | ||||||
|  |  * | ||||||
|  |  * @param tableName The table from which documents should be retrieved | ||||||
|  |  * @return A list of documents from the given table | ||||||
|  |  */ | ||||||
|  | inline fun <reified TDoc> Connection.findAll(tableName: String) = | ||||||
|  |     Find.all<TDoc>(tableName, this) | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Retrieve all documents in the given table | ||||||
|  |  * | ||||||
|  |  * @param tableName The table from which documents should be retrieved | ||||||
|  |  * @return A list of documents from the given table | ||||||
|  |  */ | ||||||
|  | inline fun <reified TDoc> Connection.findAll(tableName: String, orderBy: Collection<Field<*>>) = | ||||||
|  |     Find.all<TDoc>(tableName, orderBy, this) | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								src/main/kotlin/Count.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/main/kotlin/Count.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import solutions.bitbadger.documents.query.Count | ||||||
|  | import java.sql.Connection | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Functions to count documents | ||||||
|  |  */ | ||||||
|  | object Count { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Count all documents in the table | ||||||
|  |      * | ||||||
|  |      * @param tableName The name of the table in which documents should be counted | ||||||
|  |      * @param conn The connection over which documents should be counted | ||||||
|  |      * @return A count of the documents in the table | ||||||
|  |      */ | ||||||
|  |     fun all(tableName: String, conn: Connection) = | ||||||
|  |         conn.customScalar(Count.all(tableName), mapFunc = Results::toCount) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Count all documents in the table | ||||||
|  |      * | ||||||
|  |      * @param tableName The name of the table in which documents should be counted | ||||||
|  |      * @return A count of the documents in the table | ||||||
|  |      */ | ||||||
|  |     fun all(tableName: String) = | ||||||
|  |         Configuration.dbConn().use { all(tableName, it) } | ||||||
|  | } | ||||||
| @ -18,7 +18,7 @@ object Custom { | |||||||
|      * @return A list of results for the given query |      * @return A list of results for the given query | ||||||
|      */ |      */ | ||||||
|     inline fun <reified TDoc> list( |     inline fun <reified TDoc> list( | ||||||
|         query: String, parameters: Collection<Parameter<*>>, conn: Connection, mapFunc: (ResultSet) -> TDoc |         query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection, mapFunc: (ResultSet) -> TDoc | ||||||
|     ) = Parameters.apply(conn, query, parameters).use { Results.toCustomList(it, mapFunc) } |     ) = Parameters.apply(conn, query, parameters).use { Results.toCustomList(it, mapFunc) } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -30,7 +30,7 @@ object Custom { | |||||||
|      * @return A list of results for the given query |      * @return A list of results for the given query | ||||||
|      */ |      */ | ||||||
|     inline fun <reified TDoc> list( |     inline fun <reified TDoc> list( | ||||||
|         query: String, parameters: Collection<Parameter<*>>, mapFunc: (ResultSet) -> TDoc |         query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc | ||||||
|     ) = Configuration.dbConn().use { list(query, parameters, it, mapFunc) } |     ) = Configuration.dbConn().use { list(query, parameters, it, mapFunc) } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -43,7 +43,7 @@ object Custom { | |||||||
|      * @return The document if one matches the query, `null` otherwise |      * @return The document if one matches the query, `null` otherwise | ||||||
|      */ |      */ | ||||||
|     inline fun <reified TDoc> single( |     inline fun <reified TDoc> single( | ||||||
|         query: String, parameters: Collection<Parameter<*>>, conn: Connection, mapFunc: (ResultSet) -> TDoc |         query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection, mapFunc: (ResultSet) -> TDoc | ||||||
|     ) = list("$query LIMIT 1", parameters, conn, mapFunc).singleOrNull() |     ) = list("$query LIMIT 1", parameters, conn, mapFunc).singleOrNull() | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -55,7 +55,7 @@ object Custom { | |||||||
|      * @return The document if one matches the query, `null` otherwise |      * @return The document if one matches the query, `null` otherwise | ||||||
|      */ |      */ | ||||||
|     inline fun <reified TDoc> single( |     inline fun <reified TDoc> single( | ||||||
|         query: String, parameters: Collection<Parameter<*>>, mapFunc: (ResultSet) -> TDoc |         query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> TDoc | ||||||
|     ) = Configuration.dbConn().use { single(query, parameters, it, mapFunc) } |     ) = Configuration.dbConn().use { single(query, parameters, it, mapFunc) } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -65,7 +65,7 @@ object Custom { | |||||||
|      * @param conn The connection over which the query should be executed |      * @param conn The connection over which the query should be executed | ||||||
|      * @param parameters Parameters to use for the query |      * @param parameters Parameters to use for the query | ||||||
|      */ |      */ | ||||||
|     fun nonQuery(query: String, parameters: Collection<Parameter<*>>, conn: Connection) { |     fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection) { | ||||||
|         Parameters.apply(conn, query, parameters).use { it.executeUpdate() } |         Parameters.apply(conn, query, parameters).use { it.executeUpdate() } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -75,7 +75,7 @@ object Custom { | |||||||
|      * @param query The query to retrieve the results |      * @param query The query to retrieve the results | ||||||
|      * @param parameters Parameters to use for the query |      * @param parameters Parameters to use for the query | ||||||
|      */ |      */ | ||||||
|     fun nonQuery(query: String, parameters: Collection<Parameter<*>>) = |     fun nonQuery(query: String, parameters: Collection<Parameter<*>> = listOf()) = | ||||||
|         Configuration.dbConn().use { nonQuery(query, parameters, it) } |         Configuration.dbConn().use { nonQuery(query, parameters, it) } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -88,7 +88,7 @@ object Custom { | |||||||
|      * @return The scalar value from the query |      * @return The scalar value from the query | ||||||
|      */ |      */ | ||||||
|     inline fun <reified T> scalar( |     inline fun <reified T> scalar( | ||||||
|         query: String, parameters: Collection<Parameter<*>>, conn: Connection, mapFunc: (ResultSet) -> T & Any |         query: String, parameters: Collection<Parameter<*>> = listOf(), conn: Connection, mapFunc: (ResultSet) -> T & Any | ||||||
|     ) = Parameters.apply(conn, query, parameters).use { stmt -> |     ) = Parameters.apply(conn, query, parameters).use { stmt -> | ||||||
|         stmt.executeQuery().use { rs -> |         stmt.executeQuery().use { rs -> | ||||||
|             rs.next() |             rs.next() | ||||||
| @ -105,6 +105,6 @@ object Custom { | |||||||
|      * @return The scalar value from the query |      * @return The scalar value from the query | ||||||
|      */ |      */ | ||||||
|     inline fun <reified T> scalar( |     inline fun <reified T> scalar( | ||||||
|         query: String, parameters: Collection<Parameter<*>>, mapFunc: (ResultSet) -> T & Any |         query: String, parameters: Collection<Parameter<*>> = listOf(), mapFunc: (ResultSet) -> T & Any | ||||||
|     ) = Configuration.dbConn().use { scalar(query, parameters, it, mapFunc) } |     ) = Configuration.dbConn().use { scalar(query, parameters, it, mapFunc) } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										51
									
								
								src/main/kotlin/Definition.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/main/kotlin/Definition.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import java.sql.Connection | ||||||
|  | import solutions.bitbadger.documents.query.Definition | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Functions to define tables and indexes | ||||||
|  |  */ | ||||||
|  | object Definition { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a document table if necessary | ||||||
|  |      * | ||||||
|  |      * @param tableName The table whose existence should be ensured (may include schema) | ||||||
|  |      * @param conn The connection on which the query should be executed | ||||||
|  |      */ | ||||||
|  |     fun ensureTable(tableName: String, conn: Connection) = | ||||||
|  |         Configuration.dialect("ensure $tableName exists").let { | ||||||
|  |             conn.customNonQuery(Definition.ensureTable(tableName, it)) | ||||||
|  |             conn.customNonQuery(Definition.ensureKey(tableName, it)) | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a document table if necessary | ||||||
|  |      * | ||||||
|  |      * @param tableName The table whose existence should be ensured (may include schema) | ||||||
|  |      */ | ||||||
|  |     fun ensureTable(tableName: String) = | ||||||
|  |         Configuration.dbConn().use { ensureTable(tableName, it) } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create an index on field(s) within documents in the specified table if necessary | ||||||
|  |      * | ||||||
|  |      * @param tableName The table to be indexed (may include schema) | ||||||
|  |      * @param indexName The name of the index to create | ||||||
|  |      * @param fields One or more fields to be indexed< | ||||||
|  |      * @param conn The connection on which the query should be executed | ||||||
|  |      */ | ||||||
|  |     fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>, conn: Connection) = | ||||||
|  |         conn.customNonQuery(Definition.ensureIndexOn(tableName, indexName, fields)) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Create an index on field(s) within documents in the specified table if necessary | ||||||
|  |      * @param tableName The table to be indexed (may include schema) | ||||||
|  |      * @param indexName The name of the index to create | ||||||
|  |      * @param fields One or more fields to be indexed< | ||||||
|  |      * @param conn The connection on which the query should be executed | ||||||
|  |      */ | ||||||
|  |     fun ensureFieldIndex(tableName: String, indexName: String, fields: Collection<String>) = | ||||||
|  |         Configuration.dbConn().use { ensureFieldIndex(tableName, indexName, fields, it) } | ||||||
|  | } | ||||||
							
								
								
									
										54
									
								
								src/main/kotlin/Document.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/main/kotlin/Document.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import java.sql.Connection | ||||||
|  | import solutions.bitbadger.documents.query.Document | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Functions for manipulating documents | ||||||
|  |  */ | ||||||
|  | object Document { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Insert a new document | ||||||
|  |      * | ||||||
|  |      * @param tableName The table into which the document should be inserted (may include schema) | ||||||
|  |      * @param document The document to be inserted | ||||||
|  |      * @param conn The connection on which the query should be executed | ||||||
|  |      */ | ||||||
|  |     inline fun <reified TDoc> insert(tableName: String, document: TDoc, conn: Connection) { | ||||||
|  |         val strategy = Configuration.autoIdStrategy | ||||||
|  |         val query = if (strategy == AutoId.DISABLED) { | ||||||
|  |             Document.insert(tableName) | ||||||
|  |         } else { | ||||||
|  |             val idField  = Configuration.idField | ||||||
|  |             val dialect  = Configuration.dialect("Create auto-ID insert query") | ||||||
|  |             val dataParam = if (AutoId.needsAutoId(strategy, document, idField)) { | ||||||
|  |                 when (strategy) { | ||||||
|  |                     AutoId.NUMBER        -> "(SELECT coalesce(max(data->>'$idField'), 0) + 1 FROM $tableName)" | ||||||
|  |                     AutoId.UUID          -> "'${AutoId.generateUUID()}'" | ||||||
|  |                     AutoId.RANDOM_STRING -> "'${AutoId.generateRandomString()}'" | ||||||
|  |                     else                 -> "(:data)->>'$idField'" | ||||||
|  |                 }.let { | ||||||
|  |                     when (dialect) { | ||||||
|  |                         Dialect.POSTGRESQL -> ":data::jsonb || ('{\"$idField\":$it}')::jsonb" | ||||||
|  |                         Dialect.SQLITE     -> "json_set(:data, '$.$idField', $it)" | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 ":data" | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             Document.insert(tableName).replace(":data", dataParam) | ||||||
|  |         } | ||||||
|  |         conn.customNonQuery(query, listOf(Parameters.json(":data", document))) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Insert a new document | ||||||
|  |      * | ||||||
|  |      * @param tableName The table into which the document should be inserted (may include schema) | ||||||
|  |      * @param document The document to be inserted | ||||||
|  |      */ | ||||||
|  |     inline fun <reified TDoc> insert(tableName: String, document: TDoc) = | ||||||
|  |         Configuration.dbConn().use { insert(tableName, document, it) } | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								src/main/kotlin/Find.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/main/kotlin/Find.kt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | |||||||
|  | package solutions.bitbadger.documents | ||||||
|  | 
 | ||||||
|  | import java.sql.Connection | ||||||
|  | import solutions.bitbadger.documents.query.Find | ||||||
|  | import solutions.bitbadger.documents.query.orderBy | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Functions to find and retrieve documents | ||||||
|  |  */ | ||||||
|  | object Find { | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Retrieve all documents in the given table | ||||||
|  |      * | ||||||
|  |      * @param tableName The table from which documents should be retrieved | ||||||
|  |      * @param conn The connection over which documents should be retrieved | ||||||
|  |      * @return A list of documents from the given table | ||||||
|  |      */ | ||||||
|  |     inline fun <reified TDoc> all(tableName: String, conn: Connection) = | ||||||
|  |         conn.customList<TDoc>(Find.all(tableName), mapFunc = Results::fromData) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Retrieve all documents in the given table | ||||||
|  |      * | ||||||
|  |      * @param tableName The table from which documents should be retrieved | ||||||
|  |      * @return A list of documents from the given table | ||||||
|  |      */ | ||||||
|  |     inline fun <reified TDoc> all(tableName: String) = | ||||||
|  |         Configuration.dbConn().use { all<TDoc>(tableName, it) } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Retrieve all documents in the given table | ||||||
|  |      * | ||||||
|  |      * @param tableName The table from which documents should be retrieved | ||||||
|  |      * @param orderBy Fields by which the query should be ordered | ||||||
|  |      * @param conn The connection over which documents should be retrieved | ||||||
|  |      * @return A list of documents from the given table | ||||||
|  |      */ | ||||||
|  |     inline fun <reified TDoc> all(tableName: String, orderBy: Collection<Field<*>>, conn: Connection) = | ||||||
|  |         conn.customList<TDoc>(Find.all(tableName) + orderBy(orderBy), mapFunc = Results::fromData) | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Retrieve all documents in the given table | ||||||
|  |      * | ||||||
|  |      * @param tableName The table from which documents should be retrieved | ||||||
|  |      * @param orderBy Fields by which the query should be ordered | ||||||
|  |      * @return A list of documents from the given table | ||||||
|  |      */ | ||||||
|  |     inline fun <reified TDoc> all(tableName: String, orderBy: Collection<Field<*>>) = | ||||||
|  |         Configuration.dbConn().use { all<TDoc>(tableName, orderBy, it) } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -12,4 +12,7 @@ class Parameter<T>(val name: String, val type: ParameterType, val value: T) { | |||||||
|         if (!name.startsWith(':') && !name.startsWith('@')) |         if (!name.startsWith(':') && !name.startsWith('@')) | ||||||
|             throw DocumentException("Name must start with : or @ ($name)") |             throw DocumentException("Name must start with : or @ ($name)") | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     override fun toString() = | ||||||
|  |         "$type[$name] = $value" | ||||||
| } | } | ||||||
|  | |||||||
| @ -29,6 +29,16 @@ object Parameters { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Create a parameter by encoding a JSON object | ||||||
|  |      * | ||||||
|  |      * @param name The parameter name | ||||||
|  |      * @param value The object to be encoded as JSON | ||||||
|  |      * @return A parameter with the value encoded | ||||||
|  |      */ | ||||||
|  |     inline fun <reified T> json(name: String, value: T) = | ||||||
|  |         Parameter(name, ParameterType.JSON, Configuration.json.encodeToString(value)) | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Replace the parameter names in the query with question marks |      * Replace the parameter names in the query with question marks | ||||||
|      * |      * | ||||||
| @ -93,7 +103,7 @@ object Parameters { | |||||||
|                                 } |                                 } | ||||||
|                             } |                             } | ||||||
| 
 | 
 | ||||||
|                             ParameterType.JSON -> stmt.setString(idx, Configuration.json.encodeToString(param.value)) |                             ParameterType.JSON -> stmt.setString(idx, param.value as String) | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -24,10 +24,11 @@ object Definition { | |||||||
|      * SQL statement to create a document table in the current dialect |      * SQL statement to create a document table in the current dialect | ||||||
|      * |      * | ||||||
|      * @param tableName The name of the table to create (may include schema) |      * @param tableName The name of the table to create (may include schema) | ||||||
|  |      * @param dialect The dialect to generate (optional, used in place of current) | ||||||
|      * @return A query to create a document table |      * @return A query to create a document table | ||||||
|      */ |      */ | ||||||
|     fun ensureTable(tableName: String) = |     fun ensureTable(tableName: String, dialect: Dialect? = null) = | ||||||
|         when (Configuration.dialect("create table creation query")) { |         when (dialect ?: Configuration.dialect("create table creation query")) { | ||||||
|             Dialect.POSTGRESQL -> ensureTableFor(tableName, "JSONB") |             Dialect.POSTGRESQL -> ensureTableFor(tableName, "JSONB") | ||||||
|             Dialect.SQLITE     -> ensureTableFor(tableName, "TEXT") |             Dialect.SQLITE     -> ensureTableFor(tableName, "TEXT") | ||||||
|         } |         } | ||||||
| @ -47,15 +48,21 @@ object Definition { | |||||||
|      * @param tableName The table on which an index should be created (may include schema) |      * @param tableName The table on which an index should be created (may include schema) | ||||||
|      * @param indexName The name of the index to be created |      * @param indexName The name of the index to be created | ||||||
|      * @param fields One or more fields to include in the index |      * @param fields One or more fields to include in the index | ||||||
|      * @param dialect The SQL dialect to use when creating this index |      * @param dialect The SQL dialect to use when creating this index (optional, used in place of current) | ||||||
|      * @return A query to create the field index |      * @return A query to create the field index | ||||||
|      */ |      */ | ||||||
|     fun ensureIndexOn(tableName: String, indexName: String, fields: Collection<String>, dialect: Dialect): String { |     fun ensureIndexOn( | ||||||
|  |         tableName: String, | ||||||
|  |         indexName: String, | ||||||
|  |         fields: Collection<String>, | ||||||
|  |         dialect: Dialect? = null | ||||||
|  |     ): String { | ||||||
|         val (_, tbl) = splitSchemaAndTable(tableName) |         val (_, tbl) = splitSchemaAndTable(tableName) | ||||||
|  |         val mode = dialect ?: Configuration.dialect("create index $tbl.$indexName") | ||||||
|         val jsonFields = fields.joinToString(", ") { |         val jsonFields = fields.joinToString(", ") { | ||||||
|             val parts = it.split(' ') |             val parts = it.split(' ') | ||||||
|             val direction = if (parts.size > 1) " ${parts[1]}" else "" |             val direction = if (parts.size > 1) " ${parts[1]}" else "" | ||||||
|             "(" + Field.nameToPath(parts[0], dialect, FieldFormat.SQL) + ")$direction" |             "(" + Field.nameToPath(parts[0], mode, FieldFormat.SQL) + ")$direction" | ||||||
|         } |         } | ||||||
|         return "CREATE INDEX IF NOT EXISTS idx_${tbl}_$indexName ON $tableName ($jsonFields)" |         return "CREATE INDEX IF NOT EXISTS idx_${tbl}_$indexName ON $tableName ($jsonFields)" | ||||||
|     } |     } | ||||||
| @ -64,9 +71,9 @@ object Definition { | |||||||
|      * SQL statement to create a key index for a document table |      * SQL statement to create a key index for a document table | ||||||
|      * |      * | ||||||
|      * @param tableName The table on which a key index should be created (may include schema) |      * @param tableName The table on which a key index should be created (may include schema) | ||||||
|      * @param dialect The SQL dialect to use when creating this index |      * @param dialect The SQL dialect to use when creating this index (optional, used in place of current) | ||||||
|      * @return A query to create the key index |      * @return A query to create the key index | ||||||
|      */ |      */ | ||||||
|     fun ensureKey(tableName: String, dialect: Dialect) = |     fun ensureKey(tableName: String, dialect: Dialect? = null) = | ||||||
|         ensureIndexOn(tableName, "key", listOf(Configuration.idField), dialect).replace("INDEX", "UNIQUE INDEX") |         ensureIndexOn(tableName, "key", listOf(Configuration.idField), dialect).replace("INDEX", "UNIQUE INDEX") | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
| package solutions.bitbadger.documents.query | package solutions.bitbadger.documents.query | ||||||
| 
 | 
 | ||||||
|  | import solutions.bitbadger.documents.Configuration | ||||||
| import solutions.bitbadger.documents.Dialect | import solutions.bitbadger.documents.Dialect | ||||||
| import solutions.bitbadger.documents.Field | import solutions.bitbadger.documents.Field | ||||||
| import solutions.bitbadger.documents.FieldMatch | import solutions.bitbadger.documents.FieldMatch | ||||||
| @ -44,7 +45,8 @@ fun byFields(statement: String, fields: Collection<Field<*>>, howMatched: FieldM | |||||||
|  * @param dialect The SQL dialect for the generated clause |  * @param dialect The SQL dialect for the generated clause | ||||||
|  * @return An `ORDER BY` clause for the given fields |  * @return An `ORDER BY` clause for the given fields | ||||||
|  */ |  */ | ||||||
| fun orderBy(fields: Collection<Field<*>>, dialect: Dialect): String { | fun orderBy(fields: Collection<Field<*>>, dialect: Dialect? = null): String { | ||||||
|  |     val mode = dialect ?: Configuration.dialect("generate ORDER BY clause") | ||||||
|     if (fields.isEmpty()) return "" |     if (fields.isEmpty()) return "" | ||||||
|     val orderFields = fields.joinToString(", ") { |     val orderFields = fields.joinToString(", ") { | ||||||
|         val (field, direction) = |         val (field, direction) = | ||||||
| @ -56,18 +58,18 @@ fun orderBy(fields: Collection<Field<*>>, dialect: Dialect): String { | |||||||
|             } |             } | ||||||
|         val path = when { |         val path = when { | ||||||
|             field.name.startsWith("n:") -> Field.named(field.name.substring(2)).let { fld -> |             field.name.startsWith("n:") -> Field.named(field.name.substring(2)).let { fld -> | ||||||
|                 when (dialect) { |                 when (mode) { | ||||||
|                     Dialect.POSTGRESQL -> "(${fld.path(dialect)})::numeric" |                     Dialect.POSTGRESQL -> "(${fld.path(mode)})::numeric" | ||||||
|                     Dialect.SQLITE -> fld.path(dialect) |                     Dialect.SQLITE -> fld.path(mode) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             field.name.startsWith("i:") ->  Field.named(field.name.substring(2)).path(dialect).let { p -> |             field.name.startsWith("i:") ->  Field.named(field.name.substring(2)).path(mode).let { p -> | ||||||
|                 when (dialect) { |                 when (mode) { | ||||||
|                     Dialect.POSTGRESQL -> "LOWER($p)" |                     Dialect.POSTGRESQL -> "LOWER($p)" | ||||||
|                     Dialect.SQLITE -> "$p COLLATE NOCASE" |                     Dialect.SQLITE -> "$p COLLATE NOCASE" | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             else -> field.path(dialect) |             else -> field.path(mode) | ||||||
|         } |         } | ||||||
|         "$path${direction ?: ""}" |         "$path${direction ?: ""}" | ||||||
|     } |     } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user