+
Skip to content

Fix for issue #258 - insertOrUpdate for SQLite #370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ subprojects { project ->
name = "Eric Fenderbosch"
email = "eric@fender.net"
}
developer {
id = "2938137849"
name = "ccr"
email = "2938137849@qq.com"
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.ktorm.support.sqlite

import org.ktorm.database.Database
import org.ktorm.dsl.AssignmentsBuilder
import org.ktorm.dsl.KtormDsl
import org.ktorm.dsl.batchInsert
import org.ktorm.expression.*
import org.ktorm.schema.BaseTable
import org.ktorm.schema.Column
import org.ktorm.schema.ColumnDeclaring

/**
* Bulk insert expression, represents a bulk insert statement in SQLite.
*
* For example:
*
* ```sql
* insert into table (column1, column2)
* values (?, ?), (?, ?), (?, ?)...
* on conflict (...) do update set ...`
* ```
*
* @property table the table to be inserted.
* @property assignments column assignments of the bulk insert statement.
* @property conflictColumns the index columns on which the conflict may happen.
* @property updateAssignments the updated column assignments while key conflict exists.
*/
public data class BulkInsertExpression(
val table: TableExpression,
val assignments: List<List<ColumnAssignmentExpression<*>>>,
val conflictColumns: List<ColumnExpression<*>> = emptyList(),
val updateAssignments: List<ColumnAssignmentExpression<*>> = emptyList(),
val where: ScalarExpression<Boolean>? = null,
override val isLeafNode: Boolean = false,
override val extraProperties: Map<String, Any> = emptyMap()
) : SqlExpression()

/**
* Bulk insert records to the table and return the effected row count.
*
* The usage is almost the same as [batchInsert], but this function is implemented by generating a special SQL
* using SQLite's bulk insert syntax, instead of based on JDBC batch operations. For this reason, its performance
* is much better than [batchInsert].
*
* The generated SQL is like: `insert into table (column1, column2) values (?, ?), (?, ?), (?, ?)...`.
*
* Usage:
*
* ```kotlin
* database.bulkInsert(Employees) {
* item {
* set(it.name, "jerry")
* set(it.job, "trainee")
* set(it.managerId, 1)
* set(it.hireDate, LocalDate.now())
* set(it.salary, 50)
* set(it.departmentId, 1)
* }
* item {
* set(it.name, "linda")
* set(it.job, "assistant")
* set(it.managerId, 3)
* set(it.hireDate, LocalDate.now())
* set(it.salary, 100)
* set(it.departmentId, 2)
* }
* }
* ```
*
* @param table the table to be inserted.
* @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression.
* @return the effected row count.
* @see batchInsert
*/
public fun <T : BaseTable<*>> Database.bulkInsert(
table: T, block: BulkInsertStatementBuilder<T>.(T) -> Unit
): Int {
val builder = BulkInsertStatementBuilder(table).apply { block(table) }

val expression = AliasRemover.visit(
BulkInsertExpression(table.asExpression(), builder.assignments)
)

return executeUpdate(expression)
}

/**
* Bulk insert records to the table, determining if there is a key conflict while inserting each of them,
* and automatically performs updates if any conflict exists.
*
* Usage:
*
* ```kotlin
* database.bulkInsertOrUpdate(Employees) {
* item {
* set(it.id, 1)
* set(it.name, "vince")
* set(it.job, "engineer")
* set(it.salary, 1000)
* set(it.hireDate, LocalDate.now())
* set(it.departmentId, 1)
* }
* item {
* set(it.id, 5)
* set(it.name, "vince")
* set(it.job, "engineer")
* set(it.salary, 1000)
* set(it.hireDate, LocalDate.now())
* set(it.departmentId, 1)
* }
* onConflict {
* set(it.salary, it.salary + 900)
* }
* }
* ```
*
* Generated SQL:
*
* ```sql
* insert into t_employee (id, name, job, salary, hire_date, department_id)
* values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)
* on conflict (id) do update set salary = t_employee.salary + ?
* ```
*
* @param table the table to be inserted.
* @param block the DSL block used to construct the expression.
* @return the effected row count.
*/
public fun <T : BaseTable<*>> Database.bulkInsertOrUpdate(
table: T, block: BulkInsertOrUpdateStatementBuilder<T>.(T) -> Unit
): Int {
val expression = AliasRemover.visit(
buildBulkInsertOrUpdateExpression(table, block = block)
)

return executeUpdate(expression)
}

/**
* Build a bulk insert or update expression.
*/
private fun <T : BaseTable<*>> buildBulkInsertOrUpdateExpression(
table: T, block: BulkInsertOrUpdateStatementBuilder<T>.(T) -> Unit
): BulkInsertExpression {
val builder = BulkInsertOrUpdateStatementBuilder(table).apply { block(table) }

val conflictColumns = builder.conflictColumns.ifEmpty { table.primaryKeys }
if (conflictColumns.isEmpty()) {
val msg = "" +
"Table '$table' doesn't have a primary key, " +
"you must specify the conflict columns when calling onConflict(col) { .. }"
throw IllegalStateException(msg)
}

if (!builder.doNothing && builder.updateAssignments.isEmpty()) {
val msg = "" +
"Cannot leave the onConflict clause empty! " +
"If you desire no update action at all please explicitly call `doNothing()`"
throw IllegalStateException(msg)
}

return BulkInsertExpression(
table = table.asExpression(),
assignments = builder.assignments,
conflictColumns = conflictColumns.map { it.asExpression() },
updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments,
where = builder.where?.asExpression()
)
}

/**
* DSL builder for bulk insert statements.
*/
@KtormDsl
public open class BulkInsertStatementBuilder<T : BaseTable<*>>(internal val table: T) {
internal val assignments = ArrayList<List<ColumnAssignmentExpression<*>>>()

/**
* Add the assignments of a new row to the bulk insert.
*/
public fun item(block: AssignmentsBuilder.() -> Unit) {
val builder = SQLiteAssignmentsBuilder().apply(block)

if (assignments.isEmpty()
|| assignments[0].map { it.column.name } == builder.assignments.map { it.column.name }
) {
assignments += builder.assignments
} else {
throw IllegalArgumentException("Every item in a batch operation must be the same.")
}
}
}

/**
* DSL builder for bulk insert or update statements.
*/
@KtormDsl
public class BulkInsertOrUpdateStatementBuilder<T : BaseTable<*>>(table: T) : BulkInsertStatementBuilder<T>(table) {
internal val conflictColumns = ArrayList<Column<*>>()
internal val updateAssignments = ArrayList<ColumnAssignmentExpression<*>>()
internal var where: ColumnDeclaring<Boolean>? = null
internal var doNothing: Boolean = false

/**
* Specify the update assignments while any key conflict exists.
*/
public fun onConflict(vararg columns: Column<*>, block: InsertOrUpdateOnConflictClauseBuilder.() -> Unit) {
val builder = InsertOrUpdateOnConflictClauseBuilder().apply(block)
this.conflictColumns += columns
this.updateAssignments += builder.assignments
this.where = builder.where
this.doNothing = builder.doNothing
}
}
Loading
点击 这是indexloc提供的php浏览器服务,不要输入任何密码和下载