[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:
Juan José González Abril
2020-01-25 17:32:49 +01:00
committed by Andrey.Tarashevskiy
parent 771a60f7e2
commit cfd14c5b86
9 changed files with 1551 additions and 1143 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}