From 93d0919735ca5b37904bf31fbc8dc0e759046e3d Mon Sep 17 00:00:00 2001 From: Paolo Rossi Date: Fri, 7 Apr 2023 12:27:01 +0200 Subject: [PATCH 01/40] Update documentation for insertOrUpdate and bulkInsertOrUpdate --- .../support/postgresql/InsertOrUpdate.kt | 91 ++++++++++++++++++ .../org/ktorm/support/sqlite/BulkInsert.kt | 92 +++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt index f48eaa34..b69b42ed 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt @@ -75,6 +75,29 @@ public data class InsertOrUpdateExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 2.7 * @param table the table to be inserted. * @param block the DSL block used to construct the expression. @@ -115,6 +138,28 @@ public fun > Database.insertOrUpdate( * on conflict (id) do update set salary = t_employee.salary + ? * returning id * ``` + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` * * @since 3.4.0 * @param table the table to be inserted. @@ -162,6 +207,29 @@ public fun , C : Any> Database.insertOrUpdateReturning( * returning id, job * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 3.4.0 * @param table the table to be inserted. * @param returning the columns to return @@ -210,6 +278,29 @@ public fun , C1 : Any, C2 : Any> Database.insertOrUpdateReturni * returning id, job, salary * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 3.4.0 * @param table the table to be inserted. * @param returning the columns to return diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt index 0d3f7fbf..e9420cc1 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt @@ -311,6 +311,29 @@ private fun > Database.buildBulkInsertExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) 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. @@ -361,6 +384,29 @@ public fun > Database.bulkInsertOrUpdate( * returning id * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 3.6.0 * @param table the table to be inserted. * @param returning the column to return @@ -414,6 +460,29 @@ public fun , C : Any> Database.bulkInsertOrUpdateReturning( * returning id, job * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 3.6.0 * @param table the table to be inserted. * @param returning the columns to return @@ -468,6 +537,29 @@ public fun , C1 : Any, C2 : Any> Database.bulkInsertOrUpdateRet * returning id, job, salary * ``` * + * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 3.6.0 * @param table the table to be inserted. * @param returning the columns to return From 87d166e1200332c14dc45441ff941853c5b07438 Mon Sep 17 00:00:00 2001 From: hc224 Date: Sun, 12 Nov 2023 18:39:59 -0600 Subject: [PATCH 02/40] provide access to changed properties --- .../src/main/kotlin/ktorm.publish.gradle.kts | 24 ++-------- .../main/kotlin/org/ktorm/entity/Entity.kt | 8 ++++ .../org/ktorm/entity/EntityImplementation.kt | 11 +++-- .../kotlin/org/ktorm/entity/EntityTest.kt | 46 ++++++++++++++++++- 4 files changed, 65 insertions(+), 24 deletions(-) diff --git a/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts b/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts index bb46335d..1f0bf707 100644 --- a/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts @@ -155,25 +155,11 @@ publishing { id.set("brohacz") name.set("Michal Brosig") } - } - } - } - - repositories { - maven { - name = "central" - url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") - credentials { - username = System.getenv("OSSRH_USER") - password = System.getenv("OSSRH_PASSWORD") - } - } - maven { - name = "snapshot" - url = uri("https://oss.sonatype.org/content/repositories/snapshots") - credentials { - username = System.getenv("OSSRH_USER") - password = System.getenv("OSSRH_PASSWORD") + developer { + id.set("hc224") + name.set("hc224") + email.set("hc224@pm.me") + } } } } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index ce2d1cc6..16424a6e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -143,6 +143,14 @@ public interface Entity> : Serializable { */ public val properties: Map + /** + * Return the immutable view of the original values for this entity's changed properties. + * + * Properties are set as changed when constructing an entity with non-default values or when setting a property. + * If the property is set to the same value it is still marked as changed. + */ + public val changedProperties: Map + /** * Update the property changes of this entity into the database and return the affected record number. * diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index 7bbd8a08..a057b0d7 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -38,7 +38,7 @@ internal class EntityImplementation( @Transient var fromDatabase: Database? = fromDatabase @Transient var fromTable: Table<*>? = fromTable @Transient var parent: EntityImplementation? = parent - @Transient var changedProperties = LinkedHashSet() + @Transient var changedProperties = LinkedHashMap() override fun invoke(proxy: Any, method: Method, args: Array?): Any? { return when (method.declaringClass.kotlin) { @@ -54,6 +54,7 @@ internal class EntityImplementation( when (method.name) { "getEntityClass" -> this.entityClass "getProperties" -> Collections.unmodifiableMap(this.values) + "getChangedProperties" -> Collections.unmodifiableMap(this.changedProperties) "flushChanges" -> this.doFlushChanges() "discardChanges" -> this.doDiscardChanges() "delete" -> this.doDelete() @@ -150,13 +151,15 @@ internal class EntityImplementation( throw UnsupportedOperationException(msg) } + // Map#putIfAbsent treats null as absent, but null is a valid entity value + if (!changedProperties.containsKey(name)) + changedProperties[name] = this.values.getOrDefault(name, null) values[name] = value - changedProperties.add(name) } private fun copy(): Entity<*> { val entity = Entity.create(entityClass, parent, fromDatabase, fromTable) - entity.implementation.changedProperties.addAll(changedProperties) + entity.implementation.changedProperties.putAll(changedProperties) for ((name, value) in values) { if (value is Entity<*>) { @@ -204,7 +207,7 @@ internal class EntityImplementation( val javaClass = Class.forName(input.readUTF(), true, Thread.currentThread().contextClassLoader) entityClass = javaClass.kotlin values = input.readObject() as LinkedHashMap - changedProperties = LinkedHashSet() + changedProperties = LinkedHashMap() } override fun equals(other: Any?): Boolean { diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index 54044f18..a89f95f5 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -47,6 +47,50 @@ class EntityTest : BaseTest() { assert(employee.job == "") } + @Test + fun testEntityChangedPropertiesInitializer() { + val employee = Employee { name = "walter" } + println(employee) + + assert(employee.changedProperties.size == 1) + assert(employee.changedProperties.contains("name")) + assert(employee.changedProperties["name"] == null) + } + + @Test + fun testEntityChangedPropertiesSetter() { + val employee = Employee { } + println(employee) + + val oldChangedPropertiesIsEmpty = employee.changedProperties.isEmpty() + + employee.name = "walter" + println(employee) + + assert(oldChangedPropertiesIsEmpty) + assert(employee.changedProperties.size == 1) + assert(employee.changedProperties.contains("name")) + assert(employee.changedProperties["name"] == null) + } + + @Test + fun testEntityChangedPropertiesMultiple() { + val employee = Employee { }.copy() + println(employee) + + val oldChangedPropertiesIsEmpty = employee.changedProperties.isEmpty() + + employee.name = "walter" + println(employee) + employee.name = "vince" + println(employee) + + assert(oldChangedPropertiesIsEmpty) + assert(employee.changedProperties.size == 1) + assert(employee.changedProperties.containsKey("name")) + assert(employee.changedProperties["name"] == null) + } + @Test fun testDefaultMethod() { for (method in Employee::class.java.methods) { @@ -783,4 +827,4 @@ class EntityTest : BaseTest() { assert(departmentTransient !== departmentAttached) assert(departmentTransient.hashCode() == departmentAttached.hashCode()) } -} \ No newline at end of file +} From a5b1be8ee2b54684b7e0e3cd356b01595807f9e0 Mon Sep 17 00:00:00 2001 From: hc224 Date: Sun, 12 Nov 2023 19:04:45 -0600 Subject: [PATCH 03/40] restore repositories from local testing --- .../src/main/kotlin/ktorm.publish.gradle.kts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts b/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts index 1f0bf707..1f40f5e2 100644 --- a/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.publish.gradle.kts @@ -163,6 +163,25 @@ publishing { } } } + + repositories { + maven { + name = "central" + url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") + credentials { + username = System.getenv("OSSRH_USER") + password = System.getenv("OSSRH_PASSWORD") + } + } + maven { + name = "snapshot" + url = uri("https://oss.sonatype.org/content/repositories/snapshots") + credentials { + username = System.getenv("OSSRH_USER") + password = System.getenv("OSSRH_PASSWORD") + } + } + } } } From 45e6c5e2d971810ac3204583b4cb175a6fccb462 Mon Sep 17 00:00:00 2001 From: wrongwrong Date: Sun, 19 Nov 2023 01:56:52 +0900 Subject: [PATCH 04/40] Fixed a binary compatibility error when using a higher version of KotlinModule --- .../src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt index ef0b90f3..bacb713e 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/JsonSqlType.kt @@ -19,7 +19,7 @@ package org.ktorm.jackson import com.fasterxml.jackson.databind.JavaType import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.KotlinModule +import com.fasterxml.jackson.module.kotlin.kotlinModule import org.ktorm.schema.* import java.lang.reflect.InvocationTargetException import java.sql.PreparedStatement @@ -31,7 +31,7 @@ import java.sql.Types */ public val sharedObjectMapper: ObjectMapper = ObjectMapper() .registerModule(KtormModule()) - .registerModule(KotlinModule()) + .registerModule(kotlinModule()) .registerModule(JavaTimeModule()) /** From 2e038777cbb7f4e044bcd45ca9b94334da6504da Mon Sep 17 00:00:00 2001 From: qumn Date: Thu, 13 Jun 2024 16:48:49 +0800 Subject: [PATCH 05/40] add open modifier for BaseTable.asExpression function --- ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt index b1f425b9..fbe62d3e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/BaseTable.kt @@ -372,7 +372,7 @@ public abstract class BaseTable( /** * Convert this table to a [TableExpression]. */ - public fun asExpression(): TableExpression { + public open fun asExpression(): TableExpression { return TableExpression(tableName, alias, catalog, schema) } From 8360f333ed285e31f2a9750f10f717afc6655e32 Mon Sep 17 00:00:00 2001 From: Alex Riedler Date: Thu, 20 Jun 2024 18:50:18 -0400 Subject: [PATCH 06/40] fix: reduce memory allocation on Column.label used for accessing in query --- ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt index 6d4f8095..5784fdf2 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt @@ -118,7 +118,7 @@ public data class Column( * * @see ColumnDeclaringExpression */ - val label: String get() = toString(separator = "_") + val label: String by lazy { toString(separator = "_") } /** * Return all the bindings of this column, including the primary [binding] and [extraBindings]. From 33a631f126f04d94a7dd3ae3b6753f0ea35d0d66 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 24 Jun 2024 14:24:45 +0800 Subject: [PATCH 07/40] prepare for 4.1.0 --- ktorm.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm.version b/ktorm.version index fcdb2e10..6c3e1dec 100644 --- a/ktorm.version +++ b/ktorm.version @@ -1 +1 @@ -4.0.0 +4.1.0-SNAPSHOT From 1bcc90585ed28cb616dd764181f74b0303f839e2 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 24 Jun 2024 15:06:10 +0800 Subject: [PATCH 08/40] fix typo --- README.md | 2 +- ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt | 2 +- .../src/main/kotlin/org/ktorm/expression/SqlExpressions.kt | 6 +++--- ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt | 2 +- ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c510fd87..d7e6a078 100644 --- a/README.md +++ b/README.md @@ -260,7 +260,7 @@ object Employees : Table("t_employee") { } ``` -> Naming Strategy: It's highly recommended to name your entity classes by singular nouns, name table objects by plurals (eg. Employee/Employees, Department/Departments). +> Naming Strategy: It's highly recommended to name your entity classes by singular nouns, name table objects by plurals (e.g. Employee/Employees, Department/Departments). Now that column bindings are configured, so we can use [sequence APIs](#Entity-Sequence-APIs) to perform many operations on entities. Let's add two extension properties for `Database` first. These properties return new created sequence objects via `sequenceOf` and they can help us improve the readability of the code: diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt index b903d0d0..d97fcc55 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/Database.kt @@ -147,7 +147,7 @@ public class Database( public val name: String /** - * The name of the connected database product, eg. MySQL, H2. + * The name of the connected database product, e.g. MySQL, H2. */ public val productName: String diff --git a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt index 8e9701ed..ae623e56 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/expression/SqlExpressions.kt @@ -52,7 +52,7 @@ public abstract class SqlExpression { } /** - * Base class of scalar expressions. An expression is "scalar" if it has a return value (eg. `a + 1`). + * Base class of scalar expressions. An expression is "scalar" if it has a return value (e.g. `a + 1`). * * @param T the return value's type of this scalar expression. */ @@ -85,7 +85,7 @@ public abstract class QuerySourceExpression : SqlExpression() * @property orderBy a list of order-by expressions, used in the `order by` clause of a query. * @property offset the offset of the first returned record. * @property limit max record numbers returned by the query. - * @property tableAlias the alias when this query is nested in another query's source, eg. `select * from (...) alias`. + * @property tableAlias the alias when this query is nested in another query's source, e.g. `select * from (...) alias`. */ public sealed class QueryExpression : QuerySourceExpression() { public abstract val orderBy: List @@ -151,7 +151,7 @@ public data class InsertExpression( ) : SqlExpression() /** - * Insert-from-query expression, eg. `insert into tmp(num) select 1 from dual`. + * Insert-from-query expression, e.g. `insert into tmp(num) select 1 from dual`. * * @property table the table to be inserted. * @property columns the columns to be inserted. diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt index 6d4f8095..20038cea 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt @@ -29,7 +29,7 @@ import kotlin.reflect.KProperty1 public sealed class ColumnBinding /** - * Bind the column to nested properties, eg. `employee.manager.department.id`. + * Bind the column to nested properties, e.g. `employee.manager.department.id`. * * @property properties the nested properties, cannot be empty. */ diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt index 7ad6581c..90903862 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Table.kt @@ -56,7 +56,7 @@ public open class Table>( ) : BaseTable(tableName, alias, catalog, schema, entityClass) { /** - * Bind the column to nested properties, eg. `employee.manager.department.id`. + * Bind the column to nested properties, e.g. `employee.manager.department.id`. * * Note: Since [Column] is immutable, this function will create a new [Column] instance and replace the origin * registered one. From 733494a39f8ea6ba6fc5f51c434784ebf4cfa916 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Jul 2024 11:35:17 +0800 Subject: [PATCH 09/40] update comment --- .../ktorm/support/postgresql/BulkInsert.kt | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt index 927b7147..9eb4a84f 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/BulkInsert.kt @@ -309,6 +309,42 @@ private fun > buildBulkInsertExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```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(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * ``` + * * @since 3.3.0 * @param table the table to be inserted. * @param block the DSL block used to construct the expression. @@ -360,6 +396,43 @@ public fun > Database.bulkInsertOrUpdate( * returning id * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * database.bulkInsertOrUpdateReturning(Employees, Employees.id) { + * 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(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id + * ``` + * * @since 3.4.0 * @param table the table to be inserted. * @param returning the column to return @@ -413,6 +486,43 @@ public fun , C : Any> Database.bulkInsertOrUpdateReturning( * returning id, job * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * database.bulkInsertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { + * 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(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job + * ``` + * * @since 3.4.0 * @param table the table to be inserted. * @param returning the columns to return @@ -467,6 +577,43 @@ public fun , C1 : Any, C2 : Any> Database.bulkInsertOrUpdateRet * returning id, job, salary * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * database.bulkInsertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * 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(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job, salary + * ``` + * * @since 3.4.0 * @param table the table to be inserted. * @param returning the columns to return From 59583dfdd37b6e707ee05391b2313cc1da4c29c7 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Jul 2024 11:43:41 +0800 Subject: [PATCH 10/40] update comment --- .../org/ktorm/support/postgresql/Global.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt index 9596c5fa..ec721bb9 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/Global.kt @@ -68,6 +68,31 @@ internal val Database.Companion.global: Database get() { * on conflict (id) do update set salary = salary + ? * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * Employees.insertOrUpdate { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = salary + ? + * ``` + * * @param block the DSL block used to construct the expression. * @return the effected row count. */ @@ -157,6 +182,42 @@ public fun > T.bulkInsert(block: BulkInsertStatementBuilder. * on conflict (id) do update set salary = salary + ? * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * Employees.bulkInsertOrUpdate { + * 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(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = salary + ? + * ``` + * * @since 3.3.0 * @param block the DSL block used to construct the expression. * @return the effected row count. From e47924c5a2062e6646c28d32ebad82b24e7233ef Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Jul 2024 11:55:22 +0800 Subject: [PATCH 11/40] update comment --- .../support/postgresql/InsertOrUpdate.kt | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt index 265ad437..22419ac8 100644 --- a/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt +++ b/ktorm-support-postgresql/src/main/kotlin/org/ktorm/support/postgresql/InsertOrUpdate.kt @@ -75,7 +75,9 @@ public data class InsertOrUpdateExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin * database.insertOrUpdate(Employees) { @@ -90,6 +92,7 @@ public data class InsertOrUpdateExpression( * } * } * ``` + * * Generated SQL: * * ```sql @@ -138,10 +141,13 @@ public fun > Database.insertOrUpdate( * on conflict (id) do update set salary = t_employee.salary + ? * returning id * ``` - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { + * val id = database.insertOrUpdateReturning(Employees, Employees.id) { * set(it.id, 1) * set(it.name, "vince") * set(it.job, "engineer") @@ -153,12 +159,14 @@ public fun > Database.insertOrUpdate( * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) * values (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id * ``` * * @since 3.4.0 @@ -207,10 +215,12 @@ public fun , C : Any> Database.insertOrUpdateReturning( * returning id, job * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { + * val (id, job) = database.insertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { * set(it.id, 1) * set(it.name, "vince") * set(it.job, "engineer") @@ -222,12 +232,14 @@ public fun , C : Any> Database.insertOrUpdateReturning( * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) * values (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job * ``` * * @since 3.4.0 @@ -278,10 +290,13 @@ public fun , C1 : Any, C2 : Any> Database.insertOrUpdateReturni * returning id, job, salary * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { + * val (id, job, salary) = + * database.insertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { * set(it.id, 1) * set(it.name, "vince") * set(it.job, "engineer") @@ -293,12 +308,14 @@ public fun , C1 : Any, C2 : Any> Database.insertOrUpdateReturni * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) * values (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job, salary * ``` * * @since 3.4.0 From 6bc8c419f02d08bfa82e861e5c1114961f5ab2e1 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Jul 2024 13:41:10 +0800 Subject: [PATCH 12/40] update comment --- .../org/ktorm/support/sqlite/BulkInsert.kt | 127 +++++++++++++----- 1 file changed, 91 insertions(+), 36 deletions(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt index e46428cc..13383313 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/BulkInsert.kt @@ -311,26 +311,39 @@ private fun > Database.buildBulkInsertExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { - * 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) + * 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(it.name, it.job) { * set(it.salary, it.salary + 900) * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? * ``` * @@ -384,27 +397,41 @@ public fun > Database.bulkInsertOrUpdate( * returning id * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { - * 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) + * database.bulkInsertOrUpdateReturning(Employees, Employees.id) { + * 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(it.name, it.job) { * set(it.salary, it.salary + 900) * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id * ``` * * @since 3.6.0 @@ -460,27 +487,41 @@ public fun , C : Any> Database.bulkInsertOrUpdateReturning( * returning id, job * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { - * 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) + * database.bulkInsertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { + * 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(it.name, it.job) { * set(it.salary, it.salary + 900) * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job * ``` * * @since 3.6.0 @@ -537,27 +578,41 @@ public fun , C1 : Any, C2 : Any> Database.bulkInsertOrUpdateRet * returning id, job, salary * ``` * - * By default, the column used into the `on conflict` statement is the primary key you already defined in the schema definition. If you want, you can specify one or more columns for the `on conflict` statement like in the following example: + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: * * ```kotlin - * database.insertOrUpdate(Employees) { - * 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) + * database.bulkInsertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * 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(it.name, it.job) { * set(it.salary, it.salary + 900) * } * } * ``` + * * Generated SQL: * * ```sql * insert into t_employee (id, name, job, salary, hire_date, department_id) - * values (?, ?, ?, ?, ?, ?) + * values (?, ?, ?, ?, ?, ?), (?, ?, ?, ?, ?, ?) * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job, salary * ``` * * @since 3.6.0 From 6dba151e287fced09caa51ff2218d03d0b095a2d Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Jul 2024 13:50:57 +0800 Subject: [PATCH 13/40] update comment --- .../ktorm/support/sqlite/InsertOrUpdate.kt | 110 +++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt index 04b2a371..d59994c8 100644 --- a/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt +++ b/ktorm-support-sqlite/src/main/kotlin/org/ktorm/support/sqlite/InsertOrUpdate.kt @@ -62,7 +62,7 @@ public data class InsertOrUpdateExpression( * set(it.salary, 1000) * set(it.hireDate, LocalDate.now()) * set(it.departmentId, 1) - * onConflict(it.id) { + * onConflict { * set(it.salary, it.salary + 900) * } * } @@ -76,6 +76,32 @@ public data class InsertOrUpdateExpression( * on conflict (id) do update set salary = t_employee.salary + ? * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * database.insertOrUpdate(Employees) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) 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. @@ -116,6 +142,33 @@ public fun > Database.insertOrUpdate( * returning id * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * val id = database.insertOrUpdateReturning(Employees, Employees.id) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id + * ``` + * * @since 3.6.0 * @param table the table to be inserted. * @param returning the column to return @@ -162,6 +215,33 @@ public fun , C : Any> Database.insertOrUpdateReturning( * returning id, job * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * val (id, job) = database.insertOrUpdateReturning(Employees, Pair(Employees.id, Employees.job)) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job + * ``` + * * @since 3.6.0 * @param table the table to be inserted. * @param returning the columns to return @@ -210,6 +290,34 @@ public fun , C1 : Any, C2 : Any> Database.insertOrUpdateReturni * returning id, job, salary * ``` * + * By default, the column used in the `on conflict` statement is the primary key you already defined in + * the schema definition. If you want, you can specify one or more columns for the `on conflict` statement + * as belows: + * + * ```kotlin + * val (id, job, salary) = + * database.insertOrUpdateReturning(Employees, Triple(Employees.id, Employees.job, Employees.salary)) { + * 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) + * onConflict(it.name, it.job) { + * set(it.salary, it.salary + 900) + * } + * } + * ``` + * + * Generated SQL: + * + * ```sql + * insert into t_employee (id, name, job, salary, hire_date, department_id) + * values (?, ?, ?, ?, ?, ?) + * on conflict (name, job) do update set salary = t_employee.salary + ? + * returning id, job, salary + * ``` + * * @since 3.6.0 * @param table the table to be inserted. * @param returning the columns to return From 46c5f9ebe4ac53189bb8a0428c1da80cc1f61060 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 6 Jul 2024 10:51:30 +0800 Subject: [PATCH 14/40] update comment --- ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index 2630ad24..816d42b0 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -69,12 +69,12 @@ import kotlin.reflect.jvm.jvmErasure * * - For [Boolean] type, the default value is `false`. * - For [Char] type, the default value is `\u0000`. - * - For number types (such as [Int], [Long], [Double], etc), the default value is zero. + * - For number types (such as [Int], [Long], [Double], etc.), the default value is zero. * - For [String] type, the default value is an empty string. * - For entity types, the default value is a new-created entity object which is empty. * - For enum types, the default value is the first value of the enum, whose ordinal is 0. * - For array types, the default value is a new-created empty array. - * - For collection types (such as [Set], [List], [Map], etc), the default value is a new created mutable collection + * - For collection types (such as [Set], [List], [Map], etc.), the default value is a new created mutable collection * of the concrete type. * - For any other types, the default value is an instance created by its no-args constructor. If the constructor * doesn't exist, an exception is thrown. @@ -156,7 +156,7 @@ public interface Entity> : Serializable { * `fromDatabase` references point to the database they are obtained from. For entity objects created by * [Entity.create] or [Entity.Factory], their `fromDatabase` references are `null` initially, so we can not call * [flushChanges] on them. But once we use them with [add] or [update] function, `fromDatabase` will be modified - * to the current database, so we will be able to call [flushChanges] on them afterwards. + * to the current database, so we will be able to call [flushChanges] on them afterward. * * @see add * @see update From 0a03a2923c3c55060759bcc5a87ac5d8cf9a897e Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 6 Jul 2024 10:56:42 +0800 Subject: [PATCH 15/40] fix typo --- ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index 816d42b0..5634a872 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -128,7 +128,7 @@ import kotlin.reflect.jvm.jvmErasure * refer to their documentation for more details. * * Besides of JDK serialization, the ktorm-jackson module also supports serializing entities in JSON format. This - * module provides an extension for Jackson, the famous JSON framework in Java word. It supports serializing entity + * module provides an extension for Jackson, the famous JSON framework in Java world. It supports serializing entity * objects into JSON format and parsing JSONs as entity objects. More details can be found in its documentation. */ public interface Entity> : Serializable { From 3747ce7d4bf12b8384dc211df72728077b21cfd1 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Jul 2024 09:12:23 +0800 Subject: [PATCH 16/40] update comment --- .../src/main/kotlin/org/ktorm/entity/Entity.kt | 14 +++++--------- .../src/main/kotlin/org/ktorm/entity/EntityDml.kt | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index d3835eb0..c8899daf 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -144,10 +144,7 @@ public interface Entity> : Serializable { public val properties: Map /** - * Return the immutable view of the original values for this entity's changed properties. - * - * Properties are set as changed when constructing an entity with non-default values or when setting a property. - * If the property is set to the same value it is still marked as changed. + * Return the immutable view of this entity's changed properties and their original values. */ public val changedProperties: Map @@ -174,8 +171,7 @@ public interface Entity> : Serializable { /** * Clear the tracked property changes of this entity. * - * After calling this function, the [flushChanges] doesn't do anything anymore because the property changes - * are discarded. + * After calling this function, [flushChanges] will do nothing because property changes are discarded. */ public fun discardChanges() @@ -199,7 +195,7 @@ public interface Entity> : Serializable { * Obtain a property's value by its name. * * Note that this function doesn't follow the rules of default values discussed in the class level documentation. - * If the value doesn't exist, we will return `null` simply. + * If the value doesn't exist, it will simply return `null`. */ public operator fun get(name: String): Any? @@ -229,8 +225,8 @@ public interface Entity> : Serializable { public override fun hashCode(): Int /** - * Return a string representation of this table. - * The format is like `Employee{id=1, name=Eric, job=contributor, hireDate=2021-05-05, salary=50}`. + * Return a string representation of this entity. + * The format is like `Employee(id=1, name=Eric, job=contributor, hireDate=2021-05-05, salary=50)`. */ public override fun toString(): String diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt index bfed33b7..c3f4cf3e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt @@ -22,7 +22,7 @@ import org.ktorm.expression.* import org.ktorm.schema.* /** - * Insert the given entity into this sequence and return the affected record number. + * Insert the given entity into the table and return the affected record number. * * If we use an auto-increment key in our table, we need to tell Ktorm which is the primary key by calling * [Table.primaryKey] while registering columns, then this function will obtain the generated key from the From 0e3925b0c1583d0fac539aa99a1072ea9888ce72 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Jul 2024 10:50:38 +0800 Subject: [PATCH 17/40] fix code style --- .../main/kotlin/org/ktorm/entity/EntityImplementation.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index ae7c88c8..c7c04a12 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -151,9 +151,11 @@ internal class EntityImplementation( throw UnsupportedOperationException(msg) } - // Map#putIfAbsent treats null as absent, but null is a valid entity value - if (!changedProperties.containsKey(name)) - changedProperties[name] = this.values.getOrDefault(name, null) + // Save property changes and original values. + if (name !in changedProperties) { + changedProperties[name] = values[name] + } + values[name] = value } From 8bdb8a720adf6407eec19308241c4fd6f25adb61 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Jul 2024 21:46:14 +0800 Subject: [PATCH 18/40] fix detekt rule --- detekt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detekt.yml b/detekt.yml index 6e1c8b3b..13f93b09 100644 --- a/detekt.yml +++ b/detekt.yml @@ -49,7 +49,7 @@ complexity: active: true threshold: 4 ComplexInterface: - active: true + active: false threshold: 12 includeStaticDeclarations: false CyclomaticComplexMethod: From 41821a4a897cf5a616a6a868b47173a1fb08fc12 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 11:19:17 +0800 Subject: [PATCH 19/40] fix skip property names --- .../main/kotlin/org/ktorm/jackson/EntityDeserializers.kt | 3 ++- .../src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt | 3 ++- .../ksp/compiler/generator/ComponentFunctionGenerator.kt | 5 ++++- .../generator/PseudoConstructorFunctionGenerator.kt | 7 +++++-- .../kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt | 5 ++++- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt index 6776f776..00474bad 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntityDeserializers.kt @@ -63,10 +63,11 @@ internal class EntityDeserializers : SimpleDeserializers() { parser: JsonParser, ctx: DeserializationContext ): Map> { + val skipNames = Entity::class.memberProperties.map { it.name }.toSet() return entityClass.memberProperties .asSequence() .filter { it.isAbstract } - .filter { it.name != "entityClass" && it.name != "properties" } + .filter { it.name !in skipNames } .filter { it.findAnnotationForDeserialization() == null } .filter { prop -> val jsonProperty = prop.findAnnotationForDeserialization() diff --git a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt index aeab57ec..59fce3cc 100644 --- a/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt +++ b/ktorm-jackson/src/main/kotlin/org/ktorm/jackson/EntitySerializers.kt @@ -62,10 +62,11 @@ internal class EntitySerializers : SimpleSerializers() { } private fun findReadableProperties(entity: Entity<*>): Map> { + val skipNames = Entity::class.memberProperties.map { it.name }.toSet() return entity.entityClass.memberProperties .asSequence() .filter { it.isAbstract } - .filter { it.name != "entityClass" && it.name != "properties" } + .filter { it.name !in skipNames } .filter { it.findAnnotationForSerialization() == null } .filter { prop -> val jsonProperty = prop.findAnnotationForSerialization() diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt index 0846cd73..dd2317fc 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt @@ -22,16 +22,19 @@ import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ksp.KotlinPoetKspPreview import com.squareup.kotlinpoet.ksp.toClassName import com.squareup.kotlinpoet.ksp.toTypeName +import org.ktorm.entity.Entity import org.ktorm.ksp.compiler.util._type import org.ktorm.ksp.spi.TableMetadata +import kotlin.reflect.full.memberProperties @OptIn(KotlinPoetKspPreview::class) internal object ComponentFunctionGenerator { fun generate(table: TableMetadata): Sequence { + val skipNames = Entity::class.memberProperties.map { it.name }.toSet() return table.entityClass.getAllProperties() .filter { it.isAbstract() } - .filterNot { it.simpleName.asString() in setOf("entityClass", "properties") } + .filterNot { it.simpleName.asString() in skipNames } .mapIndexed { i, prop -> FunSpec.builder("component${i + 1}") .addKdoc("Return the value of [%L.%L]. ", diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt index 046467ce..d48c7b94 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt @@ -25,6 +25,7 @@ import org.ktorm.entity.Entity import org.ktorm.ksp.annotation.Undefined import org.ktorm.ksp.compiler.util.* import org.ktorm.ksp.spi.TableMetadata +import kotlin.reflect.full.memberProperties @OptIn(KotlinPoetKspPreview::class) internal object PseudoConstructorFunctionGenerator { @@ -43,9 +44,10 @@ internal object PseudoConstructorFunctionGenerator { } internal fun buildParameters(table: TableMetadata): Sequence { + val skipNames = Entity::class.memberProperties.map { it.name }.toSet() return table.entityClass.getAllProperties() .filter { it.isAbstract() } - .filterNot { it.simpleName.asString() in setOf("entityClass", "properties") } + .filterNot { it.simpleName.asString() in skipNames } .map { prop -> val propName = prop.simpleName.asString() val propType = prop._type.makeNullable().toTypeName() @@ -63,8 +65,9 @@ internal object PseudoConstructorFunctionGenerator { addStatement("val·entity·=·%T.create<%T>()", Entity::class.asClassName(), table.entityClass.toClassName()) } + val skipNames = Entity::class.memberProperties.map { it.name }.toSet() for (prop in table.entityClass.getAllProperties()) { - if (!prop.isAbstract() || prop.simpleName.asString() in setOf("entityClass", "properties")) { + if (!prop.isAbstract() || prop.simpleName.asString() in skipNames) { continue } diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt index 55b94489..872b1802 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/parser/MetadataParser.kt @@ -31,6 +31,7 @@ import org.ktorm.ksp.spi.TableMetadata import org.ktorm.schema.TypeReference import java.lang.reflect.InvocationTargetException import java.util.* +import kotlin.reflect.full.memberProperties import kotlin.reflect.jvm.jvmName @OptIn(KspExperimental::class) @@ -119,6 +120,8 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn } private fun KSClassDeclaration.getProperties(ignoreProperties: Set): Sequence { + val skipNames = Entity::class.memberProperties.map { it.name }.toSet() + val constructorParams = HashSet() if (classKind == CLASS) { primaryConstructor?.parameters?.mapTo(constructorParams) { it.name!!.asString() } @@ -129,7 +132,7 @@ internal class MetadataParser(resolver: Resolver, environment: SymbolProcessorEn .filterNot { it.isAnnotationPresent(Ignore::class) } .filterNot { classKind == CLASS && !it.hasBackingField } .filterNot { classKind == INTERFACE && !it.isAbstract() } - .filterNot { classKind == INTERFACE && it.simpleName.asString() in setOf("entityClass", "properties") } + .filterNot { classKind == INTERFACE && it.simpleName.asString() in skipNames } .sortedByDescending { it.simpleName.asString() in constructorParams } } From 8f0390e85b30c1252cf2f5297d26a5de7843f614 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 11:47:31 +0800 Subject: [PATCH 20/40] refactor --- .../ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt | 2 +- .../compiler/generator/PseudoConstructorFunctionGenerator.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt index dd2317fc..bbf4f8ab 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/ComponentFunctionGenerator.kt @@ -34,7 +34,7 @@ internal object ComponentFunctionGenerator { val skipNames = Entity::class.memberProperties.map { it.name }.toSet() return table.entityClass.getAllProperties() .filter { it.isAbstract() } - .filterNot { it.simpleName.asString() in skipNames } + .filter { it.simpleName.asString() !in skipNames } .mapIndexed { i, prop -> FunSpec.builder("component${i + 1}") .addKdoc("Return the value of [%L.%L]. ", diff --git a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt index d48c7b94..14d79898 100644 --- a/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt +++ b/ktorm-ksp-compiler/src/main/kotlin/org/ktorm/ksp/compiler/generator/PseudoConstructorFunctionGenerator.kt @@ -47,7 +47,7 @@ internal object PseudoConstructorFunctionGenerator { val skipNames = Entity::class.memberProperties.map { it.name }.toSet() return table.entityClass.getAllProperties() .filter { it.isAbstract() } - .filterNot { it.simpleName.asString() in skipNames } + .filter { it.simpleName.asString() !in skipNames } .map { prop -> val propName = prop.simpleName.asString() val propType = prop._type.makeNullable().toTypeName() From 91730deec8ce7c763a681488c274b7948f430e18 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 14:39:08 +0800 Subject: [PATCH 21/40] test internal changed properties for nested binding --- .../kotlin/org/ktorm/entity/EntityTest.kt | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index a89f95f5..5fc753a3 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -315,10 +315,41 @@ class EntityTest : BaseTest() { companion object : Entity.Factory() var id: Int? var name: String? + var job: String? } object Parents : Table("t_employee") { val id = int("id").primaryKey().bindTo { it.child?.grandChild?.id } + val name = varchar("name").bindTo { it.child?.grandChild?.name } + val job = varchar("job").bindTo { it.child?.grandChild?.job } + } + + @Test + fun testInternalChangedPropertiesForNestedBinding1() { + val p1 = database.sequenceOf(Parents).find { it.id eq 1 } ?: throw AssertionError() + p1.child?.grandChild?.job = "Senior Engineer" + p1.child?.grandChild?.job = "Expert Engineer" + + assert(p1.implementation.changedProperties.size == 0) + assert(p1.child?.implementation?.changedProperties?.size == 0) + assert(p1.child?.grandChild?.implementation?.changedProperties?.size == 1) + assert(p1.child?.grandChild?.implementation?.changedProperties?.get("job") == "engineer") + assert(p1.flushChanges() == 1) + } + + @Test + fun testInternalChangedPropertiesForNestedBinding2() { + val p2 = database.sequenceOf(Parents).find { it.id eq 1 } ?: throw AssertionError() + p2.child?.grandChild?.name = "Vincent" + p2.child?.grandChild?.job = "Senior Engineer" + p2.child?.grandChild?.job = "Expert Engineer" + + assert(p2.implementation.changedProperties.size == 0) + assert(p2.child?.implementation?.changedProperties?.size == 0) + assert(p2.child?.grandChild?.implementation?.changedProperties?.size == 2) + assert(p2.child?.grandChild?.implementation?.changedProperties?.get("name") == "vince") + assert(p2.child?.grandChild?.implementation?.changedProperties?.get("job") == "engineer") + assert(p2.flushChanges() == 1) } @Test From 0bb6d3d37507f7903a96eadfc78c3207334134e9 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 19:39:44 +0800 Subject: [PATCH 22/40] find changed properties --- .../main/kotlin/org/ktorm/entity/EntityDml.kt | 67 ++++++++++++++++--- .../org/ktorm/entity/EntityExtensions.kt | 2 + .../org/ktorm/entity/EntityImplementation.kt | 2 +- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt index c3f4cf3e..3e90194e 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityDml.kt @@ -17,7 +17,6 @@ package org.ktorm.entity import org.ktorm.dsl.* -import org.ktorm.dsl.AliasRemover import org.ktorm.expression.* import org.ktorm.schema.* @@ -233,7 +232,6 @@ private fun EntitySequence<*, *>.checkForDml() { */ private fun Entity<*>.findInsertColumns(table: Table<*>): Map, Any?> { val assignments = LinkedHashMap, Any?>() - for (column in table.columns) { if (column.binding != null && implementation.hasColumnValue(column.binding)) { assignments[column] = implementation.getColumnValue(column.binding) @@ -246,10 +244,9 @@ private fun Entity<*>.findInsertColumns(table: Table<*>): Map, Any?> { /** * Return columns associated with their values for update. */ +@Suppress("ConvertArgumentToSet") private fun Entity<*>.findUpdateColumns(table: Table<*>): Map, Any?> { val assignments = LinkedHashMap, Any?>() - - @Suppress("ConvertArgumentToSet") for (column in table.columns - table.primaryKeys) { if (column.binding != null && implementation.hasColumnValue(column.binding)) { assignments[column] = implementation.getColumnValue(column.binding) @@ -264,7 +261,6 @@ private fun Entity<*>.findUpdateColumns(table: Table<*>): Map, Any?> { */ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map, Any?> { val assignments = LinkedHashMap, Any?>() - for (column in fromTable.columns) { val binding = column.binding ?: continue @@ -286,11 +282,13 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map): Map { + check(parent == null) { "The entity is not attached to any database yet." } + val fromTable = fromTable ?: error("The entity is not attached to any database yet.") + + // Create an empty entity object to collect changed properties. + val result = Entity.create(entityClass, parent, fromDatabase, fromTable) + for (column in fromTable.columns) { + val binding = column.binding ?: continue + + when (binding) { + is ReferenceBinding -> { + if (binding.onProperty.name in changedProperties) { + val origin = changedProperties[binding.onProperty.name] as Entity<*>? + val originId = origin?.implementation?.getPrimaryKeyValue(binding.referenceTable as Table<*>) + result.implementation.setColumnValue(binding, originId) + } + } + is NestedBinding -> { + var anyChanged = false + var curr: Any? = this + + for (prop in binding.properties) { + if (curr is Entity<*>) { + curr = curr.implementation + } + + check(curr is EntityImplementation?) + + if (curr != null) { + if (prop.name in curr.changedProperties) { + curr = curr.changedProperties[prop.name] + anyChanged = true + } else { + curr = curr.getProperty(prop) + } + } + } + + if (anyChanged) { + result.implementation.setColumnValue(binding, curr) + } + } + } + } + + return result.properties +} + /** * Clear the tracked property changes of this entity. * diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt index caf34f73..d49957f6 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensions.kt @@ -92,6 +92,7 @@ internal fun EntityImplementation.getColumnValue(binding: ColumnBinding): Any? { curr = child?.implementation } } + return curr?.getProperty(binding.properties.last()) } } @@ -127,6 +128,7 @@ internal fun EntityImplementation.setColumnValue(binding: ColumnBinding, value: fromDatabase = this.fromDatabase, fromTable = binding.referenceTable as Table<*> ) + this.setProperty(binding.onProperty, child, forceSet) } diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt index c7c04a12..3a223d2a 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityImplementation.kt @@ -54,7 +54,7 @@ internal class EntityImplementation( when (method.name) { "getEntityClass" -> this.entityClass "getProperties" -> Collections.unmodifiableMap(this.values) - "getChangedProperties" -> Collections.unmodifiableMap(this.changedProperties) + "getChangedProperties" -> this.findChangedProperties() "flushChanges" -> this.doFlushChanges() "discardChanges" -> this.doDiscardChanges() "delete" -> this.doDelete() From 56638d60c11037d6d39b65b41b41969c55fe0f48 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 20:02:49 +0800 Subject: [PATCH 23/40] add api to check if entity is attached --- ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt | 2 ++ .../kotlin/org/ktorm/entity/EntityExtensionsApi.kt | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index c8899daf..b0f00884 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -145,6 +145,8 @@ public interface Entity> : Serializable { /** * Return the immutable view of this entity's changed properties and their original values. + * + * @since 4.1.0 */ public val changedProperties: Map diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensionsApi.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensionsApi.kt index b0134fc3..2bf5bd93 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensionsApi.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/EntityExtensionsApi.kt @@ -50,4 +50,14 @@ public class EntityExtensionsApi { public fun Entity<*>.setColumnValue(binding: ColumnBinding, value: Any?) { implementation.setColumnValue(binding, value) } + + /** + * Check if this entity is attached to the database. + * + * @since 4.1.0 + */ + public fun Entity<*>.isAttached(): Boolean { + val impl = this.implementation + return impl.fromDatabase != null && impl.fromTable != null && impl.parent == null + } } From 5fdd30eeadee2f447d84f9493549eaaf9b19adc0 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 20:20:35 +0800 Subject: [PATCH 24/40] test changed properties for nested binding --- .../kotlin/org/ktorm/entity/EntityTest.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index 5fc753a3..432d675b 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -352,6 +352,29 @@ class EntityTest : BaseTest() { assert(p2.flushChanges() == 1) } + @Test + fun testChangedPropertiesForNestedBinding1() { + val p1 = database.sequenceOf(Parents).find { it.id eq 1 } ?: throw AssertionError() + p1.child?.grandChild?.job = "Senior Engineer" + p1.child?.grandChild?.job = "Expert Engineer" + + assert(p1.changedProperties.size == 1) + assert(p1.changedProperties["child"].toString() == "Child(grandChild=GrandChild(job=engineer))") + assert(p1.flushChanges() == 1) + } + + @Test + fun testChangedPropertiesForNestedBinding2() { + val p2 = database.sequenceOf(Parents).find { it.id eq 1 } ?: throw AssertionError() + p2.child?.grandChild?.name = "Vincent" + p2.child?.grandChild?.job = "Senior Engineer" + p2.child?.grandChild?.job = "Expert Engineer" + + assert(p2.changedProperties.size == 1) + assert(p2.changedProperties["child"].toString() == "Child(grandChild=GrandChild(name=vince, job=engineer))") + assert(p2.flushChanges() == 1) + } + @Test fun testHasColumnValue() { val p1 = Parent() From fb0c257ceaab9e14a2e705d7cd1169e921eb234c Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 20:45:13 +0800 Subject: [PATCH 25/40] test changed properties for reference binding --- .../kotlin/org/ktorm/entity/EntityTest.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index 432d675b..f3bb3211 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -375,6 +375,28 @@ class EntityTest : BaseTest() { assert(p2.flushChanges() == 1) } + @Test + fun testChangedPropertiesForReferenceBinding() { + val e = database.employees.find { it.id eq 1 } ?: throw AssertionError() + e.name = "Vincent" + e.job = "Senior Engineer" + e.job = "Expert Engineer" + e.manager = database.employees.find { it.id eq 2 } + e.manager = database.employees.find { it.id eq 2 } + e.salary = 999999 + e.department = database.departments.find { it.id eq 2 } ?: throw AssertionError() + e.department = database.departments.find { it.id eq 2 } ?: throw AssertionError() + + val changed = e.changedProperties + assert(changed.size == 5) + assert(changed["name"] == "vince") + assert(changed["job"] == "engineer") + assert(changed["manager"].toString() == "Employee(id=null)") + assert(changed["salary"] == 100L) + assert(changed["department"].toString() == "Department(id=1)") + assert(e.flushChanges() == 1) + } + @Test fun testHasColumnValue() { val p1 = Parent() From 46e18c9efa89613eaffcbef31142b1453c457f93 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 20:55:24 +0800 Subject: [PATCH 26/40] flushChanges & delete throw SQLException --- .../src/main/kotlin/org/ktorm/entity/Entity.kt | 3 +++ .../src/test/kotlin/org/ktorm/entity/EntityTest.kt | 14 ++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt index b0f00884..83fc2997 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Entity.kt @@ -23,6 +23,7 @@ import java.io.ObjectInputStream import java.io.ObjectOutputStream import java.io.Serializable import java.lang.reflect.Proxy +import java.sql.SQLException import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf import kotlin.reflect.jvm.jvmErasure @@ -168,6 +169,7 @@ public interface Entity> : Serializable { * @see add * @see update */ + @Throws(SQLException::class) public fun flushChanges(): Int /** @@ -191,6 +193,7 @@ public interface Entity> : Serializable { * @see update * @see flushChanges */ + @Throws(SQLException::class) public fun delete(): Int /** diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index f3bb3211..b7c20047 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -1,5 +1,6 @@ package org.ktorm.entity +import org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException import org.junit.Test import org.ktorm.BaseTest import org.ktorm.database.Database @@ -397,6 +398,19 @@ class EntityTest : BaseTest() { assert(e.flushChanges() == 1) } + @Test + fun testExceptionThrowsByProxy() { + try { + val e = database.employees.find { it.id eq 1 } ?: throw AssertionError() + e.department = Department() + e.flushChanges() + + throw AssertionError("failed") + } catch (e: JdbcSQLIntegrityConstraintViolationException) { + assert(e.message!!.contains("NULL not allowed for column \"department_id\"")) + } + } + @Test fun testHasColumnValue() { val p1 = Parent() From 4c3d90457f9849b8716024968c1f7ef70904a802 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 8 Jul 2024 21:10:27 +0800 Subject: [PATCH 27/40] fix detekt rules --- detekt.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detekt.yml b/detekt.yml index 13f93b09..8c08a502 100644 --- a/detekt.yml +++ b/detekt.yml @@ -75,7 +75,7 @@ complexity: active: false threshold: 7 NestedBlockDepth: - active: true + active: false threshold: 5 StringLiteralDuplication: active: false From 94b95c76b9d0ce2dcb4806b4af5e4b42e4ab699b Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 9 Jul 2024 10:05:22 +0800 Subject: [PATCH 28/40] fix failed tests --- .../kotlin/org/ktorm/entity/EntityTest.kt | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt index b7c20047..98b1ad04 100644 --- a/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/org/ktorm/entity/EntityTest.kt @@ -48,50 +48,6 @@ class EntityTest : BaseTest() { assert(employee.job == "") } - @Test - fun testEntityChangedPropertiesInitializer() { - val employee = Employee { name = "walter" } - println(employee) - - assert(employee.changedProperties.size == 1) - assert(employee.changedProperties.contains("name")) - assert(employee.changedProperties["name"] == null) - } - - @Test - fun testEntityChangedPropertiesSetter() { - val employee = Employee { } - println(employee) - - val oldChangedPropertiesIsEmpty = employee.changedProperties.isEmpty() - - employee.name = "walter" - println(employee) - - assert(oldChangedPropertiesIsEmpty) - assert(employee.changedProperties.size == 1) - assert(employee.changedProperties.contains("name")) - assert(employee.changedProperties["name"] == null) - } - - @Test - fun testEntityChangedPropertiesMultiple() { - val employee = Employee { }.copy() - println(employee) - - val oldChangedPropertiesIsEmpty = employee.changedProperties.isEmpty() - - employee.name = "walter" - println(employee) - employee.name = "vince" - println(employee) - - assert(oldChangedPropertiesIsEmpty) - assert(employee.changedProperties.size == 1) - assert(employee.changedProperties.containsKey("name")) - assert(employee.changedProperties["name"] == null) - } - @Test fun testDefaultMethod() { for (method in Employee::class.java.methods) { From 1377e38fdde17a6de5013e665c56add027afad0c Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 9 Jul 2024 10:38:40 +0800 Subject: [PATCH 29/40] fix code style --- ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt index e0e8f1a4..3caf8f24 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/entity/Reflections.kt @@ -44,6 +44,7 @@ internal val Method.kotlinProperty: Pair, Boolean>? get() { return Pair(prop, false) } } + return null } From 493aa76216a06867b7f3271ed79538cf6d05fe7e Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 12 Jul 2024 17:36:28 +0800 Subject: [PATCH 30/40] fix code style --- ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt b/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt index 257813fc..781c8748 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/database/CachedRowSet.kt @@ -609,6 +609,7 @@ public open class CachedRowSet(rs: ResultSet) : ResultSet { return index } } + throw SQLException("Invalid column name: $columnLabel") } From bd9990f22cafe82720d60e39321b92d768ffb673 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 12 Jul 2024 17:42:58 +0800 Subject: [PATCH 31/40] init Column's properties when constructing the instance --- ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt index 6a799505..5bfb1b3c 100644 --- a/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt +++ b/ktorm-core/src/main/kotlin/org/ktorm/schema/Column.kt @@ -118,19 +118,19 @@ public data class Column( * * @see ColumnDeclaringExpression */ - val label: String by lazy { toString(separator = "_") } + val label: String = toString(separator = "_") /** * Return all the bindings of this column, including the primary [binding] and [extraBindings]. */ - val allBindings: List get() = binding?.let { listOf(it) + extraBindings } ?: emptyList() + val allBindings: List = binding?.let { listOf(it) + extraBindings } ?: emptyList() /** * If the column is bound to a reference table, return the table, otherwise return null. * * Shortcut for `(binding as? ReferenceBinding)?.referenceTable`. */ - val referenceTable: BaseTable<*>? get() = (binding as? ReferenceBinding)?.referenceTable + val referenceTable: BaseTable<*>? = (binding as? ReferenceBinding)?.referenceTable /** * Convert this column to a [ColumnExpression]. From 94e4f6f85048ea855919f6549c320fda438fa01f Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 13 Jul 2024 12:10:22 +0800 Subject: [PATCH 32/40] upgrade github actions --- .github/workflows/build.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24d9d052..e70dc509 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,16 +17,16 @@ jobs: java: [8, 11, 17, 20] steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: zulu java-version: ${{ matrix.java }} - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - name: Assemble the Project run: ./gradlew assemble @@ -45,6 +45,10 @@ jobs: ktorm-core/build/reports/jacoco/test/jacocoTestReport.csv ktorm-global/build/reports/jacoco/test/jacocoTestReport.csv ktorm-jackson/build/reports/jacoco/test/jacocoTestReport.csv + ktorm-ksp-annotations/build/reports/jacoco/test/jacocoTestReport.csv + ktorm-ksp-compiler/build/reports/jacoco/test/jacocoTestReport.csv + ktorm-ksp-compiler-maven-plugin/build/reports/jacoco/test/jacocoTestReport.csv + ktorm-ksp-spi/build/reports/jacoco/test/jacocoTestReport.csv ktorm-support-mysql/build/reports/jacoco/test/jacocoTestReport.csv ktorm-support-oracle/build/reports/jacoco/test/jacocoTestReport.csv ktorm-support-postgresql/build/reports/jacoco/test/jacocoTestReport.csv @@ -78,16 +82,16 @@ jobs: needs: build steps: - name: Checkout Code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Java - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: zulu java-version: 8 - name: Setup Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 - name: Assemble the Project run: ./gradlew assemble From 57e09c1e2682e229e23df11f6e6fb074bffb8fa9 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 13 Jul 2024 12:44:16 +0800 Subject: [PATCH 33/40] fix failed build --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e70dc509..24956699 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,8 +47,6 @@ jobs: ktorm-jackson/build/reports/jacoco/test/jacocoTestReport.csv ktorm-ksp-annotations/build/reports/jacoco/test/jacocoTestReport.csv ktorm-ksp-compiler/build/reports/jacoco/test/jacocoTestReport.csv - ktorm-ksp-compiler-maven-plugin/build/reports/jacoco/test/jacocoTestReport.csv - ktorm-ksp-spi/build/reports/jacoco/test/jacocoTestReport.csv ktorm-support-mysql/build/reports/jacoco/test/jacocoTestReport.csv ktorm-support-oracle/build/reports/jacoco/test/jacocoTestReport.csv ktorm-support-postgresql/build/reports/jacoco/test/jacocoTestReport.csv From 2c75fec287bfb33366a859820fe0dbe463bb0a9c Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 13 Jul 2024 22:06:33 +0800 Subject: [PATCH 34/40] build on jdk 21 & 22 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 24956699..59157544 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: true matrix: - java: [8, 11, 17, 20] + java: [8, 11, 17, 21, 22] steps: - name: Checkout Code uses: actions/checkout@v4 From 14f43dab20f07fa9c695fbde116941ccb619dcdc Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 13 Jul 2024 22:23:03 +0800 Subject: [PATCH 35/40] upgrade to gradle 8.9 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 27313fbc..2fa91c5f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From c44626b5ccf4958f4e00b68c14cca55071e9f875 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 15 Jul 2024 17:10:00 +0800 Subject: [PATCH 36/40] upgrade kotlin version --- buildSrc/build.gradle.kts | 2 +- .../ktorm-ksp-compiler-maven-plugin.gradle.kts | 2 +- ktorm-ksp-compiler/ktorm-ksp-compiler.gradle.kts | 2 +- ktorm-ksp-spi/ktorm-ksp-spi.gradle.kts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 9c56d76a..cedd013e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -9,7 +9,7 @@ repositories { } dependencies { - api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0") + api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") api("org.moditect:moditect-gradle-plugin:1.0.0-rc3") api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.1") } diff --git a/ktorm-ksp-compiler-maven-plugin/ktorm-ksp-compiler-maven-plugin.gradle.kts b/ktorm-ksp-compiler-maven-plugin/ktorm-ksp-compiler-maven-plugin.gradle.kts index 3c2766db..b5354918 100644 --- a/ktorm-ksp-compiler-maven-plugin/ktorm-ksp-compiler-maven-plugin.gradle.kts +++ b/ktorm-ksp-compiler-maven-plugin/ktorm-ksp-compiler-maven-plugin.gradle.kts @@ -9,7 +9,7 @@ dependencies { compileOnly(kotlin("maven-plugin")) compileOnly(kotlin("compiler")) compileOnly("org.apache.maven:maven-core:3.9.3") - implementation("com.google.devtools.ksp:symbol-processing-cmdline:1.9.0-1.0.13") + implementation("com.google.devtools.ksp:symbol-processing-cmdline:1.9.23-1.0.20") implementation(project(":ktorm-ksp-compiler")) { exclude(group = "com.pinterest.ktlint", module = "ktlint-rule-engine") exclude(group = "com.pinterest.ktlint", module = "ktlint-ruleset-standard") diff --git a/ktorm-ksp-compiler/ktorm-ksp-compiler.gradle.kts b/ktorm-ksp-compiler/ktorm-ksp-compiler.gradle.kts index 2285feb5..7a927ca1 100644 --- a/ktorm-ksp-compiler/ktorm-ksp-compiler.gradle.kts +++ b/ktorm-ksp-compiler/ktorm-ksp-compiler.gradle.kts @@ -9,7 +9,7 @@ dependencies { implementation(project(":ktorm-core")) implementation(project(":ktorm-ksp-annotations")) implementation(project(":ktorm-ksp-spi")) - implementation("com.google.devtools.ksp:symbol-processing-api:1.9.0-1.0.13") + implementation("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20") implementation("com.squareup:kotlinpoet-ksp:1.11.0") implementation("org.atteo:evo-inflector:1.3") implementation("com.pinterest.ktlint:ktlint-rule-engine:0.50.0") { diff --git a/ktorm-ksp-spi/ktorm-ksp-spi.gradle.kts b/ktorm-ksp-spi/ktorm-ksp-spi.gradle.kts index 86f5b1ce..6e869a9f 100644 --- a/ktorm-ksp-spi/ktorm-ksp-spi.gradle.kts +++ b/ktorm-ksp-spi/ktorm-ksp-spi.gradle.kts @@ -6,6 +6,6 @@ plugins { } dependencies { - api("com.google.devtools.ksp:symbol-processing-api:1.9.0-1.0.13") + api("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20") api("com.squareup:kotlinpoet-ksp:1.11.0") } From a7edb4aacd5fd2bc7dba8575e0d8fade05972764 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 15 Jul 2024 17:53:14 +0800 Subject: [PATCH 37/40] upgrade detekt version --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index cedd013e..03f6f71f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -11,5 +11,5 @@ repositories { dependencies { api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") api("org.moditect:moditect-gradle-plugin:1.0.0-rc3") - api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.1") + api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.6") } From cdc7eb98a6e64238a486cbb6e9c711ebac62bdb9 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 15 Jul 2024 18:43:22 +0800 Subject: [PATCH 38/40] remove jdk 22 build --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 59157544..7553edc6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: true matrix: - java: [8, 11, 17, 21, 22] + java: [8, 11, 17, 21] steps: - name: Checkout Code uses: actions/checkout@v4 From ea8d2c097d975825c272be125e674f91f3199cf2 Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 16 Jul 2024 18:06:05 +0800 Subject: [PATCH 39/40] remove moditet plugin --- buildSrc/build.gradle.kts | 2 +- .../main/kotlin/ktorm.modularity.gradle.kts | 43 +++++++++++-------- ktorm-core/ktorm-core.gradle.kts | 2 +- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 03f6f71f..449f27dd 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,6 +10,6 @@ repositories { dependencies { api("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.23") - api("org.moditect:moditect-gradle-plugin:1.0.0-rc3") + api("org.moditect:moditect:1.0.0.RC1") api("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.23.6") } diff --git a/buildSrc/src/main/kotlin/ktorm.modularity.gradle.kts b/buildSrc/src/main/kotlin/ktorm.modularity.gradle.kts index 1d6fd479..baf94b13 100644 --- a/buildSrc/src/main/kotlin/ktorm.modularity.gradle.kts +++ b/buildSrc/src/main/kotlin/ktorm.modularity.gradle.kts @@ -1,30 +1,37 @@ plugins { id("kotlin") - id("org.moditect.gradleplugin") } -moditect { - // Generate a multi-release jar, the module descriptor will be located at META-INF/versions/9/module-info.class - addMainModuleInfo { - jvmVersion.set("9") - overwriteExistingFiles.set(true) - module { - moduleInfoFile = file("src/main/moditect/module-info.java") +val moditect by tasks.registering { + doLast { + // Generate a multi-release modulized jar, module descriptor position: META-INF/versions/9/module-info.class + val inputJar = tasks.jar.flatMap { it.archiveFile }.map { it.asFile.toPath() }.get() + val outputDir = file("build/moditect").apply { mkdirs() }.toPath() + val moduleInfo = file("src/main/moditect/module-info.java").readText() + val version = project.version.toString() + org.moditect.commands.AddModuleInfo(moduleInfo, null, version, inputJar, outputDir, "9", true).run() + + // Replace the original jar with the modulized jar. + copy { + from(outputDir.resolve(inputJar.fileName)) + into(inputJar.parent) } } +} - // Let kotlin compiler know the module descriptor. - if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { - sourceSets.main { - kotlin.srcDir("src/main/moditect") - } +tasks { + moditect { + dependsOn(jar) } + jar { + finalizedBy(moditect) + } +} - // Workaround to avoid circular task dependencies, see https://github.com/moditect/moditect-gradle-plugin/issues/14 - afterEvaluate { - val compileJava = tasks.compileJava.get() - val addDependenciesModuleInfo = tasks.addDependenciesModuleInfo.get() - compileJava.setDependsOn(compileJava.dependsOn - addDependenciesModuleInfo) +if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { + // Let kotlin compiler know the module descriptor. + sourceSets.main { + kotlin.srcDir("src/main/moditect") } } diff --git a/ktorm-core/ktorm-core.gradle.kts b/ktorm-core/ktorm-core.gradle.kts index f405badd..6ae9cc1c 100644 --- a/ktorm-core/ktorm-core.gradle.kts +++ b/ktorm-core/ktorm-core.gradle.kts @@ -19,7 +19,7 @@ val testOutput by configurations.creating { } val testJar by tasks.registering(Jar::class) { - dependsOn("testClasses") + dependsOn(tasks.testClasses) from(sourceSets.test.map { it.output }) archiveClassifier.set("test") } From c4b9347a17acd7bdbe490e148e91cb7325fa66af Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 16 Jul 2024 19:40:39 +0800 Subject: [PATCH 40/40] release 4.1.0 --- ktorm.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ktorm.version b/ktorm.version index 6c3e1dec..ee74734a 100644 --- a/ktorm.version +++ b/ktorm.version @@ -1 +1 @@ -4.1.0-SNAPSHOT +4.1.0