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

optReference column should allow update { it[column] = nullableValue } #1275

Closed
vlsi opened this issue Jun 18, 2021 · 9 comments · May be fixed by #1277
Closed

optReference column should allow update { it[column] = nullableValue } #1275

vlsi opened this issue Jun 18, 2021 · 9 comments · May be fixed by #1277

Comments

@vlsi
Copy link

vlsi commented Jun 18, 2021

See Users#cityId

object Cities : Table() {
    val id = integer("id").autoIncrement() // Column<Int>
    val name = varchar("name", 50) // Column<String>

    override val primaryKey = PrimaryKey(id, name = "PK_Cities_ID")
}

object Users : Table() {
    val id = varchar("id", 10) // Column<String>
    val name = varchar("name", length = 50) // Column<String>
    val cityId = optReference("city_id", Cities.id) // Column<Int?>

    override val primaryKey = PrimaryKey(id, name = "PK_User_ID") // name is optional here
}

Expected:

Users.insert {
    it[cityId] = null // should work, but it does not
    it[cityId] = 42 // should work, but it does not
}

Actual: code does not compile.

I suspect the issue is with UpdateBuilder#setWithEntityIdValue:

@JvmName("setWithEntityIdValue")
operator fun <S : Comparable<S>, ID : EntityID<S>, E : S?> set(column: Column<ID>, value: E) {
require(!values.containsKey(column)) { "$column is already initialized" }
column.columnType.validateValueBeforeUpdate(value)
values[column] = value
}

The type of optReference(...) is Column<EntityID<...>?> which is not compatible with ID : EntityID<E>.

It looks like the workaround is to permit Column<ID?>.
For instance,

operator fun <S : Comparable<S>, ID : EntityID<S>?, E : S?> set(column: Column<ID>, value: E)

Unfortunately, that still allows users to store null to non-nullable columns (however, even the current code allows them to do so).

@vlsi
Copy link
Author

vlsi commented Jun 18, 2021

Workaround is

@Suppress("UNCHECKED_CAST")
operator fun <T, S : Comparable<S>, ID : EntityID<S>?, E : S?> UpdateBuilder<T>.set(column: Column<ID>, value: E) =
    // see https://github.com/JetBrains/Exposed/issues/1275
    set(column as Column<EntityID<S>>, value)

@vlsi
Copy link
Author

vlsi commented Jun 19, 2021

A sad case is

Table2.update {
    it[table1] = null // table1 is optional reference
}

both overloads set(Column<S>, S) and set(Column<EntityID<S>?>, S?) suit :-/

vlsi added a commit to vlsi/Exposed that referenced this issue Jun 19, 2021
Key change is UpdateBuilder#setWithEntityIdValue, which now accepts
column: Column<out EntityID<S>?>, value: S?
previous signature was
column: Column<out EntityID<S>>, value: S?

A downside is that "[optionalReferenceColumn] = null" can't decide between
"null as ColumnType?" and "null as EntityID<ColumnType>?"

update {
  // it[optionalReferenceColumn] = null // <- does not work
  // Workaround: specify null type explicitly to call the proper overload
  it[optionalReferenceColumn] = null as EntityID<ColumnType>?
  // The following works as well:
  // it[optionalReferenceColumn] = null as ColumnType?
}

fixes JetBrains#1275
vlsi added a commit to vlsi/Exposed that referenced this issue Jun 19, 2021
Key change is UpdateBuilder#setWithEntityIdValue, which now accepts
column: Column<out EntityID<S>?>, value: S?
previous signature was
column: Column<out EntityID<S>>, value: S?

A downside is that "[optionalReferenceColumn] = null" can't decide between
"null as ColumnType?" and "null as EntityID<ColumnType>?"

update {
  // it[optionalReferenceColumn] = null // <- does not work
  // Workaround: specify null type explicitly to call the proper overload
  it[optionalReferenceColumn] = null as EntityID<ColumnType>?
  // The following works as well:
  // it[optionalReferenceColumn] = null as ColumnType?
}

fixes JetBrains#1275
vlsi added a commit to vlsi/Exposed that referenced this issue Jun 19, 2021
Key change is UpdateBuilder#setWithEntityIdValue, which now accepts
column: Column<out EntityID<S>?>, value: S?
previous signature was
column: Column<out EntityID<S>>, value: S?

A downside is that "[optionalReferenceColumn] = null" can't decide between
"null as ColumnType?" and "null as EntityID<ColumnType>?"

update {
  // it[optionalReferenceColumn] = null // <- does not work
  // Workaround: specify null type explicitly to call the proper overload
  it[optionalReferenceColumn] = null as EntityID<ColumnType>?
  // The following works as well:
  // it[optionalReferenceColumn] = null as ColumnType?
}

fixes JetBrains#1275
vlsi added a commit to vlsi/Exposed that referenced this issue Jun 19, 2021
Key change is UpdateBuilder#setWithEntityIdValue, which now accepts
column: Column<out EntityID<S>?>, value: S?
previous signature was
column: Column<out EntityID<S>>, value: S?

A downside is that "[optionalReferenceColumn] = null" can't decide between
"null as ColumnType?" and "null as EntityID<ColumnType>?"

