Skip to content

Commit a590342

Browse files
fix bug #10 for adding entities
1 parent 2b5c262 commit a590342

File tree

3 files changed

+91
-30
lines changed

3 files changed

+91
-30
lines changed

ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import me.liuwj.ktorm.schema.*
1010
*/
1111
@Suppress("UNCHECKED_CAST")
1212
fun <E : Entity<E>> Table<E>.add(entity: E): Int {
13+
entity.implementation.checkUnexpectedDiscarding(this)
14+
1315
val assignments = findInsertColumns(entity).takeIf { it.isNotEmpty() } ?: return 0
1416

1517
val expression = AliasRemover.visit(
@@ -66,6 +68,8 @@ private fun Table<*>.findInsertColumns(entity: Entity<*>): Map<Column<*>, Any?>
6668
@Suppress("UNCHECKED_CAST")
6769
internal fun EntityImplementation.doFlushChanges(): Int {
6870
val fromTable = this.fromTable?.takeIf { this.parent == null } ?: error("The entity is not associated with any table yet.")
71+
checkUnexpectedDiscarding(fromTable)
72+
6973
val primaryKey = fromTable.primaryKey ?: error("Table ${fromTable.tableName} doesn't have a primary key.")
7074
val assignments = findChangedColumns(fromTable).takeIf { it.isNotEmpty() } ?: return 0
7175

@@ -111,26 +115,17 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map<Co
111115
var anyChanged = false
112116
var curr: Any? = this
113117

114-
for ((i, prop) in binding.properties.withIndex()) {
118+
for (prop in binding.properties) {
115119
if (curr is Entity<*>) {
116120
curr = curr.implementation
117121
}
118122

119123
check(curr is EntityImplementation?)
120124

121-
val changed = if (curr == null) false else prop.name in curr.changedProperties
122-
123-
// Add check to avoid bug #10
124-
if (changed && i > 0) {
125-
check(curr != null)
126-
127-
if (curr.fromTable != null && curr.getRoot() != this) {
128-
val propPath = binding.properties.subList(0, i + 1).joinToString(separator = ".", prefix = "this.") { it.name }
129-
throw IllegalStateException("$propPath may be unexpectedly discarded after flushChanges, please save it to database first.")
130-
}
125+
if (curr != null && prop.name in curr.changedProperties) {
126+
anyChanged = true
131127
}
132128

133-
anyChanged = anyChanged || changed
134129
curr = curr?.getProperty(prop.name)
135130
}
136131

@@ -144,15 +139,6 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map<Co
144139
return assignments
145140
}
146141

147-
private tailrec fun EntityImplementation.getRoot(): EntityImplementation {
148-
val parent = this.parent
149-
if (parent == null) {
150-
return this
151-
} else {
152-
return parent.getRoot()
153-
}
154-
}
155-
156142
internal fun EntityImplementation.doDiscardChanges() {
157143
val fromTable = this.fromTable?.takeIf { this.parent == null } ?: error("The entity is not associated with any table yet.")
158144

@@ -183,6 +169,54 @@ internal fun EntityImplementation.doDiscardChanges() {
183169
}
184170
}
185171

172+
// Add check to avoid bug #10
173+
private fun EntityImplementation.checkUnexpectedDiscarding(fromTable: Table<*>) {
174+
for (column in fromTable.columns) {
175+
val binding = column.binding?.takeIf { column is SimpleColumn } ?: continue
176+
177+
if (binding is NestedBinding) {
178+
var curr: Any? = this
179+
180+
for ((i, prop) in binding.properties.withIndex()) {
181+
if (curr == null) {
182+
break
183+
}
184+
if (curr is Entity<*>) {
185+
curr = curr.implementation
186+
}
187+
188+
check(curr is EntityImplementation)
189+
190+
if (i > 0 && prop.name in curr.changedProperties && curr.fromTable != null && curr.getRoot() != this) {
191+
val propPath = binding.properties.subList(0, i + 1).joinToString(separator = ".", prefix = "this.") { it.name }
192+
throw IllegalStateException("$propPath may be unexpectedly discarded, please save it to database first.")
193+
}
194+
195+
curr = curr.getProperty(prop.name)
196+
}
197+
}
198+
}
199+
}
200+
201+
private tailrec fun EntityImplementation.getRoot(): EntityImplementation {
202+
val parent = this.parent
203+
if (parent == null) {
204+
return this
205+
} else {
206+
return parent.getRoot()
207+
}
208+
}
209+
210+
internal fun Entity<*>.clearChangesRecursively() {
211+
implementation.changedProperties.clear()
212+
213+
for ((_, value) in properties) {
214+
if (value is Entity<*>) {
215+
value.clearChangesRecursively()
216+
}
217+
}
218+
}
219+
186220
@Suppress("UNCHECKED_CAST")
187221
internal fun EntityImplementation.doDelete(): Int {
188222
val fromTable = this.fromTable?.takeIf { this.parent == null } ?: error("The entity is not associated with any table yet.")

ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private infix fun ColumnDeclaring<*>.eq(column: ColumnDeclaring<*>): BinaryExpre
114114
@Suppress("UNCHECKED_CAST")
115115
fun <E : Entity<E>> Table<E>.createEntity(row: QueryRowSet): E {
116116
val entity = doCreateEntity(row, skipReferences = false) as E
117-
return entity.apply { discardChanges() }
117+
return entity.apply { clearChangesRecursively() }
118118
}
119119

120120
/**
@@ -123,7 +123,7 @@ fun <E : Entity<E>> Table<E>.createEntity(row: QueryRowSet): E {
123123
@Suppress("UNCHECKED_CAST")
124124
fun <E : Entity<E>> Table<E>.createEntityWithoutReferences(row: QueryRowSet): E {
125125
val entity = doCreateEntity(row, skipReferences = true) as E
126-
return entity.apply { discardChanges() }
126+
return entity.apply { clearChangesRecursively() }
127127
}
128128

129129
private fun Table<*>.doCreateEntity(row: QueryRowSet, skipReferences: Boolean = false): Entity<*> {
@@ -154,12 +154,12 @@ private fun QueryRowSet.retrieveColumn(column: Column<*>, intoEntity: Entity<*>,
154154
skipReferences -> {
155155
val child = Entity.create(binding.onProperty.returnType.classifier as KClass<*>, fromTable = rightTable)
156156
child.implementation.setColumnValue(primaryKey, columnValue)
157-
intoEntity[binding.onProperty.name] = child.apply { discardChanges() }
157+
intoEntity[binding.onProperty.name] = child
158158
}
159159
this.hasColumn(primaryKey) && this[primaryKey] != null -> {
160160
val child = rightTable.doCreateEntity(this)
161161
child.implementation.setColumnValue(primaryKey, columnValue, forceSet = true)
162-
intoEntity[binding.onProperty.name] = child.apply { discardChanges() }
162+
intoEntity[binding.onProperty.name] = child
163163
}
164164
}
165165
}

ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ package me.liuwj.ktorm.entity
22

33
import me.liuwj.ktorm.BaseTest
44
import me.liuwj.ktorm.dsl.*
5-
import me.liuwj.ktorm.schema.Table
6-
import me.liuwj.ktorm.schema.int
7-
import me.liuwj.ktorm.schema.varchar
5+
import me.liuwj.ktorm.schema.*
86
import org.junit.Test
97
import java.io.ByteArrayInputStream
108
import java.io.ByteArrayOutputStream
@@ -286,16 +284,23 @@ class EntityTest : BaseTest() {
286284
}
287285

288286
interface Emp : Entity<Emp> {
287+
companion object : Entity.Factory<Emp>()
289288
val id: Int
290289
var employee: Employee
291290
var manager: Employee
291+
var hireDate: LocalDate
292+
var salary: Long
293+
var departmentId: Int
292294
}
293295

294296
object Emps : Table<Emp>("t_employee") {
295297
val id by int("id").primaryKey().bindTo { it.id }
296298
val name by varchar("name").bindTo { it.employee.name }
297299
val job by varchar("job").bindTo { it.employee.job }
298300
val managerId by int("manager_id").bindTo { it.manager.id }
301+
val hireDate by date("hire_date").bindTo { it.hireDate }
302+
val salary by long("salary").bindTo { it.salary }
303+
val departmentId by int("department_id").bindTo { it.departmentId }
299304
}
300305

301306
@Test
@@ -304,6 +309,28 @@ class EntityTest : BaseTest() {
304309
emp1.employee.name = "jerry"
305310
// emp1.flushChanges()
306311

312+
val emp2 = Emp {
313+
employee = emp1.employee
314+
hireDate = LocalDate.now()
315+
salary = 100
316+
departmentId = 1
317+
}
318+
319+
try {
320+
Emps.add(emp2)
321+
throw AssertionError("failed")
322+
323+
} catch (e: IllegalStateException) {
324+
assert(e.message == "this.employee.name may be unexpectedly discarded, please save it to database first.")
325+
}
326+
}
327+
328+
@Test
329+
fun testCheckUnexpectedFlush0() {
330+
val emp1 = Emps.findById(1) ?: return
331+
emp1.employee.name = "jerry"
332+
// emp1.flushChanges()
333+
307334
val emp2 = Emps.findById(2) ?: return
308335
emp2.employee = emp1.employee
309336

@@ -312,7 +339,7 @@ class EntityTest : BaseTest() {
312339
throw AssertionError("failed")
313340

314341
} catch (e: IllegalStateException) {
315-
assert(e.message == "this.employee.name may be unexpectedly discarded after flushChanges, please save it to database first.")
342+
assert(e.message == "this.employee.name may be unexpectedly discarded, please save it to database first.")
316343
}
317344
}
318345

@@ -330,7 +357,7 @@ class EntityTest : BaseTest() {
330357
throw AssertionError("failed")
331358

332359
} catch (e: IllegalStateException) {
333-
assert(e.message == "this.employee.name may be unexpectedly discarded after flushChanges, please save it to database first.")
360+
assert(e.message == "this.employee.name may be unexpectedly discarded, please save it to database first.")
334361
}
335362
}
336363

0 commit comments

Comments
 (0)