Open vlsi opened 3 years ago
Just a side note:
interface ForEachColumnValue {
fun <U> consume(column: Column<U>, value: U)
}
could be
interface SetColumnValue {
operator fun <U> set(column: Column<U>, value: U)
}
Then the implementation of CompositeColumn
would be slightly easier to read:
override fun splitParts(compositeValue: T, columns: SetColumnValue) {
val (v1, v2) = transformFromValue(compositeValue)
columns[column1] = v1 // <-- note that types of column1 and v1 are verified here
columns[column2] = v2
}
However, the usage of splitParts
method might be slightly more confusing:
UpdateBuilder
:
open operator fun <S> set(column: CompositeColumn<S>, value: S) {
column.splitParts(value, object: SetColumnValue {
override fun <U> set(column: Column<U>, value: U) {
this@UpdateBuilder[column] = value // <-- this@.. is needed to avoid recursive set call
}
})
Table
fun <T> CompositeColumn<T>.default(defaultValue: T): CompositeColumn<T> = apply {
splitParts(defaultValue, object: SetColumnValue {
override fun <U> set(column: Column<U>, value: U) {
column.default(value)
}
})
Frankly speaking, I like how GetColumnValue
is paired with SetColumnValue
, so I like that naming better than ForEachColumnValue
.
I refined
UpdateBuilder#set
methods in https://github.com/JetBrains/Exposed/pull/1277, and I noticedset(column: CompositeColumn<S>, value: S)
is not very elegant.Then it turned out that all usages of
CompositeColumn,getRealColumnsWithValues
areforEach {...}
which end up withAny?
kind o API since Map can't express<Column<T>, T>
for individual entries.In practice,
ColumnValue
needs two APIs: one for "creating composite value out of parts" (e.g. to construct Kotlin object out of database values) and the second one for "producing parts out of composite" (e.g. to store them into database or to write them in WHERE clause)The current issues include:
CompositeColumn#getRealColumnsWithValues
introduceUNCHECKED_CAST
RawResult#getRaw
is ill defined forCompositeColumn
. For instance,ResultRow#hasValue(c)
,ResultRow#getOrNull
is likely to produce wrong results forCompositeColumn
, and it does not really useindividual column value conversion
when parsing the value to composite. For instance, the currentCompositeMoneyColumn#transformToValue
implementation does have to basically duplicateCurrencyColumnType
logic.What do you think of the following idea?
Producing parts out of composite
Current API:
abstract fun getRealColumnsWithValues(compositeValue: T): Map<Column<*>, Any?>
Suggested API:
Sample usage:
UpdateBuilder
https://github.com/JetBrains/Exposed/blob/d59247283dc8c1af019e3e2a8c75c7519d1fdb6d/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/UpdateBuilder.kt#L42-L44
Table
https://github.com/JetBrains/Exposed/blob/d59247283dc8c1af019e3e2a8c75c7519d1fdb6d/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt#L675-L682
Entity
https://github.com/JetBrains/Exposed/blob/d59247283dc8c1af019e3e2a8c75c7519d1fdb6d/exposed-dao/src/main/kotlin/org/jetbrains/exposed/dao/Entity.kt#L133-L139
Unfortunately,
object: ..
syntax is a bit verbose, however, it does make types safe for the callers (UNCHECKED_CAST
no longer needed).The implementation of
splitValues
would be safer too.Here's a sample implementation for
BiCompositeColumn
:Creating composite value out of parts
Make "raw" value for composites non-existing. In other words, composites are pure virtual, and there's no way to tell if the
money
value is null without convertingamount + currency
tomoney
Add interface so composite implementation can "query" the needed value:
The implementation for
BiCompositeColumn
would be trivial:Sample usages:
Entity
ResultRow
The better implementation would be behind the lines of