Skip to content

Commit 451717d

Browse files
Merge branch '2938137849-master' into v3.5.x
2 parents 0c7cb89 + bf79efd commit 451717d

File tree

7 files changed

+1259
-3
lines changed

7 files changed

+1259
-3
lines changed

buildSrc/src/main/kotlin/ktorm.maven-publish.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,11 @@ publishing {
126126
name.set("Kacper Stasiuk")
127127
email.set("kocproz@pm.me")
128128
}
129+
developer {
130+
id.set("2938137849")
131+
name.set("ccr")
132+
email.set("2938137849@qq.com")
133+
}
129134
}
130135
}
131136
}
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
/*
2+
* Copyright 2018-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.ktorm.support.sqlite
18+
19+
import org.ktorm.database.Database
20+
import org.ktorm.dsl.AssignmentsBuilder
21+
import org.ktorm.dsl.KtormDsl
22+
import org.ktorm.dsl.batchInsert
23+
import org.ktorm.expression.*
24+
import org.ktorm.schema.BaseTable
25+
import org.ktorm.schema.Column
26+
import org.ktorm.schema.ColumnDeclaring
27+
28+
/**
29+
* Bulk insert expression, represents a bulk insert statement in SQLite.
30+
*
31+
* For example:
32+
*
33+
* ```sql
34+
* insert into table (column1, column2)
35+
* values (?, ?), (?, ?), (?, ?)...
36+
* on conflict (...) do update set ...`
37+
* ```
38+
*
39+
* @property table the table to be inserted.
40+
* @property assignments column assignments of the bulk insert statement.
41+
* @property conflictColumns the index columns on which the conflict may happen.
42+
* @property updateAssignments the updated column assignments while key conflict exists.
43+
*/
44+
public data class BulkInsertExpression(
45+
val table: TableExpression,
46+
val assignments: List<List<ColumnAssignmentExpression<*>>>,
47+
val conflictColumns: List<ColumnExpression<*>> = emptyList(),
48+
val updateAssignments: List<ColumnAssignmentExpression<*>> = emptyList(),
49+
val where: ScalarExpression<Boolean>? = null,
50+
override val isLeafNode: Boolean = false,
51+
override val extraProperties: Map<String, Any> = emptyMap()
52+
) : SqlExpression()
53+
54+
/**
55+
* Bulk insert records to the table and return the effected row count.
56+
*
57+
* The usage is almost the same as [batchInsert], but this function is implemented by generating a special SQL
58+
* using SQLite's bulk insert syntax, instead of based on JDBC batch operations. For this reason, its performance
59+
* is much better than [batchInsert].
60+
*
61+
* The generated SQL is like: `insert into table (column1, column2) values (?, ?), (?, ?), (?, ?)...`.
62+
*
63+
* Usage:
64+
*
65+
* ```kotlin
66+
* database.bulkInsert(Employees) {
67+
* item {
68+
* set(it.name, "jerry")
69+
* set(it.job, "trainee")
70+
* set(it.managerId, 1)
71+
* set(it.hireDate, LocalDate.now())
72+
* set(it.salary, 50)
73+
* set(it.departmentId, 1)
74+
* }
75+
* item {
76+
* set(it.name, "linda")
77+
* set(it.job, "assistant")
78+
* set(it.managerId, 3)
79+
* set(it.hireDate, LocalDate.now())
80+
* set(it.salary, 100)
81+
* set(it.departmentId, 2)
82+
* }
83+
* }
84+
* ```
85+
*
86+
* @param table the table to be inserted.
87+
* @param block the DSL block, extension function of [BulkInsertStatementBuilder], used to construct the expression.
88+
* @return the effected row count.
89+
* @see batchInsert
90+
*/
91+
public fun <T : BaseTable<*>> Database.bulkInsert(
92+
table: T, block: BulkInsertStatementBuilder<T>.(T) -> Unit
93+
): Int {
94+
val builder = BulkInsertStatementBuilder(table).apply { block(table) }
95+
96+
val expression = AliasRemover.visit(
97+
BulkInsertExpression(table.asExpression(), builder.assignments)
98+
)
99+
100+
return executeUpdate(expression)
101+
}
102+
103+
/**
104+
* Bulk insert records to the table, determining if there is a key conflict while inserting each of them,
105+
* and automatically performs updates if any conflict exists.
106+
*
107+
* Usage:
108+
*
109+
* ```kotlin
110+
* database.bulkInsertOrUpdate(Employees) {
111+
* item {
112+
* set(it.id, 1)
113+
* set(it.name, "vince")
114+
* set(it.job, "engineer")
115+
* set(it.salary, 1000)
116+
* set(it.hireDate, LocalDate.now())
117+
* set(it.departmentId, 1)
118+
* }
119+
* item {
120+
* set(it.id, 5)
121+
* set(it.name, "vince")
122+
* set(it.job, "engineer")
123+
* set(it.salary, 1000)
124+
* set(it.hireDate, LocalDate.now())
125+
* set(it.departmentId, 1)
126+
* }
127+
* onConflict {
128+
* set(it.salary, it.salary + 900)
129+
* }
130+
* }
131+
* ```
132+
*
133+
* Generated SQL:
134+
*
135+
* ```sql
136+
* insert into t_employee (id, name, job, salary, hire_date, department_id)
137+
* values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)
138+
* on conflict (id) do update set salary = t_employee.salary + ?
139+
* ```
140+
*
141+
* @param table the table to be inserted.
142+
* @param block the DSL block used to construct the expression.
143+
* @return the effected row count.
144+
*/
145+
public fun <T : BaseTable<*>> Database.bulkInsertOrUpdate(
146+
table: T, block: BulkInsertOrUpdateStatementBuilder<T>.(T) -> Unit
147+
): Int {
148+
val expression = AliasRemover.visit(
149+
buildBulkInsertOrUpdateExpression(table, block = block)
150+
)
151+
152+
return executeUpdate(expression)
153+
}
154+
155+
/**
156+
* Build a bulk insert or update expression.
157+
*/
158+
private fun <T : BaseTable<*>> buildBulkInsertOrUpdateExpression(
159+
table: T, block: BulkInsertOrUpdateStatementBuilder<T>.(T) -> Unit
160+
): BulkInsertExpression {
161+
val builder = BulkInsertOrUpdateStatementBuilder(table).apply { block(table) }
162+
163+
val conflictColumns = builder.conflictColumns.ifEmpty { table.primaryKeys }
164+
if (conflictColumns.isEmpty()) {
165+
val msg = "" +
166+
"Table '$table' doesn't have a primary key, " +
167+
"you must specify the conflict columns when calling onConflict(col) { .. }"
168+
throw IllegalStateException(msg)
169+
}
170+
171+
if (!builder.doNothing && builder.updateAssignments.isEmpty()) {
172+
val msg = "" +
173+
"Cannot leave the onConflict clause empty! " +
174+
"If you desire no update action at all please explicitly call `doNothing()`"
175+
throw IllegalStateException(msg)
176+
}
177+
178+
return BulkInsertExpression(
179+
table = table.asExpression(),
180+
assignments = builder.assignments,
181+
conflictColumns = conflictColumns.map { it.asExpression() },
182+
updateAssignments = if (builder.doNothing) emptyList() else builder.updateAssignments,
183+
where = builder.where?.asExpression()
184+
)
185+
}
186+
187+
/**
188+
* DSL builder for bulk insert statements.
189+
*/
190+
@KtormDsl
191+
public open class BulkInsertStatementBuilder<T : BaseTable<*>>(internal val table: T) {
192+
internal val assignments = ArrayList<List<ColumnAssignmentExpression<*>>>()
193+
194+
/**
195+
* Add the assignments of a new row to the bulk insert.
196+
*/
197+
public fun item(block: AssignmentsBuilder.() -> Unit) {
198+
val builder = SQLiteAssignmentsBuilder().apply(block)
199+
200+
if (assignments.isEmpty()
201+
|| assignments[0].map { it.column.name } == builder.assignments.map { it.column.name }
202+
) {
203+
assignments += builder.assignments
204+
} else {
205+
throw IllegalArgumentException("Every item in a batch operation must be the same.")
206+
}
207+
}
208+
}
209+
210+
/**
211+
* DSL builder for bulk insert or update statements.
212+
*/
213+
@KtormDsl
214+
public class BulkInsertOrUpdateStatementBuilder<T : BaseTable<*>>(table: T) : BulkInsertStatementBuilder<T>(table) {
215+
internal val conflictColumns = ArrayList<Column<*>>()
216+
internal val updateAssignments = ArrayList<ColumnAssignmentExpression<*>>()
217+
internal var where: ColumnDeclaring<Boolean>? = null
218+
internal var doNothing: Boolean = false
219+
220+
/**
221+
* Specify the update assignments while any key conflict exists.
222+
*/
223+
public fun onConflict(vararg columns: Column<*>, block: InsertOrUpdateOnConflictClauseBuilder.() -> Unit) {
224+
val builder = InsertOrUpdateOnConflictClauseBuilder().apply(block)
225+
this.conflictColumns += columns
226+
this.updateAssignments += builder.assignments
227+
this.where = builder.where
228+
this.doNothing = builder.doNothing
229+
}
230+
}

0 commit comments

Comments
 (0)