Initial Development #1
3
.gitignore
vendored
3
.gitignore
vendored
@ -26,3 +26,6 @@ replay_pid*
|
||||
|
||||
# Kotlin Gradle plugin data, see https://kotlinlang.org/docs/whatsnew20.html#new-directory-for-kotlin-data-in-gradle-projects
|
||||
.kotlin/
|
||||
|
||||
# Temporary output directories
|
||||
**/target
|
||||
|
8
solutions.bitbadger.documents.iml
Normal file
8
solutions.bitbadger.documents.iml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="GENERAL_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
88
src/common/pom.xml
Normal file
88
src/common/pom.xml
Normal file
@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>solutions.bitbadger.documents</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>4.0-ALPHA</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<kotlin.code.style>official</kotlin.code.style>
|
||||
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>mavenCentral</id>
|
||||
<url>https://repo1.maven.org/maven2/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-maven-plugin</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>compile</id>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>test-compile</id>
|
||||
<phase>test-compile</phase>
|
||||
<goals>
|
||||
<goal>test-compile</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-failsafe-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>1.6.0</version>
|
||||
<configuration>
|
||||
<mainClass>MainKt</mainClass>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-test-junit5</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>5.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<version>2.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
39
src/common/src/main/kotlin/AutoId.kt
Normal file
39
src/common/src/main/kotlin/AutoId.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* Strategies for automatic document IDs
|
||||
*/
|
||||
enum class AutoId {
|
||||
/** No automatic IDs will be generated */
|
||||
DISABLED,
|
||||
/** Generate a `MAX`-plus-1 numeric ID */
|
||||
NUMBER,
|
||||
/** Generate a `UUID` string ID */
|
||||
UUID,
|
||||
/** Generate a random hex character string ID */
|
||||
RANDOM_STRING;
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Generate a `UUID` string
|
||||
*
|
||||
* @return A `UUID` string
|
||||
*/
|
||||
fun generateUUID(): String =
|
||||
java.util.UUID.randomUUID().toString().replace("-", "")
|
||||
|
||||
/**
|
||||
* Generate a string of random hex characters
|
||||
*
|
||||
* @param length The length of the string
|
||||
* @return A string of random hex characters of the requested length
|
||||
*/
|
||||
fun generateRandomString(length: Int): String =
|
||||
kotlin.random.Random.nextBytes((length + 2) / 2)
|
||||
.joinToString("") { String.format("%02x", it) }
|
||||
.substring(0, length - 1)
|
||||
|
||||
// TODO: fun <T> needsAutoId(strategy: AutoId, document: T, idProp: String): Boolean
|
||||
}
|
||||
}
|
9
src/common/src/main/kotlin/Comparison.kt
Normal file
9
src/common/src/main/kotlin/Comparison.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* A comparison against a field in a JSON document
|
||||
*
|
||||
* @property op The operation for the field comparison
|
||||
* @property value The value against which the comparison will be made
|
||||
*/
|
||||
class Comparison<T>(val op: Op, val value: T)
|
15
src/common/src/main/kotlin/Configuration.kt
Normal file
15
src/common/src/main/kotlin/Configuration.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
object Configuration {
|
||||
|
||||
// TODO: var jsonOpts = Json { some cool options }
|
||||
|
||||
/** The field in which a document's ID is stored */
|
||||
var idField = "Id"
|
||||
|
||||
/** The automatic ID strategy to use */
|
||||
var autoIdStrategy = AutoId.DISABLED
|
||||
|
||||
/** The length of automatic random hex character string */
|
||||
var idStringLength = 16
|
||||
}
|
11
src/common/src/main/kotlin/Dialect.kt
Normal file
11
src/common/src/main/kotlin/Dialect.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* The SQL dialect to use when building queries
|
||||
*/
|
||||
enum class Dialect {
|
||||
/** PostgreSQL */
|
||||
POSTGRESQL,
|
||||
/** SQLite */
|
||||
SQLITE
|
||||
}
|
132
src/common/src/main/kotlin/Field.kt
Normal file
132
src/common/src/main/kotlin/Field.kt
Normal file
@ -0,0 +1,132 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* A field and its comparison
|
||||
*
|
||||
* @property name The name of the field in the JSON document
|
||||
* @property comparison The comparison to apply against the field
|
||||
* @property parameterName The name of the parameter to use in the query (optional, generated if missing)
|
||||
* @property qualifier A table qualifier to use to address the `data` field (useful for multi-table queries)
|
||||
*/
|
||||
class Field<T>(
|
||||
val name: String,
|
||||
val comparison: Comparison<T>,
|
||||
val parameterName: String? = null,
|
||||
val qualifier: String? = null) {
|
||||
|
||||
/**
|
||||
* Get the path for this field
|
||||
*
|
||||
* @param dialect The SQL dialect to use for the path to the JSON field
|
||||
* @param format Whether the value should be retrieved as JSON or SQL (optional, default SQL)
|
||||
* @return The path for the field
|
||||
*/
|
||||
fun path(dialect: Dialect, format: FieldFormat = FieldFormat.SQL): String =
|
||||
(if (qualifier == null) "" else "${qualifier}.") + nameToPath(name, dialect, format)
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create a field equality comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> Equal(name: String, value: T): Field<T> =
|
||||
Field<T>(name, Comparison(Op.EQUAL, value))
|
||||
|
||||
/**
|
||||
* Create a field greater-than comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> Greater(name: String, value: T): Field<T> =
|
||||
Field(name, Comparison(Op.GREATER, value))
|
||||
|
||||
/**
|
||||
* Create a field greater-than-or-equal-to comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> GreaterOrEqual(name: String, value: T): Field<T> =
|
||||
Field(name, Comparison(Op.GREATER_OR_EQUAL, value))
|
||||
|
||||
/**
|
||||
* Create a field less-than comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> Less(name: String, value: T): Field<T> =
|
||||
Field(name, Comparison(Op.LESS, value))
|
||||
|
||||
/**
|
||||
* Create a field less-than-or-equal-to comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> LessOrEqual(name: String, value: T): Field<T> =
|
||||
Field(name, Comparison(Op.LESS_OR_EQUAL, value))
|
||||
|
||||
/**
|
||||
* Create a field inequality comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param value The value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> NotEqual(name: String, value: T): Field<T> =
|
||||
Field(name, Comparison(Op.NOT_EQUAL, value))
|
||||
|
||||
/**
|
||||
* Create a field range comparison
|
||||
*
|
||||
* @param name The name of the field to be compared
|
||||
* @param minValue The lower value for the comparison
|
||||
* @param maxValue The upper value for the comparison
|
||||
* @return A `Field` with the given comparison
|
||||
*/
|
||||
fun <T> Between(name: String, minValue: T, maxValue: T): Field<Pair<T, T>> =
|
||||
Field(name, Comparison(Op.BETWEEN, Pair(minValue, maxValue)))
|
||||
|
||||
fun <T> In(name: String, values: List<T>): Field<List<T>> =
|
||||
Field(name, Comparison(Op.IN, values))
|
||||
|
||||
fun <T> InArray(name: String, tableName: String, values: List<T>): Field<Pair<String, List<T>>> =
|
||||
Field(name, Comparison(Op.IN_ARRAY, Pair(tableName, values)))
|
||||
|
||||
fun Exists(name: String): Field<String> =
|
||||
Field(name, Comparison(Op.EXISTS, ""))
|
||||
|
||||
fun NotExists(name: String): Field<String> =
|
||||
Field(name, Comparison(Op.NOT_EXISTS, ""))
|
||||
|
||||
fun Named(name: String): Field<String> =
|
||||
Field(name, Comparison(Op.EQUAL, ""))
|
||||
|
||||
fun nameToPath(name: String, dialect: Dialect, format: FieldFormat): String {
|
||||
val path = StringBuilder("data")
|
||||
val extra = if (format == FieldFormat.SQL) ">" else ""
|
||||
if (name.indexOf('.') > -1) {
|
||||
if (dialect == Dialect.POSTGRESQL) {
|
||||
path.append("#>", extra, "'{", name.replace('.', ','), "}'")
|
||||
} else {
|
||||
val names = mutableListOf(name.split('.'))
|
||||
val last = names.removeLast()
|
||||
names.forEach { path.append("->'", it, "'") }
|
||||
path.append("->", extra, "'", last, "'")
|
||||
}
|
||||
} else {
|
||||
path.append("->", extra, "'", name, "'")
|
||||
}
|
||||
return path.toString()
|
||||
}
|
||||
}
|
||||
}
|
11
src/common/src/main/kotlin/FieldFormat.kt
Normal file
11
src/common/src/main/kotlin/FieldFormat.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* The data format for a document field retrieval
|
||||
*/
|
||||
enum class FieldFormat {
|
||||
/** Retrieve the field as a SQL value (string in PostgreSQL, best guess in SQLite */
|
||||
SQL,
|
||||
/** Retrieve the field as a JSON value */
|
||||
JSON
|
||||
}
|
11
src/common/src/main/kotlin/FieldMatch.kt
Normal file
11
src/common/src/main/kotlin/FieldMatch.kt
Normal file
@ -0,0 +1,11 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* How fields should be matched in by-field queries
|
||||
*/
|
||||
enum class FieldMatch(sql: String) {
|
||||
/** Match any of the field criteria (`OR`) */
|
||||
ANY("OR"),
|
||||
/** Match all the field criteria (`AND`) */
|
||||
ALL("AND"),
|
||||
}
|
14
src/common/src/main/kotlin/Main.kt
Normal file
14
src/common/src/main/kotlin/Main.kt
Normal file
@ -0,0 +1,14 @@
|
||||
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
|
||||
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
|
||||
fun main() {
|
||||
val name = "Kotlin"
|
||||
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
|
||||
// to see how IntelliJ IDEA suggests fixing it.
|
||||
println("Hello, " + name + "!")
|
||||
|
||||
for (i in 1..5) {
|
||||
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
|
||||
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
|
||||
println("i = $i")
|
||||
}
|
||||
}
|
29
src/common/src/main/kotlin/Op.kt
Normal file
29
src/common/src/main/kotlin/Op.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* A comparison operator used for fields
|
||||
*/
|
||||
enum class Op(sql: String) {
|
||||
/** Compare using equality */
|
||||
EQUAL("="),
|
||||
/** Compare using greater-than */
|
||||
GREATER(">"),
|
||||
/** Compare using greater-than-or-equal-to */
|
||||
GREATER_OR_EQUAL(">="),
|
||||
/** Compare using less-than */
|
||||
LESS("<"),
|
||||
/** Compare using less-than-or-equal-to */
|
||||
LESS_OR_EQUAL("<="),
|
||||
/** Compare using inequality */
|
||||
NOT_EQUAL("<>"),
|
||||
/** Compare between two values */
|
||||
BETWEEN("BETWEEN"),
|
||||
/** Compare existence in a list of values */
|
||||
IN("IN"),
|
||||
/** Compare overlap between an array and a list of values */
|
||||
IN_ARRAY("?|"),
|
||||
/** Compare existence */
|
||||
EXISTS("IS NOT NULL"),
|
||||
/** Compare nonexistence */
|
||||
NOT_EXISTS("IS NULL")
|
||||
}
|
18
src/common/src/main/kotlin/ParameterName.kt
Normal file
18
src/common/src/main/kotlin/ParameterName.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
/**
|
||||
* Derive parameter names; each instance wraps a counter to provide names for anonymous fields
|
||||
*/
|
||||
class ParameterName {
|
||||
|
||||
private var currentIdx = 0
|
||||
|
||||
/**
|
||||
* Derive the parameter name from the current possibly-null string
|
||||
*
|
||||
* @param paramName The name of the parameter as specified by the field
|
||||
* @return The name from the field, if present, or a derived name if missing
|
||||
*/
|
||||
fun derive(paramName: String?): String =
|
||||
paramName ?: ":field${currentIdx++}"
|
||||
}
|
170
src/common/src/main/kotlin/Query.kt
Normal file
170
src/common/src/main/kotlin/Query.kt
Normal file
@ -0,0 +1,170 @@
|
||||
package solutions.bitbadger.documents.common
|
||||
|
||||
object Query {
|
||||
|
||||
/**
|
||||
* Combine a query (`SELECT`, `UPDATE`, etc.) and a `WHERE` clause
|
||||
*
|
||||
* @param statement The first part of the statement
|
||||
* @param where The `WHERE` clause for the statement
|
||||
* @return The two parts of the query combined with `WHERE`
|
||||
*/
|
||||
fun statementWhere(statement: String, where: String): String =
|
||||
"$statement WHERE $where"
|
||||
|
||||
object Definition {
|
||||
|
||||
/**
|
||||
* SQL statement to create a document table
|
||||
*
|
||||
* @param tableName The name of the table to create (may include schema)
|
||||
* @param dataType The type of data for the column (`JSON`, `JSONB`, etc.)
|
||||
* @return A query to create a document table
|
||||
*/
|
||||
fun ensureTableFor(tableName: String, dataType: String): String =
|
||||
"CREATE TABLE IF NOT EXISTS $tableName (data $dataType NOT NULL)"
|
||||
|
||||
/**
|
||||
* Split a schema and table name
|
||||
*
|
||||
* @param tableName The name of the table, possibly with a schema
|
||||
* @return A pair with the first item as the schema and the second as the table name
|
||||
*/
|
||||
private fun splitSchemaAndTable(tableName: String): Pair<String, String> {
|
||||
val parts = tableName.split('.')
|
||||
return if (parts.size == 1) Pair("", tableName) else Pair(parts[0], parts[1])
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL statement to create an index on one or more fields in a JSON document
|
||||
*
|
||||
* @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 fields One or more fields to include in the index
|
||||
* @param dialect The SQL dialect to use when creating this index
|
||||
* @return A query to create the field index
|
||||
*/
|
||||
fun ensureIndexOn(tableName: String, indexName: String, fields: List<String>, dialect: Dialect): String {
|
||||
val (_, tbl) = splitSchemaAndTable(tableName)
|
||||
val jsonFields = fields.joinToString(", ") {
|
||||
val parts = it.split(' ')
|
||||
val direction = if (parts.size > 1) " ${parts[1]}" else ""
|
||||
"(" + Field.nameToPath(parts[0], dialect, FieldFormat.SQL) + ") $direction"
|
||||
}
|
||||
return "CREATE INDEX IF NOT EXISTS idx_${tbl}_$indexName ON $tableName ($jsonFields)"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 dialect The SQL dialect to use when creating this index
|
||||
* @return A query to create the key index
|
||||
*/
|
||||
fun ensureKey(tableName: String, dialect: Dialect): String =
|
||||
ensureIndexOn(tableName, "key", listOf(Configuration.idField), dialect).replace("INDEX", "UNIQUE INDEX")
|
||||
}
|
||||
|
||||
/**
|
||||
* Query to insert a document
|
||||
*
|
||||
* @param tableName The table into which to insert (may include schema)
|
||||
* @return A query to insert a document
|
||||
*/
|
||||
fun insert(tableName: String): String =
|
||||
"INSERT INTO $tableName VALUES (:data)"
|
||||
|
||||
/**
|
||||
* Query to save a document, inserting it if it does not exist and updating it if it does (AKA "upsert")
|
||||
*
|
||||
* @param tableName The table into which to save (may include schema)
|
||||
* @return A query to save a document
|
||||
*/
|
||||
fun save(tableName: String): String =
|
||||
String.format("INSERT INTO %s VALUES (:data) ON CONFLICT ((data->>'%s')) DO UPDATE SET data = EXCLUDED.data",
|
||||
tableName, Configuration.idField)
|
||||
|
||||
/**
|
||||
* Query to count documents in a table (this query has no `WHERE` clause)
|
||||
*
|
||||
* @param tableName The table in which to count documents (may include schema)
|
||||
* @return A query to count documents
|
||||
*/
|
||||
fun count(tableName: String): String =
|
||||
"SELECT COUNT(*) AS it FROM $tableName"
|
||||
|
||||
/**
|
||||
* Query to check for document existence in a table
|
||||
*
|
||||
* @param tableName The table in which existence should be checked (may include schema)
|
||||
* @param where The `WHERE` clause with the existence criteria
|
||||
* @return A query to check document existence
|
||||
*/
|
||||
fun exists(tableName: String, where: String): String =
|
||||
"SELECT EXISTS (SELECT 1 FROM $tableName WHERE $where) AS it"
|
||||
|
||||
/**
|
||||
* Query to select documents from a table (this query has no `WHERE` clause)
|
||||
*
|
||||
* @param tableName The table from which documents should be found (may include schema)
|
||||
* @return A query to retrieve documents
|
||||
*/
|
||||
fun find(tableName: String): String =
|
||||
"SELECT data FROM $tableName"
|
||||
|
||||
/**
|
||||
* Query to update (replace) a document (this query has no `WHERE` clause)
|
||||
*
|
||||
* @param tableName The table in which documents should be replaced (may include schema)
|
||||
* @return A query to update documents
|
||||
*/
|
||||
fun update(tableName: String): String =
|
||||
"UPDATE $tableName SET data = :data"
|
||||
|
||||
/**
|
||||
* Query to delete documents from a table (this query has no `WHERE` clause)
|
||||
*
|
||||
* @param tableName The table in which documents should be deleted (may include schema)
|
||||
* @return A query to delete documents
|
||||
*/
|
||||
fun delete(tableName: String): String =
|
||||
"DELETE FROM $tableName"
|
||||
|
||||
/**
|
||||
* Create an `ORDER BY` clause for the given fields
|
||||
*
|
||||
* @param fields One or more fields by which to order
|
||||
* @param dialect The SQL dialect for the generated clause
|
||||
* @return An `ORDER BY` clause for the given fields
|
||||
*/
|
||||
fun orderBy(fields: List<Field<*>>, dialect: Dialect): String {
|
||||
if (fields.isEmpty()) return ""
|
||||
val orderFields = fields.joinToString(", ") {
|
||||
val (field, direction) =
|
||||
if (it.name.indexOf(' ') > -1) {
|
||||
val parts = it.name.split(' ')
|
||||
Pair(Field.Named(parts[0]), " " + parts.drop(1).joinToString(" "))
|
||||
} else {
|
||||
Pair<Field<*>, String?>(it, null)
|
||||
}
|
||||
val path =
|
||||
if (field.name.startsWith("n:")) {
|
||||
val fld = Field.Named(field.name.substring(2))
|
||||
when (dialect) {
|
||||
Dialect.POSTGRESQL -> "(${fld.path(dialect)})::numeric"
|
||||
Dialect.SQLITE -> fld.path(dialect)
|
||||
}
|
||||
} else if (field.name.startsWith("i:")) {
|
||||
val p = Field.Named(field.name.substring(2)).path(dialect)
|
||||
when (dialect) {
|
||||
Dialect.POSTGRESQL -> "LOWER($p)"
|
||||
Dialect.SQLITE -> "$p COLLATE NOCASE"
|
||||
}
|
||||
} else {
|
||||
field.path(dialect)
|
||||
}
|
||||
"$path${direction ?: ""}"
|
||||
}
|
||||
return "ORDER BY $orderFields"
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user