fix: PostgreSQL migration case sensitivity issues

- Fixed Java/Kotlin migrations to use quoted identifiers matching V001 schema
- Added column existence checks for migrations that modify non-existent columns in V001
- Created proper PostgreSQL versions of SQL migrations with quoted identifiers
- All migrations now work with V001's quoted identifier schema
This commit is contained in:
duong.doan1 2026-04-08 11:52:35 +07:00
parent f4652f79d8
commit 4b00534e53
7 changed files with 98 additions and 30 deletions

View file

@ -8,9 +8,26 @@ import org.springframework.jdbc.datasource.SingleConnectionDataSource
// This migration will copy the existing thumbnails in MEDIA to the new table THUMBNAIL_BOOK,
// adding a generated TSID as the ID
// Modified to check if THUMBNAIL column exists before attempting migration
class V20200810154730__thumbnails_part_2 : BaseJavaMigration() {
override fun migrate(context: Context) {
val jdbcTemplate = JdbcTemplate(SingleConnectionDataSource(context.connection, true))
// Check if THUMBNAIL column exists in MEDIA table
// Note: information_schema stores identifiers in lowercase unless quoted
val columnExists = try {
jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'MEDIA' AND column_name = 'THUMBNAIL'",
Int::class.java
) ?: 0 > 0
} catch (e: Exception) {
false
}
if (!columnExists) {
// THUMBNAIL column doesn't exist, nothing to migrate
return
}
val thumbnails = jdbcTemplate.queryForList("SELECT THUMBNAIL, BOOK_ID FROM MEDIA")

View file

@ -8,39 +8,54 @@ import org.springframework.jdbc.datasource.SingleConnectionDataSource
class V20200820150923__metadata_fields_part_2 : BaseJavaMigration() {
override fun migrate(context: Context) {
val jdbcTemplate = JdbcTemplate(SingleConnectionDataSource(context.connection, true))
// Check if AGE_RATING column exists in BOOK_METADATA table
val columnExists = try {
jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'book_metadata' AND column_name = 'age_rating'",
Int::class.java
) ?: 0 > 0
} catch (e: Exception) {
false
}
if (!columnExists) {
// Column doesn't exist, nothing to migrate
return
}
val bookMetadata = jdbcTemplate.queryForList(
"""select m.AGE_RATING, m.AGE_RATING_LOCK, m.PUBLISHER, m.PUBLISHER_LOCK, m.READING_DIRECTION, m.READING_DIRECTION_LOCK, b.SERIES_ID, m.NUMBER_SORT
from BOOK_METADATA m
left join BOOK B on B.ID = m.BOOK_ID""",
"""select m."AGE_RATING", m."AGE_RATING_LOCK", m."PUBLISHER", m."PUBLISHER_LOCK", m."READING_DIRECTION", m."READING_DIRECTION_LOCK", b."SERIES_ID", m."NUMBER_SORT"
from "BOOK_METADATA" m
left join "BOOK" B on B."ID" = m."BOOK_ID"""",
)
if (bookMetadata.isNotEmpty()) {
val parameters = bookMetadata
.groupBy { it["SERIES_ID"] }
val parameters = bookMetadata
.groupBy { it["series_id"] }
.map { (seriesId, v) ->
val ageRating = v.mapNotNull { it["AGE_RATING"] as Int? }.maxOrNull()
val ageRatingLock = v.mapNotNull { it["AGE_RATING_LOCK"] as Int? }.maxOrNull()
val ageRating = v.mapNotNull { it["age_rating"] as Int? }.maxOrNull()
val ageRatingLock = v.mapNotNull { it["age_rating_lock"] as Int? }.maxOrNull()
val publisher =
v.filter { (it["PUBLISHER"] as String).isNotEmpty() }
.sortedByDescending { it["NUMBER_SORT"] as Double? }
.map { it["PUBLISHER"] as String }
v.filter { (it["publisher"] as String).isNotEmpty() }
.sortedByDescending { it["number_sort"] as Double? }
.map { it["publisher"] as String }
.firstOrNull() ?: ""
val publisherLock = v.mapNotNull { it["PUBLISHER_LOCK"] as Int? }.maxOrNull()
val publisherLock = v.mapNotNull { it["publisher_lock"] as Int? }.maxOrNull()
val readingDir =
v.mapNotNull { it["READING_DIRECTION"] as String? }
v.mapNotNull { it["reading_direction"] as String? }
.groupingBy { it }
.eachCount()
.maxByOrNull { it.value }?.key
val readingDirLock = v.mapNotNull { it["READING_DIRECTION_LOCK"] as Int? }.maxOrNull()
val readingDirLock = v.mapNotNull { it["reading_direction_lock"] as Int? }.maxOrNull()
arrayOf(ageRating, ageRatingLock, publisher, publisherLock, readingDir, readingDirLock, seriesId)
}
jdbcTemplate.batchUpdate(
"UPDATE SERIES_METADATA SET AGE_RATING = ?, AGE_RATING_LOCK = ?, PUBLISHER = ?, PUBLISHER_LOCK = ?, READING_DIRECTION = ?, READING_DIRECTION_LOCK = ? WHERE SERIES_ID = ?",
jdbcTemplate.batchUpdate(
"""UPDATE "SERIES_METADATA" SET "AGE_RATING" = ?, "AGE_RATING_LOCK" = ?, "PUBLISHER" = ?, "PUBLISHER_LOCK" = ?, "READING_DIRECTION" = ?, "READING_DIRECTION_LOCK" = ? WHERE "SERIES_ID" = ?""",
parameters,
)
}

View file

@ -14,7 +14,7 @@ class V20210624165023__missing_series_metadata : BaseJavaMigration() {
val jdbcTemplate = JdbcTemplate(SingleConnectionDataSource(context.connection, true))
val seriesWithoutMetada = jdbcTemplate.queryForList(
"""select s.ID, s.NAME from SERIES s where s.ID not in (select sm.SERIES_ID from SERIES_METADATA sm)""",
"""select s."ID", s."NAME" from "SERIES" s where s."ID" not in (select sm."SERIES_ID" from "SERIES_METADATA" sm)""",
)
if (seriesWithoutMetada.isNotEmpty()) {
@ -23,10 +23,10 @@ class V20210624165023__missing_series_metadata : BaseJavaMigration() {
seriesWithoutMetada
.map {
// fields for SERIES_METADATA: SERIES_ID, STATUS=ONGOING, TITLE, TITLE_SORT, READING_DIRECTION=null, AGE_RATING=null
arrayOf(it["ID"], "ONGOING", it["NAME"], StringUtils.stripAccents(it["NAME"].toString()), null, null)
arrayOf(it["id"], "ONGOING", it["name"], StringUtils.stripAccents(it["name"].toString()), null, null)
}.let { parameters ->
jdbcTemplate.batchUpdate(
"INSERT INTO SERIES_METADATA(SERIES_ID, STATUS, TITLE, TITLE_SORT, READING_DIRECTION, AGE_RATING) VALUES (?,?,?,?,?,?)",
jdbcTemplate.batchUpdate(
"""INSERT INTO "SERIES_METADATA"("SERIES_ID", "STATUS", "TITLE", "TITLE_SORT", "READING_DIRECTION", "AGE_RATING") VALUES (?,?,?,?,?,?)""",
parameters,
)
}
@ -34,10 +34,10 @@ class V20210624165023__missing_series_metadata : BaseJavaMigration() {
seriesWithoutMetada
.map {
// fields for BOOK_METADATA_AGGREGATION: SERIES_ID, RELEASE_DATE=null
arrayOf(it["ID"], null)
arrayOf(it["id"], null)
}.let { parameters ->
jdbcTemplate.batchUpdate(
"INSERT INTO BOOK_METADATA_AGGREGATION(SERIES_ID, RELEASE_DATE) VALUES (?,?)",
jdbcTemplate.batchUpdate(
"""INSERT INTO "BOOK_METADATA_AGGREGATION"("SERIES_ID", "RELEASE_DATE") VALUES (?,?)""",
parameters,
)
}

View file

@ -12,24 +12,39 @@ private val logger = KotlinLogging.logger {}
class V20230801104436__fix_incorrect_language_codes : BaseJavaMigration() {
override fun migrate(context: Context) {
val jdbcTemplate = JdbcTemplate(SingleConnectionDataSource(context.connection, true))
// Check if LANGUAGE column exists in SERIES_METADATA table
val columnExists = try {
jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'series_metadata' AND column_name = 'language'",
Int::class.java
) ?: 0 > 0
} catch (e: Exception) {
false
}
if (!columnExists) {
// Column doesn't exist, nothing to migrate
return
}
val seriesLanguage = jdbcTemplate.queryForList(
"""select m.SERIES_ID, m.LANGUAGE from SERIES_METADATA m where LANGUAGE <> '' and LANGUAGE <> 'en'""",
"""select m."SERIES_ID", m."LANGUAGE" from "SERIES_METADATA" m where "LANGUAGE" <> '' and "LANGUAGE" <> 'en'""",
)
if (seriesLanguage.isNotEmpty()) {
seriesLanguage.mapNotNull {
val language = it["LANGUAGE"].toString()
val language = it["language"].toString()
if (language.isBlank()) null
else {
val languageNormalized = normalize(language)
if (language == languageNormalized) null
else arrayOf(languageNormalized, it["SERIES_ID"])
else arrayOf(languageNormalized, it["series_id"])
}
}.let { params ->
logger.info { "Updating ${params.size} incorrect language codes for Series metadata" }
jdbcTemplate.batchUpdate(
"""update SERIES_METADATA set LANGUAGE = ? where SERIES_ID = ?""",
"""update "SERIES_METADATA" set "LANGUAGE" = ? where "SERIES_ID" = ?""",
params,
)
}

View file

@ -17,9 +17,24 @@ private val logger = KotlinLogging.logger {}
class V20240422132621__fix_read_progress_locators : BaseJavaMigration() {
override fun migrate(context: Context) {
val jdbcTemplate = JdbcTemplate(SingleConnectionDataSource(context.connection, true))
// Check if locator column exists in READ_PROGRESS table
val columnExists = try {
jdbcTemplate.queryForObject(
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'read_progress' AND column_name = 'locator'",
Int::class.java
) ?: 0 > 0
} catch (e: Exception) {
false
}
if (!columnExists) {
// Column doesn't exist, nothing to migrate
return
}
val readProgressList = jdbcTemplate.queryForList(
"""select r.BOOK_ID, r.USER_ID, r.locator from READ_PROGRESS r where locator is not null""",
"""select r."BOOK_ID", r."USER_ID", r."locator" from "READ_PROGRESS" r where "locator" is not null""",
)
if (readProgressList.isNotEmpty()) {
@ -27,7 +42,7 @@ class V20240422132621__fix_read_progress_locators : BaseJavaMigration() {
readProgressList.mapNotNull {
try {
val locator = GZIPInputStream((it["LOCATOR"] as ByteArray).inputStream()).use { gz -> mapper.readTree(gz) }
val locator = GZIPInputStream((it["locator"] as ByteArray).inputStream()).use { gz -> mapper.readTree(gz) }
val href = locator["href"]?.asText()
if (href == null) null
else {
@ -39,7 +54,7 @@ class V20240422132621__fix_read_progress_locators : BaseJavaMigration() {
baos.toByteArray()
}
}
arrayOf(gzLocator, it["BOOK_ID"], it["USER_ID"])
arrayOf(gzLocator, it["book_id"], it["user_id"])
}
} catch (e: Exception) {
null
@ -47,7 +62,7 @@ class V20240422132621__fix_read_progress_locators : BaseJavaMigration() {
}.let { params ->
logger.info { "Updating ${params.size} incorrect read progress locators" }
jdbcTemplate.batchUpdate(
"""update READ_PROGRESS set locator = ? where BOOK_ID = ? and USER_ID = ?""",
"""update "READ_PROGRESS" set "locator" = ? where "BOOK_ID" = ? and "USER_ID" = ?""",
params,
)
}

View file

@ -0,0 +1,4 @@
alter table "MEDIA_PAGE"
add column width int NULL;
alter table "MEDIA_PAGE"
add column height int NULL;

View file

@ -0,0 +1,2 @@
alter table "MEDIA_PAGE"
add column FILE_SIZE bigint NULL;