From 43b2b3f0a4fe0cddc4a401827a20c1d128a3d5af Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 22 Mar 2019 11:30:02 +0800 Subject: [PATCH 01/34] iterate all entities in a table --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 17 ++++++++++++++ .../kotlin/me/liuwj/ktorm/schema/Table.kt | 23 +++++++++++++++++-- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 23 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt create mode 100644 ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt new file mode 100644 index 00000000..7b988048 --- /dev/null +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -0,0 +1,17 @@ +package me.liuwj.ktorm.entity + +import me.liuwj.ktorm.dsl.Query + +interface EntitySequence { + + operator fun iterator(): EntitySequenceIterator +} + +interface EntitySequenceIterator : Iterator { + + val query: Query + + override fun hasNext(): Boolean + + override fun next(): T +} diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt index 1fd1f428..1ad8d850 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt @@ -1,6 +1,7 @@ package me.liuwj.ktorm.schema -import me.liuwj.ktorm.entity.Entity +import me.liuwj.ktorm.dsl.Query +import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.expression.TableExpression import java.util.* import java.util.concurrent.atomic.AtomicInteger @@ -22,7 +23,7 @@ open class Table>( val tableName: String, val alias: String? = null, entityClass: KClass? = null -) : TypeReference() { +) : TypeReference(), EntitySequence { private val _refCounter = AtomicInteger() private val _columns = LinkedHashMap>() @@ -264,6 +265,24 @@ open class Table>( return TableExpression(tableName, alias) } + /** + * Iterate all entities in this table. + */ + override fun iterator() = object : EntitySequenceIterator { + private val rs by lazy(LazyThreadSafetyMode.NONE) { query.rowSet } + private var hasNext: Boolean? = null + + override val query: Query = this@Table.joinReferencesAndSelect() + + override fun hasNext(): Boolean { + return hasNext ?: rs.next().also { hasNext = it } + } + + override fun next(): E { + return if (hasNext()) this@Table.createEntity(rs).also { hasNext = null } else throw NoSuchElementException() + } + } + /** * 返回此表的字符串表示形式 */ diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt new file mode 100644 index 00000000..ca00f801 --- /dev/null +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -0,0 +1,23 @@ +package me.liuwj.ktorm.entity + +import me.liuwj.ktorm.BaseTest +import org.junit.Test + +/** + * Created by vince on Mar 22, 2019. + */ +class EntitySequenceTest : BaseTest() { + + @Test + fun testIterateAll() { + val employees = ArrayList() + + for (employee in Employees) { + employees += employee + } + + assert(employees.size == 4) + assert(employees[0].name == "vince") + assert(employees[0].department.name == "tech") + } +} \ No newline at end of file From 9feff75881173cc2b24fe8e61e27b238063c39e0 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 22 Mar 2019 13:37:28 +0800 Subject: [PATCH 02/34] sequence to list --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 28 +++++++++++++++---- .../kotlin/me/liuwj/ktorm/schema/Table.kt | 23 +++++++-------- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 9 ++---- 3 files changed, 34 insertions(+), 26 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 7b988048..654f3e58 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -1,17 +1,33 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.Query +import me.liuwj.ktorm.dsl.QueryRowSet +import java.util.NoSuchElementException interface EntitySequence { - operator fun iterator(): EntitySequenceIterator -} + fun createQuery(): Query -interface EntitySequenceIterator : Iterator { + fun obtainRow(row: QueryRowSet): T - val query: Query + operator fun iterator() = object : Iterator { + private val rs = createQuery().rowSet + private var hasNext: Boolean? = null - override fun hasNext(): Boolean + override fun hasNext(): Boolean { + return hasNext ?: rs.next().also { hasNext = it } + } - override fun next(): T + override fun next(): T { + return if (hasNext()) obtainRow(rs).also { hasNext = null } else throw NoSuchElementException() + } + } } + +fun EntitySequence.toList(): List { + val list = ArrayList() + for (item in this) { + list += item + } + return list +} \ No newline at end of file diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt index 1ad8d850..7653b29f 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt @@ -1,6 +1,7 @@ package me.liuwj.ktorm.schema import me.liuwj.ktorm.dsl.Query +import me.liuwj.ktorm.dsl.QueryRowSet import me.liuwj.ktorm.entity.* import me.liuwj.ktorm.expression.TableExpression import java.util.* @@ -266,21 +267,17 @@ open class Table>( } /** - * Iterate all entities in this table. + * Create a query auto left joining all reference tables and selecting all columns. */ - override fun iterator() = object : EntitySequenceIterator { - private val rs by lazy(LazyThreadSafetyMode.NONE) { query.rowSet } - private var hasNext: Boolean? = null - - override val query: Query = this@Table.joinReferencesAndSelect() - - override fun hasNext(): Boolean { - return hasNext ?: rs.next().also { hasNext = it } - } + override fun createQuery(): Query { + return this.joinReferencesAndSelect() + } - override fun next(): E { - return if (hasNext()) this@Table.createEntity(rs).also { hasNext = null } else throw NoSuchElementException() - } + /** + * Obtain the current entity from the [QueryRowSet]. + */ + override fun obtainRow(row: QueryRowSet): E { + return this.createEntity(row) } /** diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index ca00f801..efead65b 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -9,13 +9,8 @@ import org.junit.Test class EntitySequenceTest : BaseTest() { @Test - fun testIterateAll() { - val employees = ArrayList() - - for (employee in Employees) { - employees += employee - } - + fun testToList() { + val employees = Employees.toList() assert(employees.size == 4) assert(employees[0].name == "vince") assert(employees[0].department.name == "tech") From 3e2021dc7fe81a93756d3087eb1cab6a8caa2440 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 29 Mar 2019 16:09:43 +0800 Subject: [PATCH 03/34] sequence map --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 44 +++++++++++++++++++ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 38 ++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 654f3e58..dfd8352f 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -30,4 +30,48 @@ fun EntitySequence.toList(): List { list += item } return list +} + +fun EntitySequence.map(transform: (T) -> R): EntitySequence { + return object : EntitySequence { + val upstream = this@map + + override fun createQuery(): Query { + return upstream.createQuery() + } + + override fun obtainRow(row: QueryRowSet): R { + return transform(upstream.obtainRow(row)) + } + } +} + +fun EntitySequence.mapIndexed(transform: (index: Int, T) -> R): EntitySequence { + return object : EntitySequence { + val upstream = this@mapIndexed + var index = 0 + + override fun createQuery(): Query { + return upstream.createQuery() + } + + override fun obtainRow(row: QueryRowSet): R { + return transform(index++, upstream.obtainRow(row)) + } + } +} + +fun > EntitySequence.mapTo(destination: C, transform: (T) -> R): C { + for (item in this) { + destination.add(transform(item)) + } + return destination +} + +fun > EntitySequence.mapIndexedTo(destination: C, transform: (index: Int, T) -> R): C { + var index = 0 + for (item in this) { + destination.add(transform(index++, item)) + } + return destination } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index efead65b..e294f378 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -8,6 +8,12 @@ import org.junit.Test */ class EntitySequenceTest : BaseTest() { + @Test + fun testRealSequence() { + //val sequence = listOf(1, 2, 3).asSequence() + //sequence.mapIndexedTo() + } + @Test fun testToList() { val employees = Employees.toList() @@ -15,4 +21,36 @@ class EntitySequenceTest : BaseTest() { assert(employees[0].name == "vince") assert(employees[0].department.name == "tech") } + + @Test + fun testMap() { + val names = Employees.map { it.name }.map { it.toUpperCase() }.toList() + assert(names.size == 4) + assert(names[0] == "VINCE") + assert(names[1] == "MARRY") + } + + @Test + fun testMapIndexed() { + val names = Employees.map { it.name }.mapIndexed { i, name -> "$i.$name" }.toList() + assert(names.size == 4) + assert(names[0] == "0.vince") + assert(names[1] == "1.marry") + } + + @Test + fun testMapTo() { + val names = Employees.map { it.name }.mapTo(ArrayList()) { it.toUpperCase() } + assert(names.size == 4) + assert(names[0] == "VINCE") + assert(names[1] == "MARRY") + } + + @Test + fun testMapIndexedTo() { + val names = Employees.map { it.name }.mapIndexedTo(ArrayList()) { i, name -> "$i.$name" } + assert(names.size == 4) + assert(names[0] == "0.vince") + assert(names[1] == "1.marry") + } } \ No newline at end of file From 247294d6441d47f1ab43891eeffbbb7fd13729f4 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 29 Mar 2019 17:50:28 +0800 Subject: [PATCH 04/34] filter --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 92 +++++++++++++------ .../kotlin/me/liuwj/ktorm/schema/Table.kt | 20 +--- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 41 +++++++-- 3 files changed, 99 insertions(+), 54 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index dfd8352f..e9def6b7 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -2,52 +2,58 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.Query import me.liuwj.ktorm.dsl.QueryRowSet +import me.liuwj.ktorm.dsl.and +import me.liuwj.ktorm.dsl.not +import me.liuwj.ktorm.expression.ScalarExpression +import me.liuwj.ktorm.expression.SelectExpression +import me.liuwj.ktorm.schema.Table import java.util.NoSuchElementException -interface EntitySequence { +abstract class EntitySequence, out R>(val sourceTable: T) { - fun createQuery(): Query + abstract fun createQuery(): Query - fun obtainRow(row: QueryRowSet): T + abstract fun obtainRow(row: QueryRowSet): R - operator fun iterator() = object : Iterator { - private val rs = createQuery().rowSet + operator fun iterator() = object : Iterator { + private val query = createQuery() private var hasNext: Boolean? = null override fun hasNext(): Boolean { - return hasNext ?: rs.next().also { hasNext = it } + return hasNext ?: query.rowSet.next().also { hasNext = it } } - override fun next(): T { - return if (hasNext()) obtainRow(rs).also { hasNext = null } else throw NoSuchElementException() + override fun next(): R { + return if (hasNext()) obtainRow(query.rowSet).also { hasNext = null } else throw NoSuchElementException() } } } -fun EntitySequence.toList(): List { - val list = ArrayList() - for (item in this) { - list += item - } - return list -} +internal fun EntitySequence<*, *>.createSelectExpression() = createQuery().expression as SelectExpression -fun EntitySequence.map(transform: (T) -> R): EntitySequence { - return object : EntitySequence { - val upstream = this@map +fun , T : Table> T.asSequence(): EntitySequence { + return object : EntitySequence(sourceTable = this) { override fun createQuery(): Query { - return upstream.createQuery() + return sourceTable.joinReferencesAndSelect() } - override fun obtainRow(row: QueryRowSet): R { - return transform(upstream.obtainRow(row)) + override fun obtainRow(row: QueryRowSet): E { + return sourceTable.createEntity(row) } } } -fun EntitySequence.mapIndexed(transform: (index: Int, T) -> R): EntitySequence { - return object : EntitySequence { +fun EntitySequence<*, R>.toList(): List { + return this.mapTo(ArrayList()) { it } +} + +fun , S, R> EntitySequence.map(transform: (S) -> R): EntitySequence { + return this.mapIndexed { _, item -> transform(item) } +} + +fun , S, R> EntitySequence.mapIndexed(transform: (index: Int, S) -> R): EntitySequence { + return object : EntitySequence(sourceTable) { val upstream = this@mapIndexed var index = 0 @@ -61,17 +67,45 @@ fun EntitySequence.mapIndexed(transform: (index: Int, T) -> R): Entity } } -fun > EntitySequence.mapTo(destination: C, transform: (T) -> R): C { - for (item in this) { - destination.add(transform(item)) - } - return destination +fun , S, R, C : MutableCollection> EntitySequence.mapTo(destination: C, transform: (S) -> R): C { + return this.mapIndexedTo(destination) { _, item -> transform(item) } } -fun > EntitySequence.mapIndexedTo(destination: C, transform: (index: Int, T) -> R): C { +fun , S, R, C : MutableCollection> EntitySequence.mapIndexedTo(destination: C, transform: (index: Int, S) -> R): C { var index = 0 for (item in this) { destination.add(transform(index++, item)) } return destination +} + +fun , R> EntitySequence.filter(predicate: (T) -> ScalarExpression): EntitySequence { + return object : EntitySequence(sourceTable) { + val upstream = this@filter + + override fun createQuery(): Query { + val select = upstream.createSelectExpression() + if (select.where == null) { + return Query(select.copy(where = predicate(sourceTable))) + } else { + return Query(select.copy(where = select.where and predicate(sourceTable))) + } + } + + override fun obtainRow(row: QueryRowSet): R { + return upstream.obtainRow(row) + } + } +} + +fun , R> EntitySequence.filterNot(predicate: (T) -> ScalarExpression): EntitySequence { + return this.filter { !predicate(it) } +} + +fun , R, C : MutableCollection> EntitySequence.filterTo(destination: C, predicate: (T) -> ScalarExpression): C { + return this.filter(predicate).mapTo(destination) { it } +} + +fun , R, C : MutableCollection> EntitySequence.filterNotTo(destination: C, predicate: (T) -> ScalarExpression): C { + return this.filterNot(predicate).mapTo(destination) { it } } \ No newline at end of file diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt index 7653b29f..1fd1f428 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/schema/Table.kt @@ -1,8 +1,6 @@ package me.liuwj.ktorm.schema -import me.liuwj.ktorm.dsl.Query -import me.liuwj.ktorm.dsl.QueryRowSet -import me.liuwj.ktorm.entity.* +import me.liuwj.ktorm.entity.Entity import me.liuwj.ktorm.expression.TableExpression import java.util.* import java.util.concurrent.atomic.AtomicInteger @@ -24,7 +22,7 @@ open class Table>( val tableName: String, val alias: String? = null, entityClass: KClass? = null -) : TypeReference(), EntitySequence { +) : TypeReference() { private val _refCounter = AtomicInteger() private val _columns = LinkedHashMap>() @@ -266,20 +264,6 @@ open class Table>( return TableExpression(tableName, alias) } - /** - * Create a query auto left joining all reference tables and selecting all columns. - */ - override fun createQuery(): Query { - return this.joinReferencesAndSelect() - } - - /** - * Obtain the current entity from the [QueryRowSet]. - */ - override fun obtainRow(row: QueryRowSet): E { - return this.createEntity(row) - } - /** * 返回此表的字符串表示形式 */ diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index e294f378..6598b0bc 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -1,6 +1,8 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.BaseTest +import me.liuwj.ktorm.dsl.eq +import me.liuwj.ktorm.dsl.isNull import org.junit.Test /** @@ -10,13 +12,13 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { - //val sequence = listOf(1, 2, 3).asSequence() - //sequence.mapIndexedTo() + val sequence = listOf(1, 2, 3).asSequence() + sequence.filter { it > 0 } } @Test fun testToList() { - val employees = Employees.toList() + val employees = Employees.asSequence().toList() assert(employees.size == 4) assert(employees[0].name == "vince") assert(employees[0].department.name == "tech") @@ -24,7 +26,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testMap() { - val names = Employees.map { it.name }.map { it.toUpperCase() }.toList() + val names = Employees.asSequence().map { it.name }.map { it.toUpperCase() }.toList() assert(names.size == 4) assert(names[0] == "VINCE") assert(names[1] == "MARRY") @@ -32,7 +34,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testMapIndexed() { - val names = Employees.map { it.name }.mapIndexed { i, name -> "$i.$name" }.toList() + val names = Employees.asSequence().map { it.name }.mapIndexed { i, name -> "$i.$name" }.toList() assert(names.size == 4) assert(names[0] == "0.vince") assert(names[1] == "1.marry") @@ -40,7 +42,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testMapTo() { - val names = Employees.map { it.name }.mapTo(ArrayList()) { it.toUpperCase() } + val names = Employees.asSequence().map { it.name }.mapTo(ArrayList()) { it.toUpperCase() } assert(names.size == 4) assert(names[0] == "VINCE") assert(names[1] == "MARRY") @@ -48,9 +50,34 @@ class EntitySequenceTest : BaseTest() { @Test fun testMapIndexedTo() { - val names = Employees.map { it.name }.mapIndexedTo(ArrayList()) { i, name -> "$i.$name" } + val names = Employees.asSequence().map { it.name }.mapIndexedTo(ArrayList()) { i, name -> "$i.$name" } assert(names.size == 4) assert(names[0] == "0.vince") assert(names[1] == "1.marry") } + + @Test + fun testFilter() { + val names = Employees + .asSequence() + .filter { it.departmentId eq 1 } + .filterNot { it.managerId.isNull() } + .map { it.name } + .toList() + + assert(names.size == 1) + assert(names[0] == "marry") + } + + @Test + fun testFilterTo() { + val names = Employees + .asSequence() + .map { it.name } + .filter { it.departmentId eq 1 } + .filterTo(ArrayList()) { it.managerId.isNull() } + + assert(names.size == 1) + assert(names[0] == "vince") + } } \ No newline at end of file From 594b2628947faab905bdcd282ca977e8aa44ad6b Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 29 Mar 2019 22:00:43 +0800 Subject: [PATCH 05/34] rm sequence map --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 111 ++++++------------ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 36 +----- 2 files changed, 38 insertions(+), 109 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index e9def6b7..ebd701b7 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -1,111 +1,72 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.Query -import me.liuwj.ktorm.dsl.QueryRowSet import me.liuwj.ktorm.dsl.and import me.liuwj.ktorm.dsl.not import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.expression.SelectExpression import me.liuwj.ktorm.schema.Table -import java.util.NoSuchElementException -abstract class EntitySequence, out R>(val sourceTable: T) { +data class EntitySequence, T : Table>(val sourceTable: T, val expression: SelectExpression) { - abstract fun createQuery(): Query + val query = Query(expression) - abstract fun obtainRow(row: QueryRowSet): R + val sql get() = query.sql - operator fun iterator() = object : Iterator { - private val query = createQuery() - private var hasNext: Boolean? = null + val rowSet get() = query.rowSet - override fun hasNext(): Boolean { - return hasNext ?: query.rowSet.next().also { hasNext = it } - } - - override fun next(): R { - return if (hasNext()) obtainRow(query.rowSet).also { hasNext = null } else throw NoSuchElementException() - } - } -} - -internal fun EntitySequence<*, *>.createSelectExpression() = createQuery().expression as SelectExpression - -fun , T : Table> T.asSequence(): EntitySequence { - return object : EntitySequence(sourceTable = this) { - - override fun createQuery(): Query { - return sourceTable.joinReferencesAndSelect() - } - - override fun obtainRow(row: QueryRowSet): E { - return sourceTable.createEntity(row) - } - } -} + val totalRecords get() = query.totalRecords -fun EntitySequence<*, R>.toList(): List { - return this.mapTo(ArrayList()) { it } -} - -fun , S, R> EntitySequence.map(transform: (S) -> R): EntitySequence { - return this.mapIndexed { _, item -> transform(item) } -} - -fun , S, R> EntitySequence.mapIndexed(transform: (index: Int, S) -> R): EntitySequence { - return object : EntitySequence(sourceTable) { - val upstream = this@mapIndexed - var index = 0 + operator fun iterator() = object : Iterator { + private val queryIterator = query.iterator() - override fun createQuery(): Query { - return upstream.createQuery() + override fun hasNext(): Boolean { + return queryIterator.hasNext() } - override fun obtainRow(row: QueryRowSet): R { - return transform(index++, upstream.obtainRow(row)) + override fun next(): E { + return sourceTable.createEntity(queryIterator.next()) } } } -fun , S, R, C : MutableCollection> EntitySequence.mapTo(destination: C, transform: (S) -> R): C { - return this.mapIndexedTo(destination) { _, item -> transform(item) } +fun , T : Table> T.asSequence(): EntitySequence { + val query = this.joinReferencesAndSelect() + return EntitySequence(this, query.expression as SelectExpression) } -fun , S, R, C : MutableCollection> EntitySequence.mapIndexedTo(destination: C, transform: (index: Int, S) -> R): C { - var index = 0 +fun > EntitySequence.toList(): List { + val list = ArrayList() for (item in this) { - destination.add(transform(index++, item)) + list += item } - return destination + return list } -fun , R> EntitySequence.filter(predicate: (T) -> ScalarExpression): EntitySequence { - return object : EntitySequence(sourceTable) { - val upstream = this@filter - - override fun createQuery(): Query { - val select = upstream.createSelectExpression() - if (select.where == null) { - return Query(select.copy(where = predicate(sourceTable))) - } else { - return Query(select.copy(where = select.where and predicate(sourceTable))) - } - } - - override fun obtainRow(row: QueryRowSet): R { - return upstream.obtainRow(row) - } +fun , T : Table> EntitySequence.filter(predicate: (T) -> ScalarExpression): EntitySequence { + if (expression.where == null) { + return this.copy(expression = expression.copy(where = predicate(sourceTable))) + } else { + return this.copy(expression = expression.copy(where = expression.where and predicate(sourceTable))) } } -fun , R> EntitySequence.filterNot(predicate: (T) -> ScalarExpression): EntitySequence { +fun , T : Table> EntitySequence.filterNot(predicate: (T) -> ScalarExpression): EntitySequence { return this.filter { !predicate(it) } } -fun , R, C : MutableCollection> EntitySequence.filterTo(destination: C, predicate: (T) -> ScalarExpression): C { - return this.filter(predicate).mapTo(destination) { it } +fun , T : Table, C : MutableCollection> EntitySequence.filterTo(destination: C, predicate: (T) -> ScalarExpression): C { + val sequence = this.filter(predicate) + for (item in sequence) { + destination += item + } + return destination } -fun , R, C : MutableCollection> EntitySequence.filterNotTo(destination: C, predicate: (T) -> ScalarExpression): C { - return this.filterNot(predicate).mapTo(destination) { it } +fun , T : Table, C : MutableCollection> EntitySequence.filterNotTo(destination: C, predicate: (T) -> ScalarExpression): C { + val sequence = this.filterNot(predicate) + for (item in sequence) { + destination += item + } + return destination } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 6598b0bc..135ab77a 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -24,46 +24,14 @@ class EntitySequenceTest : BaseTest() { assert(employees[0].department.name == "tech") } - @Test - fun testMap() { - val names = Employees.asSequence().map { it.name }.map { it.toUpperCase() }.toList() - assert(names.size == 4) - assert(names[0] == "VINCE") - assert(names[1] == "MARRY") - } - - @Test - fun testMapIndexed() { - val names = Employees.asSequence().map { it.name }.mapIndexed { i, name -> "$i.$name" }.toList() - assert(names.size == 4) - assert(names[0] == "0.vince") - assert(names[1] == "1.marry") - } - - @Test - fun testMapTo() { - val names = Employees.asSequence().map { it.name }.mapTo(ArrayList()) { it.toUpperCase() } - assert(names.size == 4) - assert(names[0] == "VINCE") - assert(names[1] == "MARRY") - } - - @Test - fun testMapIndexedTo() { - val names = Employees.asSequence().map { it.name }.mapIndexedTo(ArrayList()) { i, name -> "$i.$name" } - assert(names.size == 4) - assert(names[0] == "0.vince") - assert(names[1] == "1.marry") - } - @Test fun testFilter() { val names = Employees .asSequence() .filter { it.departmentId eq 1 } .filterNot { it.managerId.isNull() } - .map { it.name } .toList() + .map { it.name } assert(names.size == 1) assert(names[0] == "marry") @@ -73,9 +41,9 @@ class EntitySequenceTest : BaseTest() { fun testFilterTo() { val names = Employees .asSequence() - .map { it.name } .filter { it.departmentId eq 1 } .filterTo(ArrayList()) { it.managerId.isNull() } + .map { it.name } assert(names.size == 1) assert(names[0] == "vince") From 4b1ae7201daf92632edaa9ef5ceb1483d3414d14 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 29 Mar 2019 22:35:44 +0800 Subject: [PATCH 06/34] sequence count --- .../me/liuwj/ktorm/dsl/CountExpression.kt | 32 ++++++++++---- .../main/kotlin/me/liuwj/ktorm/dsl/Query.kt | 2 +- .../me/liuwj/ktorm/entity/EntitySequence.kt | 42 +++++++++++++++++++ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 14 ++++++- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt index 68b79a95..d56eeec8 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt @@ -3,7 +3,7 @@ package me.liuwj.ktorm.dsl import me.liuwj.ktorm.expression.* import me.liuwj.ktorm.schema.IntSqlType -internal fun QueryExpression.toCountExpression(): SelectExpression { +internal fun QueryExpression.toCountExpression(keepPaging: Boolean): SelectExpression { val expression = OrderByRemover.visit(this) as QueryExpression val countColumns = listOf( @@ -18,15 +18,29 @@ internal fun QueryExpression.toCountExpression(): SelectExpression { ) if (expression is SelectExpression && expression.isSimpleSelect()) { - return expression.copy(columns = countColumns, offset = null, limit = null) + if (keepPaging) { + return expression.copy(columns = countColumns) + } else { + return expression.copy(columns = countColumns, offset = null, limit = null) + } } else { - return SelectExpression( - columns = countColumns, - from = when (expression) { - is SelectExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") - is UnionExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") - } - ) + if (keepPaging) { + return SelectExpression( + columns = countColumns, + from = when (expression) { + is SelectExpression -> expression.copy(tableAlias = "tmp_count") + is UnionExpression -> expression.copy(tableAlias = "tmp_count") + } + ) + } else { + return SelectExpression( + columns = countColumns, + from = when (expression) { + is SelectExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") + is UnionExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") + } + ) + } } } diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt index 2227796f..d6ac6759 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt @@ -42,7 +42,7 @@ data class Query(val expression: QueryExpression) : Iterable { if (expression.offset == null && expression.limit == null) { rowSet.size() } else { - val countExpr = expression.toCountExpression() + val countExpr = expression.toCountExpression(keepPaging = false) countExpr.prepareStatement { statement, logger -> statement.executeQuery().use { rs -> diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index ebd701b7..c06882ab 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -1,8 +1,11 @@ package me.liuwj.ktorm.entity +import me.liuwj.ktorm.database.Database +import me.liuwj.ktorm.database.prepareStatement import me.liuwj.ktorm.dsl.Query import me.liuwj.ktorm.dsl.and import me.liuwj.ktorm.dsl.not +import me.liuwj.ktorm.dsl.toCountExpression import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.expression.SelectExpression import me.liuwj.ktorm.schema.Table @@ -69,4 +72,43 @@ fun , T : Table, C : MutableCollection> EntitySequence.count(): Int { + val countExpr = expression.toCountExpression(keepPaging = true) + + countExpr.prepareStatement { statement, logger -> + statement.executeQuery().use { rs -> + if (rs.next()) { + return rs.getInt(1).also { logger.debug("Count: {}", it) } + } else { + val (sql, _) = Database.global.formatExpression(countExpr, beautifySql = true) + throw IllegalStateException("No result return for sql: $sql") + } + } + } +} + +fun , T : Table> EntitySequence.count(predicate: (T) -> ScalarExpression): Int { + return this.filter(predicate).count() +} + +fun EntitySequence<*, *>.none(): Boolean { + return this.count() == 0 +} + +fun , T : Table> EntitySequence.none(predicate: (T) -> ScalarExpression): Boolean { + return this.count(predicate) == 0 +} + +fun EntitySequence<*, *>.any(): Boolean { + return this.count() > 0 +} + +fun , T : Table> EntitySequence.any(predicate: (T) -> ScalarExpression): Boolean { + return this.count(predicate) > 0 +} + +fun , T : Table> EntitySequence.all(predicate: (T) -> ScalarExpression): Boolean { + return this.none { !predicate(it) } } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 135ab77a..cf80876c 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -2,6 +2,7 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.BaseTest import me.liuwj.ktorm.dsl.eq +import me.liuwj.ktorm.dsl.greater import me.liuwj.ktorm.dsl.isNull import org.junit.Test @@ -13,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.filter { it > 0 } + sequence.any() } @Test @@ -48,4 +49,15 @@ class EntitySequenceTest : BaseTest() { assert(names.size == 1) assert(names[0] == "vince") } + + @Test + fun testCount() { + assert(Employees.asSequence().filter { it.departmentId eq 1 }.count() == 2) + assert(Employees.asSequence().count { it.departmentId eq 1 } == 2) + } + + @Test + fun testAll() { + assert(Employees.asSequence().filter { it.departmentId eq 1 }.all { it.salary greater 49L }) + } } \ No newline at end of file From ab4ce13cedf9ff25ccc19f185f204e8f72f72d80 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 30 Mar 2019 09:40:40 +0800 Subject: [PATCH 07/34] create entity without references --- .../me/liuwj/ktorm/entity/EntityFinding.kt | 16 ++++++++++++++-- .../kotlin/me/liuwj/ktorm/entity/EntityTest.kt | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt index 3d239659..497b2aa7 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt @@ -109,14 +109,26 @@ private infix fun ColumnDeclaring<*>.eq(column: ColumnDeclaring<*>): BinaryExpre */ @Suppress("UNCHECKED_CAST") fun > Table.createEntity(row: QueryRowSet): E { - return doCreateEntity(row) as E + return doCreateEntity(row, skipReferences = false) as E } -private fun Table<*>.doCreateEntity(row: QueryRowSet, foreignKey: Column<*>? = null): Entity<*> { +/** + * 从结果集中创建实体对象,不会自动级联创建引用表的实体对象 + */ +@Suppress("UNCHECKED_CAST") +fun > Table.createEntityWithoutReferences(row: QueryRowSet): E { + return doCreateEntity(row, skipReferences = true) as E +} + +private fun Table<*>.doCreateEntity(row: QueryRowSet, skipReferences: Boolean = false, foreignKey: Column<*>? = null): Entity<*> { val entityClass = this.entityClass ?: error("No entity class configured for table: $tableName") val entity = Entity.create(entityClass, fromTable = this) for (column in columns) { + if (skipReferences && column.binding is ReferenceBinding) { + continue + } + try { row.retrieveColumn(column, intoEntity = entity) } catch (e: Throwable) { diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt index 3bcb9fa6..40ef3c2e 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt @@ -247,6 +247,17 @@ class EntityTest : BaseTest() { employees.forEach { println(it) } } + @Test + fun testCreateEntityWithoutReferences() { + Employees + .leftJoin(Departments, on = Employees.departmentId eq Departments.id) + .select(Employees.columns + Departments.columns) + .forEach { row -> + val employee = Employees.createEntityWithoutReferences(row) + assert(employee["department"] == null) + } + } + @Test fun testAutoDiscardChanges() { var department = Departments.findById(2) ?: return From 5963d6a0184a9ec7e8b95f7262e78da0ad92b6d7 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 30 Mar 2019 14:58:52 +0800 Subject: [PATCH 08/34] keep refrenced entities' ids --- .../me/liuwj/ktorm/entity/EntityFinding.kt | 64 +++++++++---------- .../me/liuwj/ktorm/entity/EntityTest.kt | 15 +++-- 2 files changed, 42 insertions(+), 37 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt index 497b2aa7..d12af583 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt @@ -109,7 +109,8 @@ private infix fun ColumnDeclaring<*>.eq(column: ColumnDeclaring<*>): BinaryExpre */ @Suppress("UNCHECKED_CAST") fun > Table.createEntity(row: QueryRowSet): E { - return doCreateEntity(row, skipReferences = false) as E + val entity = doCreateEntity(row, skipReferences = false) as E + return entity.apply { discardChanges() } } /** @@ -117,63 +118,62 @@ fun > Table.createEntity(row: QueryRowSet): E { */ @Suppress("UNCHECKED_CAST") fun > Table.createEntityWithoutReferences(row: QueryRowSet): E { - return doCreateEntity(row, skipReferences = true) as E + val entity = doCreateEntity(row, skipReferences = true) as E + return entity.apply { discardChanges() } } -private fun Table<*>.doCreateEntity(row: QueryRowSet, skipReferences: Boolean = false, foreignKey: Column<*>? = null): Entity<*> { +private fun Table<*>.doCreateEntity(row: QueryRowSet, skipReferences: Boolean = false): Entity<*> { val entityClass = this.entityClass ?: error("No entity class configured for table: $tableName") val entity = Entity.create(entityClass, fromTable = this) for (column in columns) { - if (skipReferences && column.binding is ReferenceBinding) { - continue - } - try { - row.retrieveColumn(column, intoEntity = entity) + row.retrieveColumn(column, intoEntity = entity, skipReferences = skipReferences) } catch (e: Throwable) { throw IllegalStateException("Error occur while retrieving column: $column, binding: ${column.binding}", e) } } - val foreignKeyValue = if (foreignKey != null && row.hasColumn(foreignKey)) row[foreignKey] else null - if (foreignKeyValue != null) { - entity.implementation.forceSetPrimaryKeyValue(this, foreignKeyValue) - } - - return entity.apply { discardChanges() } + return entity } -private fun QueryRowSet.retrieveColumn(column: Column<*>, intoEntity: Entity<*>) { +private fun QueryRowSet.retrieveColumn(column: Column<*>, intoEntity: Entity<*>, skipReferences: Boolean) { + val columnValue = (if (this.hasColumn(column)) this[column] else null) ?: return + val binding = column.binding ?: return when (binding) { is ReferenceBinding -> { val rightTable = binding.referenceTable val primaryKey = rightTable.primaryKey ?: error("Table ${rightTable.tableName} doesn't have a primary key.") - if (this.hasColumn(primaryKey) && this[primaryKey] != null) { - intoEntity[binding.onProperty.name] = rightTable.doCreateEntity(this, foreignKey = column) + when { + skipReferences -> { + val child = Entity.create(binding.onProperty.returnType.classifier as KClass<*>, fromTable = rightTable) + child.implementation.setPrimaryKeyValue(rightTable, columnValue) + intoEntity[binding.onProperty.name] = child.apply { discardChanges() } + } + this.hasColumn(primaryKey) && this[primaryKey] != null -> { + val child = rightTable.doCreateEntity(this) + child.implementation.forceSetPrimaryKeyValue(rightTable, columnValue) + intoEntity[binding.onProperty.name] = child.apply { discardChanges() } + } } } is NestedBinding -> { - val columnValue = if (this.hasColumn(column)) this[column] else null - - if (columnValue != null) { - var curr = intoEntity.implementation - for ((i, prop) in binding.withIndex()) { - if (i != binding.lastIndex) { - var child = curr.getProperty(prop.name) as Entity<*>? - if (child == null) { - child = Entity.create(prop.returnType.classifier as KClass<*>, parent = curr) - curr.setProperty(prop.name, child) - } - - curr = child.implementation + var curr = intoEntity.implementation + for ((i, prop) in binding.withIndex()) { + if (i != binding.lastIndex) { + var child = curr.getProperty(prop.name) as Entity<*>? + if (child == null) { + child = Entity.create(prop.returnType.classifier as KClass<*>, parent = curr) + curr.setProperty(prop.name, child) } - } - curr.setProperty(binding.last().name, columnValue) + curr = child.implementation + } } + + curr.setProperty(binding.last().name, columnValue) } } } diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt index 40ef3c2e..d3952898 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt @@ -249,13 +249,18 @@ class EntityTest : BaseTest() { @Test fun testCreateEntityWithoutReferences() { - Employees + val employees = Employees .leftJoin(Departments, on = Employees.departmentId eq Departments.id) .select(Employees.columns + Departments.columns) - .forEach { row -> - val employee = Employees.createEntityWithoutReferences(row) - assert(employee["department"] == null) - } + .map { Employees.createEntityWithoutReferences(it) } + + employees.forEach { println(it) } + + assert(employees.size == 4) + assert(employees[0].department.id == 1) + assert(employees[1].department.id == 1) + assert(employees[2].department.id == 2) + assert(employees[3].department.id == 2) } @Test From 5cb4b890149a00a89480229deaa1ec3357b6fe61 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 30 Mar 2019 18:13:27 +0800 Subject: [PATCH 09/34] refactor --- .../kotlin/me/liuwj/ktorm/entity/EntityDml.kt | 1 + .../me/liuwj/ktorm/entity/EntityExtensions.kt | 51 +++---------------- .../me/liuwj/ktorm/entity/EntityFinding.kt | 4 +- 3 files changed, 10 insertions(+), 46 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt index 268ac010..e5b13bb8 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt @@ -116,6 +116,7 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map 0) { check(curr != null) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityExtensions.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityExtensions.kt index ebad02b6..44b37dcb 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityExtensions.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityExtensions.kt @@ -30,12 +30,12 @@ internal fun EntityImplementation.getColumnValue(column: Column<*>): Any? { } } -internal fun EntityImplementation.setPrimaryKeyValue(fromTable: Table<*>, value: Any?) { +internal fun EntityImplementation.setPrimaryKeyValue(fromTable: Table<*>, value: Any?, forceSet: Boolean = false) { val primaryKey = fromTable.primaryKey ?: error("Table ${fromTable.tableName} doesn't have a primary key.") - setColumnValue(primaryKey, value) + setColumnValue(primaryKey, value, forceSet) } -internal fun EntityImplementation.setColumnValue(column: Column<*>, value: Any?) { +internal fun EntityImplementation.setColumnValue(column: Column<*>, value: Any?, forceSet: Boolean = false) { val binding = column.binding ?: error("Column $column has no bindings to any entity field.") when (binding) { @@ -43,10 +43,10 @@ internal fun EntityImplementation.setColumnValue(column: Column<*>, value: Any?) var child = this.getProperty(binding.onProperty.name) as Entity<*>? if (child == null) { child = Entity.create(binding.onProperty.returnType.classifier as KClass<*>, fromTable = binding.referenceTable) - this.setProperty(binding.onProperty.name, child) + this.setProperty(binding.onProperty.name, child, forceSet) } - child.implementation.setPrimaryKeyValue(binding.referenceTable, value) + child.implementation.setPrimaryKeyValue(binding.referenceTable, value, forceSet) } is NestedBinding -> { var curr: EntityImplementation = this @@ -55,51 +55,14 @@ internal fun EntityImplementation.setColumnValue(column: Column<*>, value: Any?) var child = curr.getProperty(prop.name) as Entity<*>? if (child == null) { child = Entity.create(prop.returnType.classifier as KClass<*>, parent = curr, fromTable = column.table) - curr.setProperty(prop.name, child) + curr.setProperty(prop.name, child, forceSet) } curr = child.implementation } } - curr.setProperty(binding.last().name, value) - } - } -} - -internal fun EntityImplementation.forceSetPrimaryKeyValue(fromTable: Table<*>, value: Any?) { - val primaryKey = fromTable.primaryKey ?: error("Table ${fromTable.tableName} doesn't have a primary key.") - forceSetColumnValue(primaryKey, value) -} - -internal fun EntityImplementation.forceSetColumnValue(column: Column<*>, value: Any?) { - val binding = column.binding ?: error("Column $column has no bindings to any entity field.") - - when (binding) { - is ReferenceBinding -> { - var child = this.getProperty(binding.onProperty.name) as Entity<*>? - if (child == null) { - child = Entity.create(binding.onProperty.returnType.classifier as KClass<*>, fromTable = binding.referenceTable) - this.setProperty(binding.onProperty.name, child, forceSet = true) - } - - child.implementation.forceSetPrimaryKeyValue(binding.referenceTable, value) - } - is NestedBinding -> { - var curr: EntityImplementation = this - for ((i, prop) in binding.withIndex()) { - if (i != binding.lastIndex) { - var child = curr.getProperty(prop.name) as Entity<*>? - if (child == null) { - child = Entity.create(prop.returnType.classifier as KClass<*>, parent = curr, fromTable = column.table) - curr.setProperty(prop.name, child, forceSet = true) - } - - curr = child.implementation - } - } - - curr.setProperty(binding.last().name, value, forceSet = true) + curr.setProperty(binding.last().name, value, forceSet) } } } diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt index d12af583..66967c8d 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt @@ -149,12 +149,12 @@ private fun QueryRowSet.retrieveColumn(column: Column<*>, intoEntity: Entity<*>, when { skipReferences -> { val child = Entity.create(binding.onProperty.returnType.classifier as KClass<*>, fromTable = rightTable) - child.implementation.setPrimaryKeyValue(rightTable, columnValue) + child.implementation.setColumnValue(primaryKey, columnValue) intoEntity[binding.onProperty.name] = child.apply { discardChanges() } } this.hasColumn(primaryKey) && this[primaryKey] != null -> { val child = rightTable.doCreateEntity(this) - child.implementation.forceSetPrimaryKeyValue(rightTable, columnValue) + child.implementation.setColumnValue(primaryKey, columnValue, forceSet = true) intoEntity[binding.onProperty.name] = child.apply { discardChanges() } } } From b775fb16d2fd7a48edd490a1c697e642803482c5 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 30 Mar 2019 18:48:32 +0800 Subject: [PATCH 10/34] associate --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 46 +++++++++++++++++++ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 9 +++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index c06882ab..8fd9a491 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -46,6 +46,8 @@ fun > EntitySequence.toList(): List { return list } + + fun , T : Table> EntitySequence.filter(predicate: (T) -> ScalarExpression): EntitySequence { if (expression.where == null) { return this.copy(expression = expression.copy(where = predicate(sourceTable))) @@ -111,4 +113,48 @@ fun , T : Table> EntitySequence.any(predicate: (T) -> Sca fun , T : Table> EntitySequence.all(predicate: (T) -> ScalarExpression): Boolean { return this.none { !predicate(it) } +} + +fun , K, V> EntitySequence.associate(transform: (E) -> Pair): Map { + return this.associateTo(LinkedHashMap(), transform) +} + +fun , K> EntitySequence.associateBy(keySelector: (E) -> K): Map { + return this.associateByTo(LinkedHashMap(), keySelector) +} + +fun , K, V> EntitySequence.associateBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map { + return this.associateByTo(LinkedHashMap(), keySelector, valueTransform) +} + +fun , V> EntitySequence.associateWith(valueTransform: (K) -> V): Map { + return this.associateWithTo(LinkedHashMap(), valueTransform) +} + +fun , K, V, M : MutableMap> EntitySequence.associateTo(destination: M, transform: (E) -> Pair): M { + for (item in this) { + destination += transform(item) + } + return destination +} + +fun , K, M : MutableMap> EntitySequence.associateByTo(destination: M, keySelector: (E) -> K): M { + for (item in this) { + destination.put(keySelector(item), item) + } + return destination +} + +fun , K, V, M : MutableMap> EntitySequence.associateByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M { + for (item in this) { + destination.put(keySelector(item), valueTransform(item)) + } + return destination +} + +fun , V, M : MutableMap> EntitySequence.associateWithTo(destination: M, valueTransform: (K) -> V): M { + for (item in this) { + destination.put(item, valueTransform(item)) + } + return destination } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index cf80876c..2e81a211 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -14,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.any() + sequence.associateBy { it } } @Test @@ -60,4 +60,11 @@ class EntitySequenceTest : BaseTest() { fun testAll() { assert(Employees.asSequence().filter { it.departmentId eq 1 }.all { it.salary greater 49L }) } + + @Test + fun testAssociate() { + val employees = Employees.asSequence().filter { it.departmentId eq 1 }.associateBy { it.id } + assert(employees.size == 2) + assert(employees[1]!!.name == "vince") + } } \ No newline at end of file From a405f69f6d30e6d0a63ed093e98b502cb2459188 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 30 Mar 2019 21:37:29 +0800 Subject: [PATCH 11/34] drop & take --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 12 ++++++++++ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 24 ++++++++++++++++++- .../me/liuwj/ktorm/support/mysql/MySqlTest.kt | 16 ++++++++++++- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 8fd9a491..37b11351 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -157,4 +157,16 @@ fun , V, M : MutableMap> EntitySequence.associat destination.put(item, valueTransform(item)) } return destination +} + +fun , T : Table> EntitySequence.drop(n: Int): EntitySequence { + if (n <= 0) { + return this + } else { + return this.copy(expression = expression.copy(offset = n)) + } +} + +fun , T : Table> EntitySequence.take(n: Int): EntitySequence { + return this.copy(expression = expression.copy(limit = n)) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 2e81a211..0ac525e9 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -14,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.associateBy { it } + sequence.take(0) } @Test @@ -67,4 +67,26 @@ class EntitySequenceTest : BaseTest() { assert(employees.size == 2) assert(employees[1]!!.name == "vince") } + + @Test + fun testDrop() { + try { + val employees = Employees.asSequence().drop(3).toList() + assert(employees.size == 1) + assert(employees[0].name == "penny") + } catch (e: UnsupportedOperationException) { + // Expected, pagination should be provided by dialects... + } + } + + @Test + fun testTake() { + try { + val employees = Employees.asSequence().take(1).toList() + assert(employees.size == 1) + assert(employees[0].name == "vince") + } catch (e: UnsupportedOperationException) { + // Expected, pagination should be provided by dialects... + } + } } \ No newline at end of file diff --git a/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt b/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt index e1b3f35b..080aec5d 100644 --- a/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt +++ b/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt @@ -4,7 +4,7 @@ import me.liuwj.ktorm.BaseTest import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.useConnection import me.liuwj.ktorm.dsl.* -import me.liuwj.ktorm.entity.findById +import me.liuwj.ktorm.entity.* import org.junit.Test import java.time.LocalDate @@ -152,4 +152,18 @@ class MySqlTest : BaseTest() { assert(query.totalRecords == 4) } + + @Test + fun testDrop() { + val employees = Employees.asSequence().drop(3).toList() + assert(employees.size == 1) + assert(employees[0].name == "penny") + } + + @Test + fun testTake() { + val employees = Employees.asSequence().take(1).toList() + assert(employees.size == 1) + assert(employees[0].name == "vince") + } } \ No newline at end of file From f1798b1682c3535709bdcfc2549a956b054dcbf8 Mon Sep 17 00:00:00 2001 From: vince Date: Sat, 30 Mar 2019 22:22:35 +0800 Subject: [PATCH 12/34] element at --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 26 ++++++++++++++++--- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 2 +- .../me/liuwj/ktorm/support/mysql/MySqlTest.kt | 15 +++++++++-- 3 files changed, 37 insertions(+), 6 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 37b11351..3bf5d2f4 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -9,6 +9,7 @@ import me.liuwj.ktorm.dsl.toCountExpression import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.expression.SelectExpression import me.liuwj.ktorm.schema.Table +import kotlin.math.min data class EntitySequence, T : Table>(val sourceTable: T, val expression: SelectExpression) { @@ -160,13 +161,32 @@ fun , V, M : MutableMap> EntitySequence.associat } fun , T : Table> EntitySequence.drop(n: Int): EntitySequence { - if (n <= 0) { + if (n == 0) { return this } else { - return this.copy(expression = expression.copy(offset = n)) + val offset = expression.offset ?: 0 + return this.copy(expression = expression.copy(offset = offset + n)) } } fun , T : Table> EntitySequence.take(n: Int): EntitySequence { - return this.copy(expression = expression.copy(limit = n)) + val limit = expression.limit ?: Int.MAX_VALUE + return this.copy(expression = expression.copy(limit = min(limit, n))) +} + +fun , T : Table> EntitySequence.elementAtOrNull(index: Int): E? { + val iterator = this.drop(index).take(1).iterator() + if (iterator.hasNext()) { + return iterator.next() + } else { + return null + } +} + +fun , T : Table> EntitySequence.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E { + return this.elementAtOrNull(index) ?: defaultValue(index) +} + +fun , T : Table> EntitySequence.elementAt(index: Int): E { + return this.elementAtOrNull(index) ?: throw IndexOutOfBoundsException("Sequence doesn't contain element at index $index.") } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 0ac525e9..bc2201bc 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -14,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.take(0) + sequence.elementAt(1) } @Test diff --git a/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt b/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt index 080aec5d..a05128e8 100644 --- a/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt +++ b/ktorm-support-mysql/src/test/kotlin/me/liuwj/ktorm/support/mysql/MySqlTest.kt @@ -155,15 +155,26 @@ class MySqlTest : BaseTest() { @Test fun testDrop() { - val employees = Employees.asSequence().drop(3).toList() + val employees = Employees.asSequence().drop(1).drop(1).drop(1).toList() assert(employees.size == 1) assert(employees[0].name == "penny") } @Test fun testTake() { - val employees = Employees.asSequence().take(1).toList() + val employees = Employees.asSequence().take(2).take(1).toList() assert(employees.size == 1) assert(employees[0].name == "vince") } + + @Test + fun testElementAt() { + val employee = Employees + .asSequence() + .drop(2) + .elementAt(1) + + assert(employee.name == "penny") + assert(Employees.asSequence().elementAtOrNull(4) == null) + } } \ No newline at end of file From d890866183c6aa23edb46aa477f44c3b12fdefee Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 31 Mar 2019 08:49:40 +0800 Subject: [PATCH 13/34] first & last --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 66 +++++++++++++++++-- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 12 +++- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 3bf5d2f4..90244643 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -175,10 +175,24 @@ fun , T : Table> EntitySequence.take(n: Int): EntitySeque } fun , T : Table> EntitySequence.elementAtOrNull(index: Int): E? { - val iterator = this.drop(index).take(1).iterator() - if (iterator.hasNext()) { - return iterator.next() - } else { + try { + val iterator = this.drop(index).take(1).iterator() + if (iterator.hasNext()) { + return iterator.next() + } else { + return null + } + + } catch (e: UnsupportedOperationException) { + + val iterator = this.iterator() + var count = 0 + while (iterator.hasNext()) { + val item = iterator.next() + if (index == count++) { + return item + } + } return null } } @@ -189,4 +203,48 @@ fun , T : Table> EntitySequence.elementAtOrElse(index: In fun , T : Table> EntitySequence.elementAt(index: Int): E { return this.elementAtOrNull(index) ?: throw IndexOutOfBoundsException("Sequence doesn't contain element at index $index.") +} + +fun , T : Table> EntitySequence.firstOrNull(): E? { + return this.elementAtOrNull(0) +} + +fun , T : Table> EntitySequence.firstOrNull(predicate: (T) -> ScalarExpression): E? { + return this.filter(predicate).elementAtOrNull(0) +} + +fun , T : Table> EntitySequence.first(): E { + return this.elementAt(0) +} + +fun , T : Table> EntitySequence.first(predicate: (T) -> ScalarExpression): E { + return this.filter(predicate).elementAt(0) +} + +fun > EntitySequence.lastOrNull(): E? { + var last: E? = null + for (item in this) { + last = item + } + return last +} + +fun , T : Table> EntitySequence.lastOrNull(predicate: (T) -> ScalarExpression): E? { + return this.filter(predicate).lastOrNull() +} + +fun > EntitySequence.last(): E { + return lastOrNull() ?: throw NoSuchElementException("Sequence is empty.") +} + +fun , T : Table> EntitySequence.last(predicate: (T) -> ScalarExpression): E { + return this.filter(predicate).last() +} + +fun , T : Table> EntitySequence.find(predicate: (T) -> ScalarExpression): E? { + return this.firstOrNull(predicate) +} + +fun , T : Table> EntitySequence.findLast(predicate: (T) -> ScalarExpression): E? { + return this.lastOrNull(predicate) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index bc2201bc..b2a0b8b5 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -14,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.elementAt(1) + sequence.find { it > 0 } } @Test @@ -89,4 +89,14 @@ class EntitySequenceTest : BaseTest() { // Expected, pagination should be provided by dialects... } } + + @Test + fun testFindLast() { + val employee = Employees + .asSequence() + .elementAt(3) + + assert(employee.name == "penny") + assert(Employees.asSequence().elementAtOrNull(4) == null) + } } \ No newline at end of file From fde61371ff6686829887cc84fe6baa92f0916129 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 31 Mar 2019 11:23:08 +0800 Subject: [PATCH 14/34] fold & foreach --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 22 +++++++++++++++++++ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 6 +++++ 2 files changed, 28 insertions(+) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 90244643..3102da8d 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -247,4 +247,26 @@ fun , T : Table> EntitySequence.find(predicate: (T) -> Sc fun , T : Table> EntitySequence.findLast(predicate: (T) -> ScalarExpression): E? { return this.lastOrNull(predicate) +} + +fun , R> EntitySequence.fold(initial: R, operation: (acc: R, E) -> R): R { + var accumulator = initial + for (item in this) { + accumulator = operation(accumulator, item) + } + return accumulator +} + +fun , R> EntitySequence.foldIndexed(initial: R, operation: (index: Int, acc: R, E) -> R): R { + var index = 0 + return this.fold(initial) { acc, e -> operation(index++, acc, e) } +} + +fun > EntitySequence.forEach(action: (E) -> Unit) { + for (item in this) action(item) +} + +fun > EntitySequence.forEachIndexed(action: (index: Int, E) -> Unit) { + var index = 0 + for (item in this) action(index++, item) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index b2a0b8b5..c42887f3 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -99,4 +99,10 @@ class EntitySequenceTest : BaseTest() { assert(employee.name == "penny") assert(Employees.asSequence().elementAtOrNull(4) == null) } + + @Test + fun testFold() { + val totalSalary = Employees.asSequence().fold(0L) { acc, employee -> acc + employee.salary } + assert(totalSalary == 450L) + } } \ No newline at end of file From cb352cf17e829a836839f9ab9f2b01910f7492c2 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 31 Mar 2019 13:09:45 +0800 Subject: [PATCH 15/34] sorted --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 19 +++++++++++++++---- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 8 +++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 3102da8d..59dc8ebd 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -2,12 +2,11 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.database.Database import me.liuwj.ktorm.database.prepareStatement -import me.liuwj.ktorm.dsl.Query -import me.liuwj.ktorm.dsl.and -import me.liuwj.ktorm.dsl.not -import me.liuwj.ktorm.dsl.toCountExpression +import me.liuwj.ktorm.dsl.* +import me.liuwj.ktorm.expression.OrderByExpression import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.expression.SelectExpression +import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table import kotlin.math.min @@ -269,4 +268,16 @@ fun > EntitySequence.forEach(action: (E) -> Unit) { fun > EntitySequence.forEachIndexed(action: (index: Int, E) -> Unit) { var index = 0 for (item in this) action(index++, item) +} + +fun , T : Table> EntitySequence.sorted(selector: (T) -> List): EntitySequence { + return this.copy(expression = expression.copy(orderBy = selector(sourceTable))) +} + +fun , T : Table> EntitySequence.sortedBy(selector: (T) -> ColumnDeclaring<*>): EntitySequence { + return this.sorted { listOf(selector(it).asc()) } +} + +fun , T : Table> EntitySequence.sortedByDescending(selector: (T) -> ColumnDeclaring<*>): EntitySequence { + return this.sorted { listOf(selector(it).desc()) } } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index c42887f3..26259c3e 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -14,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.find { it > 0 } + sequence.sorted() } @Test @@ -105,4 +105,10 @@ class EntitySequenceTest : BaseTest() { val totalSalary = Employees.asSequence().fold(0L) { acc, employee -> acc + employee.salary } assert(totalSalary == 450L) } + + @Test + fun testSorted() { + val employee = Employees.asSequence().sortedByDescending { it.salary }.first() + assert(employee.name == "tom") + } } \ No newline at end of file From ce095842b50b36ce61cf23020aedbed0c014b933 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 31 Mar 2019 21:40:31 +0800 Subject: [PATCH 16/34] filter columns --- .../kotlin/me/liuwj/ktorm/entity/EntitySequence.kt | 5 +++++ .../src/test/kotlin/me/liuwj/ktorm/BaseTest.kt | 1 + .../me/liuwj/ktorm/entity/EntitySequenceTest.kt | 13 ++++++++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 59dc8ebd..3b20e15d 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -6,6 +6,7 @@ import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.expression.OrderByExpression import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.expression.SelectExpression +import me.liuwj.ktorm.schema.Column import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table import kotlin.math.min @@ -47,6 +48,10 @@ fun > EntitySequence.toList(): List { } +fun , T : Table> EntitySequence.filterColumns(selector: (T) -> List>): EntitySequence { + val declarations = selector(sourceTable).map { it.asDeclaringExpression() } + return this.copy(expression = expression.copy(columns = declarations)) +} fun , T : Table> EntitySequence.filter(predicate: (T) -> ScalarExpression): EntitySequence { if (expression.where == null) { diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/BaseTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/BaseTest.kt index 96a64575..fa4006c6 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/BaseTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/BaseTest.kt @@ -83,5 +83,6 @@ open class BaseTest { val hireDate by date("hire_date").bindTo(Employee::hireDate) val salary by long("salary").bindTo(Employee::salary) val departmentId by int("department_id").references(Departments, onProperty = Employee::department) + val department get() = departmentId.referenceTable as Departments } } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 26259c3e..12555213 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -14,7 +14,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.sorted() + sequence.withIndex() } @Test @@ -111,4 +111,15 @@ class EntitySequenceTest : BaseTest() { val employee = Employees.asSequence().sortedByDescending { it.salary }.first() assert(employee.name == "tom") } + + @Test + fun testFilterColumns() { + val employee = Employees + .asSequence() + .filterColumns { it.columns + it.department.columns - it.department.location } + .filter { it.department.id eq 1 } + .first() + + assert(employee.department.location.isEmpty()) + } } \ No newline at end of file From ecd6fec7fd9d10d685d3b684ea26305b0a5694b3 Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Apr 2019 22:00:45 +0800 Subject: [PATCH 17/34] entity grouping --- .../me/liuwj/ktorm/entity/EntityGrouping.kt | 101 ++++++++++++++++++ .../me/liuwj/ktorm/entity/EntitySequence.kt | 4 + .../liuwj/ktorm/entity/EntitySequenceTest.kt | 19 +++- 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt new file mode 100644 index 00000000..071cdf57 --- /dev/null +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -0,0 +1,101 @@ +package me.liuwj.ktorm.entity + +import me.liuwj.ktorm.dsl.Query +import me.liuwj.ktorm.schema.ColumnDeclaring +import me.liuwj.ktorm.schema.Table +import java.util.* + +data class EntityGrouping, T : Table, K : Any>( + val sequence: EntitySequence, + val keySelector: (T) -> ColumnDeclaring +) { + private val allEntities by lazy(LazyThreadSafetyMode.NONE) { + val keyColumn = keySelector(sequence.sourceTable) + val expr = sequence.expression.copy(columns = sequence.expression.columns + keyColumn.asDeclaringExpression()) + + LinkedHashMap().also { + for (row in Query(expr)) { + val entity = sequence.sourceTable.createEntity(row) + val groupKey = keyColumn.sqlType.getResult(row, expr.columns.size) + it[entity] = groupKey + } + } + } + + fun sourceIterator(): Iterator { + return allEntities.keys.iterator() + } + + fun keyOf(element: E): K { + return allEntities[element] ?: error("The grouping key is null for element: $element") + } + + fun keyOf(table: T): ColumnDeclaring { + return keySelector(table) + } +} + +inline fun , K : Any, R> EntityGrouping.aggregate( + operation: (key: K, accumulator: R?, element: E, first: Boolean) -> R +): Map { + return aggregateTo(mutableMapOf(), operation) +} + +inline fun , K : Any, R, M : MutableMap> EntityGrouping.aggregateTo( + destination: M, + operation: (key: K, accumulator: R?, element: E, first: Boolean) -> R +): M { + for (e in this.sourceIterator()) { + val key = keyOf(e) + val accumulator = destination[key] + destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key)) + } + return destination +} + +inline fun , K : Any, R> EntityGrouping.fold( + initialValueSelector: (key: K, element: E) -> R, + operation: (key: K, accumulator: R, element: E) -> R +): Map { + @Suppress("UNCHECKED_CAST") + return aggregate { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) } +} + +inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( + destination: M, + initialValueSelector: (key: K, element: E) -> R, + operation: (key: K, accumulator: R, element: E) -> R +): M { + @Suppress("UNCHECKED_CAST") + return aggregateTo(destination) { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) } +} + +inline fun , K : Any, R> EntityGrouping.fold( + initialValue: R, + operation: (accumulator: R, element: E) -> R +): Map { + @Suppress("UNCHECKED_CAST") + return aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } +} + +inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( + destination: M, + initialValue: R, + operation: (accumulator: R, element: E) -> R +): M { + @Suppress("UNCHECKED_CAST") + return aggregateTo(destination) { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } +} + +inline fun , K : Any> EntityGrouping.reduce( + operation: (key: K, accumulator: E, element: E) -> E +): Map { + return aggregate { key, acc, e, first -> if (first) e else operation(key, acc as E, e) } +} + +inline fun , K : Any, M : MutableMap> EntityGrouping.reduceTo( + destination: M, + operation: (key: K, accumulator: E, element: E) -> E +): M { + return aggregateTo(destination) { key, acc, e, first -> if (first) e else operation(key, acc as E, e) } +} diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 3b20e15d..9de5ef2a 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -285,4 +285,8 @@ fun , T : Table> EntitySequence.sortedBy(selector: (T) -> fun , T : Table> EntitySequence.sortedByDescending(selector: (T) -> ColumnDeclaring<*>): EntitySequence { return this.sorted { listOf(selector(it).desc()) } +} + +fun , T : Table, K : Any> EntitySequence.groupingBy(keySelector: (T) -> ColumnDeclaring): EntityGrouping { + return EntityGrouping(this, keySelector) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 12555213..868d19b4 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -1,9 +1,7 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.BaseTest -import me.liuwj.ktorm.dsl.eq -import me.liuwj.ktorm.dsl.greater -import me.liuwj.ktorm.dsl.isNull +import me.liuwj.ktorm.dsl.* import org.junit.Test /** @@ -122,4 +120,19 @@ class EntitySequenceTest : BaseTest() { assert(employee.department.location.isEmpty()) } + + @Test + fun testGroupingBy() { + val salaries = Employees + .asSequence() + .groupingBy { it.departmentId * 2 } + .fold(0L) { acc, employee -> + acc + employee.salary + } + + println(salaries) + assert(salaries.size == 2) + assert(salaries[2] == 150L) + assert(salaries[4] == 300L) + } } \ No newline at end of file From 81f3a965c3b81f41090a9bb6daa8c2f72102da6a Mon Sep 17 00:00:00 2001 From: vince Date: Mon, 1 Apr 2019 22:24:37 +0800 Subject: [PATCH 18/34] as kotlin grouping --- .../me/liuwj/ktorm/entity/EntityGrouping.kt | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt index 071cdf57..e6a208a0 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -3,62 +3,53 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.Query import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table -import java.util.* data class EntityGrouping, T : Table, K : Any>( val sequence: EntitySequence, val keySelector: (T) -> ColumnDeclaring ) { - private val allEntities by lazy(LazyThreadSafetyMode.NONE) { - val keyColumn = keySelector(sequence.sourceTable) - val expr = sequence.expression.copy(columns = sequence.expression.columns + keyColumn.asDeclaringExpression()) + fun asKotlinGrouping() = object : Grouping { + private val allEntities = LinkedHashMap() + + init { + val keyColumn = keySelector(sequence.sourceTable) + val expr = sequence.expression.copy(columns = sequence.expression.columns + keyColumn.asDeclaringExpression()) - LinkedHashMap().also { for (row in Query(expr)) { val entity = sequence.sourceTable.createEntity(row) val groupKey = keyColumn.sqlType.getResult(row, expr.columns.size) - it[entity] = groupKey + allEntities[entity] = groupKey } } - } - - fun sourceIterator(): Iterator { - return allEntities.keys.iterator() - } - fun keyOf(element: E): K { - return allEntities[element] ?: error("The grouping key is null for element: $element") - } + override fun sourceIterator(): Iterator { + return allEntities.keys.iterator() + } - fun keyOf(table: T): ColumnDeclaring { - return keySelector(table) + override fun keyOf(element: E): K { + return allEntities[element] ?: error("The grouping key is null for element $element") + } } } inline fun , K : Any, R> EntityGrouping.aggregate( operation: (key: K, accumulator: R?, element: E, first: Boolean) -> R ): Map { - return aggregateTo(mutableMapOf(), operation) + return asKotlinGrouping().aggregate(operation) } inline fun , K : Any, R, M : MutableMap> EntityGrouping.aggregateTo( destination: M, operation: (key: K, accumulator: R?, element: E, first: Boolean) -> R ): M { - for (e in this.sourceIterator()) { - val key = keyOf(e) - val accumulator = destination[key] - destination[key] = operation(key, accumulator, e, accumulator == null && !destination.containsKey(key)) - } - return destination + return asKotlinGrouping().aggregateTo(destination, operation) } inline fun , K : Any, R> EntityGrouping.fold( initialValueSelector: (key: K, element: E) -> R, operation: (key: K, accumulator: R, element: E) -> R ): Map { - @Suppress("UNCHECKED_CAST") - return aggregate { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) } + return asKotlinGrouping().fold(initialValueSelector, operation) } inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( @@ -66,16 +57,14 @@ inline fun , K : Any, R, M : MutableMap> EntityGrouping R, operation: (key: K, accumulator: R, element: E) -> R ): M { - @Suppress("UNCHECKED_CAST") - return aggregateTo(destination) { key, acc, e, first -> operation(key, if (first) initialValueSelector(key, e) else acc as R, e) } + return asKotlinGrouping().foldTo(destination, initialValueSelector, operation) } inline fun , K : Any, R> EntityGrouping.fold( initialValue: R, operation: (accumulator: R, element: E) -> R ): Map { - @Suppress("UNCHECKED_CAST") - return aggregate { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } + return asKotlinGrouping().fold(initialValue, operation) } inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( @@ -83,19 +72,18 @@ inline fun , K : Any, R, M : MutableMap> EntityGrouping R ): M { - @Suppress("UNCHECKED_CAST") - return aggregateTo(destination) { _, acc, e, first -> operation(if (first) initialValue else acc as R, e) } + return asKotlinGrouping().foldTo(destination, initialValue, operation) } inline fun , K : Any> EntityGrouping.reduce( operation: (key: K, accumulator: E, element: E) -> E ): Map { - return aggregate { key, acc, e, first -> if (first) e else operation(key, acc as E, e) } + return asKotlinGrouping().reduce(operation) } inline fun , K : Any, M : MutableMap> EntityGrouping.reduceTo( destination: M, operation: (key: K, accumulator: E, element: E) -> E ): M { - return aggregateTo(destination) { key, acc, e, first -> if (first) e else operation(key, acc as E, e) } + return asKotlinGrouping().reduceTo(destination, operation) } From ff065718173faac8ecea4094c2d766fb5db80ece Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 2 Apr 2019 11:11:28 +0800 Subject: [PATCH 19/34] copy entities --- .../src/main/kotlin/me/liuwj/ktorm/entity/Entity.kt | 5 +++++ .../me/liuwj/ktorm/entity/EntityImplementation.kt | 8 ++++++++ .../test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt | 10 ++++++++++ 3 files changed, 23 insertions(+) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/Entity.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/Entity.kt index 0bab10ad..9f8af39a 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/Entity.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/Entity.kt @@ -56,6 +56,11 @@ interface Entity> : Serializable { */ operator fun set(name: String, value: Any?) + /** + * 复制一个当前实体对象的拷贝,返回的对象具有与当前对象完全相同的属性值和状态 + */ + fun copy(): E + companion object { /** diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityImplementation.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityImplementation.kt index 677ab5c3..1438bf55 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityImplementation.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityImplementation.kt @@ -59,6 +59,7 @@ internal class EntityImplementation( "delete" -> this.doDelete() "get" -> this.getProperty(args!![0] as String) "set" -> this.setProperty(args!![0] as String, args[1]) + "copy" -> this.copy() else -> throw IllegalStateException("Unrecognized method: $method") } } @@ -191,6 +192,13 @@ internal class EntityImplementation( changedProperties.add(name) } + private fun copy(): Entity<*> { + val entity = Entity.create(entityClass, parent, fromTable) + entity.implementation.values.putAll(values) + entity.implementation.changedProperties.addAll(changedProperties) + return entity + } + private fun writeObject(output: ObjectOutputStream) { output.writeUTF(entityClass.jvmName) output.writeObject(values) diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt index d3952898..b6ab65d6 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt @@ -343,4 +343,14 @@ class EntityTest : BaseTest() { emp = Emps.findById(1) ?: return assert(emp.manager.id == 2) } + + @Test + fun testCopy() { + var employee = Employees.findById(1)?.copy() ?: return + employee.name = "jerry" + employee.flushChanges() + + employee = Employees.findById(1) ?: return + assert(employee.name == "jerry") + } } \ No newline at end of file From 093abef8d5bd57fe0bd79726066e3252b87183b4 Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 2 Apr 2019 21:38:44 +0800 Subject: [PATCH 20/34] each count --- .../me/liuwj/ktorm/entity/EntityGrouping.kt | 32 +++++++++++++++++++ .../liuwj/ktorm/entity/EntitySequenceTest.kt | 11 +++++++ 2 files changed, 43 insertions(+) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt index e6a208a0..647c1678 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -1,7 +1,11 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.Query +import me.liuwj.ktorm.expression.AggregateExpression +import me.liuwj.ktorm.expression.AggregateType +import me.liuwj.ktorm.expression.ColumnDeclaringExpression import me.liuwj.ktorm.schema.ColumnDeclaring +import me.liuwj.ktorm.schema.IntSqlType import me.liuwj.ktorm.schema.Table data class EntityGrouping, T : Table, K : Any>( @@ -87,3 +91,31 @@ inline fun , K : Any, M : MutableMap> EntityGrouping, T : Table, K : Any, M : MutableMap> EntityGrouping.eachCountTo( + destination: M +): M { + val keyColumn = keySelector(sequence.sourceTable) + val countExpr = AggregateExpression( + type = AggregateType.COUNT, + argument = null, + isDistinct = false, + sqlType = IntSqlType + ) + + val expr = sequence.expression.copy( + columns = listOf(keyColumn.asExpression(), countExpr).map { ColumnDeclaringExpression(it) }, + groupBy = listOf(keyColumn.asExpression()) + ) + + for (row in Query(expr)) { + val groupKey = keyColumn.sqlType.getResult(row, 1) ?: error("One of the grouping key is null.") + destination[groupKey] = row.getInt(2) + } + + return destination +} + +fun , T : Table, K : Any> EntityGrouping.eachCount(): Map { + return eachCountTo(LinkedHashMap()) +} \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 868d19b4..5fbe67a9 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -135,4 +135,15 @@ class EntitySequenceTest : BaseTest() { assert(salaries[2] == 150L) assert(salaries[4] == 300L) } + + @Test + fun testEachCount() { + val counts = Employees + .asSequence() + .filter { it.salary less 100000L } + .groupingBy { it.departmentId } + .eachCount() + + println(counts) + } } \ No newline at end of file From 9f06c1d3d0c044a2d9d2b4f4f904af046c83dcc5 Mon Sep 17 00:00:00 2001 From: vince Date: Tue, 2 Apr 2019 22:13:31 +0800 Subject: [PATCH 21/34] each sum --- .../me/liuwj/ktorm/entity/EntityGrouping.kt | 31 +++++++++++++++++-- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 17 ++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt index 647c1678..895c531c 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -1,6 +1,7 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.Query +import me.liuwj.ktorm.dsl.sum import me.liuwj.ktorm.expression.AggregateExpression import me.liuwj.ktorm.expression.AggregateType import me.liuwj.ktorm.expression.ColumnDeclaringExpression @@ -92,6 +93,10 @@ inline fun , K : Any, M : MutableMap> EntityGrouping, T : Table, K : Any> EntityGrouping.eachCount(): Map { + return eachCountTo(LinkedHashMap()) +} + fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachCountTo( destination: M ): M { @@ -116,6 +121,28 @@ fun , T : Table, K : Any, M : MutableMap> EntityGrou return destination } -fun , T : Table, K : Any> EntityGrouping.eachCount(): Map { - return eachCountTo(LinkedHashMap()) +inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachSumOf( + columnSelector: (T) -> ColumnDeclaring +): Map { + return eachSumOfTo(LinkedHashMap(), columnSelector) +} + +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachSumOfTo( + destination: M, + columnSelector: (T) -> ColumnDeclaring +): M { + val keyColumn = keySelector(sequence.sourceTable) + val aggregation = sum(columnSelector(sequence.sourceTable)) + + val expr = sequence.expression.copy( + columns = listOf(keyColumn.asExpression(), aggregation).map { ColumnDeclaringExpression(it) }, + groupBy = listOf(keyColumn.asExpression()) + ) + + for (row in Query(expr)) { + val groupKey = keyColumn.sqlType.getResult(row, 1) ?: error("One of the grouping key is null.") + destination[groupKey] = aggregation.sqlType.getResult(row, 2) ?: error("One of the aggregation result is null") + } + + return destination } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 5fbe67a9..ee505c46 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -145,5 +145,22 @@ class EntitySequenceTest : BaseTest() { .eachCount() println(counts) + assert(counts.size == 2) + assert(counts[1] == 2) + assert(counts[2] == 2) + } + + @Test + fun testEachSum() { + val sums = Employees + .asSequence() + .filter { it.salary lessEq 100000L } + .groupingBy { it.departmentId } + .eachSumOf { it.salary } + + println(sums) + assert(sums.size == 2) + assert(sums[1] == 150L) + assert(sums[2] == 300L) } } \ No newline at end of file From 73e0ac6d242259b3b92872087772bfacca5b4f8a Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 3 Apr 2019 22:06:34 +0800 Subject: [PATCH 22/34] refactor grouping nullable key --- .../me/liuwj/ktorm/entity/EntityGrouping.kt | 79 +++++++++---------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt index 895c531c..c8b95d97 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -13,7 +13,7 @@ data class EntityGrouping, T : Table, K : Any>( val sequence: EntitySequence, val keySelector: (T) -> ColumnDeclaring ) { - fun asKotlinGrouping() = object : Grouping { + fun asKotlinGrouping() = object : Grouping { private val allEntities = LinkedHashMap() init { @@ -31,36 +31,36 @@ data class EntityGrouping, T : Table, K : Any>( return allEntities.keys.iterator() } - override fun keyOf(element: E): K { - return allEntities[element] ?: error("The grouping key is null for element $element") + override fun keyOf(element: E): K? { + return allEntities[element] } } } inline fun , K : Any, R> EntityGrouping.aggregate( - operation: (key: K, accumulator: R?, element: E, first: Boolean) -> R -): Map { + operation: (key: K?, accumulator: R?, element: E, first: Boolean) -> R +): Map { return asKotlinGrouping().aggregate(operation) } -inline fun , K : Any, R, M : MutableMap> EntityGrouping.aggregateTo( +inline fun , K : Any, R, M : MutableMap> EntityGrouping.aggregateTo( destination: M, - operation: (key: K, accumulator: R?, element: E, first: Boolean) -> R + operation: (key: K?, accumulator: R?, element: E, first: Boolean) -> R ): M { return asKotlinGrouping().aggregateTo(destination, operation) } inline fun , K : Any, R> EntityGrouping.fold( - initialValueSelector: (key: K, element: E) -> R, - operation: (key: K, accumulator: R, element: E) -> R -): Map { + initialValueSelector: (key: K?, element: E) -> R, + operation: (key: K?, accumulator: R, element: E) -> R +): Map { return asKotlinGrouping().fold(initialValueSelector, operation) } -inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( +inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( destination: M, - initialValueSelector: (key: K, element: E) -> R, - operation: (key: K, accumulator: R, element: E) -> R + initialValueSelector: (key: K?, element: E) -> R, + operation: (key: K?, accumulator: R, element: E) -> R ): M { return asKotlinGrouping().foldTo(destination, initialValueSelector, operation) } @@ -68,11 +68,11 @@ inline fun , K : Any, R, M : MutableMap> EntityGrouping, K : Any, R> EntityGrouping.fold( initialValue: R, operation: (accumulator: R, element: E) -> R -): Map { +): Map { return asKotlinGrouping().fold(initialValue, operation) } -inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( +inline fun , K : Any, R, M : MutableMap> EntityGrouping.foldTo( destination: M, initialValue: R, operation: (accumulator: R, element: E) -> R @@ -81,26 +81,25 @@ inline fun , K : Any, R, M : MutableMap> EntityGrouping, K : Any> EntityGrouping.reduce( - operation: (key: K, accumulator: E, element: E) -> E -): Map { + operation: (key: K?, accumulator: E, element: E) -> E +): Map { return asKotlinGrouping().reduce(operation) } -inline fun , K : Any, M : MutableMap> EntityGrouping.reduceTo( +inline fun , K : Any, M : MutableMap> EntityGrouping.reduceTo( destination: M, - operation: (key: K, accumulator: E, element: E) -> E + operation: (key: K?, accumulator: E, element: E) -> E ): M { return asKotlinGrouping().reduceTo(destination, operation) } -fun , T : Table, K : Any> EntityGrouping.eachCount(): Map { +fun , T : Table, K : Any> EntityGrouping.eachCount(): Map { return eachCountTo(LinkedHashMap()) } -fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachCountTo( +fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachCountTo( destination: M ): M { - val keyColumn = keySelector(sequence.sourceTable) val countExpr = AggregateExpression( type = AggregateType.COUNT, argument = null, @@ -108,40 +107,38 @@ fun , T : Table, K : Any, M : MutableMap> EntityGrou sqlType = IntSqlType ) - val expr = sequence.expression.copy( - columns = listOf(keyColumn.asExpression(), countExpr).map { ColumnDeclaringExpression(it) }, - groupBy = listOf(keyColumn.asExpression()) - ) - - for (row in Query(expr)) { - val groupKey = keyColumn.sqlType.getResult(row, 1) ?: error("One of the grouping key is null.") - destination[groupKey] = row.getInt(2) - } - - return destination + @Suppress("UNCHECKED_CAST") + return aggregateTo(destination as MutableMap, countExpr) as M } inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachSumOf( columnSelector: (T) -> ColumnDeclaring -): Map { +): Map { return eachSumOfTo(LinkedHashMap(), columnSelector) } -inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachSumOfTo( +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachSumOfTo( destination: M, columnSelector: (T) -> ColumnDeclaring ): M { - val keyColumn = keySelector(sequence.sourceTable) - val aggregation = sum(columnSelector(sequence.sourceTable)) + return aggregateTo(destination, sum(columnSelector(sequence.sourceTable))) +} + +fun , T : Table, K : Any, C : Any, M : MutableMap> EntityGrouping.aggregateTo( + destination: M, + aggregation: AggregateExpression +): M { + val keyColumn = keySelector(sequence.sourceTable).asExpression() val expr = sequence.expression.copy( - columns = listOf(keyColumn.asExpression(), aggregation).map { ColumnDeclaringExpression(it) }, - groupBy = listOf(keyColumn.asExpression()) + columns = listOf(keyColumn, aggregation).map { ColumnDeclaringExpression(it) }, + groupBy = listOf(keyColumn) ) for (row in Query(expr)) { - val groupKey = keyColumn.sqlType.getResult(row, 1) ?: error("One of the grouping key is null.") - destination[groupKey] = aggregation.sqlType.getResult(row, 2) ?: error("One of the aggregation result is null") + val key = keyColumn.sqlType.getResult(row, 1) + val value = aggregation.sqlType.getResult(row, 2) + destination[key] = value } return destination From 68df348f2d27e24372d03f134d1f01ce4c22f1cb Mon Sep 17 00:00:00 2001 From: vince Date: Wed, 3 Apr 2019 22:45:35 +0800 Subject: [PATCH 23/34] complete aggregations --- .../kotlin/me/liuwj/ktorm/dsl/Aggregation.kt | 8 +++ .../me/liuwj/ktorm/entity/EntityGrouping.kt | 64 ++++++++++++++----- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt index 21c840cd..86290da1 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt @@ -44,10 +44,18 @@ fun count(column: ColumnDeclaring): AggregateExpression { return AggregateExpression(AggregateType.COUNT, column.asExpression(), false, IntSqlType) } +fun count(): AggregateExpression { + return AggregateExpression(AggregateType.COUNT, null, false, IntSqlType) +} + fun countDistinct(column: ColumnDeclaring): AggregateExpression { return AggregateExpression(AggregateType.COUNT, column.asExpression(), true, IntSqlType) } +fun countDistinct(): AggregateExpression { + return AggregateExpression(AggregateType.COUNT, null, true, IntSqlType) +} + /** * 如果表中的所有行都符合指定条件,返回 true,否则 false */ diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt index c8b95d97..c752918c 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -1,12 +1,9 @@ package me.liuwj.ktorm.entity -import me.liuwj.ktorm.dsl.Query -import me.liuwj.ktorm.dsl.sum -import me.liuwj.ktorm.expression.AggregateExpression -import me.liuwj.ktorm.expression.AggregateType +import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.expression.ColumnDeclaringExpression +import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.schema.ColumnDeclaring -import me.liuwj.ktorm.schema.IntSqlType import me.liuwj.ktorm.schema.Table data class EntityGrouping, T : Table, K : Any>( @@ -97,18 +94,11 @@ fun , T : Table, K : Any> EntityGrouping.eachCount(): return eachCountTo(LinkedHashMap()) } +@Suppress("RedundantLambdaArrow", "UNCHECKED_CAST") fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachCountTo( destination: M ): M { - val countExpr = AggregateExpression( - type = AggregateType.COUNT, - argument = null, - isDistinct = false, - sqlType = IntSqlType - ) - - @Suppress("UNCHECKED_CAST") - return aggregateTo(destination as MutableMap, countExpr) as M + return aggregateTo(destination as MutableMap) { _ -> count() } as M } inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachSumOf( @@ -121,14 +111,54 @@ inline fun , T : Table, K : Any, C : Number, M : MutableMap ColumnDeclaring ): M { - return aggregateTo(destination, sum(columnSelector(sequence.sourceTable))) + return aggregateTo(destination) { sum(columnSelector(it)) } +} + +inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachMaxOf( + columnSelector: (T) -> ColumnDeclaring +): Map { + return eachMaxOfTo(LinkedHashMap(), columnSelector) +} + +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachMaxOfTo( + destination: M, + columnSelector: (T) -> ColumnDeclaring +): M { + return aggregateTo(destination) { max(columnSelector(it)) } +} + +inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachMinOf( + columnSelector: (T) -> ColumnDeclaring +): Map { + return eachMinOfTo(LinkedHashMap(), columnSelector) +} + +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachMinOfTo( + destination: M, + columnSelector: (T) -> ColumnDeclaring +): M { + return aggregateTo(destination) { min(columnSelector(it)) } +} + +inline fun , T : Table, K : Any> EntityGrouping.eachAverageOf( + columnSelector: (T) -> ColumnDeclaring +): Map { + return eachAverageOfTo(LinkedHashMap(), columnSelector) +} + +inline fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachAverageOfTo( + destination: M, + columnSelector: (T) -> ColumnDeclaring +): M { + return aggregateTo(destination) { avg(columnSelector(it)) } } -fun , T : Table, K : Any, C : Any, M : MutableMap> EntityGrouping.aggregateTo( +inline fun , T : Table, K : Any, C : Any, M : MutableMap> EntityGrouping.aggregateTo( destination: M, - aggregation: AggregateExpression + aggregationSelector: (T) -> ScalarExpression ): M { val keyColumn = keySelector(sequence.sourceTable).asExpression() + val aggregation = aggregationSelector(sequence.sourceTable) val expr = sequence.expression.copy( columns = listOf(keyColumn, aggregation).map { ColumnDeclaringExpression(it) }, From 46a903d2762ecd6204190ef20efb9abd9c165959 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 5 Apr 2019 10:05:42 +0800 Subject: [PATCH 24/34] refactor aggregation --- .../kotlin/me/liuwj/ktorm/dsl/Aggregation.kt | 121 +++++------------- .../me/liuwj/ktorm/dsl/CountExpression.kt | 45 ++----- .../src/main/kotlin/me/liuwj/ktorm/dsl/Dml.kt | 12 +- .../main/kotlin/me/liuwj/ktorm/dsl/Join.kt | 41 +++--- .../main/kotlin/me/liuwj/ktorm/dsl/Query.kt | 20 +-- .../me/liuwj/ktorm/entity/EntityFinding.kt | 4 +- .../me/liuwj/ktorm/entity/EntityGrouping.kt | 73 ++++++----- .../me/liuwj/ktorm/entity/EntitySequence.kt | 83 +++++++----- .../me/liuwj/ktorm/dsl/AggregationTest.kt | 11 +- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 2 +- 10 files changed, 181 insertions(+), 231 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt index 86290da1..236efb3d 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Aggregation.kt @@ -1,8 +1,8 @@ package me.liuwj.ktorm.dsl -import me.liuwj.ktorm.database.Database -import me.liuwj.ktorm.database.prepareStatement -import me.liuwj.ktorm.expression.* +import me.liuwj.ktorm.entity.* +import me.liuwj.ktorm.expression.AggregateExpression +import me.liuwj.ktorm.expression.AggregateType import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.DoubleSqlType import me.liuwj.ktorm.schema.IntSqlType @@ -40,150 +40,87 @@ fun sumDistinct(column: ColumnDeclaring): AggregateExpression return AggregateExpression(AggregateType.SUM, column.asExpression(), true, column.sqlType) } -fun count(column: ColumnDeclaring): AggregateExpression { - return AggregateExpression(AggregateType.COUNT, column.asExpression(), false, IntSqlType) +fun count(column: ColumnDeclaring<*>? = null): AggregateExpression { + return AggregateExpression(AggregateType.COUNT, column?.asExpression(), false, IntSqlType) } -fun count(): AggregateExpression { - return AggregateExpression(AggregateType.COUNT, null, false, IntSqlType) -} - -fun countDistinct(column: ColumnDeclaring): AggregateExpression { - return AggregateExpression(AggregateType.COUNT, column.asExpression(), true, IntSqlType) -} - -fun countDistinct(): AggregateExpression { - return AggregateExpression(AggregateType.COUNT, null, true, IntSqlType) +fun countDistinct(column: ColumnDeclaring<*>? = null): AggregateExpression { + return AggregateExpression(AggregateType.COUNT, column?.asExpression(), true, IntSqlType) } /** * 如果表中的所有行都符合指定条件,返回 true,否则 false */ -fun > T.all(block: (T) -> ScalarExpression): Boolean { - return none { !block(this) } +inline fun , T : Table> T.all(predicate: (T) -> ColumnDeclaring): Boolean { + return asSequence().all(predicate) } /** * 如果表中有数据,返回 true,否则 false */ -fun Table<*>.any(): Boolean { - return count() > 0 +fun , T : Table> T.any(): Boolean { + return asSequence().any() } /** * 如果表中存在任何一条记录满足指定条件,返回 true,否则 false */ -fun > T.any(block: (T) -> ScalarExpression): Boolean { - return count(block) > 0 +inline fun , T : Table> T.any(predicate: (T) -> ColumnDeclaring): Boolean { + return asSequence().any(predicate) } /** * 如果表中没有数据,返回 true,否则 false */ -fun Table<*>.none(): Boolean { - return count() == 0 +fun , T : Table> T.none(): Boolean { + return asSequence().none() } /** * 如果表中所有记录都不满足指定条件,返回 true,否则 false */ -fun > T.none(block: (T) -> ScalarExpression): Boolean { - return count(block) == 0 +inline fun , T : Table> T.none(predicate: (T) -> ColumnDeclaring): Boolean { + return asSequence().none(predicate) } /** * 返回表中的记录数 */ -fun Table<*>.count(): Int { - return doCount(null) +fun , T : Table> T.count(): Int { + return asSequence().count() } /** * 返回表中满足指定条件的记录数 */ -fun > T.count(block: (T) -> ScalarExpression): Int { - return doCount(block) -} - -private fun > T.doCount(block: ((T) -> ScalarExpression)?): Int { - val expression = SelectExpression( - columns = listOf( - ColumnDeclaringExpression( - expression = AggregateExpression( - type = AggregateType.COUNT, - argument = null, - isDistinct = false, - sqlType = IntSqlType - ) - ) - ), - from = this.asExpression(), - where = block?.invoke(this) - ) - - expression.prepareStatement { statement, logger -> - statement.executeQuery().use { rs -> - if (rs.next()) { - return rs.getInt(1).also { logger.debug("Count: {}", it) } - } else { - val (sql, _) = Database.global.formatExpression(expression, beautifySql = true) - throw IllegalStateException("No result return for sql: $sql") - } - } - } +inline fun , T : Table> T.count(predicate: (T) -> ColumnDeclaring): Int { + return asSequence().count(predicate) } /** * 返回表中指定字段的和,若表中没有数据,返回 null */ -fun , C : Number> T.sumBy(block: (T) -> ColumnDeclaring): C? { - return doAggregation(sum(block(this))) +inline fun , T : Table, C : Number> T.sumBy(selector: (T) -> ColumnDeclaring): C? { + return asSequence().sumBy(selector) } /** * 返回表中指定字段的最大值,若表中没有数据,返回 null */ -fun , C : Number> T.maxBy(block: (T) -> ColumnDeclaring): C? { - return doAggregation(max(block(this))) +inline fun , T : Table, C : Number> T.maxBy(selector: (T) -> ColumnDeclaring): C? { + return asSequence().maxBy(selector) } /** * 返回表中指定字段的最小值,若表中没有数据,返回 null */ -fun , C : Number> T.minBy(block: (T) -> ColumnDeclaring): C? { - return doAggregation(min(block(this))) +inline fun , T : Table, C : Number> T.minBy(selector: (T) -> ColumnDeclaring): C? { + return asSequence().minBy(selector) } /** * 返回表中指定字段的平均值,若表中没有数据,返回 null */ -fun > T.avgBy(block: (T) -> ColumnDeclaring): Double? { - return doAggregation(avg(block(this))) -} - -private fun Table<*>.doAggregation(aggregation: AggregateExpression): R? { - val expression = SelectExpression( - columns = listOf( - ColumnDeclaringExpression( - expression = aggregation.asExpression() - ) - ), - from = this.asExpression() - ) - - expression.prepareStatement { statement, logger -> - statement.executeQuery().use { rs -> - if (rs.next()) { - val result = aggregation.sqlType.getResult(rs, 1) - - if (logger.isDebugEnabled) { - logger.debug("{}: {}", aggregation.type.toString().capitalize(), result) - } - - return result - } else { - return null - } - } - } +inline fun , T : Table> T.averageBy(selector: (T) -> ColumnDeclaring): Double? { + return asSequence().averageBy(selector) } \ No newline at end of file diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt index d56eeec8..1f9f15ea 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/CountExpression.kt @@ -1,46 +1,21 @@ package me.liuwj.ktorm.dsl import me.liuwj.ktorm.expression.* -import me.liuwj.ktorm.schema.IntSqlType -internal fun QueryExpression.toCountExpression(keepPaging: Boolean): SelectExpression { +internal fun QueryExpression.toCountExpression(): SelectExpression { val expression = OrderByRemover.visit(this) as QueryExpression - - val countColumns = listOf( - ColumnDeclaringExpression( - expression = AggregateExpression( - type = AggregateType.COUNT, - argument = null, - isDistinct = false, - sqlType = IntSqlType - ) - ) - ) + val countColumns = listOf(count().asDeclaringExpression()) if (expression is SelectExpression && expression.isSimpleSelect()) { - if (keepPaging) { - return expression.copy(columns = countColumns) - } else { - return expression.copy(columns = countColumns, offset = null, limit = null) - } + return expression.copy(columns = countColumns, offset = null, limit = null) } else { - if (keepPaging) { - return SelectExpression( - columns = countColumns, - from = when (expression) { - is SelectExpression -> expression.copy(tableAlias = "tmp_count") - is UnionExpression -> expression.copy(tableAlias = "tmp_count") - } - ) - } else { - return SelectExpression( - columns = countColumns, - from = when (expression) { - is SelectExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") - is UnionExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") - } - ) - } + return SelectExpression( + columns = countColumns, + from = when (expression) { + is SelectExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") + is UnionExpression -> expression.copy(offset = null, limit = null, tableAlias = "tmp_count") + } + ) } } diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Dml.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Dml.kt index b3207011..42c9bff8 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Dml.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Dml.kt @@ -17,7 +17,7 @@ fun > T.update(block: UpdateStatementBuilder.(T) -> Unit): Int { val assignments = ArrayList>() val builder = UpdateStatementBuilder(assignments).apply { block(this@update) } - val expression = AliasRemover.visit(UpdateExpression(asExpression(), assignments, builder.where)) + val expression = AliasRemover.visit(UpdateExpression(asExpression(), assignments, builder.where?.asExpression())) expression.prepareStatement { statement, logger -> return statement.executeUpdate().also { logger.debug("Effects: {}", it) } @@ -145,8 +145,8 @@ fun Query.insertTo(table: Table<*>, vararg columns: Column<*>): Int { /** * 根据条件删除表中的记录,返回受影响的记录数 */ -fun > T.delete(block: (T) -> ScalarExpression): Int { - val expression = AliasRemover.visit(DeleteExpression(asExpression(), block(this))) +fun > T.delete(block: (T) -> ColumnDeclaring): Int { + val expression = AliasRemover.visit(DeleteExpression(asExpression(), block(this).asExpression())) expression.prepareStatement { statement, logger -> return statement.executeUpdate().also { logger.debug("Effects: {}", it) } @@ -191,9 +191,9 @@ open class AssignmentsBuilder(private val assignments: MutableList>) : AssignmentsBuilder(assignments) { - internal var where: ScalarExpression? = null + internal var where: ColumnDeclaring? = null - fun where(block: () -> ScalarExpression) { + fun where(block: () -> ColumnDeclaring) { this.where = block() } } @@ -208,7 +208,7 @@ class BatchUpdateStatementBuilder>(internal val table: T) { val builder = UpdateStatementBuilder(assignments) builder.block(table) - val expr = UpdateExpression(table.asExpression(), assignments, builder.where) + val expr = UpdateExpression(table.asExpression(), assignments, builder.where?.asExpression()) val (sql, _) = Database.global.formatExpression(expr, beautifySql = true) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Join.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Join.kt index 52c3375c..84ee3c92 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Join.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Join.kt @@ -4,68 +4,69 @@ package me.liuwj.ktorm.dsl import me.liuwj.ktorm.expression.* +import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table -fun QuerySourceExpression.crossJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { - return JoinExpression(type = JoinType.CROSS_JOIN, left = this, right = right, condition = on) +fun QuerySourceExpression.crossJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { + return JoinExpression(type = JoinType.CROSS_JOIN, left = this, right = right, condition = on?.asExpression()) } -fun QuerySourceExpression.crossJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun QuerySourceExpression.crossJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return crossJoin(right.asExpression(), on) } -fun Table<*>.crossJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.crossJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { return asExpression().crossJoin(right, on) } -fun Table<*>.crossJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.crossJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return crossJoin(right.asExpression(), on) } -fun QuerySourceExpression.innerJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { - return JoinExpression(type = JoinType.INNER_JOIN, left = this, right = right, condition = on) +fun QuerySourceExpression.innerJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { + return JoinExpression(type = JoinType.INNER_JOIN, left = this, right = right, condition = on?.asExpression()) } -fun QuerySourceExpression.innerJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun QuerySourceExpression.innerJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return innerJoin(right.asExpression(), on) } -fun Table<*>.innerJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.innerJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { return asExpression().innerJoin(right, on) } -fun Table<*>.innerJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.innerJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return innerJoin(right.asExpression(), on) } -fun QuerySourceExpression.leftJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { - return JoinExpression(type = JoinType.LEFT_JOIN, left = this, right = right, condition = on) +fun QuerySourceExpression.leftJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { + return JoinExpression(type = JoinType.LEFT_JOIN, left = this, right = right, condition = on?.asExpression()) } -fun QuerySourceExpression.leftJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun QuerySourceExpression.leftJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return leftJoin(right.asExpression(), on) } -fun Table<*>.leftJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.leftJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { return asExpression().leftJoin(right, on) } -fun Table<*>.leftJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.leftJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return leftJoin(right.asExpression(), on) } -fun QuerySourceExpression.rightJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { - return JoinExpression(type = JoinType.RIGHT_JOIN, left = this, right = right, condition = on) +fun QuerySourceExpression.rightJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { + return JoinExpression(type = JoinType.RIGHT_JOIN, left = this, right = right, condition = on?.asExpression()) } -fun QuerySourceExpression.rightJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun QuerySourceExpression.rightJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return rightJoin(right.asExpression(), on) } -fun Table<*>.rightJoin(right: QuerySourceExpression, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.rightJoin(right: QuerySourceExpression, on: ColumnDeclaring? = null): JoinExpression { return asExpression().rightJoin(right, on) } -fun Table<*>.rightJoin(right: Table<*>, on: ScalarExpression? = null): JoinExpression { +fun Table<*>.rightJoin(right: Table<*>, on: ColumnDeclaring? = null): JoinExpression { return rightJoin(right.asExpression(), on) } diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt index d6ac6759..d9dd76ce 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/dsl/Query.kt @@ -42,7 +42,7 @@ data class Query(val expression: QueryExpression) : Iterable { if (expression.offset == null && expression.limit == null) { rowSet.size() } else { - val countExpr = expression.toCountExpression(keepPaging = false) + val countExpr = expression.toCountExpression() countExpr.prepareStatement { statement, logger -> statement.executeQuery().use { rs -> @@ -123,17 +123,17 @@ fun Table<*>.selectDistinct(vararg columns: ColumnDeclaring<*>): Query { return asExpression().selectDistinct(columns.asList()) } -inline fun Query.where(block: () -> ScalarExpression): Query { +inline fun Query.where(block: () -> ColumnDeclaring): Query { return this.copy( expression = when (expression) { - is SelectExpression -> expression.copy(where = block()) + is SelectExpression -> expression.copy(where = block().asExpression()) is UnionExpression -> throw IllegalStateException("Where clause is not supported in a union expression.") } ) } -inline fun Query.whereWithConditions(block: (MutableList>) -> Unit): Query { - val conditions = ArrayList>().apply(block) +inline fun Query.whereWithConditions(block: (MutableList>) -> Unit): Query { + val conditions = ArrayList>().apply(block) if (conditions.isEmpty()) { return this @@ -142,8 +142,8 @@ inline fun Query.whereWithConditions(block: (MutableList>) -> Unit): Query { - val conditions = ArrayList>().apply(block) +inline fun Query.whereWithOrConditions(block: (MutableList>) -> Unit): Query { + val conditions = ArrayList>().apply(block) if (conditions.isEmpty()) { return this @@ -152,7 +152,7 @@ inline fun Query.whereWithOrConditions(block: (MutableList>.combineConditions(): ScalarExpression { +fun Iterable>.combineConditions(): ColumnDeclaring { if (this.any()) { return this.reduce { a, b -> a and b } } else { @@ -169,10 +169,10 @@ fun Query.groupBy(vararg columns: ColumnDeclaring<*>): Query { ) } -inline fun Query.having(block: () -> ScalarExpression): Query { +inline fun Query.having(block: () -> ColumnDeclaring): Query { return this.copy( expression = when (expression) { - is SelectExpression -> expression.copy(having = block()) + is SelectExpression -> expression.copy(having = block().asExpression()) is UnionExpression -> throw IllegalStateException("Having clause is not supported in a union expression.") } ) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt index 66967c8d..4b4fc63b 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt @@ -38,7 +38,7 @@ fun > Table.findById(id: Any): E? { /** * 根据指定条件获取对象,会自动 left join 所有的引用表 */ -inline fun , T : Table> T.findOne(block: (T) -> ScalarExpression): E? { +inline fun , T : Table> T.findOne(block: (T) -> ColumnDeclaring): E? { val list = findList(block) when (list.size) { 0 -> return null @@ -59,7 +59,7 @@ fun > Table.findAll(): List { /** * 根据指定条件获取对象列表,会自动 left join 所有的引用表 */ -inline fun , T : Table> T.findList(block: (T) -> ScalarExpression): List { +inline fun , T : Table> T.findList(block: (T) -> ColumnDeclaring): List { return this .joinReferencesAndSelect() .where { block(this) } diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt index c752918c..192b29a1 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityGrouping.kt @@ -2,7 +2,6 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.expression.ColumnDeclaringExpression -import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table @@ -34,6 +33,33 @@ data class EntityGrouping, T : Table, K : Any>( } } +inline fun , T : Table, K : Any, C : Any> EntityGrouping.aggregate( + aggregationSelector: (T) -> ColumnDeclaring +): MutableMap { + return aggregateTo(LinkedHashMap(), aggregationSelector) +} + +inline fun , T : Table, K : Any, C : Any, M : MutableMap> EntityGrouping.aggregateTo( + destination: M, + aggregationSelector: (T) -> ColumnDeclaring +): M { + val keyColumn = keySelector(sequence.sourceTable).asExpression() + val aggregation = aggregationSelector(sequence.sourceTable) + + val expr = sequence.expression.copy( + columns = listOf(keyColumn, aggregation.asExpression()).map { ColumnDeclaringExpression(it) }, + groupBy = listOf(keyColumn) + ) + + for (row in Query(expr)) { + val key = keyColumn.sqlType.getResult(row, 1) + val value = aggregation.sqlType.getResult(row, 2) + destination[key] = value + } + + return destination +} + inline fun , K : Any, R> EntityGrouping.aggregate( operation: (key: K?, accumulator: R?, element: E, first: Boolean) -> R ): Map { @@ -101,75 +127,54 @@ fun , T : Table, K : Any, M : MutableMap> EntityGro return aggregateTo(destination as MutableMap) { _ -> count() } as M } -inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachSumOf( +inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachSumBy( columnSelector: (T) -> ColumnDeclaring ): Map { - return eachSumOfTo(LinkedHashMap(), columnSelector) + return eachSumByTo(LinkedHashMap(), columnSelector) } -inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachSumOfTo( +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachSumByTo( destination: M, columnSelector: (T) -> ColumnDeclaring ): M { return aggregateTo(destination) { sum(columnSelector(it)) } } -inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachMaxOf( +inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachMaxBy( columnSelector: (T) -> ColumnDeclaring ): Map { - return eachMaxOfTo(LinkedHashMap(), columnSelector) + return eachMaxByTo(LinkedHashMap(), columnSelector) } -inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachMaxOfTo( +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachMaxByTo( destination: M, columnSelector: (T) -> ColumnDeclaring ): M { return aggregateTo(destination) { max(columnSelector(it)) } } -inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachMinOf( +inline fun , T : Table, K : Any, C : Number> EntityGrouping.eachMinBy( columnSelector: (T) -> ColumnDeclaring ): Map { - return eachMinOfTo(LinkedHashMap(), columnSelector) + return eachMinByTo(LinkedHashMap(), columnSelector) } -inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachMinOfTo( +inline fun , T : Table, K : Any, C : Number, M : MutableMap> EntityGrouping.eachMinByTo( destination: M, columnSelector: (T) -> ColumnDeclaring ): M { return aggregateTo(destination) { min(columnSelector(it)) } } -inline fun , T : Table, K : Any> EntityGrouping.eachAverageOf( +inline fun , T : Table, K : Any> EntityGrouping.eachAverageBy( columnSelector: (T) -> ColumnDeclaring ): Map { - return eachAverageOfTo(LinkedHashMap(), columnSelector) + return eachAverageByTo(LinkedHashMap(), columnSelector) } -inline fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachAverageOfTo( +inline fun , T : Table, K : Any, M : MutableMap> EntityGrouping.eachAverageByTo( destination: M, columnSelector: (T) -> ColumnDeclaring ): M { return aggregateTo(destination) { avg(columnSelector(it)) } -} - -inline fun , T : Table, K : Any, C : Any, M : MutableMap> EntityGrouping.aggregateTo( - destination: M, - aggregationSelector: (T) -> ScalarExpression -): M { - val keyColumn = keySelector(sequence.sourceTable).asExpression() - val aggregation = aggregationSelector(sequence.sourceTable) - - val expr = sequence.expression.copy( - columns = listOf(keyColumn, aggregation).map { ColumnDeclaringExpression(it) }, - groupBy = listOf(keyColumn) - ) - - for (row in Query(expr)) { - val key = keyColumn.sqlType.getResult(row, 1) - val value = aggregation.sqlType.getResult(row, 2) - destination[key] = value - } - - return destination } \ No newline at end of file diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 9de5ef2a..0e8b46ce 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -1,10 +1,8 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.database.Database -import me.liuwj.ktorm.database.prepareStatement import me.liuwj.ktorm.dsl.* import me.liuwj.ktorm.expression.OrderByExpression -import me.liuwj.ktorm.expression.ScalarExpression import me.liuwj.ktorm.expression.SelectExpression import me.liuwj.ktorm.schema.Column import me.liuwj.ktorm.schema.ColumnDeclaring @@ -53,19 +51,19 @@ fun , T : Table> EntitySequence.filterColumns(selector: ( return this.copy(expression = expression.copy(columns = declarations)) } -fun , T : Table> EntitySequence.filter(predicate: (T) -> ScalarExpression): EntitySequence { +inline fun , T : Table> EntitySequence.filter(predicate: (T) -> ColumnDeclaring): EntitySequence { if (expression.where == null) { - return this.copy(expression = expression.copy(where = predicate(sourceTable))) + return this.copy(expression = expression.copy(where = predicate(sourceTable).asExpression())) } else { return this.copy(expression = expression.copy(where = expression.where and predicate(sourceTable))) } } -fun , T : Table> EntitySequence.filterNot(predicate: (T) -> ScalarExpression): EntitySequence { +fun , T : Table> EntitySequence.filterNot(predicate: (T) -> ColumnDeclaring): EntitySequence { return this.filter { !predicate(it) } } -fun , T : Table, C : MutableCollection> EntitySequence.filterTo(destination: C, predicate: (T) -> ScalarExpression): C { +fun , T : Table, C : MutableCollection> EntitySequence.filterTo(destination: C, predicate: (T) -> ColumnDeclaring): C { val sequence = this.filter(predicate) for (item in sequence) { destination += item @@ -73,7 +71,7 @@ fun , T : Table, C : MutableCollection> EntitySequence, T : Table, C : MutableCollection> EntitySequence.filterNotTo(destination: C, predicate: (T) -> ScalarExpression): C { +fun , T : Table, C : MutableCollection> EntitySequence.filterNotTo(destination: C, predicate: (T) -> ColumnDeclaring): C { val sequence = this.filterNot(predicate) for (item in sequence) { destination += item @@ -81,45 +79,70 @@ fun , T : Table, C : MutableCollection> EntitySequence.count(): Int { - val countExpr = expression.toCountExpression(keepPaging = true) +inline fun , T : Table, C : Any> EntitySequence.aggregate( + aggregationSelector: (T) -> ColumnDeclaring +): C? { + val aggregation = aggregationSelector(sourceTable) - countExpr.prepareStatement { statement, logger -> - statement.executeQuery().use { rs -> - if (rs.next()) { - return rs.getInt(1).also { logger.debug("Count: {}", it) } - } else { - val (sql, _) = Database.global.formatExpression(countExpr, beautifySql = true) - throw IllegalStateException("No result return for sql: $sql") - } - } + val expr = expression.copy( + columns = listOf(aggregation.asDeclaringExpression()) + ) + + val rowSet = Query(expr).rowSet + + if (rowSet.size() == 1) { + assert(rowSet.next()) + return aggregation.sqlType.getResult(rowSet, 1) + } else { + val (sql, _) = Database.global.formatExpression(expr, beautifySql = true) + throw IllegalStateException("Expected 1 result but ${rowSet.size()} returned from sql: \n\n$sql") } } -fun , T : Table> EntitySequence.count(predicate: (T) -> ScalarExpression): Int { +fun , T : Table> EntitySequence.count(): Int { + return aggregate { me.liuwj.ktorm.dsl.count() } ?: error("Count expression returns null, which never happens.") +} + +inline fun , T : Table> EntitySequence.count(predicate: (T) -> ColumnDeclaring): Int { return this.filter(predicate).count() } -fun EntitySequence<*, *>.none(): Boolean { +fun , T : Table> EntitySequence.none(): Boolean { return this.count() == 0 } -fun , T : Table> EntitySequence.none(predicate: (T) -> ScalarExpression): Boolean { +inline fun , T : Table> EntitySequence.none(predicate: (T) -> ColumnDeclaring): Boolean { return this.count(predicate) == 0 } -fun EntitySequence<*, *>.any(): Boolean { +fun , T : Table> EntitySequence.any(): Boolean { return this.count() > 0 } -fun , T : Table> EntitySequence.any(predicate: (T) -> ScalarExpression): Boolean { +inline fun , T : Table> EntitySequence.any(predicate: (T) -> ColumnDeclaring): Boolean { return this.count(predicate) > 0 } -fun , T : Table> EntitySequence.all(predicate: (T) -> ScalarExpression): Boolean { +inline fun , T : Table> EntitySequence.all(predicate: (T) -> ColumnDeclaring): Boolean { return this.none { !predicate(it) } } +inline fun , T : Table, C : Number> EntitySequence.sumBy(selector: (T) -> ColumnDeclaring): C? { + return aggregate { sum(selector(it)) } +} + +inline fun , T : Table, C : Number> EntitySequence.maxBy(selector: (T) -> ColumnDeclaring): C? { + return aggregate { max(selector(it)) } +} + +inline fun , T : Table, C : Number> EntitySequence.minBy(selector: (T) -> ColumnDeclaring): C? { + return aggregate { min(selector(it)) } +} + +inline fun , T : Table> EntitySequence.averageBy(selector: (T) -> ColumnDeclaring): Double? { + return aggregate { avg(selector(it)) } +} + fun , K, V> EntitySequence.associate(transform: (E) -> Pair): Map { return this.associateTo(LinkedHashMap(), transform) } @@ -213,7 +236,7 @@ fun , T : Table> EntitySequence.firstOrNull(): E? { return this.elementAtOrNull(0) } -fun , T : Table> EntitySequence.firstOrNull(predicate: (T) -> ScalarExpression): E? { +fun , T : Table> EntitySequence.firstOrNull(predicate: (T) -> ColumnDeclaring): E? { return this.filter(predicate).elementAtOrNull(0) } @@ -221,7 +244,7 @@ fun , T : Table> EntitySequence.first(): E { return this.elementAt(0) } -fun , T : Table> EntitySequence.first(predicate: (T) -> ScalarExpression): E { +fun , T : Table> EntitySequence.first(predicate: (T) -> ColumnDeclaring): E { return this.filter(predicate).elementAt(0) } @@ -233,7 +256,7 @@ fun > EntitySequence.lastOrNull(): E? { return last } -fun , T : Table> EntitySequence.lastOrNull(predicate: (T) -> ScalarExpression): E? { +fun , T : Table> EntitySequence.lastOrNull(predicate: (T) -> ColumnDeclaring): E? { return this.filter(predicate).lastOrNull() } @@ -241,15 +264,15 @@ fun > EntitySequence.last(): E { return lastOrNull() ?: throw NoSuchElementException("Sequence is empty.") } -fun , T : Table> EntitySequence.last(predicate: (T) -> ScalarExpression): E { +fun , T : Table> EntitySequence.last(predicate: (T) -> ColumnDeclaring): E { return this.filter(predicate).last() } -fun , T : Table> EntitySequence.find(predicate: (T) -> ScalarExpression): E? { +fun , T : Table> EntitySequence.find(predicate: (T) -> ColumnDeclaring): E? { return this.firstOrNull(predicate) } -fun , T : Table> EntitySequence.findLast(predicate: (T) -> ScalarExpression): E? { +fun , T : Table> EntitySequence.findLast(predicate: (T) -> ColumnDeclaring): E? { return this.lastOrNull(predicate) } diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/dsl/AggregationTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/dsl/AggregationTest.kt index ed690cf1..b94f8964 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/dsl/AggregationTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/dsl/AggregationTest.kt @@ -1,6 +1,8 @@ package me.liuwj.ktorm.dsl import me.liuwj.ktorm.BaseTest +import me.liuwj.ktorm.entity.aggregate +import me.liuwj.ktorm.entity.asSequence import org.junit.Test /** @@ -40,7 +42,7 @@ class AggregationTest : BaseTest() { @Test fun testAvg() { - val avg = Employees.avgBy { it.salary } + val avg = Employees.averageBy { it.salary } println(avg) } @@ -58,4 +60,11 @@ class AggregationTest : BaseTest() { fun testAll() { assert(Employees.all { it.salary greater 0L }) } + + @Test + fun testAggregate() { + val result = Employees.asSequence().aggregate { max(it.salary) - min(it.salary) } + println(result) + assert(result == 150L) + } } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index ee505c46..022ea357 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -156,7 +156,7 @@ class EntitySequenceTest : BaseTest() { .asSequence() .filter { it.salary lessEq 100000L } .groupingBy { it.departmentId } - .eachSumOf { it.salary } + .eachSumBy { it.salary } println(sums) assert(sums.size == 2) From 0c4b960e9064999bd7e95f512e973c5331b9c100 Mon Sep 17 00:00:00 2001 From: vince Date: Fri, 5 Apr 2019 23:40:30 +0800 Subject: [PATCH 25/34] to collection --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 36 ++++++++++++++++--- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 2 +- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 0e8b46ce..f511d9b0 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -7,6 +7,7 @@ import me.liuwj.ktorm.expression.SelectExpression import me.liuwj.ktorm.schema.Column import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table +import java.util.* import kotlin.math.min data class EntitySequence, T : Table>(val sourceTable: T, val expression: SelectExpression) { @@ -19,6 +20,8 @@ data class EntitySequence, T : Table>(val sourceTable: T, val e val totalRecords get() = query.totalRecords + fun asKotlinSequence() = Sequence { iterator() } + operator fun iterator() = object : Iterator { private val queryIterator = query.iterator() @@ -37,14 +40,37 @@ fun , T : Table> T.asSequence(): EntitySequence { return EntitySequence(this, query.expression as SelectExpression) } +fun , C : MutableCollection> EntitySequence.toCollection(destination: C): C { + return asKotlinSequence().toCollection(destination) +} + fun > EntitySequence.toList(): List { - val list = ArrayList() - for (item in this) { - list += item - } - return list + return asKotlinSequence().toList() +} + +fun > EntitySequence.toMutableList(): MutableList { + return asKotlinSequence().toMutableList() +} + +fun > EntitySequence.toSet(): Set { + return asKotlinSequence().toSet() +} + +fun > EntitySequence.toMutableSet(): MutableSet { + return asKotlinSequence().toMutableSet() +} + +fun > EntitySequence.toHashSet(): HashSet { + return asKotlinSequence().toHashSet() +} + +fun EntitySequence.toSortedSet(): SortedSet where E : Entity, E : Comparable { + return asKotlinSequence().toSortedSet() } +fun EntitySequence.toSortedSet(comparator: Comparator): SortedSet where E : Entity, E : Comparable { + return asKotlinSequence().toSortedSet(comparator) +} fun , T : Table> EntitySequence.filterColumns(selector: (T) -> List>): EntitySequence { val declarations = selector(sourceTable).map { it.asDeclaringExpression() } diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 022ea357..49916b40 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -12,7 +12,7 @@ class EntitySequenceTest : BaseTest() { @Test fun testRealSequence() { val sequence = listOf(1, 2, 3).asSequence() - sequence.withIndex() + sequence.toSet() } @Test From ee5b4fc2997ce469c27ad333701e8ff90442d132 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 12:37:44 +0800 Subject: [PATCH 26/34] refactor filter --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 46 +++++++++++-------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index f511d9b0..a547e0ad 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -68,16 +68,26 @@ fun EntitySequence.toSortedSet(): SortedSet where E : Entity, E return asKotlinSequence().toSortedSet() } -fun EntitySequence.toSortedSet(comparator: Comparator): SortedSet where E : Entity, E : Comparable { +fun EntitySequence.toSortedSet( + comparator: Comparator +): SortedSet where E : Entity, E : Comparable { return asKotlinSequence().toSortedSet(comparator) } -fun , T : Table> EntitySequence.filterColumns(selector: (T) -> List>): EntitySequence { - val declarations = selector(sourceTable).map { it.asDeclaringExpression() } - return this.copy(expression = expression.copy(columns = declarations)) +inline fun , T : Table> EntitySequence.filterColumns( + selector: (T) -> List> +): EntitySequence { + val columns = selector(sourceTable) + if (columns.isEmpty()) { + return this + } else { + return this.copy(expression = expression.copy(columns = columns.map { it.asDeclaringExpression() })) + } } -inline fun , T : Table> EntitySequence.filter(predicate: (T) -> ColumnDeclaring): EntitySequence { +inline fun , T : Table> EntitySequence.filter( + predicate: (T) -> ColumnDeclaring +): EntitySequence { if (expression.where == null) { return this.copy(expression = expression.copy(where = predicate(sourceTable).asExpression())) } else { @@ -85,24 +95,24 @@ inline fun , T : Table> EntitySequence.filter(predicate: } } -fun , T : Table> EntitySequence.filterNot(predicate: (T) -> ColumnDeclaring): EntitySequence { +inline fun , T : Table> EntitySequence.filterNot( + predicate: (T) -> ColumnDeclaring +): EntitySequence { return this.filter { !predicate(it) } } -fun , T : Table, C : MutableCollection> EntitySequence.filterTo(destination: C, predicate: (T) -> ColumnDeclaring): C { - val sequence = this.filter(predicate) - for (item in sequence) { - destination += item - } - return destination +inline fun , T : Table, C : MutableCollection> EntitySequence.filterTo( + destination: C, + predicate: (T) -> ColumnDeclaring +): C { + return this.filter(predicate).toCollection(destination) } -fun , T : Table, C : MutableCollection> EntitySequence.filterNotTo(destination: C, predicate: (T) -> ColumnDeclaring): C { - val sequence = this.filterNot(predicate) - for (item in sequence) { - destination += item - } - return destination +inline fun , T : Table, C : MutableCollection> EntitySequence.filterNotTo( + destination: C, + predicate: (T) -> ColumnDeclaring +): C { + return this.filterNot(predicate).toCollection(destination) } inline fun , T : Table, C : Any> EntitySequence.aggregate( From 663de5474168185bda43040247bde2e1cc77a7a6 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 17:13:50 +0800 Subject: [PATCH 27/34] map --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index a547e0ad..98ceb030 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -8,6 +8,7 @@ import me.liuwj.ktorm.schema.Column import me.liuwj.ktorm.schema.ColumnDeclaring import me.liuwj.ktorm.schema.Table import java.util.* +import kotlin.collections.ArrayList import kotlin.math.min data class EntitySequence, T : Table>(val sourceTable: T, val expression: SelectExpression) { @@ -115,6 +116,30 @@ inline fun , T : Table, C : MutableCollection> EntitySequ return this.filterNot(predicate).toCollection(destination) } +inline fun , R> EntitySequence.map(transform: (E) -> R): List { + return this.mapTo(ArrayList(), transform) +} + +inline fun , R, C : MutableCollection> EntitySequence.mapTo( + destination: C, + transform: (E) -> R +): C { + for (item in this) destination += transform(item) + return destination +} + +inline fun , R> EntitySequence.mapIndexed(transform: (index: Int, E) -> R): List { + return this.mapIndexedTo(ArrayList(), transform) +} + +inline fun , R, C : MutableCollection> EntitySequence.mapIndexedTo( + destination: C, + transform: (index: Int, E) -> R +): C { + var index = 0 + return this.mapTo(destination) { transform(index++, it) } +} + inline fun , T : Table, C : Any> EntitySequence.aggregate( aggregationSelector: (T) -> ColumnDeclaring ): C? { From d8244c50878fa6b75f7874169e09d7f12c376243 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 17:35:46 +0800 Subject: [PATCH 28/34] associate --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 98 ++++++++++++------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 98ceb030..7874b1b7 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -164,7 +164,9 @@ fun , T : Table> EntitySequence.count(): Int { return aggregate { me.liuwj.ktorm.dsl.count() } ?: error("Count expression returns null, which never happens.") } -inline fun , T : Table> EntitySequence.count(predicate: (T) -> ColumnDeclaring): Int { +inline fun , T : Table> EntitySequence.count( + predicate: (T) -> ColumnDeclaring +): Int { return this.filter(predicate).count() } @@ -172,7 +174,9 @@ fun , T : Table> EntitySequence.none(): Boolean { return this.count() == 0 } -inline fun , T : Table> EntitySequence.none(predicate: (T) -> ColumnDeclaring): Boolean { +inline fun , T : Table> EntitySequence.none( + predicate: (T) -> ColumnDeclaring +): Boolean { return this.count(predicate) == 0 } @@ -180,72 +184,94 @@ fun , T : Table> EntitySequence.any(): Boolean { return this.count() > 0 } -inline fun , T : Table> EntitySequence.any(predicate: (T) -> ColumnDeclaring): Boolean { +inline fun , T : Table> EntitySequence.any( + predicate: (T) -> ColumnDeclaring +): Boolean { return this.count(predicate) > 0 } -inline fun , T : Table> EntitySequence.all(predicate: (T) -> ColumnDeclaring): Boolean { +inline fun , T : Table> EntitySequence.all( + predicate: (T) -> ColumnDeclaring +): Boolean { return this.none { !predicate(it) } } -inline fun , T : Table, C : Number> EntitySequence.sumBy(selector: (T) -> ColumnDeclaring): C? { +inline fun , T : Table, C : Number> EntitySequence.sumBy( + selector: (T) -> ColumnDeclaring +): C? { return aggregate { sum(selector(it)) } } -inline fun , T : Table, C : Number> EntitySequence.maxBy(selector: (T) -> ColumnDeclaring): C? { +inline fun , T : Table, C : Number> EntitySequence.maxBy( + selector: (T) -> ColumnDeclaring +): C? { return aggregate { max(selector(it)) } } -inline fun , T : Table, C : Number> EntitySequence.minBy(selector: (T) -> ColumnDeclaring): C? { +inline fun , T : Table, C : Number> EntitySequence.minBy( + selector: (T) -> ColumnDeclaring +): C? { return aggregate { min(selector(it)) } } -inline fun , T : Table> EntitySequence.averageBy(selector: (T) -> ColumnDeclaring): Double? { +inline fun , T : Table> EntitySequence.averageBy( + selector: (T) -> ColumnDeclaring +): Double? { return aggregate { avg(selector(it)) } } -fun , K, V> EntitySequence.associate(transform: (E) -> Pair): Map { - return this.associateTo(LinkedHashMap(), transform) +inline fun , K, V> EntitySequence.associate( + transform: (E) -> Pair +): Map { + return asKotlinSequence().associate(transform) } -fun , K> EntitySequence.associateBy(keySelector: (E) -> K): Map { - return this.associateByTo(LinkedHashMap(), keySelector) +inline fun , K> EntitySequence.associateBy( + keySelector: (E) -> K +): Map { + return asKotlinSequence().associateBy(keySelector) } -fun , K, V> EntitySequence.associateBy(keySelector: (E) -> K, valueTransform: (E) -> V): Map { - return this.associateByTo(LinkedHashMap(), keySelector, valueTransform) +inline fun , K, V> EntitySequence.associateBy( + keySelector: (E) -> K, + valueTransform: (E) -> V +): Map { + return asKotlinSequence().associateBy(keySelector, valueTransform) } -fun , V> EntitySequence.associateWith(valueTransform: (K) -> V): Map { - return this.associateWithTo(LinkedHashMap(), valueTransform) +inline fun , V> EntitySequence.associateWith( + valueTransform: (K) -> V +): Map { + return asKotlinSequence().associateWith(valueTransform) } -fun , K, V, M : MutableMap> EntitySequence.associateTo(destination: M, transform: (E) -> Pair): M { - for (item in this) { - destination += transform(item) - } - return destination +inline fun , K, V, M : MutableMap> EntitySequence.associateTo( + destination: M, + transform: (E) -> Pair +): M { + return asKotlinSequence().associateTo(destination, transform) } -fun , K, M : MutableMap> EntitySequence.associateByTo(destination: M, keySelector: (E) -> K): M { - for (item in this) { - destination.put(keySelector(item), item) - } - return destination +inline fun , K, M : MutableMap> EntitySequence.associateByTo( + destination: M, + keySelector: (E) -> K +): M { + return asKotlinSequence().associateByTo(destination, keySelector) } -fun , K, V, M : MutableMap> EntitySequence.associateByTo(destination: M, keySelector: (E) -> K, valueTransform: (E) -> V): M { - for (item in this) { - destination.put(keySelector(item), valueTransform(item)) - } - return destination +inline fun , K, V, M : MutableMap> EntitySequence.associateByTo( + destination: M, + keySelector: (E) -> K, + valueTransform: (E) -> V +): M { + return asKotlinSequence().associateByTo(destination, keySelector, valueTransform) } -fun , V, M : MutableMap> EntitySequence.associateWithTo(destination: M, valueTransform: (K) -> V): M { - for (item in this) { - destination.put(item, valueTransform(item)) - } - return destination +inline fun , V, M : MutableMap> EntitySequence.associateWithTo( + destination: M, + valueTransform: (K) -> V +): M { + return asKotlinSequence().associateWithTo(destination, valueTransform) } fun , T : Table> EntitySequence.drop(n: Int): EntitySequence { From de5bcca8f46cbc69dae87c936ffd27821a0692a8 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 18:17:37 +0800 Subject: [PATCH 29/34] group by --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 94 ++++++++++--------- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 12 +++ 2 files changed, 62 insertions(+), 44 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 7874b1b7..931a5165 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -290,28 +290,13 @@ fun , T : Table> EntitySequence.take(n: Int): EntitySeque fun , T : Table> EntitySequence.elementAtOrNull(index: Int): E? { try { - val iterator = this.drop(index).take(1).iterator() - if (iterator.hasNext()) { - return iterator.next() - } else { - return null - } - + return this.drop(index).take(1).asKotlinSequence().firstOrNull() } catch (e: UnsupportedOperationException) { - - val iterator = this.iterator() - var count = 0 - while (iterator.hasNext()) { - val item = iterator.next() - if (index == count++) { - return item - } - } - return null + return this.asKotlinSequence().elementAtOrNull(index) } } -fun , T : Table> EntitySequence.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E { +inline fun , T : Table> EntitySequence.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E { return this.elementAtOrNull(index) ?: defaultValue(index) } @@ -323,7 +308,7 @@ fun , T : Table> EntitySequence.firstOrNull(): E? { return this.elementAtOrNull(0) } -fun , T : Table> EntitySequence.firstOrNull(predicate: (T) -> ColumnDeclaring): E? { +inline fun , T : Table> EntitySequence.firstOrNull(predicate: (T) -> ColumnDeclaring): E? { return this.filter(predicate).elementAtOrNull(0) } @@ -331,19 +316,15 @@ fun , T : Table> EntitySequence.first(): E { return this.elementAt(0) } -fun , T : Table> EntitySequence.first(predicate: (T) -> ColumnDeclaring): E { +inline fun , T : Table> EntitySequence.first(predicate: (T) -> ColumnDeclaring): E { return this.filter(predicate).elementAt(0) } fun > EntitySequence.lastOrNull(): E? { - var last: E? = null - for (item in this) { - last = item - } - return last + return asKotlinSequence().lastOrNull() } -fun , T : Table> EntitySequence.lastOrNull(predicate: (T) -> ColumnDeclaring): E? { +inline fun , T : Table> EntitySequence.lastOrNull(predicate: (T) -> ColumnDeclaring): E? { return this.filter(predicate).lastOrNull() } @@ -351,40 +332,69 @@ fun > EntitySequence.last(): E { return lastOrNull() ?: throw NoSuchElementException("Sequence is empty.") } -fun , T : Table> EntitySequence.last(predicate: (T) -> ColumnDeclaring): E { +inline fun , T : Table> EntitySequence.last(predicate: (T) -> ColumnDeclaring): E { return this.filter(predicate).last() } -fun , T : Table> EntitySequence.find(predicate: (T) -> ColumnDeclaring): E? { +inline fun , T : Table> EntitySequence.find(predicate: (T) -> ColumnDeclaring): E? { return this.firstOrNull(predicate) } -fun , T : Table> EntitySequence.findLast(predicate: (T) -> ColumnDeclaring): E? { +inline fun , T : Table> EntitySequence.findLast(predicate: (T) -> ColumnDeclaring): E? { return this.lastOrNull(predicate) } -fun , R> EntitySequence.fold(initial: R, operation: (acc: R, E) -> R): R { - var accumulator = initial - for (item in this) { - accumulator = operation(accumulator, item) - } - return accumulator +inline fun , R> EntitySequence.fold(initial: R, operation: (acc: R, E) -> R): R { + return asKotlinSequence().fold(initial, operation) } -fun , R> EntitySequence.foldIndexed(initial: R, operation: (index: Int, acc: R, E) -> R): R { - var index = 0 - return this.fold(initial) { acc, e -> operation(index++, acc, e) } +inline fun , R> EntitySequence.foldIndexed(initial: R, operation: (index: Int, acc: R, E) -> R): R { + return asKotlinSequence().foldIndexed(initial, operation) } -fun > EntitySequence.forEach(action: (E) -> Unit) { +inline fun > EntitySequence.forEach(action: (E) -> Unit) { for (item in this) action(item) } -fun > EntitySequence.forEachIndexed(action: (index: Int, E) -> Unit) { +inline fun > EntitySequence.forEachIndexed(action: (index: Int, E) -> Unit) { var index = 0 for (item in this) action(index++, item) } +inline fun , K> EntitySequence.groupBy( + keySelector: (E) -> K +): Map> { + return asKotlinSequence().groupBy(keySelector) +} + +inline fun , K, V> EntitySequence.groupBy( + keySelector: (E) -> K, + valueTransform: (E) -> V +): Map> { + return asKotlinSequence().groupBy(keySelector, valueTransform) +} + +inline fun , K, M : MutableMap>> EntitySequence.groupByTo( + destination: M, + keySelector: (E) -> K +): M { + return asKotlinSequence().groupByTo(destination, keySelector) +} + +inline fun , K, V, M : MutableMap>> EntitySequence.groupByTo( + destination: M, + keySelector: (E) -> K, + valueTransform: (E) -> V +): M { + return asKotlinSequence().groupByTo(destination, keySelector, valueTransform) +} + +fun , T : Table, K : Any> EntitySequence.groupingBy( + keySelector: (T) -> ColumnDeclaring +): EntityGrouping { + return EntityGrouping(this, keySelector) +} + fun , T : Table> EntitySequence.sorted(selector: (T) -> List): EntitySequence { return this.copy(expression = expression.copy(orderBy = selector(sourceTable))) } @@ -395,8 +405,4 @@ fun , T : Table> EntitySequence.sortedBy(selector: (T) -> fun , T : Table> EntitySequence.sortedByDescending(selector: (T) -> ColumnDeclaring<*>): EntitySequence { return this.sorted { listOf(selector(it).desc()) } -} - -fun , T : Table, K : Any> EntitySequence.groupingBy(keySelector: (T) -> ColumnDeclaring): EntityGrouping { - return EntityGrouping(this, keySelector) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 49916b40..31e7dc43 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -121,6 +121,18 @@ class EntitySequenceTest : BaseTest() { assert(employee.department.location.isEmpty()) } + @Test + fun testGroupBy() { + val employees = Employees + .asSequence() + .groupBy { it.department.id } + + println(employees) + assert(employees.size == 2) + assert(employees[1]!!.sumBy { it.salary.toInt() } == 150) + assert(employees[2]!!.sumBy { it.salary.toInt() } == 300) + } + @Test fun testGroupingBy() { val salaries = Employees From 33bbc7711e784f89896af7d772b2f072b9ce7351 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 18:44:39 +0800 Subject: [PATCH 30/34] join to string --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 87 ++++++++++++------- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 6 ++ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 931a5165..6376fb99 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -99,25 +99,25 @@ inline fun , T : Table> EntitySequence.filter( inline fun , T : Table> EntitySequence.filterNot( predicate: (T) -> ColumnDeclaring ): EntitySequence { - return this.filter { !predicate(it) } + return filter { !predicate(it) } } inline fun , T : Table, C : MutableCollection> EntitySequence.filterTo( destination: C, predicate: (T) -> ColumnDeclaring ): C { - return this.filter(predicate).toCollection(destination) + return filter(predicate).toCollection(destination) } inline fun , T : Table, C : MutableCollection> EntitySequence.filterNotTo( destination: C, predicate: (T) -> ColumnDeclaring ): C { - return this.filterNot(predicate).toCollection(destination) + return filterNot(predicate).toCollection(destination) } inline fun , R> EntitySequence.map(transform: (E) -> R): List { - return this.mapTo(ArrayList(), transform) + return mapTo(ArrayList(), transform) } inline fun , R, C : MutableCollection> EntitySequence.mapTo( @@ -129,7 +129,7 @@ inline fun , R, C : MutableCollection> EntitySequence. } inline fun , R> EntitySequence.mapIndexed(transform: (index: Int, E) -> R): List { - return this.mapIndexedTo(ArrayList(), transform) + return mapIndexedTo(ArrayList(), transform) } inline fun , R, C : MutableCollection> EntitySequence.mapIndexedTo( @@ -137,7 +137,7 @@ inline fun , R, C : MutableCollection> EntitySequence. transform: (index: Int, E) -> R ): C { var index = 0 - return this.mapTo(destination) { transform(index++, it) } + return mapTo(destination) { transform(index++, it) } } inline fun , T : Table, C : Any> EntitySequence.aggregate( @@ -167,33 +167,33 @@ fun , T : Table> EntitySequence.count(): Int { inline fun , T : Table> EntitySequence.count( predicate: (T) -> ColumnDeclaring ): Int { - return this.filter(predicate).count() + return filter(predicate).count() } fun , T : Table> EntitySequence.none(): Boolean { - return this.count() == 0 + return count() == 0 } inline fun , T : Table> EntitySequence.none( predicate: (T) -> ColumnDeclaring ): Boolean { - return this.count(predicate) == 0 + return count(predicate) == 0 } fun , T : Table> EntitySequence.any(): Boolean { - return this.count() > 0 + return count() > 0 } inline fun , T : Table> EntitySequence.any( predicate: (T) -> ColumnDeclaring ): Boolean { - return this.count(predicate) > 0 + return count(predicate) > 0 } inline fun , T : Table> EntitySequence.all( predicate: (T) -> ColumnDeclaring ): Boolean { - return this.none { !predicate(it) } + return none { !predicate(it) } } inline fun , T : Table, C : Number> EntitySequence.sumBy( @@ -290,34 +290,34 @@ fun , T : Table> EntitySequence.take(n: Int): EntitySeque fun , T : Table> EntitySequence.elementAtOrNull(index: Int): E? { try { - return this.drop(index).take(1).asKotlinSequence().firstOrNull() + return drop(index).take(1).asKotlinSequence().firstOrNull() } catch (e: UnsupportedOperationException) { - return this.asKotlinSequence().elementAtOrNull(index) + return asKotlinSequence().elementAtOrNull(index) } } inline fun , T : Table> EntitySequence.elementAtOrElse(index: Int, defaultValue: (Int) -> E): E { - return this.elementAtOrNull(index) ?: defaultValue(index) + return elementAtOrNull(index) ?: defaultValue(index) } fun , T : Table> EntitySequence.elementAt(index: Int): E { - return this.elementAtOrNull(index) ?: throw IndexOutOfBoundsException("Sequence doesn't contain element at index $index.") + return elementAtOrNull(index) ?: throw IndexOutOfBoundsException("Sequence doesn't contain element at index $index.") } fun , T : Table> EntitySequence.firstOrNull(): E? { - return this.elementAtOrNull(0) + return elementAtOrNull(0) } inline fun , T : Table> EntitySequence.firstOrNull(predicate: (T) -> ColumnDeclaring): E? { - return this.filter(predicate).elementAtOrNull(0) + return filter(predicate).elementAtOrNull(0) } fun , T : Table> EntitySequence.first(): E { - return this.elementAt(0) + return elementAt(0) } inline fun , T : Table> EntitySequence.first(predicate: (T) -> ColumnDeclaring): E { - return this.filter(predicate).elementAt(0) + return filter(predicate).elementAt(0) } fun > EntitySequence.lastOrNull(): E? { @@ -325,7 +325,7 @@ fun > EntitySequence.lastOrNull(): E? { } inline fun , T : Table> EntitySequence.lastOrNull(predicate: (T) -> ColumnDeclaring): E? { - return this.filter(predicate).lastOrNull() + return filter(predicate).lastOrNull() } fun > EntitySequence.last(): E { @@ -333,15 +333,15 @@ fun > EntitySequence.last(): E { } inline fun , T : Table> EntitySequence.last(predicate: (T) -> ColumnDeclaring): E { - return this.filter(predicate).last() + return filter(predicate).last() } inline fun , T : Table> EntitySequence.find(predicate: (T) -> ColumnDeclaring): E? { - return this.firstOrNull(predicate) + return firstOrNull(predicate) } inline fun , T : Table> EntitySequence.findLast(predicate: (T) -> ColumnDeclaring): E? { - return this.lastOrNull(predicate) + return lastOrNull(predicate) } inline fun , R> EntitySequence.fold(initial: R, operation: (acc: R, E) -> R): R { @@ -395,14 +395,43 @@ fun , T : Table, K : Any> EntitySequence.groupingBy( return EntityGrouping(this, keySelector) } -fun , T : Table> EntitySequence.sorted(selector: (T) -> List): EntitySequence { +fun , T : Table> EntitySequence.sorted( + selector: (T) -> List +): EntitySequence { return this.copy(expression = expression.copy(orderBy = selector(sourceTable))) } -fun , T : Table> EntitySequence.sortedBy(selector: (T) -> ColumnDeclaring<*>): EntitySequence { - return this.sorted { listOf(selector(it).asc()) } +fun , T : Table> EntitySequence.sortedBy( + selector: (T) -> ColumnDeclaring<*> +): EntitySequence { + return sorted { listOf(selector(it).asc()) } } -fun , T : Table> EntitySequence.sortedByDescending(selector: (T) -> ColumnDeclaring<*>): EntitySequence { - return this.sorted { listOf(selector(it).desc()) } +fun , T : Table> EntitySequence.sortedByDescending( + selector: (T) -> ColumnDeclaring<*> +): EntitySequence { + return sorted { listOf(selector(it).desc()) } +} + +fun , A : Appendable> EntitySequence.joinTo( + buffer: A, + separator: CharSequence = ", ", + prefix: CharSequence = "", + postfix: CharSequence = "", + limit: Int = -1, + truncated: CharSequence = "...", + transform: ((E) -> CharSequence)? = null +): A { + return asKotlinSequence().joinTo(buffer, separator, prefix, postfix, limit, truncated, transform) +} + +fun > EntitySequence.joinToString( + separator: CharSequence = ", ", + prefix: CharSequence = "", + postfix: CharSequence = "", + limit: Int = -1, + truncated: CharSequence = "...", + transform: ((E) -> CharSequence)? = null +): String { + return asKotlinSequence().joinToString(separator, prefix, postfix, limit, truncated, transform) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index 31e7dc43..f872e125 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -175,4 +175,10 @@ class EntitySequenceTest : BaseTest() { assert(sums[1] == 150L) assert(sums[2] == 300L) } + + @Test + fun testJoinToString() { + val salaries = Employees.asSequence().joinToString { it.id.toString() } + assert(salaries == "1, 2, 3, 4") + } } \ No newline at end of file From b8805e80b0a7e7f8be8bf4b2b4e0a34518f7e770 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 20:38:12 +0800 Subject: [PATCH 31/34] reduce & single --- .../me/liuwj/ktorm/entity/EntitySequence.kt | 30 +++++++++++++++++-- .../liuwj/ktorm/entity/EntitySequenceTest.kt | 12 ++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 6376fb99..5e0cbf4a 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -344,6 +344,22 @@ inline fun , T : Table> EntitySequence.findLast(predicate return lastOrNull(predicate) } +fun , T : Table> EntitySequence.singleOrNull(): E? { + return asKotlinSequence().singleOrNull() +} + +inline fun , T : Table> EntitySequence.singleOrNull(predicate: (T) -> ColumnDeclaring): E? { + return filter(predicate).singleOrNull() +} + +fun , T : Table> EntitySequence.single(): E { + return asKotlinSequence().single() +} + +inline fun , T : Table> EntitySequence.single(predicate: (T) -> ColumnDeclaring): E { + return filter(predicate).single() +} + inline fun , R> EntitySequence.fold(initial: R, operation: (acc: R, E) -> R): R { return asKotlinSequence().fold(initial, operation) } @@ -395,19 +411,19 @@ fun , T : Table, K : Any> EntitySequence.groupingBy( return EntityGrouping(this, keySelector) } -fun , T : Table> EntitySequence.sorted( +inline fun , T : Table> EntitySequence.sorted( selector: (T) -> List ): EntitySequence { return this.copy(expression = expression.copy(orderBy = selector(sourceTable))) } -fun , T : Table> EntitySequence.sortedBy( +inline fun , T : Table> EntitySequence.sortedBy( selector: (T) -> ColumnDeclaring<*> ): EntitySequence { return sorted { listOf(selector(it).asc()) } } -fun , T : Table> EntitySequence.sortedByDescending( +inline fun , T : Table> EntitySequence.sortedByDescending( selector: (T) -> ColumnDeclaring<*> ): EntitySequence { return sorted { listOf(selector(it).desc()) } @@ -434,4 +450,12 @@ fun > EntitySequence.joinToString( transform: ((E) -> CharSequence)? = null ): String { return asKotlinSequence().joinToString(separator, prefix, postfix, limit, truncated, transform) +} + +inline fun > EntitySequence.reduce(operation: (acc: E, E) -> E): E { + return asKotlinSequence().reduce(operation) +} + +inline fun > EntitySequence.reduceIndexed(operation: (index: Int, acc: E, E) -> E): E { + return asKotlinSequence().reduceIndexed(operation) } \ No newline at end of file diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt index f872e125..f20841c6 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntitySequenceTest.kt @@ -181,4 +181,16 @@ class EntitySequenceTest : BaseTest() { val salaries = Employees.asSequence().joinToString { it.id.toString() } assert(salaries == "1, 2, 3, 4") } + + @Test + fun testReduce() { + val emp = Employees.asSequence().reduce { acc, employee -> acc.apply { salary += employee.salary } } + assert(emp.salary == 450L) + } + + @Test + fun testSingle() { + val employee = Employees.asSequence().singleOrNull { it.departmentId eq 1 } + assert(employee == null) + } } \ No newline at end of file From 72b5145feacb17bae12957ff7c10e69659c8886a Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 20:59:34 +0800 Subject: [PATCH 32/34] refactor sequence --- .../me/liuwj/ktorm/entity/EntityFinding.kt | 10 ++- .../me/liuwj/ktorm/entity/EntitySequence.kt | 80 +++++++++---------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt index 4b4fc63b..9e356e3d 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt @@ -38,8 +38,8 @@ fun > Table.findById(id: Any): E? { /** * 根据指定条件获取对象,会自动 left join 所有的引用表 */ -inline fun , T : Table> T.findOne(block: (T) -> ColumnDeclaring): E? { - val list = findList(block) +inline fun , T : Table> T.findOne(predicate: (T) -> ColumnDeclaring): E? { + val list = findList(predicate) when (list.size) { 0 -> return null 1 -> return list[0] @@ -51,6 +51,7 @@ inline fun , T : Table> T.findOne(block: (T) -> ColumnDeclaring * 获取表中的所有记录,会自动 left join 所有的引用表 */ fun > Table.findAll(): List { + // return this.asSequence().toList() return this .joinReferencesAndSelect() .map { row -> this.createEntity(row) } @@ -59,10 +60,11 @@ fun > Table.findAll(): List { /** * 根据指定条件获取对象列表,会自动 left join 所有的引用表 */ -inline fun , T : Table> T.findList(block: (T) -> ColumnDeclaring): List { +inline fun , T : Table> T.findList(predicate: (T) -> ColumnDeclaring): List { + // return this.asSequence().filter(predicate).toList() return this .joinReferencesAndSelect() - .where { block(this) } + .where { predicate(this) } .map { row -> this.createEntity(row) } } diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt index 5e0cbf4a..5fbe66f6 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntitySequence.kt @@ -140,6 +140,38 @@ inline fun , R, C : MutableCollection> EntitySequence. return mapTo(destination) { transform(index++, it) } } +inline fun , T : Table> EntitySequence.sorted( + selector: (T) -> List +): EntitySequence { + return this.copy(expression = expression.copy(orderBy = selector(sourceTable))) +} + +inline fun , T : Table> EntitySequence.sortedBy( + selector: (T) -> ColumnDeclaring<*> +): EntitySequence { + return sorted { listOf(selector(it).asc()) } +} + +inline fun , T : Table> EntitySequence.sortedByDescending( + selector: (T) -> ColumnDeclaring<*> +): EntitySequence { + return sorted { listOf(selector(it).desc()) } +} + +fun , T : Table> EntitySequence.drop(n: Int): EntitySequence { + if (n == 0) { + return this + } else { + val offset = expression.offset ?: 0 + return this.copy(expression = expression.copy(offset = offset + n)) + } +} + +fun , T : Table> EntitySequence.take(n: Int): EntitySequence { + val limit = expression.limit ?: Int.MAX_VALUE + return this.copy(expression = expression.copy(limit = min(limit, n))) +} + inline fun , T : Table, C : Any> EntitySequence.aggregate( aggregationSelector: (T) -> ColumnDeclaring ): C? { @@ -274,20 +306,6 @@ inline fun , V, M : MutableMap> EntitySequence.a return asKotlinSequence().associateWithTo(destination, valueTransform) } -fun , T : Table> EntitySequence.drop(n: Int): EntitySequence { - if (n == 0) { - return this - } else { - val offset = expression.offset ?: 0 - return this.copy(expression = expression.copy(offset = offset + n)) - } -} - -fun , T : Table> EntitySequence.take(n: Int): EntitySequence { - val limit = expression.limit ?: Int.MAX_VALUE - return this.copy(expression = expression.copy(limit = min(limit, n))) -} - fun , T : Table> EntitySequence.elementAtOrNull(index: Int): E? { try { return drop(index).take(1).asKotlinSequence().firstOrNull() @@ -368,6 +386,14 @@ inline fun , R> EntitySequence.foldIndexed(initial: R, opera return asKotlinSequence().foldIndexed(initial, operation) } +inline fun > EntitySequence.reduce(operation: (acc: E, E) -> E): E { + return asKotlinSequence().reduce(operation) +} + +inline fun > EntitySequence.reduceIndexed(operation: (index: Int, acc: E, E) -> E): E { + return asKotlinSequence().reduceIndexed(operation) +} + inline fun > EntitySequence.forEach(action: (E) -> Unit) { for (item in this) action(item) } @@ -411,24 +437,6 @@ fun , T : Table, K : Any> EntitySequence.groupingBy( return EntityGrouping(this, keySelector) } -inline fun , T : Table> EntitySequence.sorted( - selector: (T) -> List -): EntitySequence { - return this.copy(expression = expression.copy(orderBy = selector(sourceTable))) -} - -inline fun , T : Table> EntitySequence.sortedBy( - selector: (T) -> ColumnDeclaring<*> -): EntitySequence { - return sorted { listOf(selector(it).asc()) } -} - -inline fun , T : Table> EntitySequence.sortedByDescending( - selector: (T) -> ColumnDeclaring<*> -): EntitySequence { - return sorted { listOf(selector(it).desc()) } -} - fun , A : Appendable> EntitySequence.joinTo( buffer: A, separator: CharSequence = ", ", @@ -450,12 +458,4 @@ fun > EntitySequence.joinToString( transform: ((E) -> CharSequence)? = null ): String { return asKotlinSequence().joinToString(separator, prefix, postfix, limit, truncated, transform) -} - -inline fun > EntitySequence.reduce(operation: (acc: E, E) -> E): E { - return asKotlinSequence().reduce(operation) -} - -inline fun > EntitySequence.reduceIndexed(operation: (index: Int, acc: E, E) -> E): E { - return asKotlinSequence().reduceIndexed(operation) } \ No newline at end of file From 1709356abdeeea465c2c2ac1e4ac1d93427613d4 Mon Sep 17 00:00:00 2001 From: vince Date: Sun, 7 Apr 2019 22:20:03 +0800 Subject: [PATCH 33/34] set gradle opts --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index f2cb4adc..90444c32 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: java +env: + - GRADLE_OPTS="-Xms2048m -Xmx2048m" + services: - mysql From a59034269ab357eb57daab66b9c2fac545d9b9d7 Mon Sep 17 00:00:00 2001 From: vince Date: Thu, 11 Apr 2019 10:09:04 +0800 Subject: [PATCH 34/34] fix bug #10 for adding entities --- .../kotlin/me/liuwj/ktorm/entity/EntityDml.kt | 76 ++++++++++++++----- .../me/liuwj/ktorm/entity/EntityFinding.kt | 8 +- .../me/liuwj/ktorm/entity/EntityTest.kt | 37 +++++++-- 3 files changed, 91 insertions(+), 30 deletions(-) diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt index e0f360cf..826784b3 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityDml.kt @@ -10,6 +10,8 @@ import me.liuwj.ktorm.schema.* */ @Suppress("UNCHECKED_CAST") fun > Table.add(entity: E): Int { + entity.implementation.checkUnexpectedDiscarding(this) + val assignments = findInsertColumns(entity).takeIf { it.isNotEmpty() } ?: return 0 val expression = AliasRemover.visit( @@ -66,6 +68,8 @@ private fun Table<*>.findInsertColumns(entity: Entity<*>): Map, Any?> @Suppress("UNCHECKED_CAST") internal fun EntityImplementation.doFlushChanges(): Int { val fromTable = this.fromTable?.takeIf { this.parent == null } ?: error("The entity is not associated with any table yet.") + checkUnexpectedDiscarding(fromTable) + val primaryKey = fromTable.primaryKey ?: error("Table ${fromTable.tableName} doesn't have a primary key.") val assignments = findChangedColumns(fromTable).takeIf { it.isNotEmpty() } ?: return 0 @@ -111,26 +115,17 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map) { curr = curr.implementation } check(curr is EntityImplementation?) - val changed = if (curr == null) false else prop.name in curr.changedProperties - - // Add check to avoid bug #10 - if (changed && i > 0) { - check(curr != null) - - if (curr.fromTable != null && curr.getRoot() != this) { - val propPath = binding.properties.subList(0, i + 1).joinToString(separator = ".", prefix = "this.") { it.name } - throw IllegalStateException("$propPath may be unexpectedly discarded after flushChanges, please save it to database first.") - } + if (curr != null && prop.name in curr.changedProperties) { + anyChanged = true } - anyChanged = anyChanged || changed curr = curr?.getProperty(prop.name) } @@ -144,15 +139,6 @@ private fun EntityImplementation.findChangedColumns(fromTable: Table<*>): Map) { + for (column in fromTable.columns) { + val binding = column.binding?.takeIf { column is SimpleColumn } ?: continue + + if (binding is NestedBinding) { + var curr: Any? = this + + for ((i, prop) in binding.properties.withIndex()) { + if (curr == null) { + break + } + if (curr is Entity<*>) { + curr = curr.implementation + } + + check(curr is EntityImplementation) + + if (i > 0 && prop.name in curr.changedProperties && curr.fromTable != null && curr.getRoot() != this) { + val propPath = binding.properties.subList(0, i + 1).joinToString(separator = ".", prefix = "this.") { it.name } + throw IllegalStateException("$propPath may be unexpectedly discarded, please save it to database first.") + } + + curr = curr.getProperty(prop.name) + } + } + } +} + +private tailrec fun EntityImplementation.getRoot(): EntityImplementation { + val parent = this.parent + if (parent == null) { + return this + } else { + return parent.getRoot() + } +} + +internal fun Entity<*>.clearChangesRecursively() { + implementation.changedProperties.clear() + + for ((_, value) in properties) { + if (value is Entity<*>) { + value.clearChangesRecursively() + } + } +} + @Suppress("UNCHECKED_CAST") internal fun EntityImplementation.doDelete(): Int { val fromTable = this.fromTable?.takeIf { this.parent == null } ?: error("The entity is not associated with any table yet.") diff --git a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt index f0d549b4..4505b5c9 100644 --- a/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt +++ b/ktorm-core/src/main/kotlin/me/liuwj/ktorm/entity/EntityFinding.kt @@ -114,7 +114,7 @@ private infix fun ColumnDeclaring<*>.eq(column: ColumnDeclaring<*>): BinaryExpre @Suppress("UNCHECKED_CAST") fun > Table.createEntity(row: QueryRowSet): E { val entity = doCreateEntity(row, skipReferences = false) as E - return entity.apply { discardChanges() } + return entity.apply { clearChangesRecursively() } } /** @@ -123,7 +123,7 @@ fun > Table.createEntity(row: QueryRowSet): E { @Suppress("UNCHECKED_CAST") fun > Table.createEntityWithoutReferences(row: QueryRowSet): E { val entity = doCreateEntity(row, skipReferences = true) as E - return entity.apply { discardChanges() } + return entity.apply { clearChangesRecursively() } } private fun Table<*>.doCreateEntity(row: QueryRowSet, skipReferences: Boolean = false): Entity<*> { @@ -154,12 +154,12 @@ private fun QueryRowSet.retrieveColumn(column: Column<*>, intoEntity: Entity<*>, skipReferences -> { val child = Entity.create(binding.onProperty.returnType.classifier as KClass<*>, fromTable = rightTable) child.implementation.setColumnValue(primaryKey, columnValue) - intoEntity[binding.onProperty.name] = child.apply { discardChanges() } + intoEntity[binding.onProperty.name] = child } this.hasColumn(primaryKey) && this[primaryKey] != null -> { val child = rightTable.doCreateEntity(this) child.implementation.setColumnValue(primaryKey, columnValue, forceSet = true) - intoEntity[binding.onProperty.name] = child.apply { discardChanges() } + intoEntity[binding.onProperty.name] = child } } } diff --git a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt index ec457ca6..32703c55 100644 --- a/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt +++ b/ktorm-core/src/test/kotlin/me/liuwj/ktorm/entity/EntityTest.kt @@ -2,9 +2,7 @@ package me.liuwj.ktorm.entity import me.liuwj.ktorm.BaseTest import me.liuwj.ktorm.dsl.* -import me.liuwj.ktorm.schema.Table -import me.liuwj.ktorm.schema.int -import me.liuwj.ktorm.schema.varchar +import me.liuwj.ktorm.schema.* import org.junit.Test import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -286,9 +284,13 @@ class EntityTest : BaseTest() { } interface Emp : Entity { + companion object : Entity.Factory() val id: Int var employee: Employee var manager: Employee + var hireDate: LocalDate + var salary: Long + var departmentId: Int } object Emps : Table("t_employee") { @@ -296,6 +298,9 @@ class EntityTest : BaseTest() { val name by varchar("name").bindTo { it.employee.name } val job by varchar("job").bindTo { it.employee.job } val managerId by int("manager_id").bindTo { it.manager.id } + val hireDate by date("hire_date").bindTo { it.hireDate } + val salary by long("salary").bindTo { it.salary } + val departmentId by int("department_id").bindTo { it.departmentId } } @Test @@ -304,6 +309,28 @@ class EntityTest : BaseTest() { emp1.employee.name = "jerry" // emp1.flushChanges() + val emp2 = Emp { + employee = emp1.employee + hireDate = LocalDate.now() + salary = 100 + departmentId = 1 + } + + try { + Emps.add(emp2) + throw AssertionError("failed") + + } catch (e: IllegalStateException) { + assert(e.message == "this.employee.name may be unexpectedly discarded, please save it to database first.") + } + } + + @Test + fun testCheckUnexpectedFlush0() { + val emp1 = Emps.findById(1) ?: return + emp1.employee.name = "jerry" + // emp1.flushChanges() + val emp2 = Emps.findById(2) ?: return emp2.employee = emp1.employee @@ -312,7 +339,7 @@ class EntityTest : BaseTest() { throw AssertionError("failed") } catch (e: IllegalStateException) { - assert(e.message == "this.employee.name may be unexpectedly discarded after flushChanges, please save it to database first.") + assert(e.message == "this.employee.name may be unexpectedly discarded, please save it to database first.") } } @@ -330,7 +357,7 @@ class EntityTest : BaseTest() { throw AssertionError("failed") } catch (e: IllegalStateException) { - assert(e.message == "this.employee.name may be unexpectedly discarded after flushChanges, please save it to database first.") + assert(e.message == "this.employee.name may be unexpectedly discarded, please save it to database first.") } }