-
Notifications
You must be signed in to change notification settings - Fork 693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore! EXPOSED-655 Join column default fields defaultValueFun, defautValueInDb, isDatabaseGenerated into one field #2319
base: main
Are you sure you want to change the base?
Conversation
d3f4e28
to
d925535
Compare
All the interface/class/function names are discussable of course. In terms of base interface I chose The name of field with default value inside |
5d032d2
to
dbbd5e0
Compare
…tValueInDb, isDatabaseGenerated into one field
dbbd5e0
to
751ab7c
Compare
It also should be possible to add |
interface ColumnDefault<T> | ||
|
||
/** | ||
* Represents a client-side default value for a column. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please consider adding examples and with functions where these columns are used and references to the functions as well
* Represents a default value for a column generated by a function. | ||
*/ | ||
interface ColumnDefaultValue<T> : ColumnDefault<T> { | ||
val value: () -> T |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would make a method instead cause functional fields are harder to modify without breaking change
* @param fn The transformation function. | ||
* @return The transformed column default value. | ||
*/ | ||
fun <Wrapped> transform(fn: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fn -> block
- `defaultValueFun != null && dbDefaultValue != null` (knows as `default()`) becomes `DatabaseColumnDefaultExpressionWithValue` | ||
- `dbDefaultValue != null` (knows as `defaultExpression()`) becomes `DatabaseColumnDefaultExpression` | ||
- `defaultValueFun != null` (knows as `clientDefault()`) becomes `ClientColumnDefaultValue` | ||
- `isDatabaseGenerated == true` (knows as `databaseGenerated`) becomes `DatabaseGeneratedColumnDefault` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean "known" instead of "knows" here?
- `defaultValueFun != null && dbDefaultValue != null` (knows as `default()`) becomes `DatabaseColumnDefaultExpressionWithValue` | |
- `dbDefaultValue != null` (knows as `defaultExpression()`) becomes `DatabaseColumnDefaultExpression` | |
- `defaultValueFun != null` (knows as `clientDefault()`) becomes `ClientColumnDefaultValue` | |
- `isDatabaseGenerated == true` (knows as `databaseGenerated`) becomes `DatabaseGeneratedColumnDefault` | |
- `defaultValueFun != null && dbDefaultValue != null` (known as `default()`) becomes `DatabaseColumnDefaultExpressionWithValue` | |
- `dbDefaultValue != null` (known as `defaultExpression()`) becomes `DatabaseColumnDefaultExpression` | |
- `defaultValueFun != null` (known as `clientDefault()`) becomes `ClientColumnDefaultValue` | |
- `isDatabaseGenerated == true` (known as `databaseGenerated`) becomes `DatabaseGeneratedColumnDefault` |
override fun <Wrapped> transform(fn: (T) -> Wrapped): ColumnDefault<Wrapped> = | ||
ClientColumnDefaultValue { fn(value()) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
override fun <Wrapped> transform(fn: (T) -> Wrapped): ColumnDefault<Wrapped> = | |
ClientColumnDefaultValue { fn(value()) } | |
override fun <Wrapped> transform(wrap: (T) -> Wrapped): ColumnDefault<Wrapped> = | |
ClientColumnDefaultValue { wrap(value()) } |
override fun <Wrapped> transform(fn: (T) -> Wrapped): ColumnDefault<Wrapped> = | ||
DatabaseColumnDefaultExpressionWithValue(expression as Expression<Wrapped>) { fn(value()) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
override fun <Wrapped> transform(fn: (T) -> Wrapped): ColumnDefault<Wrapped> = | |
DatabaseColumnDefaultExpressionWithValue(expression as Expression<Wrapped>) { fn(value()) } | |
override fun <Wrapped> transform(wrap: (T) -> Wrapped): ColumnDefault<Wrapped> = | |
DatabaseColumnDefaultExpressionWithValue(expression as Expression<Wrapped>) { wrap(value()) } |
fun <Unwrapped, Wrapped> ColumnDefault<Unwrapped>.transform(fn: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> { | ||
@Suppress("UNCHECKED_CAST") | ||
return when (this) { | ||
is TransformableColumnDefault<Unwrapped> -> transform(fn) | ||
else -> this as ColumnDefault<Wrapped> | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fun <Unwrapped, Wrapped> ColumnDefault<Unwrapped>.transform(fn: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> { | |
@Suppress("UNCHECKED_CAST") | |
return when (this) { | |
is TransformableColumnDefault<Unwrapped> -> transform(fn) | |
else -> this as ColumnDefault<Wrapped> | |
} | |
} | |
fun <Unwrapped, Wrapped> ColumnDefault<Unwrapped>.transform(wrap: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> { | |
@Suppress("UNCHECKED_CAST") | |
return when (this) { | |
is TransformableColumnDefault<Unwrapped> -> transform(wrap) | |
else -> this as ColumnDefault<Wrapped> | |
} | |
} |
/** | ||
* Creates new default value based on the transformation function | ||
* | ||
* @param fn The transformation function. | ||
* @return The transformed column default value. | ||
*/ | ||
fun <Wrapped> transform(fn: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/** | |
* Creates new default value based on the transformation function | |
* | |
* @param fn The transformation function. | |
* @return The transformed column default value. | |
*/ | |
fun <Wrapped> transform(fn: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> | |
/** | |
* Creates new default value based on the transformation function | |
* | |
* @param wrap The transformation function. | |
* @return The transformed column default value. | |
*/ | |
fun <Wrapped> transform(wrap: (Unwrapped) -> Wrapped): ColumnDefault<Wrapped> |
How about using "refactor" instead of "chore"? |
/** Returns the function that calculates the default value for this column. */ | ||
var defaultValueFun: (() -> T)? = null | ||
internal var dbDefaultValue: Expression<T>? = null | ||
|
||
/** Returns the default value for this column on the database-side. */ | ||
fun defaultValueInDb() = dbDefaultValue | ||
|
||
internal var isDatabaseGenerated: Boolean = false | ||
|
||
/** Returns whether this column's value will be generated in the database. */ | ||
fun isDatabaseGenerated() = isDatabaseGenerated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be great for users if we could try to go through the standard deprecation cycle where possible, using ReplaceWith
options etc. If the var
is difficult, it should at least be possible for the two functions.
* Represents a database-side column default value generated by expression | ||
* that also contains in-memory kotlin value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably better for JB to capitalise Kotlin
/** | ||
* Represents a database-side generated column default value. | ||
*/ | ||
class DatabaseGeneratedColumnDefault<T> : DatabaseColumnDefault<T> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might make the difference between this class and the other class below even more clear if the KDocs is clarified more explicitly, like Represents a column default that is a database-side value generated by a pre-defined trigger in the database.
/** | ||
* Represents a default value for a column generated by an SQL expression. | ||
*/ | ||
interface ColumnDefaultExpression<T> : ColumnDefault<T> { | ||
val expression: Expression<T> | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the property for this interface and the one below also get KDocs please?
*/ | ||
fun <T> Column<T>.defaultValue() = (default as? ColumnDefaultValue<T>)?.value?.invoke() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving forward I think it would be great if we could explicitly add what the non-obvious return type is for all these functions. Especially for any user potentially reading through the source code without using the IDE.
/** | ||
* Gets the default value of the column if it exists. | ||
* | ||
* @return The default value, or null if none exists. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per comments above, I would clarify that "default value" here specifically means the result of a Kotlin function. Or at least amend the @return
to say something like: or null if the column either has no default or the default is a database-side function
.
/** | ||
* Gets the client-side default value of the column if it exists. | ||
* | ||
* @return The client-side default value, or null if none exists. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As per comments above, I would clarify that "default value" here specifically means the result of a Kotlin function. Or at least amend the @return
to say something like: or null if the column either has no default or the default is a database-side function.
/** | ||
* Gets the client-side default value or expression if it exists. | ||
* | ||
* @return The client-side default value or SQL expression, or null if none exists. | ||
*/ | ||
fun Column<*>.clientDefaultValueOrExpression() = (default as? ClientColumnDefault)?.let { | ||
when (it) { | ||
is ColumnDefaultValue<*> -> it.value() | ||
is ColumnDefaultExpression<*> -> it.expression | ||
else -> null | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the existence of clientDefaultValue()
above, what is the value of including this function now?
From what I've drawn out of the interface hierarchy, it will not be possible currently to ever reach the second when
branch. I'm assuming this is here only in the event we add a new class like ClientColumnDefaultExpression
, so could this wait to be included until then?
col.defaultValueFun != null && col !in writeValues && readValues.hasValue(col) | ||
col.hasClientDefaultValue() && col !in writeValues && readValues.hasValue(col) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an example of some of the confusion I got with the new extension functions.
Why in this case is hasClientDefaultValue()
being used instead of just hasClientDefault()
?
Would it fail if the latter was accidentally used instead?
Should a user be able to easily understand the difference between using the 2 or are they meant to be interchangeable in basic cases?
* The fields `defaultValueFun`, `dbDefaultValue`, and `isDatabaseGenerated` have been consolidated into a single field, | ||
`default`, of type `ColumnDefault`. The previous setup with the three separate fields often led to inconsistencies | ||
and confusion regarding their various combinations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, this document should only include information that is relevant to the user. It's good to justify the rationale behind our decisions, but 2 of these fields are actually internal and will mean nothing to most users.
For the list below, a user would never have used dbDefaultValue != null
or isDatabaseGenerated == true
in their code, so I don't think they need to be noted.
The breaking changes that should be mentioned are how:
Column.defaultValueInDb() -> Column.databaseDefaultExpression()
Column.isDatabaseGenerated() -> Column.hasDatabaseDefault()
// this part I'm not sure how to suggest a migration for...
Column.defaultValueFun -> Column.default ???
Also, is there a migration element that is maybe missing?
What if for example the field defaultValueFun
was being invoked somewhere in a user's logic like:
Column.defaultValueFun?.invoke() -> ???
// what would this become?
// Column.defaultValue() or Column.clientDefaultValue() or Column.clientDefaultValueOrExpression() ?
// would the user need to know the underlying ColumnDefault and cast?
Does there maybe need to something like ColumnDefault.invoke()
that returns the result of invoking the Kotlin function if it exists or throws an error otherwise?
Description
Here is the refactoring of column default values. The main idea is to joine 3 fields of the column related to default values (
defaultValueFun
,defautValueInDb
,isDatabaseGenerated
) into one field.I see 2 main problems with the existing variant:
The solution is the following:
Replace the mentioned fields with one field of the type
ColumnDefault
.We will strongly differentiate
ClientColumnDefault
andDatabaseColumnDefault
independently of the content they have (it could be kotlin values or expressions). If the column hasClientColumnDefault
it will be used for inserts, and will not be used for DDL, and vice versa forDatabaseColumnDefault
.From another perspective column default can be
ColumnDefaultExpression
(containsExpression
),ColumnDefaultValue
(contains kotlin value), no one of them (aka database generated), or both of them (the last one is important for DAO, because even if column has database default expression we still could use kotlin value to provide the value before actual commit).Type of Change
Updates/remove existing public API methods:
Affected databases:
Related Issues
EXPOSED-655 Join column default fields defaultValueFun, defaultValueInDb, isDatabaseGenerated into one fielld