Skip to content
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

feat!: EXPOSED-388 Support for column type converters #2143

Merged
merged 8 commits into from
Jul 23, 2024

Conversation

obabichevjb
Copy link
Collaborator

@obabichevjb obabichevjb commented Jun 28, 2024

Description

Here is the PR that allows to transform columns from their original types to another.

Transformations could be nested, and could be applied to nullable columns. Transformation of a non-nullable column produces a non-nullable column, and transformation of a nullable column produces a nullable one. Creating nullable columns requires not only types changing, but also set field nullable into true, so still the method nullable() must be used.

We already have transform() for columns on the DAO layer, so I took naming from there (toColumn, toReal methods):

.transform(
    toColumn: (TReal) -> TColumn,
    toReal: (TColumn) -> TReal
)

But probably it's not the perfect variant. I was initially thinking about Target/Source instead of TReal/TColumn respectively. If you prefer the first variant (or have other suggestions) I'd be happy to know. It would be breaking change, but it could be changed for DAO as well if needed.

Breaking changes

  • DAO Entity Transformation Changes
    • Parameter Renaming: transform() and memoizedTransform() now use wrap and unwrap instead of toColumn and toReal.
      // Old:
      var name by EmployeeTable.name.transform(toColumn = { it.uppercase() }, toReal = { it.lowercase() })
      // New:
      var name by EmployeeTable.name.transform(wrap = { it.uppercase() }, unwrap = { it.lowercase() })
    • Class Renaming: ColumnWithTransform is now EntityFieldWithTransform, consolidating properties into a single transformer.
      EntityFieldWithTransform(column, object : ColumnTransformer<String, Int> {
              override fun unwrap(value: Int): String = value.toString()
              override fun wrap(value: String): Int = value.toInt()
          })
    • Entity transformation via DAO is deprecated and should be replaced with DSL transformation.
      val tester = object : Table() {
              val value = integer("value")
                  .transform(wrap = { ... }, unwrap = { ... })
          }

@obabichevjb obabichevjb requested review from bog-walk and e5l June 28, 2024 08:57
@obabichevjb obabichevjb force-pushed the obabichev/epxosed-388-column-type-converters branch 2 times, most recently from cdb1d3f to 88b2caa Compare June 28, 2024 09:01
Comment on lines 256 to 277
interface ColumnTransformer<TReal : Any, TColumn : Any> {
/**
* Transforms a column value from the underlying `ColumnType` into the real type.
*/
fun toReal(value: TColumn): TReal

/**
* Transforms a real type value into a column value.
*/
fun toColumn(value: TReal): TColumn
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the plan for this feature in relation to the existing DAO ColumnWithTransform? Could that class make use of this transformer instead?

And given how transform already works for Entity field columns, if the user does not choose to use memoizedTransform() for example, could defining the transformation on the table object actually replace defining the transformation on the entity class?

Also, would using the 2 in parallel work ok? For example, defining a column with core/DSL transform() in table object. Then defining entity class with a field for that column, which then uses a DAO transform() on every read. Would it create similar behavior as in the nested transformations test?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, DAO transform and DSL transform are independent.

The main difference is that on the DSL level it's necessary to make column replacement with a new column type. It's needed because during reading/writing values go through the underlying ColumnType, so table object must contain the column with the proper column type.

I was trying to make the transform without replacing column but haven't found a way to do that, if you probably have any ideas how it could be done, I'd be happy to know about it).

From the DAO's perspective transformed DSL column is still just a column, so DAO works as before. I added a test testDaoTransformWithDslTransform() to show that DAO and DSL transforms could be mixed.

