Skip to content

Commit bc6a57a

Browse files
committed
Snowflake dialect
1 parent 601d9a1 commit bc6a57a

20 files changed

+1812
-7
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
<p align="center">
2-
<img src="https://raw.githubusercontent.com/kotlin-orm/ktorm-docs/master/source/images/logo-full.png" alt="Ktorm" width="300" />
3-
</p>
41
<p align="center">
52
<a href="https://github.com/kotlin-orm/ktorm/actions/workflows/build.yml">
63
<img src="https://github.com/kotlin-orm/ktorm/actions/workflows/build.yml/badge.svg" alt="Build Status" />

buildSrc/src/main/kotlin/ktorm.base.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ detekt {
2525
}
2626

2727
java {
28-
sourceCompatibility = JavaVersion.VERSION_1_8
29-
targetCompatibility = JavaVersion.VERSION_1_8
28+
sourceCompatibility = JavaVersion.VERSION_11
29+
targetCompatibility = JavaVersion.VERSION_11
3030
}
3131

3232
tasks {
@@ -37,15 +37,15 @@ tasks {
3737
dependsOn(codegen)
3838

3939
kotlinOptions {
40-
jvmTarget = "1.8"
40+
jvmTarget = "11"
4141
allWarningsAsErrors = true
4242
freeCompilerArgs = listOf("-Xexplicit-api=strict")
4343
}
4444
}
4545

4646
compileTestKotlin {
4747
kotlinOptions {
48-
jvmTarget = "1.8"
48+
jvmTarget = "11"
4949
}
5050
}
5151

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,11 @@ publishing {
164164
name.set("hc224")
165165
email.set("[email protected]")
166166
}
167+
developer {
168+
id.set("dmitchell")
169+
name.set("Don Mitchell")
170+
email.set("[email protected]")
171+
}
167172
}
168173
}
169174
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*
2+
* Copyright 2018-2025 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.snowflake
18+
19+
import org.ktorm.database.Database
20+
import org.ktorm.dsl.AliasRemover
21+
import org.ktorm.dsl.AssignmentsBuilder
22+
import org.ktorm.dsl.KtormDsl
23+
import org.ktorm.dsl.batchInsert
24+
import org.ktorm.expression.ColumnAssignmentExpression
25+
import org.ktorm.expression.FunctionExpression
26+
import org.ktorm.expression.SqlExpression
27+
import org.ktorm.expression.TableExpression
28+
import org.ktorm.schema.BaseTable
29+
import org.ktorm.schema.Column
30+
31+
/**
32+
* Bulk insert expression, represents a bulk insert statement in Snowflake. This is a copy of the MySQL version.
33+
* Given that 4 modules copy this, it's probably worth moving this to a shared module.
34+
*
35+
* For example:
36+
*
37+
* ```sql
38+
* insert into table (column1, column2) values (?, ?), (?, ?), (?, ?)...
39+
* on duplicate key update ...
40+
* ```
41+
*
42+
* @property table the table to be inserted.
43+
* @property assignments column assignments of the bulk insert statement.
44+
* @property updateAssignments the updated column assignments while key conflict exists.
45+
*/
46+
public data class BulkInsertExpression(
47+
val table: TableExpression,
48+
val assignments: List<List<ColumnAssignmentExpression<*>>>,
49+
val updateAssignments: List<ColumnAssignmentExpression<*>> = emptyList(),
50+
override val isLeafNode: Boolean = false,
51+
override val extraProperties: Map<String, Any> = emptyMap()
52+
) : SqlExpression()
53+
54+
/**
55+
* Construct a bulk insert expression in the given closure, then execute it 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 MySQL 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>.() -> Unit
93+
): Int {
94+
val builder = BulkInsertStatementBuilder(table).apply(block)
95+
require(builder.assignments.isNotEmpty()) { "There are no items in the bulk operation." }
96+
97+
val expression = dialect.createExpressionVisitor(AliasRemover).visit(
98+
BulkInsertExpression(table.asExpression(), builder.assignments)
99+
)
100+
101+
return executeUpdate(expression)
102+
}
103+
104+
/**
105+
* Bulk insert records to the table, determining if there is a key conflict while inserting each of them,
106+
* and automatically performs updates if any conflict exists.
107+
*
108+
* Usage:
109+
*
110+
* ```kotlin
111+
* database.bulkInsertOrUpdate(Employees) {
112+
* item {
113+
* set(it.id, 1)
114+
* set(it.name, "vince")
115+
* set(it.job, "engineer")
116+
* set(it.salary, 1000)
117+
* set(it.hireDate, LocalDate.now())
118+
* set(it.departmentId, 1)
119+
* }
120+
* item {
121+
* set(it.id, 5)
122+
* set(it.name, "vince")
123+
* set(it.job, "engineer")
124+
* set(it.salary, 1000)
125+
* set(it.hireDate, LocalDate.now())
126+
* set(it.departmentId, 1)
127+
* }
128+
* onDuplicateKey {
129+
* set(it.salary, it.salary + 900)
130+
* }
131+
* }
132+
* ```
133+
*
134+
* Generated SQL:
135+
*
136+
* ```sql
137+
* insert into t_employee (id, name, job, salary, hire_date, department_id)
138+
* values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?)
139+
* on duplicate key update salary = salary + ?
140+
* ```
141+
*
142+
* @since 3.3.0
143+
* @param table the table to be inserted.
144+
* @param block the DSL block used to construct the expression.
145+
* @return the effected row count.
146+
* @see bulkInsert
147+
*/
148+
public fun <T : BaseTable<*>> Database.bulkInsertOrUpdate(
149+
table: T, block: BulkInsertOrUpdateStatementBuilder<T>.() -> Unit
150+
): Int {
151+
val builder = BulkInsertOrUpdateStatementBuilder(table).apply(block)
152+
require(builder.assignments.isNotEmpty()) { "There are no items in the bulk operation." }
153+
require(builder.assignments.none { it.isEmpty() }) { "There are no columns to insert in the statement." }
154+
155+
val expression = dialect.createExpressionVisitor(AliasRemover).visit(
156+
BulkInsertExpression(table.asExpression(), builder.assignments, builder.updateAssignments)
157+
)
158+
159+
return executeUpdate(expression)
160+
}
161+
162+
/**
163+
* DSL builder for bulk insert statements.
164+
*/
165+
@KtormDsl
166+
public open class BulkInsertStatementBuilder<T : BaseTable<*>>(internal val table: T) {
167+
internal val assignments = ArrayList<List<ColumnAssignmentExpression<*>>>()
168+
169+
/**
170+
* Add the assignments of a new row to the bulk insert.
171+
*/
172+
public fun item(block: AssignmentsBuilder.(T) -> Unit) {
173+
val builder = SnowflakeAssignmentsBuilder()
174+
builder.block(table)
175+
176+
require(
177+
assignments.isEmpty() ||
178+
assignments[0].map { it.column.name } == builder.assignments.map { it.column.name }
179+
) { "Every item in a batch operation must be the same." }
180+
assignments += builder.assignments
181+
}
182+
}
183+
184+
/**
185+
* DSL builder for bulk insert or update statements.
186+
*/
187+
@KtormDsl
188+
public class BulkInsertOrUpdateStatementBuilder<T : BaseTable<*>>(table: T) : BulkInsertStatementBuilder<T>(table) {
189+
internal val updateAssignments = ArrayList<ColumnAssignmentExpression<*>>()
190+
191+
/**
192+
* Specify the update assignments while any key conflict exists.
193+
*/
194+
public fun onDuplicateKey(block: BulkInsertOrUpdateOnDuplicateKeyClauseBuilder.(T) -> Unit) {
195+
val builder = BulkInsertOrUpdateOnDuplicateKeyClauseBuilder()
196+
builder.block(table)
197+
updateAssignments += builder.assignments
198+
}
199+
}
200+
201+
/**
202+
* DSL builder for bulk insert or update on duplicate key clause.
203+
*/
204+
@KtormDsl
205+
public class BulkInsertOrUpdateOnDuplicateKeyClauseBuilder : SnowflakeAssignmentsBuilder() {
206+
207+
/**
208+
* Use VALUES() function in a ON DUPLICATE KEY UPDATE clause.
209+
*/
210+
public fun <T : Any> values(column: Column<T>): FunctionExpression<T> {
211+
// values(column)
212+
return FunctionExpression(
213+
functionName = "values",
214+
arguments = listOf(column.asExpression()),
215+
sqlType = column.sqlType
216+
)
217+
}
218+
}

0 commit comments

Comments
 (0)