@@ -25,11 +25,12 @@ import org.ktorm.expression.ColumnExpression
2525import org.ktorm.expression.SqlExpression
2626import org.ktorm.expression.TableExpression
2727import org.ktorm.schema.BaseTable
28+ import org.ktorm.schema.Column
2829
2930/* *
3031 * Bulk insert expression, represents a bulk insert statement in PostgreSQL.
3132 *
32- * For example:
33+ * For example:
3334 * `insert into table (column1, column2) values (?, ?), (?, ?), (?, ?)... on conflict (...) do update set ...`.
3435 *
3536 * @property table the table to be inserted.
@@ -85,7 +86,9 @@ public data class BulkInsertExpression(
8586 * @return the effected row count.
8687 * @see batchInsert
8788 */
88- public fun <T : BaseTable <* >> Database.bulkInsert (table : T , block : BulkInsertStatementBuilder <T >.() -> Unit ): Int {
89+ public fun <T : BaseTable <* >> Database.bulkInsert (
90+ table : T , block : BulkInsertStatementBuilder <T >.() -> Unit
91+ ): Int {
8992 val builder = BulkInsertStatementBuilder (table).apply (block)
9093
9194 val expression = AliasRemover .visit(
@@ -95,11 +98,80 @@ public fun <T : BaseTable<*>> Database.bulkInsert(table: T, block: BulkInsertSta
9598 return executeUpdate(expression)
9699}
97100
101+ /* *
102+ * Bulk insert records to the table, determining if there is a key conflict while inserting each of them,
103+ * and automatically performs updates if any conflict exists.
104+ *
105+ * Usage:
106+ *
107+ * ```kotlin
108+ * database.bulkInsertOrUpdate(Employees) {
109+ * item {
110+ * set(it.id, 1)
111+ * set(it.name, "vince")
112+ * set(it.job, "engineer")
113+ * set(it.salary, 1000)
114+ * set(it.hireDate, LocalDate.now())
115+ * set(it.departmentId, 1)
116+ * }
117+ * item {
118+ * set(it.id, 5)
119+ * set(it.name, "vince")
120+ * set(it.job, "engineer")
121+ * set(it.salary, 1000)
122+ * set(it.hireDate, LocalDate.now())
123+ * set(it.departmentId, 1)
124+ * }
125+ * onConflict {
126+ * set(it.salary, it.salary + 900)
127+ * }
128+ * }
129+ * ```
130+ *
131+ * Generated SQL:
132+ *
133+ * ```sql
134+ * insert into t_employee (id, name, job, salary, hire_date, department_id)
135+ * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)
136+ * on conflict (id) do update set salary = salary + ?
137+ * ```
138+ *
139+ * @since 3.3.0
140+ * @param table the table to be inserted.
141+ * @param block the DSL block used to construct the expression.
142+ * @return the effected row count.
143+ * @see bulkInsert
144+ */
145+ public fun <T : BaseTable <* >> Database.bulkInsertOrUpdate (
146+ table : T , block : BulkInsertOrUpdateStatementBuilder <T >.() -> Unit
147+ ): Int {
148+ val builder = BulkInsertOrUpdateStatementBuilder (table).apply (block)
149+
150+ val primaryKeys = table.primaryKeys
151+ if (primaryKeys.isEmpty() && builder.conflictColumns.isEmpty()) {
152+ val msg =
153+ " Table '$table ' doesn't have a primary key, " +
154+ " you must specify the conflict columns when calling onConflict(col) { .. }"
155+ throw IllegalStateException (msg)
156+ }
157+
158+ val expression = AliasRemover .visit(
159+ BulkInsertExpression (
160+ table = table.asExpression(),
161+ assignments = builder.assignments,
162+ conflictColumns = builder.conflictColumns.ifEmpty { primaryKeys }.map { it.asExpression() },
163+ updateAssignments = builder.updateAssignments
164+ )
165+ )
166+
167+ return executeUpdate(expression)
168+ }
169+
98170/* *
99171 * DSL builder for bulk insert statements.
100172 */
101173@KtormDsl
102- public class BulkInsertStatementBuilder <T : BaseTable <* >>(internal val table : T ) {
174+ public open class BulkInsertStatementBuilder <T : BaseTable <* >>(internal val table : T ) {
103175 internal val assignments = ArrayList <List <ColumnAssignmentExpression <* >>>()
104176
105177 /* *
@@ -118,3 +190,21 @@ public class BulkInsertStatementBuilder<T : BaseTable<*>>(internal val table: T)
118190 }
119191 }
120192}
193+
194+ /* *
195+ * DSL builder for bulk insert or update statements.
196+ */
197+ @KtormDsl
198+ public class BulkInsertOrUpdateStatementBuilder <T : BaseTable <* >>(table : T ) : BulkInsertStatementBuilder<T>(table) {
199+ internal val updateAssignments = ArrayList <ColumnAssignmentExpression <* >>()
200+ internal val conflictColumns = ArrayList <Column <* >>()
201+
202+ /* *
203+ * Specify the update assignments while any key conflict exists.
204+ */
205+ public fun onConflict (vararg columns : Column <* >, block : AssignmentsBuilder .() -> Unit ) {
206+ val builder = PostgreSqlAssignmentsBuilder ().apply (block)
207+ updateAssignments + = builder.assignments
208+ conflictColumns + = columns
209+ }
210+ }
0 commit comments