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

fix: EXPOSED-415 SchemaUtils incorrectly generates ALTER statements f… #2136

Merged
merged 4 commits into from
Jul 10, 2024

Conversation

obabichevjb
Copy link
Collaborator

Here is the fix of generation update with SchemaUtils.statementsRequiredToActualizeScheme() for nullable columns.

The problem is that for MariaDB and MySql it's not possible to determine whether the default value of a nullable column was not defined or it was explicitly set to null. Also an issue that (if I'm right for MySql only) for some cases string default value "NULL" was recognized as null.

The main change that I made with this PR is treating any null-like value (non set null, or explicitly set null) as the same for DDL generation purposes. It means that if a user created a table with a nullable column and without a default value and then added explicit .default(null) Exposed will not offer to alter that column. Such a variant is not 100% accurate in terms of synchronization between actual DB and Exposed definitions but looks like follows to the same result.

It was suggested to check if we can take the information about the default values directly from DB (instead of JDBC), but looks like it's the same for MariaDB anyway.

@obabichevjb obabichevjb force-pushed the obabichev/exposed-415-mariadb-wrong-alter-statement branch from 40c4c8e to ecc8a94 Compare June 24, 2024 13:54
@obabichevjb
Copy link
Collaborator Author

Probably, we could apply such a logic to MariaDB only, and still generate DDL with updating nullable columns for other DBs. But in this case I'd say the method JdbcDatabaseMetadataImpl::sanitizedDefault should return different values (if possible) for the cases when the value was not set at all, when it was set to null explicitly, and any other values.

It's definitely possible but would require to introduce at least one more constant (enum/object/..) like DB_EXPLICIT_NULL. But actually not sure that it's practically needed...

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.

There may be a potential issue with the new MariaDB logic and b' defaults. Please see the comments.

Comment on lines -190 to -192
dialect is MysqlDialect || h2Mode == H2CompatibilityMode.MySQL || h2Mode == H2CompatibilityMode.MariaDB -> defaultValue.substringAfter(
"b'"
).trim('\'')
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if we have good enough test coverage to ok removing some of these branches. The h2Modes are probably not going to be an issue, but removing MariaDB from this substring check may cause problems in a few users.

One example I can think of:
In both MySQL and MariaDB, Exposed takes the opinion to store boolean values in BOOLEAN column type instead of BIT. But let's say a user has an existing table already in the DB that uses BIT (or chooses to create one themselves), and they are using their own custom bool() to register and work with this BIT column using Kotlin booleans.
Attempting something like the following would no longer show the same behavior in both DB:

// represents the existing table in db
exec("CREATE TABLE IF NOT EXISTS tester (bool BIT DEFAULT b'0' NOT NULL)")

// represents the identical Exposed definition
object TestTable : Table("tester") {
    val bool = bool("bool").default(false)
}

// when calling SchemaUtils.createMissingTables....
// MySQL - correctly does not generate ALTER statement
// MariaDB - incorrectly does generate ALTER statement

I'd suggest a third branch (if value starts with b' or something) in the new MariaDB logic that does what the original logic did (i.e. what the new MySQL branch does now). Not sure if the mysql H2 modes would also be affected.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added check of 'b for MariaDB also, but without proper test actually.

But what I've found during testing of this is that binary column is almost untested and quite broken.
I added test to check binary column ddl generation, and insert/reading values (with binary literal also, and the binary literal actually). So now it looks better, but PR become a bit bigger..

@obabichevjb obabichevjb force-pushed the obabichev/exposed-415-mariadb-wrong-alter-statement branch from 519f098 to a363d9f Compare July 3, 2024 08:20
@obabichevjb obabichevjb requested a review from bog-walk July 3, 2024 09:00
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 to me. I would still recommend moving the introduction of byteLiteral() and the column changes to its own separate PR (why no byteParam() btw?). It would be easier to look back on in case of anything and it could be merged first if the MariaDB tests depend on it.

Comment on lines 754 to 756

@Suppress("MagicNumber")
private fun ByteArray.toHexString(): String = joinToString("") { it.toString(16).uppercase().padStart(2, '0') }
Copy link
Member

Choose a reason for hiding this comment

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

All this and above functionality already exists within the blob (binary) column type. For example, ExposedBlob has the same function toHexString() that can be extracted and reused.

But if you don't mind the duplicate here, at least nonNullValueToString() should use currentDialect.dataTypeProvider.hexToDb(value.hexString()). From what I'm seeing, existing hexToDb() matches your branches above.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO fix with EXPOSED-433 Check all the literals for default/insert/read operations

Comment on lines 13 to 18
@Test
fun testBinaryColumn() {
val defaultValue = "default-value".toByteArray()
val tester = object : IntIdTable("testBinaryColumn") {
val bin = binary("bin", 256).default(defaultValue)
}
Copy link
Member

Choose a reason for hiding this comment

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

Please also move over existing tests from DDLTests.kt: testBinaryWithoutLength() and testBinary()

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO fix with EXPOSED-433 Check all the literals for default/insert/read operations

Comment on lines 28 to 29
val literalValue = "literal-value".toByteArray()
val id2 = tester.insertAndGetId { it[tester.bin] = binaryLiteral(literalValue) }
Copy link
Member

Choose a reason for hiding this comment

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

Literals are more meant to be used in query building, in WHERE clauses for example, or as column defaults, or where expressions are needed in functions. It might be good to at least test it it 1 of those actual use cases.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

TODO fix with EXPOSED-433 Check all the literals for default/insert/read operations

@obabichevjb obabichevjb force-pushed the obabichev/exposed-415-mariadb-wrong-alter-statement branch from 057aa92 to dba3d98 Compare July 5, 2024 14:10
@obabichevjb obabichevjb requested a review from bog-walk July 8, 2024 05:51
@obabichevjb obabichevjb merged commit 94cfb88 into main Jul 10, 2024
5 checks passed
@obabichevjb obabichevjb deleted the obabichev/exposed-415-mariadb-wrong-alter-statement branch July 10, 2024 06:55
DonRobo pushed a commit to DonRobo/home-former that referenced this pull request 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
[JetBrains/Exposed#2121
- feat: EXPOSED-435 Allow insertReturning() to set isIgnore = true by
[@&#8203;bog-walk](https://github.com/bog-walk) in
[JetBrains/Exposed#2148
- feat: EXPOSED-77 Support entity class for table with composite primary
key by [@&#8203;bog-walk](https://github.com/bog-walk) in
[JetBrains/Exposed#1987
- feat: EXPOSED-446 Support N-column inList equality comparisons by
[@&#8203;bog-walk](https://github.com/bog-walk) in
[JetBrains/Exposed#2157
- feat: EXPOSED-450 Merge command: PostgreSQL improvements by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[JetBrains/Exposed#2161
- feat: EXPOSED-388 Support for column type converters by
[@&#8203;obabichevjb](https://github.com/obabichevjb) in
[JetBrains/Exposed#2143
- Adding comment text for a query SQL by
[@&#8203;xJoeWoo](https://github.com/xJoeWoo) in
[JetBrains/Exposed#2088
- feat: EXPOSED-459 Open AbstractQuery.copyTo() to allow custom Query
class extension by [@&#8203;bog-walk](https://github.com/bog-walk) in
[JetBrains/Exposed#2173
- feat: EXPOSED-461 Add time column in Joda-Time module by
[@&#8203;joc-a](https://github.com/joc-a) in
[JetBrains/Exposed#2175

Bug fixes:

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

Docs:

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

incorrectDefaults in SchemaUtils is always true for nullable columns without exposed defaults in MySQL/MariaDB
3 participants