mirror of
https://github.com/jlengrand/Exposed.git
synced 2026-03-10 08:11:20 +00:00
[KDoc & Format] Database Dialects (#769)
* Add missing KDoc and format following the Kotlin Coding Convention * Placed short comments in a single line * Added property removed during merge in previous commit.
This commit is contained in:
committed by
Andrey.Tarashevskiy
parent
771a60f7e2
commit
cfd14c5b86
@@ -3,80 +3,341 @@ package org.jetbrains.exposed.sql.vendors
|
||||
import org.jetbrains.exposed.exceptions.throwUnsupportedException
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.lang.StringBuilder
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal typealias TableAndColumnName = Pair<String, String>
|
||||
|
||||
/**
|
||||
* Provides definitions for all the supported SQL data types.
|
||||
* By default, definitions from the SQL standard are provided but if a vendor doesn't support a specific type, or it is
|
||||
* implemented differently, the corresponding function should be overridden.
|
||||
*/
|
||||
abstract class DataTypeProvider {
|
||||
open fun integerAutoincType() = "INT AUTO_INCREMENT"
|
||||
// Numeric types
|
||||
|
||||
open fun integerType() = "INT"
|
||||
/** Numeric type for storing 4-byte integers. */
|
||||
open fun integerType(): String = "INT"
|
||||
|
||||
open fun longAutoincType() = "BIGINT AUTO_INCREMENT"
|
||||
/** Numeric type for storing 4-byte integers, marked as auto-increment. */
|
||||
open fun integerAutoincType(): String = "INT AUTO_INCREMENT"
|
||||
|
||||
open fun longType() = "BIGINT"
|
||||
/** Numeric type for storing 8-byte integers. */
|
||||
open fun longType(): String = "BIGINT"
|
||||
|
||||
open fun floatType() = "FLOAT"
|
||||
/** Numeric type for storing 8-byte integers, and marked as auto-increment. */
|
||||
open fun longAutoincType(): String = "BIGINT AUTO_INCREMENT"
|
||||
|
||||
open fun doubleType() = "DOUBLE PRECISION"
|
||||
/** Numeric type for storing 4-byte (single precision) floating-point numbers. */
|
||||
open fun floatType(): String = "FLOAT"
|
||||
|
||||
open fun uuidType() = "BINARY(16)"
|
||||
/** Numeric type for storing 8-byte (double precision) floating-point numbers. */
|
||||
open fun doubleType(): String = "DOUBLE PRECISION"
|
||||
|
||||
open fun dateTimeType() = "DATETIME"
|
||||
// Character types
|
||||
|
||||
open fun blobType(): String = "BLOB"
|
||||
/** Character type for storing strings of variable and _unlimited_ length. */
|
||||
open fun textType(): String = "TEXT"
|
||||
|
||||
// Binary data types
|
||||
|
||||
/** Binary type for storing binary strings of variable and _unlimited_ length. */
|
||||
abstract fun binaryType(): String
|
||||
|
||||
/** Binary type for storing binary strings of a specific [length]. */
|
||||
open fun binaryType(length: Int): String = "VARBINARY($length)"
|
||||
|
||||
open abstract fun binaryType(): String
|
||||
open val blobAsStream: Boolean = false
|
||||
|
||||
/** Binary type for storing BLOBs. */
|
||||
open fun blobType(): String = "BLOB"
|
||||
|
||||
/** Binary type for storing [UUID]. */
|
||||
open fun uuidType(): String = "BINARY(16)"
|
||||
|
||||
open fun uuidToDB(value: UUID): Any =
|
||||
ByteBuffer.allocate(16).putLong(value.mostSignificantBits).putLong(value.leastSignificantBits).array()
|
||||
|
||||
// Date/Time types
|
||||
|
||||
/** Data type for storing both date and time without a time zone. */
|
||||
open fun dateTimeType(): String = "DATETIME"
|
||||
|
||||
// Boolean type
|
||||
|
||||
/** Data type for storing boolean values. */
|
||||
open fun booleanType(): String = "BOOLEAN"
|
||||
|
||||
open fun booleanToStatementString(bool: Boolean) = bool.toString()
|
||||
|
||||
open fun uuidToDB(value: UUID) : Any =
|
||||
ByteBuffer.allocate(16).putLong(value.mostSignificantBits).putLong(value.leastSignificantBits).array()
|
||||
/** Returns the SQL representation of the specified [bool] value. */
|
||||
open fun booleanToStatementString(bool: Boolean): String = bool.toString()
|
||||
|
||||
/** Returns the boolean value of the specified SQL [value]. */
|
||||
open fun booleanFromStringToBoolean(value: String): Boolean = value.toBoolean()
|
||||
|
||||
open fun textType() = "TEXT"
|
||||
open val blobAsStream = false
|
||||
// Misc.
|
||||
|
||||
open fun processForDefaultValue(e: Expression<*>) : String = when {
|
||||
/** Returns the SQL representation of the specified expression, for it to be used as a column default value. */
|
||||
open fun processForDefaultValue(e: Expression<*>): String = when {
|
||||
e is LiteralOp<*> -> "$e"
|
||||
currentDialect is MysqlDialect -> "$e"
|
||||
else -> "($e)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides definitions for all the supported SQL functions.
|
||||
* By default, definitions from the SQL standard are provided but if a vendor doesn't support a specific function, or it
|
||||
* is implemented differently, the corresponding function should be overridden.
|
||||
*/
|
||||
abstract class FunctionProvider {
|
||||
// Mathematical functions
|
||||
|
||||
open val DEFAULT_VALUE_EXPRESSION = "DEFAULT VALUES"
|
||||
/**
|
||||
* SQL function that returns the next value of the specified sequence.
|
||||
*
|
||||
* @param seq Sequence that produces the value.
|
||||
* @param builder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun nextVal(seq: Sequence, builder: QueryBuilder): Unit = builder {
|
||||
append(seq.identifier, ".NEXTVAL")
|
||||
}
|
||||
|
||||
open fun<T:String?> substring(expr: Expression<T>, start: Expression<Int>,
|
||||
length: Expression<Int>, builder: QueryBuilder,
|
||||
prefix: String = "SUBSTRING") = builder {
|
||||
/**
|
||||
* SQL function that generates a random value uniformly distributed between 0 (inclusive) and 1 (exclusive).
|
||||
*
|
||||
* **Note:** Some vendors generate values outside this range, or ignore the given seed, check the documentation.
|
||||
*
|
||||
* @param seed Optional seed.
|
||||
*/
|
||||
open fun random(seed: Int?): String = "RANDOM(${seed?.toString().orEmpty()})"
|
||||
|
||||
// String functions
|
||||
|
||||
/**
|
||||
* SQL function that extracts a substring from the specified string expression.
|
||||
*
|
||||
* @param expr The expression to extract the substring from.
|
||||
* @param start The start of the substring.
|
||||
* @param length The length of the substring.
|
||||
* @param builder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T : String?> substring(
|
||||
expr: Expression<T>,
|
||||
start: Expression<Int>,
|
||||
length: Expression<Int>,
|
||||
builder: QueryBuilder,
|
||||
prefix: String = "SUBSTRING"
|
||||
): Unit = builder {
|
||||
append(prefix, "(", expr, ", ", start, ", ", length, ")")
|
||||
}
|
||||
|
||||
open fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
|
||||
append(seq.identifier,".NEXTVAL")
|
||||
/**
|
||||
* SQL function that concatenates multiple string expressions together with a given separator.
|
||||
*
|
||||
* @param separator Separator to use.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
* @param expr String expressions to concatenate.
|
||||
*/
|
||||
open fun <T : String?> concat(separator: String, queryBuilder: QueryBuilder, vararg expr: Expression<T>): Unit = queryBuilder {
|
||||
if (separator == "") {
|
||||
append("CONCAT(")
|
||||
} else {
|
||||
append("CONCAT_WS('", separator, "',")
|
||||
}
|
||||
expr.toList().appendTo { +it }
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun random(seed: Int?): String = "RANDOM(${seed?.toString().orEmpty()})"
|
||||
/**
|
||||
* SQL function that concatenates strings from a group into a single string.
|
||||
*
|
||||
* @param expr Group concat options.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("GROUP_CONCAT(")
|
||||
if (expr.distinct) {
|
||||
append("DISTINCT ")
|
||||
}
|
||||
append(expr.expr)
|
||||
if (expr.orderBy.isNotEmpty()) {
|
||||
expr.orderBy.toList().appendTo(prefix = " ORDER BY ") {
|
||||
append(it.first, " ", it.second.name)
|
||||
}
|
||||
}
|
||||
expr.separator?.let {
|
||||
append(" SEPARATOR '$it'")
|
||||
}
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun cast(expr: Expression<*>, type: IColumnType, builder: QueryBuilder) = builder {
|
||||
// Pattern matching
|
||||
|
||||
/**
|
||||
* Marker interface for the possible pattern matching modes.
|
||||
*/
|
||||
interface MatchMode {
|
||||
/** SQL representation of the mode. */
|
||||
fun mode(): String
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that checks whether the given string expression matches the given pattern.
|
||||
*
|
||||
* **Note:** The `mode` parameter is not supported by all vendors, please check the documentation.
|
||||
*
|
||||
* @receiver Expression to check.
|
||||
* @param pattern Pattern the expression is checked against.
|
||||
* @param mode Match mode used to check the expression.
|
||||
*/
|
||||
open fun <T : String?> ExpressionWithColumnType<T>.match(pattern: String, mode: MatchMode? = null): Op<Boolean> = with(SqlExpressionBuilder) {
|
||||
this@match.like(pattern)
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that performs a pattern match of a given string expression against a given pattern.
|
||||
*
|
||||
* @param expr1 String expression to test.
|
||||
* @param pattern Pattern to match against.
|
||||
* @param caseSensitive Whether the matching is case-sensitive or not.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T : String?> regexp(
|
||||
expr1: Expression<T>,
|
||||
pattern: Expression<String>,
|
||||
caseSensitive: Boolean,
|
||||
queryBuilder: QueryBuilder
|
||||
): Unit = queryBuilder {
|
||||
append("REGEXP_LIKE(", expr1, ", ", pattern, ", ")
|
||||
if (caseSensitive) {
|
||||
append("'c'")
|
||||
} else {
|
||||
append("'i'")
|
||||
}
|
||||
append(")")
|
||||
}
|
||||
|
||||
// Date/Time functions
|
||||
|
||||
/**
|
||||
* SQL function that extracts the year field from a given date.
|
||||
*
|
||||
* @param expr Expression to extract the year from.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("YEAR(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that extracts the month field from a given date.
|
||||
* The returned value is a number between 1 and 12 both inclusive.
|
||||
*
|
||||
* @param expr Expression to extract the month from.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("MONTH(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that extracts the day field from a given date.
|
||||
* The returned value is a number between 1 and 31 both inclusive.
|
||||
*
|
||||
* @param expr Expression to extract the day from.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DAY(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that extracts the hour field from a given date.
|
||||
* The returned value is a number between 0 and 23 both inclusive.
|
||||
*
|
||||
* @param expr Expression to extract the hour from.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("HOUR(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that extracts the minute field from a given date.
|
||||
* The returned value is a number between 0 and 59 both inclusive.
|
||||
*
|
||||
* @param expr Expression to extract the minute from.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("MINUTE(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
/**
|
||||
* SQL function that extracts the second field from a given date.
|
||||
* The returned value is a number between 0 and 59 both inclusive.
|
||||
*
|
||||
* @param expr Expression to extract the second from.
|
||||
* @param queryBuilder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("SECOND(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
// Cast functions
|
||||
|
||||
/**
|
||||
* SQL function that casts an expression to a specific type.
|
||||
*
|
||||
* @param expr Expression to cast.
|
||||
* @param type Type to cast hte expression to.
|
||||
* @param builder Query builder to append the SQL function to.
|
||||
*/
|
||||
open fun cast(
|
||||
expr: Expression<*>,
|
||||
type: IColumnType,
|
||||
builder: QueryBuilder
|
||||
): Unit = builder {
|
||||
append("CAST(", expr, " AS ", type.sqlType(), ")")
|
||||
}
|
||||
|
||||
open fun<T:String?> ExpressionWithColumnType<T>.match(pattern: String, mode: MatchMode? = null): Op<Boolean> = with(SqlExpressionBuilder) { this@match.like(pattern) }
|
||||
// Commands
|
||||
|
||||
open fun insert(ignore: Boolean, table: Table, columns: List<Column<*>>, expr: String, transaction: Transaction): String {
|
||||
open val DEFAULT_VALUE_EXPRESSION: String = "DEFAULT VALUES"
|
||||
|
||||
/**
|
||||
* Returns the SQL command that inserts a new row into a table.
|
||||
*
|
||||
* **Note:** The `ignore` parameter is not supported by all vendors, please check the documentation.
|
||||
*
|
||||
* @param ignore Whether to ignore errors or not.
|
||||
* @param table Table to insert the new row into.
|
||||
* @param columns Columns to insert the values into.
|
||||
* @param expr Expresion with the values to insert.
|
||||
* @param transaction Transaction where the operation is executed.
|
||||
*/
|
||||
open fun insert(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
columns: List<Column<*>>,
|
||||
expr: String,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (ignore) {
|
||||
transaction.throwUnsupportedException("There's no generic SQL for INSERT IGNORE. There must be vendor specific implementation")
|
||||
transaction.throwUnsupportedException("There's no generic SQL for INSERT IGNORE. There must be vendor specific implementation.")
|
||||
}
|
||||
|
||||
val (columnsExpr, valuesExpr) = if (columns.isNotEmpty()) {
|
||||
@@ -86,30 +347,73 @@ abstract class FunctionProvider {
|
||||
return "INSERT INTO ${transaction.identity(table)} $columnsExpr $valuesExpr"
|
||||
}
|
||||
|
||||
open fun update(targets: ColumnSet, columnsAndValues: List<Pair<Column<*>, Any?>>, limit: Int?, where: Op<Boolean>?, transaction: Transaction): String {
|
||||
return with(QueryBuilder(true)) {
|
||||
+"UPDATE "
|
||||
targets.describe(transaction, this)
|
||||
+" SET "
|
||||
columnsAndValues.appendTo(this) { (col, value) ->
|
||||
append("${transaction.identity(col)}=")
|
||||
registerArgument(col, value)
|
||||
}
|
||||
|
||||
where?.let {
|
||||
+" WHERE "
|
||||
+it
|
||||
}
|
||||
limit?.let { +" LIMIT $it" }
|
||||
toString()
|
||||
/**
|
||||
* Returns the SQL command that updates one or more rows of a table.
|
||||
*
|
||||
* @param targets Column set to update values from.
|
||||
* @param columnsAndValues Pairs of column to update and values to update with.
|
||||
* @param limit Maximum number of rows to update.
|
||||
* @param where Condition that decides the rows to update.
|
||||
* @param transaction Transaction where the operation is executed.
|
||||
*/
|
||||
open fun update(
|
||||
targets: ColumnSet,
|
||||
columnsAndValues: List<Pair<Column<*>, Any?>>,
|
||||
limit: Int?,
|
||||
where: Op<Boolean>?,
|
||||
transaction: Transaction
|
||||
): String = with(QueryBuilder(true)) {
|
||||
+"UPDATE "
|
||||
targets.describe(transaction, this)
|
||||
+" SET "
|
||||
columnsAndValues.appendTo(this) { (col, value) ->
|
||||
append("${transaction.identity(col)}=")
|
||||
registerArgument(col, value)
|
||||
}
|
||||
|
||||
where?.let {
|
||||
+" WHERE "
|
||||
+it
|
||||
}
|
||||
limit?.let { +" LIMIT $it" }
|
||||
toString()
|
||||
}
|
||||
|
||||
open fun delete(ignore: Boolean, table: Table, where: String?, limit: Int?, transaction: Transaction): String {
|
||||
if (ignore) {
|
||||
transaction.throwUnsupportedException("There's no generic SQL for DELETE IGNORE. There must be vendor specific implementation")
|
||||
}
|
||||
/**
|
||||
* Returns the SQL command that insert a new row into a table, but if another row with the same primary/unique key already exists then it updates the values of that row instead.
|
||||
* This operation is also known as "Insert or update".
|
||||
*
|
||||
* **Note:** This operation is not supported by all vendors, please check the documentation.
|
||||
*
|
||||
* @param data Pairs of column to replace and values to replace with.
|
||||
*/
|
||||
open fun replace(
|
||||
table: Table,
|
||||
data: List<Pair<Column<*>, Any?>>,
|
||||
transaction: Transaction
|
||||
): String = transaction.throwUnsupportedException("There's no generic SQL for REPLACE. There must be vendor specific implementation.")
|
||||
|
||||
/**
|
||||
* Returns the SQL command that deletes one or more rows of a table.
|
||||
*
|
||||
* **Note:** The `ignore` parameter is not supported by all vendors, please check the documentation.
|
||||
*
|
||||
* @param ignore Whether to ignore errors or not.
|
||||
* @param table Table to delete rows from.
|
||||
* @param where Condition that decides the rows to update.
|
||||
* @param limit Maximum number of rows to delete.
|
||||
* @param transaction Transaction where the operation is executed.
|
||||
*/
|
||||
open fun delete(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
where: String?,
|
||||
limit: Int?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (ignore) {
|
||||
transaction.throwUnsupportedException("There's no generic SQL for DELETE IGNORE. There must be vendor specific implementation.")
|
||||
}
|
||||
return buildString {
|
||||
append("DELETE FROM ")
|
||||
append(transaction.identity(table))
|
||||
@@ -124,10 +428,14 @@ abstract class FunctionProvider {
|
||||
}
|
||||
}
|
||||
|
||||
open fun replace(table: Table, data: List<Pair<Column<*>, Any?>>, transaction: Transaction): String
|
||||
= transaction.throwUnsupportedException("There's no generic SQL for replace. There must be vendor specific implementation")
|
||||
|
||||
open fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean) = buildString {
|
||||
/**
|
||||
* Returns the SQL command that limits and offsets the result of a query.
|
||||
*
|
||||
* @param size The limit of rows to return.
|
||||
* @param offset The number of rows to skip.
|
||||
* @param alreadyOrdered Whether the query is already ordered or not.
|
||||
*/
|
||||
open fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean): String = buildString {
|
||||
if (size > 0) {
|
||||
append("LIMIT $size")
|
||||
if (offset > 0) {
|
||||
@@ -135,155 +443,115 @@ abstract class FunctionProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("GROUP_CONCAT(")
|
||||
if (expr.distinct)
|
||||
append("DISTINCT ")
|
||||
append(expr.expr)
|
||||
if (expr.orderBy.isNotEmpty()) {
|
||||
expr.orderBy.toList().appendTo(prefix = " ORDER BY ") {
|
||||
append(it.first, " ", it.second.name)
|
||||
}
|
||||
}
|
||||
expr.separator?.let {
|
||||
append(" SEPARATOR '$it'")
|
||||
}
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T:String?> regexp(expr1: Expression<T>, pattern: Expression<String>, caseSensitive: Boolean, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("REGEXP_LIKE(")
|
||||
append(expr1)
|
||||
append(", ")
|
||||
append(pattern)
|
||||
append(", ")
|
||||
if (caseSensitive)
|
||||
append("'c'")
|
||||
else
|
||||
append("'i'")
|
||||
append(")")
|
||||
}
|
||||
|
||||
interface MatchMode {
|
||||
fun mode() : String
|
||||
}
|
||||
|
||||
open fun <T:String?> concat(separator: String, queryBuilder: QueryBuilder, vararg expr: Expression<T>) = queryBuilder {
|
||||
if (separator == "")
|
||||
append("CONCAT(")
|
||||
else {
|
||||
append("CONCAT_WS(")
|
||||
append("'")
|
||||
append(separator)
|
||||
append("',")
|
||||
}
|
||||
expr.toList().appendTo { +it }
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("YEAR(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("MONTH(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DAY(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("HOUR(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("MINUTE(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
open fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("SECOND(")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* type:
|
||||
* @see java.sql.Types
|
||||
* Represents metadata information about a specific column.
|
||||
*/
|
||||
data class ColumnMetadata(val name: String, val type: Int, val nullable: Boolean, val size: Int?)
|
||||
data class ColumnMetadata(
|
||||
/** Name of the column. */
|
||||
val name: String,
|
||||
/**
|
||||
* Type of the column.
|
||||
*
|
||||
* @see java.sql.Types
|
||||
*/
|
||||
val type: Int,
|
||||
/** Whether the column if nullable or not. */
|
||||
val nullable: Boolean,
|
||||
/** Optional size of the column. */
|
||||
val size: Int?
|
||||
)
|
||||
|
||||
/**
|
||||
* Common interface for all database dialects.
|
||||
*/
|
||||
interface DatabaseDialect {
|
||||
/** Name of this dialect. */
|
||||
val name: String
|
||||
/** Data type provider of this dialect. */
|
||||
val dataTypeProvider: DataTypeProvider
|
||||
/** Function provider of this dialect. */
|
||||
val functionProvider: FunctionProvider
|
||||
/** Returns `true` if the dialect supports the `IF EXISTS`/`IF NOT EXISTS` option when creating, altering or dropping objects, `false` otherwise. */
|
||||
val supportsIfNotExists: Boolean get() = true
|
||||
/** Returns `true` if the dialect supports the creation of sequences, `false` otherwise. */
|
||||
val supportsCreateSequence: Boolean get() = true
|
||||
/** Returns `true` if the dialect requires the use of a sequence to create an auto-increment column, `false` otherwise. */
|
||||
val needsSequenceToAutoInc: Boolean get() = false
|
||||
/** Returns the default reference option for the dialect. */
|
||||
val defaultReferenceOption: ReferenceOption get() = ReferenceOption.RESTRICT
|
||||
/** Returns `true` if the dialect requires the use of quotes when using symbols in object names, `false` otherwise. */
|
||||
val needsQuotesWhenSymbolsInNames: Boolean get() = true
|
||||
/** Returns `true` if the dialect supports returning multiple generated keys as a result of an insert operation, `false` otherwise. */
|
||||
val supportsMultipleGeneratedKeys: Boolean
|
||||
/** Returns`true` if the dialect supports returning generated keys obtained from a sequence. */
|
||||
val supportsSequenceAsGeneratedKeys: Boolean get() = supportsCreateSequence
|
||||
val supportsOnlyIdentifiersInGeneratedKeys: Boolean get() = false
|
||||
|
||||
/** Returns the name of the current database. */
|
||||
fun getDatabase(): String
|
||||
|
||||
/** Returns a list with the names of all the defined tables. */
|
||||
fun allTablesNames(): List<String>
|
||||
/**
|
||||
* returns list of pairs (column name + nullable) for every table
|
||||
*/
|
||||
fun tableColumns(vararg tables: Table): Map<Table, List<ColumnMetadata>> = emptyMap()
|
||||
|
||||
/**
|
||||
* returns map of constraint for a table name/column name pair
|
||||
*/
|
||||
fun columnConstraints(vararg tables: Table): Map<TableAndColumnName, List<ForeignKeyConstraint>> = emptyMap()
|
||||
|
||||
/**
|
||||
* return set of indices for each table
|
||||
*/
|
||||
fun existingIndices(vararg tables: Table): Map<Table, List<Index>> = emptyMap()
|
||||
|
||||
/** Checks if the specified table exists in the database. */
|
||||
fun tableExists(table: Table): Boolean
|
||||
|
||||
fun checkTableMapping(table: Table) = true
|
||||
fun checkTableMapping(table: Table): Boolean = true
|
||||
|
||||
fun resetCaches()
|
||||
/** Returns a map with the column metadata of all the defined columns in each of the specified [tables]. */
|
||||
fun tableColumns(vararg tables: Table): Map<Table, List<ColumnMetadata>> = emptyMap()
|
||||
|
||||
/** Returns a map with the foreign key constraints of all the defined columns in each of the specified [tables]. */
|
||||
fun columnConstraints(vararg tables: Table): Map<TableAndColumnName, List<ForeignKeyConstraint>> = emptyMap()
|
||||
|
||||
/** Returns a map with all the defined indices in each of the specified [tables]. */
|
||||
fun existingIndices(vararg tables: Table): Map<Table, List<Index>> = emptyMap()
|
||||
|
||||
/** Returns `true` if the dialect supports `SELECT FOR UPDATE` statements, `false` otherwise. */
|
||||
fun supportsSelectForUpdate(): Boolean
|
||||
val supportsMultipleGeneratedKeys: Boolean
|
||||
fun isAllowedAsColumnDefault(e: Expression<*>) = e is LiteralOp<*>
|
||||
|
||||
val supportsIfNotExists: Boolean get() = true
|
||||
val needsQuotesWhenSymbolsInNames: Boolean get() = true
|
||||
/** Returns `true` if the specified [e] is allowed as a default column value in the dialect, `false` otherwise. */
|
||||
fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = e is LiteralOp<*>
|
||||
|
||||
/** Returns the catalog name of the connection of the specified [transaction]. */
|
||||
fun catalog(transaction: Transaction): String = transaction.connection.catalog
|
||||
|
||||
|
||||
val defaultReferenceOption : ReferenceOption get() = ReferenceOption.RESTRICT
|
||||
|
||||
val supportsOnlyIdentifiersInGeneratedKeys get() = false
|
||||
|
||||
val supportsCreateSequence get() = true
|
||||
val needsSequenceToAutoInc: Boolean get() = false
|
||||
val supportsSequenceAsGeneratedKeys: Boolean get() = supportsCreateSequence
|
||||
/** Clears any cached values. */
|
||||
fun resetCaches()
|
||||
|
||||
// Specific SQL statements
|
||||
|
||||
/** Returns the SQL command that creates the specified [index]. */
|
||||
fun createIndex(index: Index): String
|
||||
fun dropIndex(tableName: String, indexName: String): String
|
||||
fun modifyColumn(column: Column<*>) : String
|
||||
|
||||
fun createSequence(identifier: String,
|
||||
startWith: Int?,
|
||||
incrementBy: Int?,
|
||||
minValue: Int?,
|
||||
maxValue: Int?,
|
||||
cycle: Boolean?,
|
||||
cache: Int?): String = buildString {
|
||||
/** Returns the SQL command that drops the specified [indexName] from the specified [tableName]. */
|
||||
fun dropIndex(tableName: String, indexName: String): String
|
||||
|
||||
/** Returns the SQL command that modifies the specified [column]. */
|
||||
fun modifyColumn(column: Column<*>): String
|
||||
|
||||
/**
|
||||
* Returns the SQL command that creates sequence with the specified properties.
|
||||
*
|
||||
* @param identifier Name of the sequence.
|
||||
* @param startWith Beginning of the sequence.
|
||||
* @param incrementBy Value is added to the current sequence value to create a new value.
|
||||
* @param minValue Minimum value a sequence can generate.
|
||||
* @param maxValue Maximum value for the sequence.
|
||||
* @param cycle Allows the sequence to wrap around when the [maxValue] or [minValue] has been reached by an ascending or descending sequence respectively.
|
||||
* @param cache Specifies how many sequence numbers are to be preallocated and stored in memory for faster access.
|
||||
*/
|
||||
fun createSequence(
|
||||
identifier: String,
|
||||
startWith: Int?,
|
||||
incrementBy: Int?,
|
||||
minValue: Int?,
|
||||
maxValue: Int?,
|
||||
cycle: Boolean?,
|
||||
cache: Int?
|
||||
): String = buildString {
|
||||
append("CREATE SEQUENCE ")
|
||||
if (currentDialect.supportsIfNotExists) {
|
||||
append("IF NOT EXISTS ")
|
||||
@@ -301,19 +569,26 @@ interface DatabaseDialect {
|
||||
appendIfNotNull(" CACHE", cache)
|
||||
}
|
||||
|
||||
fun StringBuilder.appendIfNotNull(str1: String, str2: Any?) = apply {
|
||||
/** Appends both [str1] and [str2] to the receiver [StringBuilder] if [str2] is not `null`. */
|
||||
fun StringBuilder.appendIfNotNull(str1: String, str2: Any?): StringBuilder = apply {
|
||||
if (str2 != null) {
|
||||
this.append("$str1 $str2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class VendorDialect(override val name: String,
|
||||
override val dataTypeProvider: DataTypeProvider,
|
||||
override val functionProvider: FunctionProvider) : DatabaseDialect {
|
||||
/**
|
||||
* Base implementation of a vendor dialect
|
||||
*/
|
||||
abstract class VendorDialect(
|
||||
override val name: String,
|
||||
override val dataTypeProvider: DataTypeProvider,
|
||||
override val functionProvider: FunctionProvider
|
||||
) : DatabaseDialect {
|
||||
|
||||
/* Cached values */
|
||||
private var _allTableNames: List<String>? = null
|
||||
/** Returns a list with the names of all the defined tables. */
|
||||
val allTablesNames: List<String>
|
||||
get() {
|
||||
if (_allTableNames == null) {
|
||||
@@ -322,24 +597,22 @@ abstract class VendorDialect(override val name: String,
|
||||
return _allTableNames!!
|
||||
}
|
||||
|
||||
/* Method always re-read data from DB. Using allTablesNames field is preferred way */
|
||||
override fun allTablesNames(): List<String> = TransactionManager.current().connection.metadata { tableNames }
|
||||
override val supportsMultipleGeneratedKeys: Boolean = true
|
||||
|
||||
override fun getDatabase(): String = catalog(TransactionManager.current())
|
||||
|
||||
override fun tableExists(table: Table) = allTablesNames.any { it == table.nameInDatabaseCase() }
|
||||
/**
|
||||
* Returns a list with the names of all the defined tables.
|
||||
* This method always re-read data from DB.
|
||||
* Using `allTablesNames` field is the preferred way.
|
||||
*/
|
||||
override fun allTablesNames(): List<String> = TransactionManager.current().connection.metadata { tableNames }
|
||||
|
||||
override fun tableExists(table: Table): Boolean = allTablesNames.any { it == table.nameInDatabaseCase() }
|
||||
|
||||
override fun tableColumns(vararg tables: Table): Map<Table, List<ColumnMetadata>> =
|
||||
TransactionManager.current().connection.metadata { columns(*tables) }
|
||||
TransactionManager.current().connection.metadata { columns(*tables) }
|
||||
|
||||
protected fun String.quoteIdentifierWhenWrongCaseOrNecessary(tr: Transaction) =
|
||||
tr.db.identifierManager.quoteIdentifierWhenWrongCaseOrNecessary(this)
|
||||
|
||||
protected val columnConstraintsCache = ConcurrentHashMap<String, List<ForeignKeyConstraint>>()
|
||||
|
||||
protected open fun fillConstraintCacheForTables(tables: List<Table>) {
|
||||
columnConstraintsCache.putAll(TransactionManager.current().db.metadata { tableConstraints(tables) })
|
||||
}
|
||||
override fun columnConstraints(vararg tables: Table): Map<Pair<String, String>, List<ForeignKeyConstraint>> {
|
||||
val constraints = HashMap<Pair<String, String>, MutableList<ForeignKeyConstraint>>()
|
||||
|
||||
@@ -348,15 +621,26 @@ abstract class VendorDialect(override val name: String,
|
||||
fillConstraintCacheForTables(tablesToLoad)
|
||||
tables.forEach { table ->
|
||||
columnConstraintsCache[table.nameInDatabaseCase()].orEmpty().forEach {
|
||||
constraints.getOrPut(it.fromTable to it.fromColumn){arrayListOf()}.add(it)
|
||||
constraints.getOrPut(it.fromTable to it.fromColumn) { arrayListOf() }.add(it)
|
||||
}
|
||||
|
||||
}
|
||||
return constraints
|
||||
}
|
||||
|
||||
override fun existingIndices(vararg tables: Table): Map<Table, List<Index>>
|
||||
= TransactionManager.current().db.metadata { existingIndices(*tables) }
|
||||
override fun existingIndices(vararg tables: Table): Map<Table, List<Index>> = TransactionManager.current().db.metadata { existingIndices(*tables) }
|
||||
|
||||
private val supportsSelectForUpdate: Boolean by lazy { TransactionManager.current().db.metadata { supportsSelectForUpdate } }
|
||||
|
||||
override fun supportsSelectForUpdate(): Boolean = supportsSelectForUpdate
|
||||
|
||||
protected fun String.quoteIdentifierWhenWrongCaseOrNecessary(tr: Transaction): String =
|
||||
tr.db.identifierManager.quoteIdentifierWhenWrongCaseOrNecessary(this)
|
||||
|
||||
protected val columnConstraintsCache: MutableMap<String, List<ForeignKeyConstraint>> = ConcurrentHashMap()
|
||||
|
||||
protected open fun fillConstraintCacheForTables(tables: List<Table>): Unit =
|
||||
columnConstraintsCache.putAll(TransactionManager.current().db.metadata { tableConstraints(tables) })
|
||||
|
||||
override fun resetCaches() {
|
||||
_allTableNames = null
|
||||
@@ -382,21 +666,18 @@ abstract class VendorDialect(override val name: String,
|
||||
return "ALTER TABLE ${identifierManager.quoteIfNecessary(tableName)} DROP CONSTRAINT ${identifierManager.quoteIfNecessary(indexName)}"
|
||||
}
|
||||
|
||||
private val supportsSelectForUpdate by lazy { TransactionManager.current().db.metadata { supportsSelectForUpdate } }
|
||||
override fun supportsSelectForUpdate() = supportsSelectForUpdate
|
||||
|
||||
override val supportsMultipleGeneratedKeys: Boolean = true
|
||||
|
||||
override fun modifyColumn(column: Column<*>): String = "MODIFY COLUMN ${column.descriptionDdl()}"
|
||||
|
||||
}
|
||||
|
||||
/** Returns the dialect used in the current transaction, may trow an exception if there is no current transaction. */
|
||||
val currentDialect: DatabaseDialect get() = TransactionManager.current().db.dialect
|
||||
|
||||
internal val currentDialectIfAvailable : DatabaseDialect? get() =
|
||||
if (TransactionManager.isInitialized() && TransactionManager.currentOrNull() != null) {
|
||||
internal val currentDialectIfAvailable: DatabaseDialect?
|
||||
get() = if (TransactionManager.isInitialized() && TransactionManager.currentOrNull() != null) {
|
||||
currentDialect
|
||||
} else null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
internal fun String.inProperCase(): String =
|
||||
TransactionManager.currentOrNull()?.db?.identifierManager?.inProperCase(this@inProperCase) ?: this
|
||||
|
||||
@@ -9,49 +9,40 @@ import java.sql.Wrapper
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
internal object H2DataTypeProvider : DataTypeProvider() {
|
||||
override fun uuidType(): String = "UUID"
|
||||
private val Transaction.isMySQLMode: Boolean
|
||||
get() {
|
||||
val h2Connection = (connection.connection as? JdbcConnection)
|
||||
?: (connection.connection as? Wrapper)?.takeIf { it.isWrapperFor(JdbcConnection::class.java) }?.unwrap(JdbcConnection::class.java)
|
||||
|
||||
return h2Connection?.let { !it.isClosed && it.settings.mode.enum == Mode.ModeEnum.MySQL } == true
|
||||
}
|
||||
|
||||
internal object H2DataTypeProvider : DataTypeProvider() {
|
||||
override fun binaryType(): String {
|
||||
exposedLogger.error("The length of the Binary column is missing.")
|
||||
error("The length of the Binary column is missing.")
|
||||
}
|
||||
|
||||
override fun uuidType(): String = "UUID"
|
||||
}
|
||||
|
||||
private val Transaction.isMySQLMode: Boolean
|
||||
get() {
|
||||
val h2Connection = (connection.connection as? JdbcConnection)
|
||||
?: (connection.connection as? Wrapper)?.takeIf { it.isWrapperFor(JdbcConnection::class.java) }?.unwrap(JdbcConnection::class.java)
|
||||
|
||||
return h2Connection?.let {
|
||||
!it.isClosed && it.settings.mode.enum == Mode.ModeEnum.MySQL
|
||||
} == true
|
||||
}
|
||||
|
||||
|
||||
internal object H2FunctionProvider : FunctionProvider() {
|
||||
|
||||
private fun dbReleaseDate(transaction: Transaction) : Date {
|
||||
private fun dbReleaseDate(transaction: Transaction): Date {
|
||||
val releaseDate = transaction.db.metadata { databaseProductVersion.substringAfterLast('(').substringBeforeLast(')') }
|
||||
val formatter = SimpleDateFormat("yyyy-MM-dd")
|
||||
return formatter.parse(releaseDate)
|
||||
}
|
||||
|
||||
override fun replace(table: Table, data: List<Pair<Column<*>, Any?>>, transaction: Transaction): String {
|
||||
if (!transaction.isMySQLMode) transaction.throwUnsupportedException("REPLACE is only supported in MySQL compatibility mode for H2")
|
||||
|
||||
val builder = QueryBuilder(true)
|
||||
data.appendTo(builder) { registerArgument(it.first.columnType, it.second) }
|
||||
val values = builder.toString()
|
||||
|
||||
val preparedValues = data.map { transaction.identity(it.first) to it.first.columnType.valueToString(it.second) }
|
||||
|
||||
return "INSERT INTO ${transaction.identity(table)} (${preparedValues.joinToString { it.first }}) VALUES ($values) ON DUPLICATE KEY UPDATE ${preparedValues.joinToString { "${it.first}=${it.second}" }}"
|
||||
}
|
||||
|
||||
override fun insert(ignore: Boolean, table: Table, columns: List<Column<*>>, expr: String, transaction: Transaction): String {
|
||||
override fun insert(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
columns: List<Column<*>>,
|
||||
expr: String,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
val uniqueIdxCols = table.indices.filter { it.unique }.flatMap { it.columns.toList() }
|
||||
val uniqueCols = columns.filter { it.indexInPK != null || it in uniqueIdxCols}
|
||||
val uniqueCols = columns.filter { it.indexInPK != null || it in uniqueIdxCols }
|
||||
val borderDate = Date(118, 2, 18)
|
||||
return when {
|
||||
// INSERT IGNORE support added in H2 version 1.4.197 (2018-03-18)
|
||||
@@ -65,33 +56,55 @@ internal object H2FunctionProvider : FunctionProvider() {
|
||||
else -> super.insert(ignore, table, columns, expr, transaction)
|
||||
}
|
||||
}
|
||||
|
||||
override fun replace(
|
||||
table: Table,
|
||||
data: List<Pair<Column<*>, Any?>>,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (!transaction.isMySQLMode) {
|
||||
transaction.throwUnsupportedException("REPLACE is only supported in MySQL compatibility mode for H2")
|
||||
}
|
||||
|
||||
val builder = QueryBuilder(true)
|
||||
data.appendTo(builder) { registerArgument(it.first.columnType, it.second) }
|
||||
val values = builder.toString()
|
||||
|
||||
val preparedValues = data.map { transaction.identity(it.first) to it.first.columnType.valueToString(it.second) }
|
||||
|
||||
return "INSERT INTO ${transaction.identity(table)} (${preparedValues.joinToString { it.first }}) VALUES ($values) ON DUPLICATE KEY UPDATE ${preparedValues.joinToString { "${it.first}=${it.second}" }}"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* H2 dialect implementation.
|
||||
*/
|
||||
open class H2Dialect : VendorDialect(dialectName, H2DataTypeProvider, H2FunctionProvider) {
|
||||
|
||||
override val name: String
|
||||
get() = when (TransactionManager.currentOrNull()?.isMySQLMode) {
|
||||
true -> "$dialectName (Mysql Mode)"
|
||||
else -> dialectName
|
||||
}
|
||||
|
||||
override val supportsMultipleGeneratedKeys: Boolean = false
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean get() = !TransactionManager.current().isMySQLMode
|
||||
|
||||
override fun existingIndices(vararg tables: Table): Map<Table, List<Index>> =
|
||||
super.existingIndices(*tables).mapValues { entry -> entry.value.filterNot { it.indexName.startsWith("PRIMARY_KEY_") } }.filterValues { it.isNotEmpty() }
|
||||
|
||||
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true
|
||||
|
||||
override val supportsMultipleGeneratedKeys: Boolean = false
|
||||
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys get() = !TransactionManager.current().isMySQLMode
|
||||
|
||||
override fun existingIndices(vararg tables: Table): Map<Table, List<Index>> =
|
||||
super.existingIndices(*tables).mapValues { it.value.filterNot { it.indexName.startsWith("PRIMARY_KEY_") } }.filterValues { it.isNotEmpty() }
|
||||
|
||||
override fun createIndex(index: Index): String {
|
||||
if (index.columns.any { it.columnType is TextColumnType }) {
|
||||
exposedLogger.warn("Index on ${index.table.tableName} for ${index.columns.joinToString {it.name}} can't be created in H2")
|
||||
exposedLogger.warn("Index on ${index.table.tableName} for ${index.columns.joinToString { it.name }} can't be created in H2")
|
||||
return ""
|
||||
}
|
||||
return super.createIndex(index)
|
||||
}
|
||||
|
||||
override val name: String
|
||||
get() = when {
|
||||
TransactionManager.currentOrNull()?.isMySQLMode == true -> "$dialectName (Mysql Mode)"
|
||||
else -> dialectName
|
||||
}
|
||||
companion object {
|
||||
const val dialectName = "h2"
|
||||
/** H2 dialect name */
|
||||
const val dialectName: String = "h2"
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,21 +4,31 @@ import org.jetbrains.exposed.sql.Expression
|
||||
import org.jetbrains.exposed.sql.QueryBuilder
|
||||
import org.jetbrains.exposed.sql.Sequence
|
||||
|
||||
internal object MariaDBFunctionProvider : MysqlFunctionProvider() {
|
||||
override fun <T : String?> regexp(expr1: Expression<T>, pattern: Expression<String>, caseSensitive: Boolean, queryBuilder: QueryBuilder) {
|
||||
queryBuilder{ append(expr1, " REGEXP ", pattern) }
|
||||
}
|
||||
|
||||
internal object MariaDBFunctionProvider : MysqlFunctionProvider() {
|
||||
override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
|
||||
append("NEXTVAL(", seq.identifier, ")")
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(
|
||||
expr1: Expression<T>,
|
||||
pattern: Expression<String>,
|
||||
caseSensitive: Boolean,
|
||||
queryBuilder: QueryBuilder
|
||||
): Unit = queryBuilder {
|
||||
append(expr1, " REGEXP ", pattern)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MariaDB dialect implementation.
|
||||
*/
|
||||
class MariaDBDialect : MysqlDialect() {
|
||||
override val functionProvider : FunctionProvider = MariaDBFunctionProvider
|
||||
override val name: String = dialectName
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys = true
|
||||
override val functionProvider: FunctionProvider = MariaDBFunctionProvider
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true
|
||||
|
||||
companion object {
|
||||
const val dialectName = "mariadb"
|
||||
/** MariaDB dialect name */
|
||||
const val dialectName: String = "mariadb"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,55 @@ import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.math.BigDecimal
|
||||
|
||||
|
||||
internal object MysqlDataTypeProvider : DataTypeProvider() {
|
||||
override fun dateTimeType(): String = if ((currentDialect as MysqlDialect).isFractionDateTimeSupported()) "DATETIME(6)" else "DATETIME"
|
||||
|
||||
override fun binaryType(): String {
|
||||
exposedLogger.error("The length of the Binary column is missing.")
|
||||
error("The length of the Binary column is missing.")
|
||||
}
|
||||
|
||||
override fun dateTimeType(): String = if ((currentDialect as MysqlDialect).isFractionDateTimeSupported()) "DATETIME(6)" else "DATETIME"
|
||||
}
|
||||
|
||||
internal open class MysqlFunctionProvider : FunctionProvider() {
|
||||
internal object INSTANSE : MysqlFunctionProvider()
|
||||
internal object INSTANCE : MysqlFunctionProvider()
|
||||
|
||||
override fun random(seed: Int?): String = "RAND(${seed?.toString().orEmpty()})"
|
||||
|
||||
private class MATCH(val expr: ExpressionWithColumnType<*>, val pattern: String, val mode: MatchMode) : Op<Boolean>() {
|
||||
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("MATCH(", expr, ") AGAINST ('", pattern, "' ", mode.mode(), ")")
|
||||
}
|
||||
}
|
||||
|
||||
private enum class MysqlMatchMode(val operator: String) : MatchMode {
|
||||
STRICT("IN BOOLEAN MODE"),
|
||||
NATURAL_LANGUAGE("IN NATURAL LANGUAGE MODE");
|
||||
|
||||
override fun mode() = operator
|
||||
}
|
||||
|
||||
override fun <T : String?> ExpressionWithColumnType<T>.match(pattern: String, mode: MatchMode?): Op<Boolean> =
|
||||
MATCH(this, pattern, mode ?: MysqlMatchMode.STRICT)
|
||||
|
||||
override fun <T : String?> regexp(
|
||||
expr1: Expression<T>,
|
||||
pattern: Expression<String>,
|
||||
caseSensitive: Boolean,
|
||||
queryBuilder: QueryBuilder
|
||||
) {
|
||||
return if ((currentDialect as MysqlDialect).isMysql8) {
|
||||
super.regexp(expr1, pattern, caseSensitive, queryBuilder)
|
||||
} else {
|
||||
queryBuilder { append(expr1, " REGEXP ", pattern) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun replace(table: Table, data: List<Pair<Column<*>, Any?>>, transaction: Transaction): String {
|
||||
val builder = QueryBuilder(true)
|
||||
val columns = data.joinToString { transaction.identity(it.first) }
|
||||
val values = builder.apply { data.appendTo { registerArgument(it.first.columnType, it.second) } }.toString()
|
||||
return "REPLACE INTO ${transaction.identity(table)} ($columns) VALUES ($values)"
|
||||
}
|
||||
|
||||
private object CharColumnType : StringColumnType() {
|
||||
override fun sqlType(): String = "CHAR"
|
||||
@@ -26,17 +63,6 @@ internal open class MysqlFunctionProvider : FunctionProvider() {
|
||||
else -> super.cast(expr, type, builder)
|
||||
}
|
||||
|
||||
override fun random(seed: Int?) = "RAND(${seed?.toString().orEmpty()})"
|
||||
|
||||
override fun <T : String?> ExpressionWithColumnType<T>.match(pattern: String, mode: MatchMode?): Op<Boolean> = MATCH(this, pattern, mode ?: MysqlMatchMode.STRICT)
|
||||
|
||||
override fun replace(table: Table, data: List<Pair<Column<*>, Any?>>, transaction: Transaction): String {
|
||||
val builder = QueryBuilder(true)
|
||||
val columns = data.joinToString { transaction.identity(it.first) }
|
||||
val values = builder.apply { data.appendTo { registerArgument(it.first.columnType, it.second) } }.toString()
|
||||
return "REPLACE INTO ${transaction.identity(table)} ($columns) VALUES ($values)"
|
||||
}
|
||||
|
||||
override val DEFAULT_VALUE_EXPRESSION: String = "() VALUES ()"
|
||||
|
||||
override fun insert(ignore: Boolean, table: Table, columns: List<Column<*>>, expr: String, transaction: Transaction): String {
|
||||
@@ -48,30 +74,20 @@ internal open class MysqlFunctionProvider : FunctionProvider() {
|
||||
val def = super.delete(false, table, where, limit, transaction)
|
||||
return if (ignore) def.replaceFirst("DELETE", "DELETE IGNORE") else def
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(expr1: Expression<T>, pattern: Expression<String>, caseSensitive: Boolean, queryBuilder: QueryBuilder) {
|
||||
return if((currentDialect as MysqlDialect).isMysql8)
|
||||
super.regexp(expr1, pattern, caseSensitive, queryBuilder)
|
||||
else
|
||||
queryBuilder { append(expr1, " REGEXP ", pattern)}
|
||||
}
|
||||
|
||||
private class MATCH(val expr: ExpressionWithColumnType<*>, val pattern: String, val mode: MatchMode) : Op<Boolean>() {
|
||||
override fun toQueryBuilder(queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("MATCH(", expr, ") AGAINST ('", pattern, "' ", mode.mode(),")")
|
||||
}
|
||||
}
|
||||
|
||||
private enum class MysqlMatchMode(val operator: String) : MatchMode {
|
||||
STRICT("IN BOOLEAN MODE"),
|
||||
NATURAL_LANGUAGE("IN NATURAL LANGUAGE MODE");
|
||||
|
||||
override fun mode() = operator
|
||||
}
|
||||
}
|
||||
|
||||
open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, MysqlFunctionProvider.INSTANSE) {
|
||||
override val supportsCreateSequence = false
|
||||
/**
|
||||
* MySQL dialect implementation.
|
||||
*/
|
||||
open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, MysqlFunctionProvider.INSTANCE) {
|
||||
|
||||
internal val isMysql8: Boolean by lazy {
|
||||
TransactionManager.current().db.isVersionCovers(BigDecimal("8.0"))
|
||||
}
|
||||
|
||||
override val supportsCreateSequence: Boolean = false
|
||||
|
||||
fun isFractionDateTimeSupported(): Boolean = TransactionManager.current().db.isVersionCovers(BigDecimal("5.6"))
|
||||
|
||||
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean {
|
||||
if (super.isAllowedAsColumnDefault(e)) return true
|
||||
@@ -86,21 +102,22 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq
|
||||
val schemaName = "'${getDatabase()}'"
|
||||
val constraintsToLoad = HashMap<String, MutableList<ForeignKeyConstraint>>()
|
||||
tr.exec(
|
||||
"SELECT\n" +
|
||||
" rc.CONSTRAINT_NAME,\n" +
|
||||
" ku.TABLE_NAME,\n" +
|
||||
" ku.COLUMN_NAME,\n" +
|
||||
" ku.REFERENCED_TABLE_NAME,\n" +
|
||||
" ku.REFERENCED_COLUMN_NAME,\n" +
|
||||
" rc.UPDATE_RULE,\n" +
|
||||
" rc.DELETE_RULE\n" +
|
||||
"FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc\n" +
|
||||
" INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku\n" +
|
||||
" ON ku.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA AND rc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME\n" +
|
||||
"WHERE ku.TABLE_SCHEMA = $schemaName " +
|
||||
" AND ku.CONSTRAINT_SCHEMA = $schemaName" +
|
||||
" AND rc.CONSTRAINT_SCHEMA = $schemaName" +
|
||||
" AND $inTableList") { rs ->
|
||||
"SELECT\n" +
|
||||
" rc.CONSTRAINT_NAME,\n" +
|
||||
" ku.TABLE_NAME,\n" +
|
||||
" ku.COLUMN_NAME,\n" +
|
||||
" ku.REFERENCED_TABLE_NAME,\n" +
|
||||
" ku.REFERENCED_COLUMN_NAME,\n" +
|
||||
" rc.UPDATE_RULE,\n" +
|
||||
" rc.DELETE_RULE\n" +
|
||||
"FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc\n" +
|
||||
" INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE ku\n" +
|
||||
" ON ku.TABLE_SCHEMA = rc.CONSTRAINT_SCHEMA AND rc.CONSTRAINT_NAME = ku.CONSTRAINT_NAME\n" +
|
||||
"WHERE ku.TABLE_SCHEMA = $schemaName " +
|
||||
" AND ku.CONSTRAINT_SCHEMA = $schemaName" +
|
||||
" AND rc.CONSTRAINT_SCHEMA = $schemaName" +
|
||||
" AND $inTableList"
|
||||
) { rs ->
|
||||
while (rs.next()) {
|
||||
val fromTableName = rs.getString("TABLE_NAME")!!
|
||||
if (fromTableName !in allTableNames) continue
|
||||
@@ -111,10 +128,12 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq
|
||||
val constraintUpdateRule = ReferenceOption.valueOf(rs.getString("UPDATE_RULE")!!.replace(" ", "_"))
|
||||
val constraintDeleteRule = ReferenceOption.valueOf(rs.getString("DELETE_RULE")!!.replace(" ", "_"))
|
||||
constraintsToLoad.getOrPut(fromTableName) { arrayListOf() }.add(
|
||||
ForeignKeyConstraint(constraintName,
|
||||
targetTableName, targetColumnName,
|
||||
fromTableName, fromColumnName,
|
||||
constraintUpdateRule, constraintDeleteRule)
|
||||
ForeignKeyConstraint(
|
||||
constraintName,
|
||||
targetTableName, targetColumnName,
|
||||
fromTableName, fromColumnName,
|
||||
constraintUpdateRule, constraintDeleteRule
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -124,13 +143,8 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq
|
||||
|
||||
override fun dropIndex(tableName: String, indexName: String): String = "ALTER TABLE $tableName DROP INDEX $indexName"
|
||||
|
||||
fun isFractionDateTimeSupported() = TransactionManager.current().db.isVersionCovers(BigDecimal("5.6"))
|
||||
|
||||
internal val isMysql8 by lazy {
|
||||
TransactionManager.current().db.isVersionCovers(BigDecimal("8.0"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val dialectName = "mysql"
|
||||
/** MySQL dialect name */
|
||||
const val dialectName: String = "mysql"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,34 +5,23 @@ import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
|
||||
internal object OracleDataTypeProvider : DataTypeProvider() {
|
||||
|
||||
override fun integerAutoincType() = "NUMBER(12)"
|
||||
|
||||
override fun integerType() = "NUMBER(12)"
|
||||
|
||||
override fun longAutoincType() = "NUMBER(19)"
|
||||
|
||||
override fun longType() = "NUMBER(19)"
|
||||
|
||||
override fun dateTimeType() = "TIMESTAMP"
|
||||
|
||||
override fun uuidType() = "RAW(16)"
|
||||
|
||||
override fun textType() = "CLOB"
|
||||
|
||||
override fun blobType() = "BLOB"
|
||||
|
||||
override fun integerType(): String = "NUMBER(12)"
|
||||
override fun integerAutoincType(): String = "NUMBER(12)"
|
||||
override fun longType(): String = "NUMBER(19)"
|
||||
override fun longAutoincType(): String = "NUMBER(19)"
|
||||
override fun textType(): String = "CLOB"
|
||||
override fun binaryType(): String = "BLOB"
|
||||
override fun binaryType(length: Int): String {
|
||||
exposedLogger.warn("The length of the binary column is not required.")
|
||||
return binaryType()
|
||||
}
|
||||
|
||||
override fun binaryType(): String = "BLOB"
|
||||
|
||||
override fun booleanType() = "CHAR(1)"
|
||||
|
||||
override val blobAsStream = true
|
||||
override fun blobType(): String = "BLOB"
|
||||
override fun uuidType(): String = "RAW(16)"
|
||||
override fun dateTimeType(): String = "TIMESTAMP"
|
||||
override fun booleanType(): String = "CHAR(1)"
|
||||
override fun booleanToStatementString(bool: Boolean) = if (bool) "1" else "0"
|
||||
|
||||
override fun booleanFromStringToBoolean(value: String): Boolean = try {
|
||||
value.toLong() != 0L
|
||||
} catch (ex: NumberFormatException) {
|
||||
@@ -43,28 +32,97 @@ internal object OracleDataTypeProvider : DataTypeProvider() {
|
||||
e is LiteralOp<*> && e.columnType is IDateColumnType -> "DATE ${super.processForDefaultValue(e)}"
|
||||
else -> super.processForDefaultValue(e)
|
||||
}
|
||||
|
||||
override val blobAsStream = true
|
||||
}
|
||||
|
||||
internal object OracleFunctionProvider : FunctionProvider() {
|
||||
|
||||
override fun<T:String?> substring(expr: Expression<T>, start: Expression<Int>, length: Expression<Int>, builder: QueryBuilder, prefix: String) =
|
||||
super.substring(expr, start, length, builder, "SUBSTR")
|
||||
/**
|
||||
* SQL function that generates a random value uniformly distributed between 0 (inclusive) and 1 (exclusive).
|
||||
*
|
||||
* **Note:** Oracle ignores the [seed]. You have to use the `dbms_random.seed` function manually.
|
||||
*/
|
||||
override fun random(seed: Int?): String = "dbms_random.value"
|
||||
|
||||
override fun update(targets: ColumnSet, columnsAndValues: List<Pair<Column<*>, Any?>>, limit: Int?, where: Op<Boolean>?, transaction: Transaction): String {
|
||||
val def = super.update(targets, columnsAndValues, null, where, transaction)
|
||||
return when {
|
||||
limit != null && where != null -> "$def AND ROWNUM <= $limit"
|
||||
limit != null -> "$def WHERE ROWNUM <= $limit"
|
||||
else -> def
|
||||
override fun <T : String?> substring(
|
||||
expr: Expression<T>,
|
||||
start: Expression<Int>,
|
||||
length: Expression<Int>,
|
||||
builder: QueryBuilder,
|
||||
prefix: String
|
||||
): Unit = super.substring(expr, start, length, builder, "SUBSTR")
|
||||
|
||||
override fun <T : String?> concat(
|
||||
separator: String,
|
||||
queryBuilder: QueryBuilder,
|
||||
vararg expr: Expression<T>
|
||||
): Unit = queryBuilder {
|
||||
if (separator == "") {
|
||||
expr.toList().appendTo(separator = " || ") { +it }
|
||||
} else {
|
||||
expr.toList().appendTo(separator = " || '$separator' || ") { +it }
|
||||
}
|
||||
}
|
||||
|
||||
/* seed is ignored. You have to use dbms_random.seed function manually */
|
||||
override fun random(seed: Int?): String = "dbms_random.value"
|
||||
override fun <T : String?> groupConcat(
|
||||
expr: GroupConcat<T>,
|
||||
queryBuilder: QueryBuilder
|
||||
): Unit = queryBuilder {
|
||||
if (expr.orderBy.size != 1) {
|
||||
TransactionManager.current().throwUnsupportedException("SQLServer supports only single column in ORDER BY clause in LISTAGG")
|
||||
}
|
||||
append("LISTAGG(")
|
||||
append(expr.expr)
|
||||
expr.separator?.let {
|
||||
append(", '$it'")
|
||||
}
|
||||
append(") WITHIN GROUP (ORDER BY ")
|
||||
val (col, order) = expr.orderBy.single()
|
||||
append(col, " ", order.name, ")")
|
||||
}
|
||||
|
||||
override fun insert(ignore: Boolean, table: Table, columns: List<Column<*>>, expr: String, transaction: Transaction): String {
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(YEAR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(MONTH FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(DAY FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(HOUR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(MINUTE FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(SECOND FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun insert(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
columns: List<Column<*>>,
|
||||
expr: String,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
return table.autoIncColumn?.takeIf { it !in columns }?.let {
|
||||
val newExpr = if (expr.isBlank()) {
|
||||
"VALUES (${it.autoIncSeqName!!}.NEXTVAL)"
|
||||
@@ -76,87 +134,56 @@ internal object OracleFunctionProvider : FunctionProvider() {
|
||||
} ?: super.insert(ignore, table, columns, expr, transaction)
|
||||
}
|
||||
|
||||
override fun delete(ignore: Boolean, table: Table, where: String?, limit: Int?, transaction: Transaction): String {
|
||||
if (limit != null) transaction.throwUnsupportedException("LIMIT is not supported in DELETE in Oracle")
|
||||
override fun update(
|
||||
targets: ColumnSet,
|
||||
columnsAndValues: List<Pair<Column<*>, Any?>>,
|
||||
limit: Int?,
|
||||
where: Op<Boolean>?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
val def = super.update(targets, columnsAndValues, null, where, transaction)
|
||||
return when {
|
||||
limit != null && where != null -> "$def AND ROWNUM <= $limit"
|
||||
limit != null -> "$def WHERE ROWNUM <= $limit"
|
||||
else -> def
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
where: String?,
|
||||
limit: Int?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (limit != null) {
|
||||
transaction.throwUnsupportedException("Oracle doesn't support LIMIT in DELETE clause.")
|
||||
}
|
||||
return super.delete(ignore, table, where, limit, transaction)
|
||||
}
|
||||
|
||||
override fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean)
|
||||
= (if (offset > 0) " OFFSET $offset ROWS" else "") + " FETCH FIRST $size ROWS ONLY"
|
||||
|
||||
override fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
if (expr.orderBy.size != 1)
|
||||
TransactionManager.current().throwUnsupportedException("LISTAGG requires single order by clause")
|
||||
append("LISTAGG(")
|
||||
append(expr.expr)
|
||||
expr.separator?.let {
|
||||
append(", '$it'")
|
||||
}
|
||||
append(") WITHIN GROUP (ORDER BY ")
|
||||
val (col, order) = expr.orderBy.single()
|
||||
append(col, " ", order.name, ")")
|
||||
}
|
||||
|
||||
override fun <T : String?> concat(separator: String, queryBuilder: QueryBuilder, vararg expr: Expression<T>) = queryBuilder {
|
||||
if (separator == "")
|
||||
expr.toList().appendTo(separator = " || ") { +it }
|
||||
else
|
||||
expr.toList().appendTo(separator = " || '$separator' || ") { +it }
|
||||
}
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(YEAR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(MONTH FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(DAY FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(HOUR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(MINUTE FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(SECOND FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
override fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean): String {
|
||||
return (if (offset > 0) " OFFSET $offset ROWS" else "") + " FETCH FIRST $size ROWS ONLY"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle dialect implementation.
|
||||
*/
|
||||
open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, OracleFunctionProvider) {
|
||||
|
||||
override val supportsMultipleGeneratedKeys = false
|
||||
override val supportsIfNotExists = false
|
||||
override val needsSequenceToAutoInc = true
|
||||
override val needsQuotesWhenSymbolsInNames = false
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true
|
||||
|
||||
override val supportsIfNotExists: Boolean = false
|
||||
override val needsSequenceToAutoInc: Boolean = true
|
||||
override val defaultReferenceOption: ReferenceOption = ReferenceOption.NO_ACTION
|
||||
override val needsQuotesWhenSymbolsInNames: Boolean = false
|
||||
override val supportsMultipleGeneratedKeys: Boolean = false
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true
|
||||
|
||||
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true
|
||||
|
||||
override fun modifyColumn(column: Column<*>) =
|
||||
super.modifyColumn(column).replace("MODIFY COLUMN", "MODIFY")
|
||||
override fun modifyColumn(column: Column<*>): String = super.modifyColumn(column).replace("MODIFY COLUMN", "MODIFY")
|
||||
|
||||
companion object {
|
||||
const val dialectName = "oracle"
|
||||
/** Oracle dialect name */
|
||||
const val dialectName: String = "oracle"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,125 +6,155 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.util.*
|
||||
|
||||
internal object PostgreSQLDataTypeProvider : DataTypeProvider() {
|
||||
|
||||
override fun integerAutoincType(): String = "SERIAL"
|
||||
|
||||
override fun longAutoincType(): String = "BIGSERIAL"
|
||||
|
||||
override fun dateTimeType(): String = "TIMESTAMP"
|
||||
|
||||
override fun uuidType(): String = "uuid"
|
||||
|
||||
override fun blobType(): String = "bytea"
|
||||
|
||||
override fun binaryType(): String = "bytea"
|
||||
override fun binaryType(length: Int): String {
|
||||
exposedLogger.warn("The length of the binary column is not required.")
|
||||
return binaryType()
|
||||
}
|
||||
|
||||
override fun binaryType(): String = "bytea"
|
||||
|
||||
override fun uuidToDB(value: UUID): Any = value
|
||||
|
||||
override val blobAsStream: Boolean = true
|
||||
override fun blobType(): String = "bytea"
|
||||
override fun uuidToDB(value: UUID): Any = value
|
||||
override fun dateTimeType(): String = "TIMESTAMP"
|
||||
}
|
||||
|
||||
internal object PostgreSQLFunctionProvider : FunctionProvider() {
|
||||
override fun update(targets: ColumnSet, columnsAndValues: List<Pair<Column<*>, Any?>>, limit: Int?, where: Op<Boolean>?, transaction: Transaction): String {
|
||||
if (limit != null) transaction.throwUnsupportedException("PostgreSQL doesn't support LIMIT in UPDATE clause.")
|
||||
|
||||
override fun nextVal(seq: Sequence, builder: QueryBuilder): Unit = builder {
|
||||
append("NEXTVAL('", seq.identifier, "')")
|
||||
}
|
||||
|
||||
override fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) {
|
||||
val tr = TransactionManager.current()
|
||||
return when {
|
||||
expr.orderBy.isNotEmpty() -> tr.throwUnsupportedException("PostgreSQL doesn't support ORDER BY in STRING_AGG function.")
|
||||
expr.distinct -> tr.throwUnsupportedException("PostgreSQL doesn't support DISTINCT in STRING_AGG function.")
|
||||
expr.separator == null -> tr.throwUnsupportedException("PostgreSQL requires explicit separator in STRING_AGG function.")
|
||||
else -> queryBuilder { append("STRING_AGG(", expr.expr, ", '", expr.separator, "')") }
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(
|
||||
expr1: Expression<T>,
|
||||
pattern: Expression<String>,
|
||||
caseSensitive: Boolean,
|
||||
queryBuilder: QueryBuilder
|
||||
): Unit = queryBuilder {
|
||||
append(expr1)
|
||||
if (caseSensitive) {
|
||||
append(" ~ ")
|
||||
} else {
|
||||
append(" ~* ")
|
||||
}
|
||||
append(pattern)
|
||||
}
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(YEAR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(MONTH FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(DAY FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(HOUR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(MINUTE FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("Extract(SECOND FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
private const val onConflictIgnore = "ON CONFLICT DO NOTHING"
|
||||
|
||||
override fun insert(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
columns: List<Column<*>>,
|
||||
expr: String,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
val def = super.insert(false, table, columns, expr, transaction)
|
||||
return if (ignore) "$def $onConflictIgnore" else def
|
||||
}
|
||||
|
||||
override fun update(
|
||||
targets: ColumnSet,
|
||||
columnsAndValues: List<Pair<Column<*>, Any?>>,
|
||||
limit: Int?,
|
||||
where: Op<Boolean>?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (limit != null) {
|
||||
transaction.throwUnsupportedException("PostgreSQL doesn't support LIMIT in UPDATE clause.")
|
||||
}
|
||||
return super.update(targets, columnsAndValues, limit, where, transaction)
|
||||
}
|
||||
|
||||
override fun replace(table: Table, data: List<Pair<Column<*>, Any?>>, transaction: Transaction): String {
|
||||
override fun replace(
|
||||
table: Table,
|
||||
data: List<Pair<Column<*>, Any?>>,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
val builder = QueryBuilder(true)
|
||||
val sql = if (data.isEmpty()) ""
|
||||
else data.appendTo(builder, prefix = "VALUES (", postfix = ")") { (col, value) ->
|
||||
registerArgument(col, value)
|
||||
}.toString()
|
||||
val sql = if (data.isEmpty()) {
|
||||
""
|
||||
} else {
|
||||
data.appendTo(builder, prefix = "VALUES (", postfix = ")") { (col, value) -> registerArgument(col, value) }.toString()
|
||||
}
|
||||
|
||||
val columns = data.map { it.first }
|
||||
|
||||
val def = super.insert(false, table, columns, sql, transaction)
|
||||
|
||||
val uniqueCols = columns.filter { it.indexInPK != null }.sortedBy { it.indexInPK }
|
||||
if (uniqueCols.isEmpty())
|
||||
transaction.throwUnsupportedException("Postgres replace table must supply at least one primary key")
|
||||
if (uniqueCols.isEmpty()) {
|
||||
transaction.throwUnsupportedException("PostgreSQL replace table must supply at least one primary key.")
|
||||
}
|
||||
val conflictKey = uniqueCols.joinToString { transaction.identity(it) }
|
||||
return def + "ON CONFLICT ($conflictKey) DO UPDATE SET " + columns.joinToString { "${transaction.identity(it)}=EXCLUDED.${transaction.identity(it)}" }
|
||||
}
|
||||
|
||||
override fun insert(ignore: Boolean, table: Table, columns: List<Column<*>>, expr: String, transaction: Transaction): String {
|
||||
val def = super.insert(false, table, columns, expr, transaction)
|
||||
return if (ignore) "$def $onConflictIgnore" else def
|
||||
}
|
||||
|
||||
override fun delete(ignore: Boolean, table: Table, where: String?, limit: Int?, transaction: Transaction): String {
|
||||
if (limit != null) transaction.throwUnsupportedException("LIMIT is not supported in DELETE in PostgreSQL")
|
||||
return super.delete(ignore, table, where, limit, transaction)
|
||||
}
|
||||
|
||||
private const val onConflictIgnore = "ON CONFLICT DO NOTHING"
|
||||
|
||||
override fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) {
|
||||
val tr = TransactionManager.current()
|
||||
return when {
|
||||
expr.orderBy.isNotEmpty() -> tr.throwUnsupportedException("PostgreSQL doesn't support ORDER BY in STRING_AGG.")
|
||||
expr.distinct -> tr.throwUnsupportedException("PostgreSQL doesn't support DISTINCT in STRING_AGG.")
|
||||
expr.separator == null -> tr.throwUnsupportedException("PostgreSQL requires explicit separator in STRING_AGG.")
|
||||
else -> queryBuilder{ append("STRING_AGG(", expr.expr,", '", expr.separator, "')") }
|
||||
override fun delete(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
where: String?,
|
||||
limit: Int?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (limit != null) {
|
||||
transaction.throwUnsupportedException("PostgreSQL doesn't support LIMIT in DELETE clause.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T:String?> regexp(expr1: Expression<T>, pattern: Expression<String>, caseSensitive: Boolean, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append(expr1)
|
||||
if (caseSensitive)
|
||||
append(" ~ ")
|
||||
else
|
||||
append(" ~* ")
|
||||
append(pattern)
|
||||
}
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(YEAR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(MONTH FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(DAY FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(HOUR FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(MINUTE FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("Extract(SECOND FROM ")
|
||||
append(expr)
|
||||
append(")")
|
||||
}
|
||||
|
||||
override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
|
||||
append("NEXTVAL('", seq.identifier, "')")
|
||||
return super.delete(ignore, table, where, limit, transaction)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL dialect implementation.
|
||||
*/
|
||||
open class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProvider, PostgreSQLFunctionProvider) {
|
||||
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true
|
||||
|
||||
@@ -143,6 +173,7 @@ open class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProv
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val dialectName = "postgresql"
|
||||
/** PostgreSQL dialect name */
|
||||
const val dialectName: String = "postgresql"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,38 +6,74 @@ import org.jetbrains.exposed.sql.transactions.TransactionManager
|
||||
import java.util.*
|
||||
|
||||
internal object SQLServerDataTypeProvider : DataTypeProvider() {
|
||||
override fun integerAutoincType() = "INT IDENTITY(1,1)"
|
||||
|
||||
override fun longAutoincType() = "BIGINT IDENTITY(1,1)"
|
||||
|
||||
override fun blobType() = "VARBINARY(MAX)"
|
||||
|
||||
override val blobAsStream: Boolean = true
|
||||
|
||||
override fun booleanType() = "BIT"
|
||||
|
||||
override fun booleanToStatementString(bool: Boolean) = if (bool) "1" else "0"
|
||||
|
||||
override fun dateTimeType() = "DATETIME2"
|
||||
|
||||
override fun uuidType() = "uniqueidentifier"
|
||||
|
||||
override fun uuidToDB(value: UUID) = value.toString()
|
||||
|
||||
override fun integerAutoincType(): String = "INT IDENTITY(1,1)"
|
||||
override fun longAutoincType(): String = "BIGINT IDENTITY(1,1)"
|
||||
override fun binaryType(): String {
|
||||
exposedLogger.error("The length of the Binary column is missing.")
|
||||
error("The length of the Binary column is missing.")
|
||||
}
|
||||
|
||||
override val blobAsStream: Boolean = true
|
||||
override fun blobType(): String = "VARBINARY(MAX)"
|
||||
override fun uuidType(): String = "uniqueidentifier"
|
||||
override fun uuidToDB(value: UUID): Any = value.toString()
|
||||
override fun dateTimeType(): String = "DATETIME2"
|
||||
override fun booleanType(): String = "BIT"
|
||||
override fun booleanToStatementString(bool: Boolean): String = if (bool) "1" else "0"
|
||||
}
|
||||
|
||||
internal object SQLServerFunctionProvider : FunctionProvider() {
|
||||
override fun random(seed: Int?) = if (seed != null) "RAND(${seed})" else "RAND(CHECKSUM(NEWID()))"
|
||||
override fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean): String {
|
||||
return if (!alreadyOrdered) {
|
||||
" ORDER BY(SELECT NULL) "
|
||||
} else {
|
||||
""
|
||||
} + " OFFSET $offset ROWS FETCH NEXT $size ROWS ONLY"
|
||||
override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
|
||||
append("NEXT VALUE FOR ", seq.identifier)
|
||||
}
|
||||
|
||||
override fun random(seed: Int?): String = if (seed != null) "RAND(${seed})" else "RAND(CHECKSUM(NEWID()))"
|
||||
|
||||
override fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) {
|
||||
val tr = TransactionManager.current()
|
||||
return when {
|
||||
expr.separator == null -> tr.throwUnsupportedException("SQLServer requires explicit separator in STRING_AGG.")
|
||||
expr.orderBy.size > 1 -> tr.throwUnsupportedException("SQLServer supports only single column in ORDER BY clause in STRING_AGG.")
|
||||
else -> queryBuilder {
|
||||
append("STRING_AGG(")
|
||||
append(expr.expr)
|
||||
append(", '${expr.separator}')")
|
||||
expr.orderBy.singleOrNull()?.let { (col, order) ->
|
||||
append(" WITHIN GROUP (ORDER BY ", col, " ", order.name, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(
|
||||
expr1: Expression<T>,
|
||||
pattern: Expression<String>,
|
||||
caseSensitive: Boolean,
|
||||
queryBuilder: QueryBuilder
|
||||
): Unit = TransactionManager.current().throwUnsupportedException("SQLServer doesn't provide built in REGEXP expression, use LIKE instead.")
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DATEPART(YEAR, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DATEPART(MONTH, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DATEPART(DAY, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DATEPART(HOUR, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DATEPART(SECOND, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("DATEPART(MINUTE, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun update(targets: ColumnSet, columnsAndValues: List<Pair<Column<*>, Any?>>, limit: Int?, where: Op<Boolean>?, transaction: Transaction): String {
|
||||
@@ -50,72 +86,28 @@ internal object SQLServerFunctionProvider : FunctionProvider() {
|
||||
return if (limit != null) def.replaceFirst("DELETE", "DELETE TOP($limit)") else def
|
||||
}
|
||||
|
||||
override fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) {
|
||||
val tr = TransactionManager.current()
|
||||
return when {
|
||||
expr.separator == null ->
|
||||
tr.throwUnsupportedException("SQLServer requires explicit separator in STRING_AGG")
|
||||
expr.orderBy.size > 1 ->
|
||||
tr.throwUnsupportedException("SQLServer supports only single column in ORDER BY clause in STRING_AGG")
|
||||
else -> queryBuilder {
|
||||
append("STRING_AGG(")
|
||||
append(expr.expr)
|
||||
append(", '${expr.separator}')")
|
||||
expr.orderBy.singleOrNull()?.let { (col, order) ->
|
||||
append(" WITHIN GROUP (ORDER BY ", col, " ", order.name, ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(expr1: Expression<T>, pattern: Expression<String>, caseSensitive: Boolean, queryBuilder: QueryBuilder) {
|
||||
TransactionManager.current().throwUnsupportedException("SQLServer doesn't provide built in REGEXP expression, use LIKE instead")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DATEPART(SECOND, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DATEPART(MINUTE, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DATEPART(HOUR, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DATEPART(DAY, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DATEPART(MONTH, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
append("DATEPART(YEAR, ", expr, ")")
|
||||
}
|
||||
|
||||
override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
|
||||
append("NEXT VALUE FOR ", seq.identifier)
|
||||
override fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean): String {
|
||||
return (if (alreadyOrdered) "" else " ORDER BY(SELECT NULL)") + " OFFSET $offset ROWS FETCH NEXT $size ROWS ONLY"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLServer dialect implementation.
|
||||
*/
|
||||
open class SQLServerDialect : VendorDialect(dialectName, SQLServerDataTypeProvider, SQLServerFunctionProvider) {
|
||||
override val supportsIfNotExists = false
|
||||
override val needsQuotesWhenSymbolsInNames = false
|
||||
|
||||
override val defaultReferenceOption: ReferenceOption = ReferenceOption.NO_ACTION
|
||||
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true
|
||||
override val supportsIfNotExists: Boolean = false
|
||||
override val defaultReferenceOption: ReferenceOption get() = ReferenceOption.NO_ACTION
|
||||
override val needsQuotesWhenSymbolsInNames: Boolean = false
|
||||
override val supportsSequenceAsGeneratedKeys: Boolean = false
|
||||
|
||||
override fun modifyColumn(column: Column<*>) =
|
||||
super.modifyColumn(column).replace("MODIFY COLUMN", "ALTER COLUMN")
|
||||
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true
|
||||
|
||||
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true
|
||||
|
||||
override fun modifyColumn(column: Column<*>): String =
|
||||
super.modifyColumn(column).replace("MODIFY COLUMN", "ALTER COLUMN")
|
||||
|
||||
companion object {
|
||||
const val dialectName = "sqlserver"
|
||||
/** SQLServer dialect name */
|
||||
const val dialectName: String = "sqlserver"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,106 +8,144 @@ internal object SQLiteDataTypeProvider : DataTypeProvider() {
|
||||
override fun integerAutoincType(): String = "INTEGER PRIMARY KEY AUTOINCREMENT"
|
||||
override fun longAutoincType(): String = "INTEGER PRIMARY KEY AUTOINCREMENT"
|
||||
override fun floatType(): String = "SINGLE"
|
||||
override fun booleanToStatementString(bool: Boolean) = if (bool) "1" else "0"
|
||||
override fun dateTimeType(): String = "NUMERIC"
|
||||
override val blobAsStream: Boolean = true
|
||||
|
||||
override fun binaryType(): String {
|
||||
exposedLogger.error("The length of the Binary column is missing.")
|
||||
error("The length of the Binary column is missing.")
|
||||
}
|
||||
|
||||
override val blobAsStream: Boolean = true
|
||||
override fun dateTimeType(): String = "NUMERIC"
|
||||
override fun booleanToStatementString(bool: Boolean) = if (bool) "1" else "0"
|
||||
}
|
||||
|
||||
internal object SQLiteFunctionProvider : FunctionProvider() {
|
||||
override fun<T:String?> substring(expr: Expression<T>, start: Expression<Int>, length: Expression<Int>, builder: QueryBuilder, prefix: String) =
|
||||
super.substring(expr, start, length, builder, "substr")
|
||||
|
||||
override fun insert(ignore: Boolean, table: Table, columns: List<Column<*>>, expr: String, transaction: Transaction): String {
|
||||
val def = super.insert(false, table, columns, expr, transaction)
|
||||
return if (ignore) def.replaceFirst("INSERT", "INSERT OR IGNORE") else def
|
||||
}
|
||||
|
||||
override fun delete(ignore: Boolean, table: Table, where: String?, limit: Int?, transaction: Transaction): String {
|
||||
if (limit != null) transaction.throwUnsupportedException("LIMIT is not supported in DELETE in SQLite")
|
||||
val def = super.delete(false, table, where, limit, transaction)
|
||||
return if (ignore) def.replaceFirst("DELETE", "DELETE OR IGNORE") else def
|
||||
}
|
||||
|
||||
override fun update(targets: ColumnSet, columnsAndValues: List<Pair<Column<*>, Any?>>, limit: Int?, where: Op<Boolean>?, transaction: Transaction): String {
|
||||
if (limit != null) transaction.throwUnsupportedException("SQLite doesn't support LIMIT in UPDATE clause.")
|
||||
return super.update(targets, columnsAndValues, limit, where, transaction)
|
||||
}
|
||||
|
||||
override fun <T: String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) {
|
||||
val tr = TransactionManager.current()
|
||||
return when {
|
||||
expr.orderBy.isNotEmpty() -> tr.throwUnsupportedException("SQLite doesn't support ORDER BY in GROUP_CONCAT.")
|
||||
expr.distinct -> tr.throwUnsupportedException("SQLite doesn't support DISTINCT in GROUP_CONCAT.")
|
||||
else -> super.groupConcat(expr, queryBuilder)//.replace(" SEPARATOR ", ", ")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(expr1: Expression<T>, pattern: Expression<String>, caseSensitive: Boolean, queryBuilder: QueryBuilder) {
|
||||
TransactionManager.current().throwUnsupportedException("SQLite doesn't provide built in REGEXP expression")
|
||||
}
|
||||
override fun <T : String?> substring(
|
||||
expr: Expression<T>,
|
||||
start: Expression<Int>,
|
||||
length: Expression<Int>,
|
||||
builder: QueryBuilder,
|
||||
prefix: String
|
||||
): Unit = super.substring(expr, start, length, builder, "substr")
|
||||
|
||||
override fun <T : String?> concat(separator: String, queryBuilder: QueryBuilder, vararg expr: Expression<T>) = queryBuilder {
|
||||
if (separator == "")
|
||||
if (separator == "") {
|
||||
expr.toList().appendTo(this, separator = " || ") { +it }
|
||||
else
|
||||
} else {
|
||||
expr.toList().appendTo(this, separator = " || '$separator' || ") { +it }
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
override fun <T : String?> groupConcat(expr: GroupConcat<T>, queryBuilder: QueryBuilder) {
|
||||
val tr = TransactionManager.current()
|
||||
return when {
|
||||
expr.orderBy.isNotEmpty() -> tr.throwUnsupportedException("SQLite doesn't support ORDER BY in GROUP_CONCAT function.")
|
||||
expr.distinct -> tr.throwUnsupportedException("SQLite doesn't support DISTINCT in GROUP_CONCAT function.")
|
||||
else -> super.groupConcat(expr, queryBuilder)//.replace(" SEPARATOR ", ", ")
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : String?> regexp(
|
||||
expr1: Expression<T>,
|
||||
pattern: Expression<String>,
|
||||
caseSensitive: Boolean,
|
||||
queryBuilder: QueryBuilder
|
||||
): Unit = TransactionManager.current().throwUnsupportedException("SQLite doesn't provide built in REGEXP expression, use LIKE instead.")
|
||||
|
||||
override fun <T> year(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("STRFTIME('%Y',")
|
||||
append(expr)
|
||||
append(" / 1000, 'unixepoch')")
|
||||
}
|
||||
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
override fun <T> month(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("STRFTIME('%m',")
|
||||
append(expr)
|
||||
append(" / 1000, 'unixepoch')")
|
||||
}
|
||||
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
override fun <T> day(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("STRFTIME('%d',")
|
||||
append(expr)
|
||||
append(" / 1000, 'unixepoch')")
|
||||
}
|
||||
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
override fun <T> hour(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("STRFTIME('%H',")
|
||||
append(expr)
|
||||
append(" / 1000, 'unixepoch')")
|
||||
}
|
||||
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
override fun <T> minute(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("STRFTIME('%M',")
|
||||
append(expr)
|
||||
append(" / 1000, 'unixepoch')")
|
||||
}
|
||||
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder) = queryBuilder {
|
||||
override fun <T> second(expr: Expression<T>, queryBuilder: QueryBuilder): Unit = queryBuilder {
|
||||
append("STRFTIME('%S',")
|
||||
append(expr)
|
||||
append(" / 1000, 'unixepoch')")
|
||||
}
|
||||
|
||||
override fun insert(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
columns: List<Column<*>>,
|
||||
expr: String,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
val def = super.insert(false, table, columns, expr, transaction)
|
||||
return if (ignore) def.replaceFirst("INSERT", "INSERT OR IGNORE") else def
|
||||
}
|
||||
|
||||
override fun update(
|
||||
targets: ColumnSet,
|
||||
columnsAndValues: List<Pair<Column<*>, Any?>>,
|
||||
limit: Int?,
|
||||
where: Op<Boolean>?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (limit != null) {
|
||||
transaction.throwUnsupportedException("SQLite doesn't support LIMIT in UPDATE clause.")
|
||||
}
|
||||
return super.update(targets, columnsAndValues, limit, where, transaction)
|
||||
}
|
||||
|
||||
override fun delete(
|
||||
ignore: Boolean,
|
||||
table: Table,
|
||||
where: String?,
|
||||
limit: Int?,
|
||||
transaction: Transaction
|
||||
): String {
|
||||
if (limit != null) {
|
||||
transaction.throwUnsupportedException("SQLite doesn't support LIMIT in DELETE clause.")
|
||||
}
|
||||
val def = super.delete(false, table, where, limit, transaction)
|
||||
return if (ignore) def.replaceFirst("DELETE", "DELETE OR IGNORE") else def
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SQLite dialect implementation.
|
||||
*/
|
||||
open class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQLiteFunctionProvider) {
|
||||
override val supportsCreateSequence: Boolean = false
|
||||
override val supportsMultipleGeneratedKeys: Boolean = false
|
||||
override val supportsCreateSequence = false
|
||||
|
||||
override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true
|
||||
|
||||
override fun createIndex(index: Index): String {
|
||||
val originalCreateIndex = super.createIndex(index.copy(unique = false))
|
||||
return if (index.unique) originalCreateIndex.replace("CREATE INDEX", "CREATE UNIQUE INDEX")
|
||||
else originalCreateIndex
|
||||
return if (index.unique) {
|
||||
originalCreateIndex.replace("CREATE INDEX", "CREATE UNIQUE INDEX")
|
||||
} else {
|
||||
originalCreateIndex
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val dialectName = "sqlite"
|
||||
/** SQLite dialect name */
|
||||
const val dialectName: String = "sqlite"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user