update {
  // it[optionalReferenceColumn] = null // <- does not work
  // Workaround: specify null type explicitly to call the proper overload
  it[optionalReferenceColumn] = null as EntityID<ColumnType>?
  // The following works as well:
  // it[optionalReferenceColumn] = null as ColumnType?
}

fixes JetBrains#1275
vlsi added a commit to vlsi/Exposed that referenced this issue Jun 19, 2021
Key change is UpdateBuilder#setWithEntityIdValue, which now accepts
column: Column<out EntityID<S>?>, value: S?
previous signature was
column: Column<out EntityID<S>>, value: S?

A downside is that "[optionalReferenceColumn] = null" can't decide between
"null as ColumnType?" and "null as EntityID<ColumnType>?"

update {
  // it[optionalReferenceColumn] = null // <- does not work
  // Workaround: specify null type explicitly to call the proper overload
  it[optionalReferenceColumn] = null as EntityID<ColumnType>?
  // The following works as well:
  // it[optionalReferenceColumn] = null as ColumnType?
}

fixes JetBrains#1275
@MiaYi
Copy link

MiaYi commented Nov 30, 2021

Is there any update on this? or a temporary solution to update optional reference with a simple id value?

@Tapac Tapac closed this as completed Dec 4, 2021
Tapac added a commit that referenced this issue Dec 5, 2021
tpasipanodya added a commit to tpasipanodya/exposed that referenced this issue Dec 23, 2021
* Libs update:
kotlin 1.6.0
kotlin coroutines 1.6.0-RC
kotlinx-datetime-jvm 0.3.1
Spring framework 5.3.13
Spring boot 2.6.1

* Add composite foreign key (JetBrains#1385)

* optReference column should allow update { it[column] = nullableValue } JetBrains#1275 / Fix test on SQLServer

* Performance: cache enumConstants as it makes copy on access

* Use h2 version variable in exposed-money (JetBrains#1402)

* Better handling for opened result sets + logging

* Add H2 version 2.0.202 dialect (JetBrains#1397)

* Detekt 1.19.0

* Add H2 version 2.0.202 dialect / Fixes for test configuration

* Add H2 version 2.0.202 dialect / Fixes for test configuration #2

* Add H2 version 2.0.202 dialect / Fixes for bitwise operations

* Add composite foreign key (JetBrains#1385) / Fix test for SQLServer

Co-authored-by: Andrey.Tarashevskiy <[email protected]>
Co-authored-by: naftalmm <[email protected]>
Co-authored-by: Philip Wedemann <[email protected]>
@vlsi
Copy link
Author

vlsi commented Jan 2, 2022

I tried Exposed 0.37.3, and now it forbids nullable value for nullable foreign key fields.

For instance:

val optionalInt: Int? = 42
Users.insert {
    it[cityId] = optionalInt // should work, however fails with Type mismatch: inferred type is String? but TypeVariable(S) was expected
}

@vlsi
Copy link
Author

vlsi commented Jan 4, 2022

@Tapac , I'm inclined the issue should be reopened as Exposed still does not allow it[column] = nullableValue syntax.

@StefanLiebig
Copy link

Is there a workaround to achieve that?

@vlsi
Copy link
Author

vlsi commented Mar 10, 2022

@StefanHUB , my current workarounds (exposed 0.37.3) are

fun<ID: Comparable<ID>, T : Entity<ID>> EntityClass<ID, T>.testCache(id: ID) =
    testCache(EntityID(id, table))

@Suppress("UNCHECKED_CAST")
operator fun <T, S : Comparable<S>, ID : EntityID<S>?, E : S?> UpdateBuilder<T>.set(column: Column<ID>, value: E) =
    // see https://github.com/JetBrains/Exposed/issues/1275
    set(column as Column<S?>, value)

@StefanLiebig
Copy link

@vlsi Thanks. This works.

@vlsi
Copy link
Author

vlsi commented Jul 27, 2022

My previous WA included a bug: it allowed to assign nullable value to a non-nullable column.

The better WA is as follows:

@Suppress("UNCHECKED_CAST")
operator fun <T, S : Comparable<S>, ID : EntityID<S>> UpdateBuilder<T>.set(column: Column<ID?>, value: S?) =
    // see https://github.com/JetBrains/Exposed/issues/1275
    set(column as Column<S?>, value)

The same pattern works for "custom casts".

For instance, my database uses numeric(20) keys, however, it is convenient to use Longs in tests (since most of the time, the keys fit in Long).

So I created the following:

@JvmName("setEntityIdLong")
operator fun <T, ID: EntityID<BigDecimal>> UpdateBuilder<T>.set(column: Column<ID>, value: Long) =
    set(column, BigDecimal(value))

@Suppress("UNCHECKED_CAST")
@JvmName("setEntityIdLongNullable")
operator fun <T, ID: EntityID<BigDecimal>> UpdateBuilder<T>.set(column: Column<ID?>, value: Long?) =
    // see https://github.com/JetBrains/Exposed/issues/1275
    set(column as Column<BigDecimal?>, value?.let { BigDecimal(it) })

It enables "automatic" cast to BigDecimal in expressions like

Objects.merge {
    it[id] = 9164178420001991212 // id is decimal(...) column (which maps to BigDecimal)
}

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 a pull request may close this issue.

4 participants