diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/Exceptions.kt b/src/main/kotlin/org/jetbrains/exposed/sql/Exceptions.kt index 19429946d6..4be2bcb28e 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/Exceptions.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/Exceptions.kt @@ -3,10 +3,11 @@ package org.jetbrains.exposed.exceptions import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.EntityID +import org.jetbrains.exposed.sql.Transaction import org.jetbrains.exposed.sql.statements.StatementContext import org.jetbrains.exposed.sql.statements.expandArgs import org.jetbrains.exposed.sql.transactions.TransactionManager -import org.jetbrains.exposed.sql.vendors.VendorDialect +import org.jetbrains.exposed.sql.vendors.DatabaseDialect import java.sql.SQLException class EntityNotFoundException(val id: EntityID<*>, val entity: EntityClass<*, *>): Exception("Entity ${entity.klass.simpleName}, id=$id not found in database") @@ -25,4 +26,6 @@ class ExposedSQLException(cause: Throwable?, val contexts: List?): SizedIterable, limit?.let { append(" ") - append(currentDialect.limit(it, offset, orderByExpressions.isNotEmpty())) + append(currentDialect.functionProvider.queryLimit(it, offset, orderByExpressions.isNotEmpty())) } } diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchInsertStatement.kt b/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchInsertStatement.kt index 094fdff6bd..b7370fc89a 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchInsertStatement.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/statements/BatchInsertStatement.kt @@ -94,7 +94,7 @@ class SQLServerBatchInsertStatement(table: Table, ignore: Boolean = false) : Bat } } } - return transaction.db.dialect.insert(isIgnore, table, values.firstOrNull()?.map { it.first }.orEmpty(), sql, transaction) + return transaction.db.dialect.functionProvider.insert(isIgnore, table, values.firstOrNull()?.map { it.first }.orEmpty(), sql, transaction) } override fun arguments() = listOfNotNull(super.arguments().flatten().takeIf { data.isNotEmpty() }) diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/statements/DeleteStatement.kt b/src/main/kotlin/org/jetbrains/exposed/sql/statements/DeleteStatement.kt index 26a78a77e8..841f600dae 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/statements/DeleteStatement.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/statements/DeleteStatement.kt @@ -11,7 +11,8 @@ class DeleteStatement(val table: Table, val where: Op? = null, val isIg return executeUpdate() } - override fun prepareSQL(transaction: Transaction): String = transaction.db.dialect.delete(isIgnore, table, where?.toSQL(QueryBuilder(true)), transaction) + override fun prepareSQL(transaction: Transaction): String = + transaction.db.dialect.functionProvider.delete(isIgnore, table, where?.toSQL(QueryBuilder(true)), transaction) override fun arguments(): Iterable>> = QueryBuilder(true).run { where?.toSQL(this) @@ -19,7 +20,6 @@ class DeleteStatement(val table: Table, val where: Op? = null, val isIg } companion object { - fun where(transaction: Transaction, table: Table, op: Op, isIgnore: Boolean = false): Int = DeleteStatement(table, op, isIgnore).execute(transaction) ?: 0 diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertSelectStatement.kt b/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertSelectStatement.kt index db56713436..229659939e 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertSelectStatement.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertSelectStatement.kt @@ -21,5 +21,5 @@ class InsertSelectStatement(val columns: List>, val selectQuery: Query override fun arguments(): Iterable>> = selectQuery.arguments() override fun prepareSQL(transaction: Transaction): String = - transaction.db.dialect.insert(isIgnore, targets.single(), columns, selectQuery.prepareSQL(transaction), transaction) + transaction.db.dialect.functionProvider.insert(isIgnore, targets.single(), columns, selectQuery.prepareSQL(transaction), transaction) } diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertStatement.kt b/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertStatement.kt index 700745618f..2a35938de9 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertStatement.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/statements/InsertStatement.kt @@ -79,7 +79,7 @@ open class InsertStatement(val table: Table, val isIgnore: Boolean = fa else values.joinToString(prefix = "VALUES (", postfix = ")") { (col, value) -> builder.registerArgument(col, value) } - return transaction.db.dialect.insert(isIgnore, table, values.map { it.first }, sql, transaction) + return transaction.db.dialect.functionProvider.insert(isIgnore, table, values.map { it.first }, sql, transaction) } protected open fun PreparedStatement.execInsertFunction() : Pair { diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/statements/ReplaceStatement.kt b/src/main/kotlin/org/jetbrains/exposed/sql/statements/ReplaceStatement.kt index d301ee2208..5f8d94828b 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/statements/ReplaceStatement.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/statements/ReplaceStatement.kt @@ -2,12 +2,10 @@ package org.jetbrains.exposed.sql.statements import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Transaction -import org.jetbrains.exposed.sql.vendors.currentDialect /** * @author max */ class ReplaceStatement(table: Table) : InsertStatement(table) { - - override fun prepareSQL(transaction: Transaction): String = currentDialect.replace(table, arguments!!.first(), transaction) + override fun prepareSQL(transaction: Transaction): String = transaction.db.dialect.functionProvider.replace(table, arguments!!.first(), transaction) } diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateStatement.kt b/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateStatement.kt index 9689574adb..aaba1d7e44 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateStatement.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateStatement.kt @@ -15,18 +15,8 @@ open class UpdateStatement(val targetsSet: ColumnSet, val limit: Int?, val where } } - override fun prepareSQL(transaction: Transaction): String = buildString { - val builder = QueryBuilder(true) - append("UPDATE ${targetsSet.describe(transaction)}") - append(" SET ") - append(firstDataSet.joinToString { (col, value) -> - "${transaction.identity(col)}=" + builder.registerArgument(col, value) - }) - - where?.let { append(" WHERE " + it.toSQL(builder)) } - limit?.let { append(" LIMIT $it")} - } - + override fun prepareSQL(transaction: Transaction): String = + transaction.db.dialect.functionProvider.update(targetsSet, firstDataSet, limit, where, transaction) override fun arguments(): Iterable>> = QueryBuilder(true).run { values.forEach { diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt index 72ce3f932a..fd4cfba205 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt @@ -1,6 +1,6 @@ package org.jetbrains.exposed.sql.vendors -import org.jetbrains.exposed.exceptions.UnsupportedByDialectException +import org.jetbrains.exposed.exceptions.throwUnsupportedException import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager import java.nio.ByteBuffer @@ -44,7 +44,9 @@ open class DataTypeProvider { } } -open class FunctionProvider { +abstract class FunctionProvider { + + open val DEFAULT_VALUE_EXPRESSION = "DEFAULT VALUES" open fun substring(expr: Expression, start: Expression, length: Expression, builder: QueryBuilder) : String = "SUBSTRING(${expr.toSQL(builder)}, ${start.toSQL(builder)}, ${length.toSQL(builder)})" @@ -55,6 +57,52 @@ open class FunctionProvider { open fun ExpressionWithColumnType.match(pattern: String, mode: MatchMode? = null): Op = with(SqlExpressionBuilder) { this@match.like(pattern) } + open fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { + if (ignore) { + transaction.throwUnsupportedException("There's no generic SQL for INSERT IGNORE. There must be vendor specific implementation") + } + + val (columnsExpr, valuesExpr) = if (columns.isNotEmpty()) { + columns.joinToString(prefix = "(", postfix = ")") { transaction.identity(it) } to expr + } else "" to DEFAULT_VALUE_EXPRESSION + + return "INSERT INTO ${transaction.identity(table)} $columnsExpr $valuesExpr" + } + + open fun update(targets: ColumnSet, columnsAndValues: List, Any?>>, limit: Int?, where: Op?, transaction: Transaction): String { + return buildString { + val builder = QueryBuilder(true) + append("UPDATE ${targets.describe(transaction)}") + append(" SET ") + append(columnsAndValues.joinToString { (col, value) -> + "${transaction.identity(col)}=" + builder.registerArgument(col, value) + }) + + where?.let { append(" WHERE " + it.toSQL(builder)) } + limit?.let { append(" LIMIT $it")} + } + } + + open fun delete(ignore: Boolean, table: Table, where: String?, 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)) + if (where != null) { + append(" WHERE ") + append(where) + } + } + } + + open fun replace(table: Table, data: List, 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) = "LIMIT $size" + if (offset > 0) " OFFSET $offset" else "" + interface MatchMode { fun mode() : String } @@ -111,20 +159,14 @@ interface DatabaseDialect { // Specific SQL statements - fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String - fun delete(ignore: Boolean, table: Table, where: String?, transaction: Transaction): String - fun replace(table: Table, data: List, Any?>>, transaction: Transaction): String - fun createIndex(index: Index): String fun dropIndex(tableName: String, indexName: String): String fun modifyColumn(column: Column<*>) : String - - fun limit(size: Int, offset: Int = 0, alreadyOrdered: Boolean = true): String } abstract class VendorDialect(override val name: String, override val dataTypeProvider: DataTypeProvider, - override val functionProvider: FunctionProvider = FunctionProvider()) : DatabaseDialect { + override val functionProvider: FunctionProvider) : DatabaseDialect { /* Cached values */ private var _allTableNames: List? = null @@ -258,39 +300,6 @@ abstract class VendorDialect(override val name: String, existingIndicesCache.clear() } - override fun replace(table: Table, data: List, Any?>>, transaction: Transaction): String { - throwUnsupportedException("There's no generic SQL for replace. There must be vendor specific implementation") - } - - protected open val DEFAULT_VALUE_EXPRESSION = "DEFAULT VALUES" - - override fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { - if (ignore) { - throwUnsupportedException("There's no generic SQL for INSERT IGNORE. There must be vendor specific implementation") - } - - val (columnsExpr, valuesExpr) = if (columns.isNotEmpty()) { - columns.joinToString(prefix = "(", postfix = ")") { transaction.identity(it) } to expr - } else "" to DEFAULT_VALUE_EXPRESSION - - return "INSERT INTO ${transaction.identity(table)} $columnsExpr $valuesExpr" - } - - override fun delete(ignore: Boolean, table: Table, where: String?, transaction: Transaction): String { - if (ignore) { - throwUnsupportedException("There's no generic SQL for DELETE IGNORE. There must be vendor specific implementation") - } - - return buildString { - append("DELETE FROM ") - append(transaction.identity(table)) - if (where != null) { - append(" WHERE ") - append(where) - } - } - } - override fun createIndex(index: Index): String { val t = TransactionManager.current() val quotedTableName = t.identity(index.table) @@ -314,11 +323,8 @@ abstract class VendorDialect(override val name: String, override val supportsMultipleGeneratedKeys: Boolean = true - override fun limit(size: Int, offset: Int, alreadyOrdered: Boolean) = "LIMIT $size" + if (offset > 0) " OFFSET $offset" else "" - override fun modifyColumn(column: Column<*>): String = "MODIFY COLUMN ${column.descriptionDdl()}" - protected fun throwUnsupportedException(message: String): Nothing = throw UnsupportedByDialectException(message, this) } internal val currentDialect: DatabaseDialect get() = TransactionManager.current().db.dialect diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt index fc6e9720e5..68aa0426cc 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt @@ -2,6 +2,7 @@ package org.jetbrains.exposed.sql.vendors import org.h2.engine.Session import org.h2.jdbc.JdbcConnection +import org.jetbrains.exposed.exceptions.throwUnsupportedException import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager import java.sql.Wrapper @@ -10,16 +11,15 @@ internal object H2DataTypeProvider : DataTypeProvider() { override fun uuidType(): String = "UUID" } -internal class H2Dialect: VendorDialect(dialectName, H2DataTypeProvider) { +internal object H2FunctionProvider : FunctionProvider() { - override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true + private fun currentMode(): String = + ((TransactionManager.current().connection as Wrapper).unwrap(JdbcConnection::class.java).session as? Session)?.database?.mode?.name ?: "" private val isMySQLMode: Boolean get() = currentMode() == "MySQL" - override val supportsMultipleGeneratedKeys: Boolean = false - override fun replace(table: Table, data: List, Any?>>, transaction: Transaction): String { - if (!isMySQLMode) throwUnsupportedException("REPLACE is only supported in MySQL compatibility mode for H2") + if (!isMySQLMode) transaction.throwUnsupportedException("REPLACE is only supported in MySQL compatibility mode for H2") val builder = QueryBuilder(true) val values = data.map { builder.registerArgument(it.first.columnType, it.second) } @@ -31,11 +31,6 @@ internal class H2Dialect: VendorDialect(dialectName, H2DataTypeProvider) { return "INSERT INTO ${transaction.identity(table)} (${preparedValues.joinToString { it.first }}) VALUES (${values.joinToString()}) ON DUPLICATE KEY UPDATE ${preparedValues.joinToString { "${it.first}=${it.second}" }}" } - private fun currentMode(): String = - ((TransactionManager.current().connection as Wrapper).unwrap(JdbcConnection::class.java).session as? Session)?.database?.mode?.name ?: "" - - override fun existingIndices(vararg tables: Table): Map> = - super.existingIndices(*tables).mapValues { it.value.filterNot { it.indexName.startsWith("PRIMARY_KEY_") } }.filterValues { it.isNotEmpty() } override fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { val uniqueIdxCols = table.indices.filter { it.unique }.flatMap { it.columns.toList() } @@ -47,6 +42,16 @@ internal class H2Dialect: VendorDialect(dialectName, H2DataTypeProvider) { super.insert(ignore, table, columns, expr, transaction) } } +} + +internal class H2Dialect : VendorDialect(dialectName, H2DataTypeProvider, H2FunctionProvider) { + + override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true + + override val supportsMultipleGeneratedKeys: Boolean = false + + override fun existingIndices(vararg tables: Table): Map> = + 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 }) { diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt index 1a97a2e683..1b3054885e 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt @@ -25,6 +25,25 @@ internal object MysqlFunctionProvider : FunctionProvider() { override fun ExpressionWithColumnType.match(pattern: String, mode: MatchMode?): Op = MATCH(this, pattern, mode ?: MysqlMatchMode.STRICT) + override fun replace(table: Table, data: List, Any?>>, transaction: Transaction): String { + val builder = QueryBuilder(true) + val columns = data.joinToString { transaction.identity(it.first) } + val values = data.joinToString { builder.registerArgument(it.first.columnType, it.second) } + 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>, expr: String, transaction: Transaction): String { + val def = super.insert(false, table, columns, expr, transaction) + return if (ignore) def.replaceFirst("INSERT", "INSERT IGNORE") else def + } + + override fun delete(ignore: Boolean, table: Table, where: String?, transaction: Transaction): String { + val def = super.delete(false, table, where, transaction) + return if (ignore) def.replaceFirst("DELETE", "DELETE IGNORE") else def + } + private class MATCH(val expr: ExpressionWithColumnType<*>, val pattern: String, val mode: MatchMode) : Op() { override fun toSQL(queryBuilder: QueryBuilder): String = "MATCH(${expr.toSQL(queryBuilder)}) AGAINST ('$pattern' ${mode.mode()})" @@ -88,25 +107,6 @@ internal class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, return constraints } - override fun replace(table: Table, data: List, Any?>>, transaction: Transaction): String { - val builder = QueryBuilder(true) - val columns = data.joinToString { transaction.identity(it.first) } - val values = data.joinToString { builder.registerArgument(it.first.columnType, it.second) } - 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>, expr: String, transaction: Transaction): String { - val def = super.insert(false, table, columns, expr, transaction) - return if (ignore) def.replaceFirst("INSERT", "INSERT IGNORE") else def - } - - override fun delete(ignore: Boolean, table: Table, where: String?, transaction: Transaction): String { - val def = super.delete(false, table, where, transaction) - return if (ignore) def.replaceFirst("DELETE", "DELETE IGNORE") else def - } - override fun dropIndex(tableName: String, indexName: String): String = "ALTER TABLE $tableName DROP INDEX $indexName" diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt index 19641eefcf..8210da25d9 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt @@ -49,6 +49,21 @@ internal object OracleFunctionProvider : FunctionProvider() { /* seed is ignored. You have to use dbms_random.seed function manually */ override fun random(seed: Int?): String = "dbms_random.value" + + override fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { + return table.autoIncColumn?.takeIf { it !in columns }?.let { + val newExpr = if (expr.isBlank()) { + "VALUES (${it.autoIncSeqName!!}.NEXTVAL)" + } else { + expr.replace("VALUES (", "VALUES (${it.autoIncSeqName!!}.NEXTVAL, ") + } + + super.insert(ignore, table, listOf(it) + columns, newExpr, transaction) + } ?: super.insert(ignore, table, columns, expr, transaction) + } + + override fun queryLimit(size: Int, offset: Int, alreadyOrdered: Boolean) + = (if (offset > 0) " OFFSET $offset ROWS" else "") + " FETCH FIRST $size ROWS ONLY" } internal class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, OracleFunctionProvider) { @@ -65,20 +80,6 @@ internal class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider override fun catalog(transaction: Transaction) : String = transaction.connection.metaData.userName - override fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { - return table.autoIncColumn?.takeIf { it !in columns }?.let { - val newExpr = if (expr.isBlank()) { - "VALUES (${it.autoIncSeqName!!}.NEXTVAL)" - } else { - expr.replace("VALUES (", "VALUES (${it.autoIncSeqName!!}.NEXTVAL, ") - } - - super.insert(ignore, table, listOf(it) + columns, newExpr, transaction) - } ?: super.insert(ignore, table, columns, expr, transaction) - } - - override fun limit(size: Int, offset: Int, alreadyOrdered: Boolean): String = (if (offset > 0) " OFFSET $offset ROWS" else "") + " FETCH FIRST $size ROWS ONLY" - override fun allTablesNames(): List { val result = ArrayList() val tr = TransactionManager.current() diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt index 5899a40f97..20fbe99085 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt @@ -1,7 +1,7 @@ package org.jetbrains.exposed.sql.vendors -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Expression +import org.jetbrains.exposed.exceptions.throwUnsupportedException +import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.transactions.TransactionManager import java.util.* @@ -24,7 +24,14 @@ internal object PostgreSQLDataTypeProvider : DataTypeProvider() { override val blobAsStream: Boolean = true } -internal class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProvider) { +internal object PostgreSQLFunctionProvider : FunctionProvider() { + override fun update(targets: ColumnSet, columnsAndValues: List, Any?>>, limit: Int?, where: Op?, transaction: Transaction): String { + if (limit != null) transaction.throwUnsupportedException("PostgreSQL doesn't support LIMIT in UPDATE clause.") + return super.update(targets, columnsAndValues, limit, where, transaction) + } +} + +internal class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProvider, PostgreSQLFunctionProvider) { override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true override fun modifyColumn(column: Column<*>): String = buildString { diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt index 4c2f4718f1..fc9b0a6e6d 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt @@ -21,8 +21,14 @@ internal object SQLServerDataTypeProvider : DataTypeProvider() { } 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" + } } internal class SQLServerDialect : VendorDialect(dialectName, SQLServerDataTypeProvider, SQLServerFunctionProvider) { @@ -31,14 +37,6 @@ internal class SQLServerDialect : VendorDialect(dialectName, SQLServerDataTypePr override val defaultReferenceOption: ReferenceOption get() = ReferenceOption.NO_ACTION - override fun limit(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 modifyColumn(column: Column<*>) = super.modifyColumn(column).replace("MODIFY COLUMN", "ALTER COLUMN") diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt index f6b7ae48ed..dfc8deca4d 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt @@ -1,5 +1,6 @@ package org.jetbrains.exposed.sql.vendors +import org.jetbrains.exposed.exceptions.throwUnsupportedException import org.jetbrains.exposed.sql.* internal object SQLiteDataTypeProvider : DataTypeProvider() { @@ -14,13 +15,6 @@ internal object SQLiteDataTypeProvider : DataTypeProvider() { internal object SQLiteFunctionProvider : FunctionProvider() { override fun substring(expr: Expression, start: Expression, length: Expression, builder: QueryBuilder): String = super.substring(expr, start, length, builder).replace("SUBSTRING", "substr") -} - -internal class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQLiteFunctionProvider) { - override val supportsMultipleGeneratedKeys: Boolean = false - override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true - - override fun getDatabase(): String = "" override fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { val def = super.insert(false, table, columns, expr, transaction) @@ -32,6 +26,18 @@ internal class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider return if (ignore) def.replaceFirst("DELETE", "DELETE OR IGNORE") else def } + override fun update(targets: ColumnSet, columnsAndValues: List, Any?>>, limit: Int?, where: Op?, transaction: Transaction): String { + if (limit != null) transaction.throwUnsupportedException("SQLite doesn't support LIMIT in UPDATE clause.") + return super.update(targets, columnsAndValues, limit, where, transaction) + } +} + +internal class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQLiteFunctionProvider) { + override val supportsMultipleGeneratedKeys: Boolean = false + override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true + + override fun getDatabase(): String = "" + override fun createIndex(index: Index): String { val originalCreateIndex = super.createIndex(index.copy(unique = false)) return if (index.unique) originalCreateIndex.replace("INDEX", "UNIQUE INDEX") diff --git a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/Assert.kt b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/Assert.kt index 1599da120e..0386a13c2f 100644 --- a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/Assert.kt +++ b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/Assert.kt @@ -7,6 +7,7 @@ import org.jetbrains.exposed.sql.vendors.currentDialectIfAvailable import org.joda.time.DateTime import kotlin.test.assertEquals import kotlin.test.assertFails +import kotlin.test.fail private fun assertEqualCollectionsImpl(collection : Collection, expected : Collection) { assertEquals (expected.size, collection.size, "Count mismatch on ${currentDialect.name}") @@ -70,3 +71,12 @@ fun equalDateTime(d1: DateTime?, d2: DateTime?) = try { } catch (e: Exception) { false } + +inline fun expectException(body: () -> Unit) { + try { + body() + fail("${T::class.simpleName} expected.") + } catch (e: Exception) { + if (e !is T) fail("Expected ${T::class.simpleName} but ${e::class.simpleName} thrown.") + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DMLTests.kt b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DMLTests.kt index 248952456d..d399bb44dd 100644 --- a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DMLTests.kt +++ b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DMLTests.kt @@ -3,6 +3,7 @@ package org.jetbrains.exposed.sql.tests.shared import org.hamcrest.Matchers.containsInAnyOrder import org.hamcrest.Matchers.not import org.jetbrains.exposed.dao.IntIdTable +import org.jetbrains.exposed.exceptions.UnsupportedByDialectException import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.Function import org.jetbrains.exposed.sql.statements.BatchDataInconsistentException @@ -166,6 +167,35 @@ class DMLTests : DatabaseTestsBase() { } } + @Test + fun testUpdateWithLimit01() { + withCitiesAndUsers(listOf(TestDB.SQLITE, TestDB.POSTGRESQL)) { cities, users, userData -> + val aNames = users.slice(users.name).select { users.id like "a%" }.map { it[users.name] } + assertEquals(2, aNames.size) + + users.update({ users.id like "a%" }, 1) { + it[users.id] = "NewName" + } + + val unchanged = users.slice(users.name).select { users.id like "a%" }.count() + val changed = users.slice(users.name).select { users.id eq "NewName" }.count() + assertEquals(1, unchanged) + assertEquals(1, changed) + } + } + + @Test + fun testUpdateWithLimit02() { + val dialects = TestDB.values().toList() - listOf(TestDB.SQLITE, TestDB.POSTGRESQL) + withCitiesAndUsers(dialects) { cities, users, userData -> + expectException { + users.update({ users.id like "a%" }, 1) { + it[users.id] = "NewName" + } + } + } + } + @Test fun testPreparedStatement() { withCitiesAndUsers { cities, users, userData -> @@ -354,7 +384,7 @@ class DMLTests : DatabaseTestsBase() { } bar.insert { - it[this.foo] = fooId!! + it[this.foo] = fooId it[baz] = 5 } @@ -363,7 +393,7 @@ class DMLTests : DatabaseTestsBase() { } } - @Test(expected = IllegalStateException::class) + @Test fun testMultipleReferenceJoin02() { val foo = object : IntIdTable("foo") { val baz = integer("baz").uniqueIndex() @@ -374,18 +404,20 @@ class DMLTests : DatabaseTestsBase() { val baz = integer("baz") references foo.baz } withTables(foo, bar) { - val fooId = foo.insertAndGetId { - it[baz] = 5 - } + expectException { + val fooId = foo.insertAndGetId { + it[baz] = 5 + } - bar.insert { - it[this.foo] = fooId!! - it[this.foo2] = fooId!! - it[baz] = 5 - } + bar.insert { + it[this.foo] = fooId + it[this.foo2] = fooId + it[baz] = 5 + } - val result = foo.innerJoin(bar).selectAll() - assertEquals(1, result.count()) + val result = foo.innerJoin(bar).selectAll() + assertEquals(1, result.count()) + } } } @@ -1018,13 +1050,15 @@ class DMLTests : DatabaseTestsBase() { it[EntityTests.TableWithDBDefault.t1] = DateTime.now() }) - @Test(expected = BatchDataInconsistentException::class) + @Test fun testRawBatchInsertFails01() { withTables(EntityTests.TableWithDBDefault) { - BatchInsertStatement(EntityTests.TableWithDBDefault).run { - initBatch.forEach { - addBatch() - it(this) + expectException { + BatchInsertStatement(EntityTests.TableWithDBDefault).run { + initBatch.forEach { + addBatch() + it(this) + } } } } diff --git a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/EntityTests.kt b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/EntityTests.kt index 4787337cf8..84ae70b67c 100644 --- a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/EntityTests.kt +++ b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/EntityTests.kt @@ -368,12 +368,14 @@ class EntityTests: DatabaseTestsBase() { } } - @Test(expected = EntityNotFoundException::class) + @Test fun testErrorOnSetToDeletedEntity() { withTables(Boards) { - val board = Board.new { name = "irrelevant" } - board.delete() - board.name = "Cool" + expectException { + val board = Board.new { name = "irrelevant" } + board.delete() + board.name = "Cool" + } } }