/**
* An interface defining the transformation between a database column type and a real type.
*
* @param TReal The real type to which the column value is transformed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to define the Real type. It's not clear what it is in the air.

I would also check what names are used in applications for that. There are already some known, like Domain, Model, ...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took these names from the existing ColumnWithTransform class. But we may think about complete renaming of these types. What do you think about TOuter/TInner (and methods toOuter()/toInner() respectively), or TTransformed/TOriginal, or TWrapper/TColumn or something different?

fun toColumn(value: TReal): TColumn
}

class ColumnTransformerImpl<TReal, TColumn>(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please consider making it private

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in both: DSL transform, and DAO transform which are in different packages. I could copy it and make private in both packages.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or it can be internal

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the first thing I tried to do, but it is not reachable from the exposed-dao module..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another variant is in ktorm:

fun <C : Any, R : Any> Column<C>.transform(
    fromUnderlyingValue: (C) -> R,
    toUnderlyingValue: (R) -> C
): Column<R>

Hibernate also has similar concept, but it applies modification directly to the sql, and looks like the following annotation:

@ColumnTransformer(
  read="decrypt(credit_card_num)" 
  write="encrypt(?)"
)
String creditCardNumber;

In SqlDelight there is a similar to our transform thing called ColumnAdapter:

object : ColumnAdapter<List<String>, String> {
  override fun decode(databaseValue: String) =
    if (databaseValue.isEmpty()) listOf() else databaseValue.split(",")
  override fun encode(value: List<String>) = value.joinToString(separator = ",")
}

@obabichevjb
Copy link
Collaborator Author

Updated PR. I tried to check how similar APIs look in other libraries and tried to make it more consistent inside Exposed.

At the current moment I stopped at the variant of type names for transformation as Source/Dest pair; we already have such namings across other methods, and it looks reasonable for generic transformation.

But for more specific cases there are changes:

  • When transformation is applied to the column API looks like fun <TColumn : Any, Target : Any> Column<TColumn>.transform(transformer: ColumnTransformer<TColumn, Target>): Column<Target>, so it's clearer where is the original column value, and where is the transformed one
  • When transformation applied to already transformed entity field API looks like fun <TColumn : Any?, CurrentTarget : Any?, NextTarget : Any?> EntityFieldWithTransform<TColumn, CurrentTarget>.transform(toCurrentTarget: (NextTarget) -> CurrentTarget, toNextTarget: (CurrentTarget) -> NextTarget): EntityFieldWithTransform<TColumn, NextTarget>. In this case Target is split into CurrentTarget and NextTarget.

Also in all the generic interfaces/methods the source/column type is the first. It was not aligned before.

One more change is the renaming of base transformation class for DSL into ColumnWithTransform, and for DAO into EntityFieldWithTransform.

@obabichevjb obabichevjb requested review from e5l and bog-walk July 9, 2024 13:37
Comment on lines 250 to 270
/**
* An interface defining the transformation between a source column type and a target type.
*
* @param Target The type of the column values after transformation
* @param Source The type of the column values without transformation
*/
interface ColumnTransformer<Source, Target> {
/**
* Applies transformation to the underlying column value ([Source] -> [Target])
*/
fun toTarget(value: Source): Target

/**
* Applies back transformation to the value of the transformed type [Target] to the column type [Source] ([Target] -> [Source])
*/
fun toSource(value: Target): Source
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think this may be quite confusing. Reading the function names alone, it's not immediately clear which value is meant for the database. With the KDocs it does help imply (but I had to really think) that Source means what is actually stored in the db and Target means the value used client-side?

I get that the original toReal() was also vague, but maybe we could still consider other options?
Something like toColumnValue + toTransformedValue,toColumnValue + fromColumnValue or some variant of read + write like readFromColumn + writeToColumn?

Comment on lines 1212 to 1215
fun <TColumn : Any, Target : Any> Column<TColumn>.transform(
toTarget: (TColumn) -> Target,
toColumn: (Target) -> TColumn
): Column<Target> = transform(ColumnTransformerImpl(toColumn, toTarget))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that transform() doesn't actually have to be used with a ColumnTransformer object, but it still seems misaligned to me that the parameter names and interface method names are only partially matched:
toTarget -> toTarget()
toColumn -> toSource()

Maybe if both were completely different it would be better, but since 1 of them is the same, shouldn't they both match?

Comment on lines 577 to 579
fun <TColumn : Any?, Target : Any?> Column<TColumn>.transform(
transformer: ColumnTransformer<TColumn, Target>
): EntityFieldWithTransform<TColumn, Target> = EntityFieldWithTransform(this, transformer, false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the introduction of transform() in DSL, I'm still not sure that I see the value of DAO's transform() being kept. They serve the same purpose (except for DAO's additional caching to avoid multiple expensive transformations) and they have the same name. At the moment, I can't think of any other example in the API that has separate DSL and DAO components that are identically named and also so close in functionality.

For example, using the DAO test file ColumnWithTransformTest.kt (of course without the new DSL+DAO unit tests):
If I cut & paste all the transform blocks from each entity class and place them in the respective table object instead, all tests still pass. So our current tests at least get the same result using the new functionality on the DSL table instead of the old functionality on the entity class.

I guess I just still don't have an answer to my original question: Why would/should a user choose transform on Entity instead of transform on Table?
If the answer is, just for potential memoisation, we could still enable that through the new DSL implementation by leaving the actual transformation logic in the core module only, deprecating the DAO transform(), & reducing the DAO EntityFieldWithTransform to only having the caching logic (& renaming function to something like withTransformation()).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it looks the opposite way: I usually choose if I need Entity or Table and next stick with what I have. Let's discuss it anyway

Copy link
Member

@e5l e5l left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, except for two questions for the design:

  • naming
  • Entity and Table transform behavior

@obabichevjb, could you find a slot for us to discuss this?

object Meals : Table() {
val mealTime: Column<Meal> = time("meal_time")
.transform(
toTarget = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss the naming. Please define the Target upfront

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to have a symmetric name for toColumn, I would go with toUserType.

There are also other options we can discuss:

  • pack / unpack
  • save / load
  • encode / decode
  • wrap / unwrap
  • persist / retrieve
  • store / fetch

Comment on lines 577 to 579
fun <TColumn : Any?, Target : Any?> Column<TColumn>.transform(
transformer: ColumnTransformer<TColumn, Target>
): EntityFieldWithTransform<TColumn, Target> = EntityFieldWithTransform(this, transformer, false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me it looks the opposite way: I usually choose if I need Entity or Table and next stick with what I have. Let's discuss it anyway

@obabichevjb
Copy link
Collaborator Author

I would sum up the questions for the discussion:

  • Do we need transformation for both DSL and DAO, probably we need it only for DSL?
  • Naming:
    • Transformation methods
    • Generic types (first transform and chained transform cases)

Naming

The naming toSource/toTarget most chosen as a most generic to fit dsl column transform, entity column transform, and chained transformations. Another variant I had before (but finally changed) was toColumn/toTransformed, but it was looking a bit weird in the case of chained transformations.

So I made a generic transformation interface and specific parameters for specific cases. That's why transform() methods have toColumn/toTarget for the first call and toCurrentTarget/toNextTarget for the second one. But it looks like it's over complicated and not so meaningful.

The method name toColumn I chose by analogy with IColumnType::valueToDB, so the second variant I liked was to name it like in IColumnType, it could toColumn/fromColumn or valueToColumn/valueFromColumn. One more question in this case would be in the names of generic types. There are also several options (during implementation I wanted several times to write TTransformed but it looks weird..) like TTransformed,TransformedType,TransformedValue/TColumn,ColumnType,ColumnValue. TColumn is a good variant because we already use it in several places, but paired TTransformed looks weird.

From the comment above the variant wrap/unwrap looks interesting. It could be used with types TWrapped/TColumn.

DSL/DAO

I also do not see something blocking from removing DAO's transform. Anyway we could mark it as deprecated and check if somebody complains about the case that could not be covered with DSL's only variant.

Potential reasons to keep both variants I see:

  • Using both DSL and DAO layers independently. Exposed allows to mis DSL and DAO in the code, so it's possible that for some cases (like scripting over the database) user may want to have original types, but for application logic transformed, in this case DAO one would be still needed.
  • Adding DAO to the existing application. In this case it could be complicated to add transformation to the DSL table, but could be added without problems to the DAO entity.

@obabichevjb obabichevjb self-assigned this Jul 17, 2024
@obabichevjb obabichevjb force-pushed the obabichev/epxosed-388-column-type-converters branch 4 times, most recently from c61be39 to 95640af Compare July 19, 2024 09:04
@obabichevjb obabichevjb force-pushed the obabichev/epxosed-388-column-type-converters branch from 95640af to 6fd8b5c Compare July 19, 2024 13:17
@obabichevjb
Copy link
Collaborator Author

Updated PR.

Methods toTarget()/toColumn(), toTarget()/toSource() (and original toReal()/toColumn()) were renamed to wrap/unwrap.
Generic types Source/Target, TColumn/TReal were renamed to Unwrapped/Wrapped

@obabichevjb obabichevjb requested review from bog-walk and e5l July 22, 2024 06:40
Copy link
Member

@bog-walk bog-walk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, just some minor KDocs comments. Oh and potential breaking change point.

Comment on lines 261 to 264
/**
* Applies back transformation to the value of the transformed type [Wrapped] to the column type [Unwrapped] ([Wrapped] -> [Unwrapped])
*/
fun unwrap(value: Wrapped): Unwrapped
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The double "transform" and double "to'" is a bit confusing. I'd just suggest something like any of the following:

  • "Returns the underlying column value without a transformation applied"
  • "Reverts the value of the transformed type and returns the underlying column type"
  • "Applies a reverse transformation on the value of the transformed type and returns the underlying column type"
  • "Applies a reverse transformation on the value of the transformed type returning it to the column type"

* ```
*
* @param Wrapped The type into which the value of the underlying column will be transformed.
* @param Unwrapped The source type of the column.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to be consistent with all the other @params in the functions below:
@param Unwrapped The type of the original column.

It's also more clear.

Comment on lines 739 to 740
"DAOs transform() is deprecated and will be removed in future releases. " +
"Please use the transform function from the DSL layer.",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the goal is to see what feedback we get from users, I'd also suggest adding a line like:
"Please log a request on YouTrack if a use-case cannot be sufficiently covered by the DSL transform()."

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a remark, the ReplaceWith code seems to be completely useless to convert code like

var finishedAt by AdvisorJobsTable.finishedAt.transform({ it?.toDatabasePrecision() }, { it })

which creates

var finishedAt by run {
    { it?.toDatabasePrecision() }
    { it }
    object : Table() {
        val c = column().transform(wrap, unwrap)
    }
}

that does not compile.

@obabichevjb obabichevjb merged commit 94e8576 into main Jul 23, 2024
5 checks passed
@obabichevjb obabichevjb deleted the obabichev/epxosed-388-column-type-converters branch July 23, 2024 08:27
DonRobo referenced this pull request in DonRobo/home-former Aug 4, 2024
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
|
[org.jetbrains.exposed:exposed-jdbc](https://github.com/JetBrains/Exposed)
| `0.52.0` -> `0.53.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.exposed:exposed-jdbc/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.exposed:exposed-jdbc/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.exposed:exposed-jdbc/0.52.0/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.exposed:exposed-jdbc/0.52.0/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
|
[org.jetbrains.exposed:exposed-core](https://github.com/JetBrains/Exposed)
| `0.52.0` -> `0.53.0` |
[![age](https://developer.mend.io/api/mc/badges/age/maven/org.jetbrains.exposed:exposed-core/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/maven/org.jetbrains.exposed:exposed-core/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/maven/org.jetbrains.exposed:exposed-core/0.52.0/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/maven/org.jetbrains.exposed:exposed-core/0.52.0/0.53.0?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|

---

> [!WARNING]
> Some dependencies could not be looked up. Check the Dependency
Dashboard for more information.

---

### Release Notes

<details>
<summary>JetBrains/Exposed
(org.jetbrains.exposed:exposed-jdbc)</summary>

###
[`v0.53.0`](https://github.com/JetBrains/Exposed/blob/HEAD/CHANGELOG.md#0530)

[Compare
Source](https://github.com/JetBrains/Exposed/compare/0.52.0...0.53.0)

Infrastructure:

-   SQLite driver 3.46.0.1
-   Spring Framework 6.1.11
-   Spring Boot 3.3.2
-   junit-bom 5.10.3

Features:

- feat: Add time extension function for temporal expressions in Kotlin
and Java by [@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2121](https://github.com/JetBrains/Exposed/pull/2121)
- feat: EXPOSED-435 Allow insertReturning() to set isIgnore = true by
[@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2148](https://github.com/JetBrains/Exposed/pull/2148)
- feat: EXPOSED-77 Support entity class for table with composite primary
key by [@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/1987](https://github.com/JetBrains/Exposed/pull/1987)
- feat: EXPOSED-446 Support N-column inList equality comparisons by
[@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2157](https://github.com/JetBrains/Exposed/pull/2157)
- feat: EXPOSED-450 Merge command: PostgreSQL improvements by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[https://github.com/JetBrains/Exposed/pull/2161](https://github.com/JetBrains/Exposed/pull/2161)
- feat: EXPOSED-388 Support for column type converters by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[https://github.com/JetBrains/Exposed/pull/2143](https://github.com/JetBrains/Exposed/pull/2143)
- Adding comment text for a query SQL by
[@&#8203;xJoeWoo](https://github.com/xJoeWoo) in
[https://github.com/JetBrains/Exposed/pull/2088](https://github.com/JetBrains/Exposed/pull/2088)
- feat: EXPOSED-459 Open AbstractQuery.copyTo() to allow custom Query
class extension by [@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2173](https://github.com/JetBrains/Exposed/pull/2173)
- feat: EXPOSED-461 Add time column in Joda-Time module by
[@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2175](https://github.com/JetBrains/Exposed/pull/2175)

Bug fixes:

- fix: EXPOSED-424 ClassCastException exception when using
`fetchBatchedResults` with `alias` by
[@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2140](https://github.com/JetBrains/Exposed/pull/2140)
- fix: EXPOSED-407 compositeMoney() nullability definition issues by
[@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2137](https://github.com/JetBrains/Exposed/pull/2137)
- fix: EXPOSED-415 SchemaUtils incorrectly generates ALTER statements
for existing nullable columns by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[https://github.com/JetBrains/Exposed/pull/2136](https://github.com/JetBrains/Exposed/pull/2136)
- fix: EXPOSED-363 LocalTime and literal(LocalTime) are not the same by
[@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2152](https://github.com/JetBrains/Exposed/pull/2152)
- fix: EXPOSED-432 CurrentDate default is generated as null in MariaDB
by [@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2149](https://github.com/JetBrains/Exposed/pull/2149)
- fix: Allow column reference in default expressions for MySQL and
MariaDB by [@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2159](https://github.com/JetBrains/Exposed/pull/2159)
- fix: EXPOSED-430 Insert and BatchInsert do not return default values
by [@&#8203;obabichevjb](https://github.com/obabichevjb) in
[https://github.com/JetBrains/Exposed/pull/2158](https://github.com/JetBrains/Exposed/pull/2158)
- fix: EXPOSED-452 Flaky H2\_Oracle test
`testTimestampWithTimeZoneDefaults` by
[@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2169](https://github.com/JetBrains/Exposed/pull/2169)
- EXPOSED-457 The column default value always compares unequal by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[https://github.com/JetBrains/Exposed/pull/2170](https://github.com/JetBrains/Exposed/pull/2170)
- EXPOSED-409 Custom primary key. Access to the primary key fails with
ClassCastException by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[https://github.com/JetBrains/Exposed/pull/2151](https://github.com/JetBrains/Exposed/pull/2151)
- fix: EXPOSED-447 Eager loading does not work with composite PK entity
by [@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2177](https://github.com/JetBrains/Exposed/pull/2177)

Docs:

- chore: Add migration sample by
[@&#8203;joc-a](https://github.com/joc-a) in
[https://github.com/JetBrains/Exposed/pull/2144](https://github.com/JetBrains/Exposed/pull/2144)
- docs: Change repetitionAttempts to maxAttempts in website docs by
[@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2164](https://github.com/JetBrains/Exposed/pull/2164)
- docs: EXPOSED-445 Add documentation for DSL & DAO composite primary
keys by [@&#8203;bog-walk](https://github.com/bog-walk) in
[https://github.com/JetBrains/Exposed/pull/2165](https://github.com/JetBrains/Exposed/pull/2165)
- docs: EXPOSED-419 Rework the getting started tutorial by
[@&#8203;vnikolova](https://github.com/vnikolova) in
[https://github.com/JetBrains/Exposed/pull/2160](https://github.com/JetBrains/Exposed/pull/2160)
- Configure API documentation for Exposed by
[@&#8203;e5l](https://github.com/e5l) in
[https://github.com/JetBrains/Exposed/pull/2171](https://github.com/JetBrains/Exposed/pull/2171)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined),
Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you
are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about these
updates again.

---

- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box

---

This PR was generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View the
[repository job
log](https://developer.mend.io/github/DonRobo/home-former).

<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy40NDAuNyIsInVwZGF0ZWRJblZlciI6IjM3LjQ0MC43IiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6W119-->

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
@obabichevjb obabichevjb changed the title feat: EXPOSED-388 Support for column type converters feat!: EXPOSED-388 Support for column type converters Oct 2, 2024
@yassenb
Copy link

yassenb commented Nov 6, 2024

This is not a proper replacement for the DAO transform! I have code that transforms one column's value using another column's value (or any DAO property for that matter). The point of having the DAO is moving the complex business domain logic there and I don't see why transformations shouldn't be able to take advantage of that. Please revert the depracation of the DAO transform

@obabichevjb
Copy link
Collaborator Author

@yassenb Thank you a lot for the comment, to be honest it was not expected that transform allows to make transformation based on the whole entity. I'll take it into account and rethink on what we'll do with the whole feature.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants