Compare commits

..

16 Commits

Author SHA1 Message Date
Constantin Piber
762d5bdbe6 [skip ci] Add workflow_dispatch trigger to wiki upload (#2046) 2026-05-17 12:32:25 -04:00
schroda
41bb6d3dc1 Fix sorting of gql mangas query (#2043)
Regression fbb383b1f1

Broke sorting due to ordering the manga by their id first, thus, the other orderings were never applied
2026-05-16 20:16:27 -04:00
schroda
fbb383b1f1 Fix mangas query with active sorting and postgresql db (#2042)
fixes #2036
2026-05-16 19:41:37 -04:00
Constantin Piber
558407d92c Update Troubleshooting (#2029)
* Simplify general section

* Troubleshooting: Document some common problems and their solutions

* Remove icon since it's not rendered anyways

[skip ci]

* Update corrupt DB examples

[skip ci]
2026-05-16 19:08:47 -04:00
schroda
6870922784 Fix chapter update failure db rollback (#2040)
The deletion of chapter data was done in its own transaction. Thus, when the update or insertion failed later on, the deletion was not rolled back

fixes #2031

Co-authored-by: Mitchell Syer <Syer10@users.noreply.github.com>
2026-05-16 19:08:38 -04:00
AeonLucid
a4b647972e Add default pageCount value to fix PSQLException (#2039)
* Add default pageCount value to fix PSQLException

* Update CHANGELOG.md
2026-05-16 19:07:56 -04:00
Constantin Piber
16a14e6ac2 Pin CEF version to one known to work with KCEF (#2027)
Fixes problems like
```
java.lang.ClassNotFoundException: org.cef.callback.CefResourceReadCallback_N
```
and
```
Exception in thread "Thread-584" java.lang.NoSuchMethodError: open
```
2026-05-14 11:45:30 -04:00
Constantin Piber
a2f29ec9dc Reset update-flag on uninstall (#2025)
* Reset update-flag on uninstall

If there is an update available when the extension is uninstalled, the
table will still have the update flag, which makes no sense if it is not
installed.

Example:
```
{
  "pkgName": "eu.kanade.tachiyomi.extension.en.comix",
  "name": "Comix",
  "lang": "en",
  "versionCode": 20,
  "versionName": "1.4.20",
  "iconUrl": "/api/v1/extension/icon/tachiyomi-en.comix-v1.4.20.apk",
  "repo": "<hidden>",
  "isNsfw": true,
  "isInstalled": false,
  "isObsolete": false,
  "hasUpdate": true,
  "__typename": "ExtensionType"
},
```

* Update changelog
2026-05-14 11:44:59 -04:00
Mitchell Syer
82df985201 Crash on startup if an unrecoverable error happens (#2019)
* Crash on startup if an unrecoverable error happens

* Changelog
2026-05-14 11:44:52 -04:00
renovate[bot]
740db4f1ab Update javalin to v7.2.2 (#2026)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:44:34 -04:00
renovate[bot]
c4711dec00 Update dependency com.github.junrar:junrar to v7.6.0 (#2022)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:44:18 -04:00
renovate[bot]
75d8d172aa Update dependency org.slf4j:slf4j-api to v2.0.18 (#2017)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:44:00 -04:00
renovate[bot]
81fb8c395d Update Gradle to v9.5.1 (#2015)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-14 11:43:46 -04:00
Mitchell Syer
e93efa9627 Fix Database Types as Needed (#2020) 2026-05-12 19:59:06 -04:00
Mitchell Syer
03a95e6652 Fix New Databases (#2016)
* Standardize toSqlName

* Rename Meta Key db field since KEY is now a reserved name in H2

* Changelog entry

* Use toSqlName

* Forgot this key

* Catch any exception
2026-05-12 17:22:35 -04:00
renovate[bot]
c117d380a3 Update exposed to v1 (major) (#1868)
* Update exposed to v1

* Update Exposed

* Add Kotlinx.DateTime extensions

* Update H2

* Review comments

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2026-05-12 12:53:41 -04:00
24 changed files with 289 additions and 113 deletions

View File

@@ -67,7 +67,7 @@ jobs:
export LD_PRELOAD="$(pwd)/scripts/resources/catch_abort.so"
JAR=$(ls ./server/build/*.jar| head -1)
set +e
timeout 30s java -DcrashOnFailedMigration=true \
timeout 30s java \
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
-Dsuwayomi.tachidesk.config.server.databaseType=POSTGRESQL \
@@ -83,7 +83,7 @@ jobs:
exit "$ecode"
fi
timeout 30s java -DcrashOnFailedMigration=true \
timeout 30s java \
-Dsuwayomi.tachidesk.config.server.systemTrayEnabled=false \
-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false \
-jar "$JAR"

View File

@@ -1,6 +1,7 @@
name: GitHub Wiki upload
on:
workflow_dispatch:
push:
branches:
- master

View File

@@ -10,13 +10,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- .
### Changed
- .
- (Database/H2) Use the latest H2 database engine
- (Startup) Crash on startup if an unrecoverable error happens
### Fixed
- (CloudFlareInterceptor) Don't send the `cf_clearance` cookie back to Flaresolverr
- (WebUI) Handle serving non-default webui with "bundled"
- (WebUI) Wait until WebUI is ready to open in browser
- (Downloads) Truncate filenames by byte length to prevent "File name too long" IO errors
- (**CloudFlareInterceptor**) Don't send the `cf_clearance` cookie back to Flaresolverr
- (**WebUI**) Handle serving non-default webui with "bundled"
- (**WebUI**) Wait until WebUI is ready to open in browser
- (**Downloads**) Truncate filenames by byte length to prevent "File name too long" IO errors
- (**Extension**) Do not indicate an update is available when the extension is not installed
- (**Chapter**) Fix losing chapter data on failed chapter list update
- (**Chapter**) Fix database error when fetching chapter updates
- (**Manga/API**) Fix "mangas" graphql query with active sorting and using a postgresql database (QUERY "mangas")
## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08

View File

@@ -1,19 +1,69 @@
# Troubleshooting
This page is laid out in several sections, where each section describes a specific problem, followed by one or more possible solutions.
At the end, you will find a General section, which is the nuclear option if nothing else works.
For further support, visit the [official Suwayomi Discord server](https://discord.gg/DDZdqZWaHA).
In such cases, it will be helpful to have logs ready. You can find them in [The Data Directory](./The-Data-Directory) in the logs directory.
**All steps below assume that you have stopped Suwayomi**.
## Broken database
- `failed due to
org.jetbrains.exposed.exceptions.ExposedSQLException: org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "CATEGORY.SORT_ORDER" not found`
- `org.h2.jdbc.JdbcSQLSyntaxErrorException: Column "CHAPTER.KOREADER_HASH" not found`
- `java.lang.IllegalStateException: Unable to read the page at position 96170708817765466`
- Any other error text that includes "SQL Statement"
Your database is either corrupted or incompatible.
One of these is the cause:
- You were running a preview version and decided to downgrade to stable.
- You did not shut down Suwayomi properly.
- Suwayomi crashed in an unexpected way.
Solutions:
- If you downgraded, upgrade to preview again.
- Otherwise, you will need to reset and restore from a backup. See [General Troubleshooting](#general-troubleshooting) below.
## `HTTP error 429`
The source (or, if trackers are enabled, possibly the tracker) has blocked you for sending too many requests.
Note that Mass-Migration can result in an unexpectedly high number of requests to both the source and any configured trackers.
Solution: Use other/more sources, download less, and wait between request-heavy actions.
## Extension times out
- `Timed out waiting for 20000 ms…`
- `Timed out waiting for page list`
First, check if this is an extension issue or a Suwayomi issue.
On the manga page of the problematic entry, click "Open in WebView".
Solutions:
- If the WebView loads: The issue is with the extension. Search [the issues](https://github.com/Suwayomi/Suwayomi-Server/issues) and discord if there are known problems with that extension.
- If the WebView errors: Go to [The Data Directory](./The-Data-Directory) and remove the `bin` and `cache` folders.
- If the WebView still does not work after a restart, your installation is incomplete. On Linux, refer to [the README](https://github.com/Suwayomi/Suwayomi-Server#webview-support-gnulinux).
## General Troubleshooting
This guide will try to fix Suwayomi by reseting it to a clean installation state.
> [!WARNING]
> This will remove all your data, including the library.
> Make sure you have copied your backups as described above!
- Make sure you have a recent backup of your library or create one in the app (if possible) because we **are going to wipe all Suwayomi data**.
- Make sure Suwayomi is not running (right click on tray icon and quit or kill it through the way your Operating System provides)
- Clear all browsing data on your browser if you use Suwayomi from a browser.
- Delete the Suwayomi data directory located below and re-run the app.
Note: Replace `<Account>` with the currently logged in account/username on your pc.
On Mac OS X : `/Users/<Account>/Library/Application Support/Tachidesk`
On Windows XP : `C:\Documents and Settings\<Account>\Application Data\Local Settings\Tachidesk`
On Windows 7 and later : `C:\Users\<Account>\AppData\Local\Tachidesk`
On Unix/Linux : `/home/<account>/.local/share/Tachidesk`
- Delete the Suwayomi data directory located below and re-run the app. See the article [The Data Directory](./The-Data-Directory) for information on how to find it.
- If you wish to keep your downloads, you may also attempt to surgically remove only parts. You will need to remove `database.mv.db`, `database.trace.db`, `bin`, `cache`, `extensions`, `settings`, `webUI`. Removing only a subset of these files and folders may fail to resolve the problem.
- Open Suwayomi and go to Settings > Backup > Restore Backup, and select the latest backup you have.
- Restoring from backup does not restore your downloads. If you chose to keep them in the above step, you will now need to re-download all manga. Suwayomi will pick up on the existing files and not actually download anything that isn't new.
- In the case that you have to periodically perform this fix or the problem persists or the method failed to fix it, open an issue or Join the [Suwayomi discord server](https://discord.gg/DDZdqZWaHA) to hang out with the community and to receive support and help.

View File

@@ -4,7 +4,7 @@ coroutines = "1.11.0"
serialization = "1.11.0"
jvmTarget = "21"
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
javalin = "7.2.0"
javalin = "7.2.2"
jte = "3.2.4"
jackson = "3.1.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "1.2.0"
@@ -37,7 +37,7 @@ serialization-xml-core = { module = "io.github.pdvrieze.xmlutil:core", version.r
serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", version.ref = "xmlserialization" }
# Logging
slf4japi = "org.slf4j:slf4j-api:2.0.17"
slf4japi = "org.slf4j:slf4j-api:2.0.18"
logback = "ch.qos.logback:logback-classic:1.5.32"
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02"
@@ -116,7 +116,7 @@ appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
zip4j = "net.lingala.zip4j:zip4j:2.11.6"
commonscompress = "org.apache.commons:commons-compress:1.28.0"
junrar = "com.github.junrar:junrar:7.5.10"
junrar = "com.github.junrar:junrar:7.6.0"
# AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500

View File

@@ -13,6 +13,6 @@ import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
* Metadata storage for clients, server/global level.
*/
object GlobalMetaTable : IntIdTable() {
val key = varchar("key", 256)
val key = varchar("meta_key", 256)
val value = varchar("value", 4096)
}

View File

@@ -15,9 +15,11 @@ import org.jetbrains.exposed.v1.core.Op
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.greater
import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.core.inSubQuery
import org.jetbrains.exposed.v1.core.less
import org.jetbrains.exposed.v1.core.like
import org.jetbrains.exposed.v1.jdbc.select
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
@@ -243,13 +245,16 @@ class MangaQuery {
): MangaNodeList {
val queryResults =
transaction {
val res =
val mangaIdsQuery =
MangaTable
.leftJoin(CategoryMangaTable)
.select(MangaTable.columns)
.withDistinctOn(MangaTable.id)
.select(MangaTable.id)
.withDistinct()
res.applyOps(condition, filter)
mangaIdsQuery.applyOps(condition, filter)
val res =
MangaTable.selectAll().where { MangaTable.id inSubQuery mangaIdsQuery }
if (order != null || orderBy != null || (last != null || before != null)) {
val baseSort = listOf(MangaOrder(MangaOrderBy.ID, SortOrder.ASC))

View File

@@ -262,16 +262,14 @@ object Chapter {
}
}
// we got some clean up due
if (chaptersIdsToDelete.isNotEmpty()) {
DownloadManager.dequeue(chaptersIdsToDelete)
transaction {
transaction {
// we got some clean up due
if (chaptersIdsToDelete.isNotEmpty()) {
DownloadManager.dequeue(chaptersIdsToDelete)
PageTable.deleteWhere { chapter inList chaptersIdsToDelete }
ChapterTable.deleteWhere { id inList chaptersIdsToDelete }
}
}
transaction {
if (chaptersToInsert.isNotEmpty()) {
ChapterTable
.batchInsert(chaptersToInsert) { chapter ->
@@ -287,6 +285,7 @@ object Chapter {
this[ChapterTable.isRead] = false
this[ChapterTable.isBookmarked] = false
this[ChapterTable.isDownloaded] = false
this[ChapterTable.pageCount] = -1
// is recognized chapter number
if (chapter.chapterNumber >= 0f && chapter.chapterNumber in deletedChapterNumbers) {

View File

@@ -358,6 +358,7 @@ object Extension {
} else {
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
it[isInstalled] = false
it[hasUpdate] = false
}
}

View File

@@ -15,7 +15,7 @@ import suwayomi.tachidesk.manga.model.table.CategoryMetaTable.ref
* Metadata storage for clients, about Category with id == [ref].
*/
object CategoryMetaTable : IntIdTable() {
val key = varchar("key", 256)
val key = varchar("meta_key", 256)
val value = varchar("value", 4096)
val ref = reference("category_ref", CategoryTable, ReferenceOption.CASCADE)
}

View File

@@ -26,7 +26,7 @@ import suwayomi.tachidesk.manga.model.table.ChapterMetaTable.ref
* }
*/
object ChapterMetaTable : IntIdTable() {
val key = varchar("key", 256)
val key = varchar("meta_key", 256)
val value = varchar("value", 4096)
val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE)
}

View File

@@ -26,7 +26,7 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable.ref
* }
*/
object MangaMetaTable : IntIdTable() {
val key = varchar("key", 256)
val key = varchar("meta_key", 256)
val value = varchar("value", 4096)
val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE)
}

View File

@@ -14,7 +14,7 @@ import suwayomi.tachidesk.manga.model.table.SourceMetaTable.ref
* Metadata storage for clients, about Source with id == [ref].
*/
object SourceMetaTable : IntIdTable() {
val key = varchar("key", 256)
val key = varchar("meta_key", 256)
val value = varchar("value", 4096)
val ref = long("source_ref")
}

View File

@@ -5,6 +5,8 @@ import android.content.Context
import io.github.oshai.kotlinlogging.KotlinLogging
import suwayomi.tachidesk.manga.impl.update.IUpdater
import suwayomi.tachidesk.server.database.H2Migration
import suwayomi.tachidesk.server.util.ExitCode
import suwayomi.tachidesk.server.util.shutdownApp
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@@ -99,31 +101,35 @@ private val MIGRATIONS =
fun runMigrations(applicationDirs: ApplicationDirs) {
val logger = KotlinLogging.logger("Migration")
try {
val migrationPreferences =
Injekt
.get<Application>()
.getSharedPreferences(
"migrations",
Context.MODE_PRIVATE,
)
val version = migrationPreferences.getInt("version", 0)
val migrationPreferences =
Injekt
.get<Application>()
.getSharedPreferences(
"migrations",
Context.MODE_PRIVATE,
)
val version = migrationPreferences.getInt("version", 0)
logger.info { "Running migrations, previous version $version, target version ${MIGRATIONS.size}" }
logger.info { "Running migrations, previous version $version, target version ${MIGRATIONS.size}" }
MIGRATIONS.forEachIndexed { index, (migrationName, migrationFunction) ->
val migrationVersion = index + 1
MIGRATIONS.forEachIndexed { index, (migrationName, migrationFunction) ->
val migrationVersion = index + 1
val isMigrationRequired = version < migrationVersion
if (!isMigrationRequired) {
logger.info { "Skipping migration version $migrationVersion: $migrationName" }
return@forEachIndexed
}
val isMigrationRequired = version < migrationVersion
if (!isMigrationRequired) {
logger.info { "Skipping migration version $migrationVersion: $migrationName" }
return@forEachIndexed
logger.info { "Running migration version $migrationVersion: $migrationName" }
migrationFunction(applicationDirs)
migrationPreferences.edit().putInt("version", migrationVersion).apply()
}
logger.info { "Running migration version $migrationVersion: $migrationName" }
migrationFunction(applicationDirs)
migrationPreferences.edit().putInt("version", migrationVersion).apply()
} catch (e: Exception) {
logger.error(e) { "Failed to run migrations" }
shutdownApp(ExitCode.MigrationsRunFailure)
}
}

View File

@@ -366,6 +366,7 @@ fun applicationSetup() {
}
} catch (e: Exception) {
logger.error(e) { "Exception while creating initial server.conf" }
shutdownApp(ExitCode.SetupConfFileFailed)
}
// copy local source icon
@@ -378,6 +379,7 @@ fun applicationSetup() {
}
} catch (e: Exception) {
logger.error(e) { "Exception while copying Local source's icon" }
shutdownApp(ExitCode.LocalSourceIconCopyFailure)
}
// fixes #119 , ref:
@@ -395,7 +397,12 @@ fun applicationSetup() {
databaseUp()
LocalSource.register()
try {
LocalSource.register()
} catch (e: Exception) {
logger.error(e) { "Failed to setup LocalSource" }
shutdownApp(ExitCode.LocalSourceSetupFailure)
}
serverConfig.subscribeTo(
combine<Any, DatabaseSettings>(
@@ -525,7 +532,7 @@ fun applicationSetup() {
}
}
}
download { github() }
download { github { release("jbr-release-21.0.10b1163.108") } }
settings {
windowlessRenderingEnabled = true
cachePath = (Path(applicationDirs.dataRoot) / "cache/kcef").toString()

View File

@@ -27,7 +27,6 @@ import suwayomi.tachidesk.server.util.ExitCode
import suwayomi.tachidesk.server.util.shutdownApp
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.sql.SQLException
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
@@ -184,10 +183,8 @@ fun databaseUp() {
}
val migrations = loadMigrationsFrom("suwayomi.tachidesk.server.database.migration", ServerConfig::class.java)
runMigrations(migrations)
} catch (e: SQLException) {
} catch (e: Exception) {
logger.error(e) { "Error up-to-database migration" }
if (System.getProperty("crashOnFailedMigration").toBoolean()) {
shutdownApp(ExitCode.DbMigrationFailure)
}
shutdownApp(ExitCode.DbMigrationFailure)
}
}

View File

@@ -9,9 +9,12 @@ import java.net.URLClassLoader
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.absolutePathString
import kotlin.io.path.bufferedReader
import kotlin.io.path.bufferedWriter
import kotlin.io.path.copyTo
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteExisting
import kotlin.io.path.deleteIfExists
import kotlin.io.path.div
import kotlin.io.path.exists
import kotlin.io.path.name
@@ -46,6 +49,10 @@ object H2Migration {
}
val script = Path("$dbBase.${h2Old.substringAfterLast('.')}.sql")
script.deleteIfExists()
val modifiedScript = Path("$dbBase.${h2Old.substringAfterLast('.')}.modified.sql")
modifiedScript.deleteIfExists()
// Backup original database.
val backup = Path("$dbBase.mv.db.${h2Old.substringAfterLast('.')}.backup")
@@ -72,19 +79,32 @@ object H2Migration {
libsDir.resolve("h2-$h2New.bin"),
)
// Delete attempted migration if failed previously
val newDatabase = Path(rootDir, "database.${h2New.substringAfterLast('.')}.mv.db")
newDatabase.deleteIfExists()
val modifiedNewDatabase = Path(rootDir, "database.${h2Old.substringAfterLast('.')}.modified.${h2New.substringAfterLast('.')}.mv.db")
modifiedNewDatabase.deleteIfExists()
runMigrationTool(
migrationJar = migrationJar,
libsDir = libsDir,
mvStore = mvStore,
script = script,
modifiedScript = modifiedScript,
h2Old = h2Old,
h2New = h2New,
)
// Move database to proper path
val newDatabase = Path(rootDir, "database.${h2New.substringAfterLast('.')}.mv.db")
newDatabase.copyTo(mvStore, overwrite = true)
newDatabase.deleteExisting()
if (modifiedNewDatabase.exists()) {
modifiedNewDatabase.copyTo(mvStore, overwrite = true)
modifiedNewDatabase.deleteExisting()
newDatabase.deleteIfExists()
} else {
newDatabase.copyTo(mvStore, overwrite = true)
newDatabase.deleteExisting()
}
logger.info { "H2 migration completed successfully." }
}
@@ -123,6 +143,7 @@ object H2Migration {
libsDir: Path,
mvStore: Path,
script: Path,
modifiedScript: Path,
h2Old: String,
h2New: String,
) {
@@ -136,32 +157,77 @@ object H2Migration {
val main =
clazz.getMethod("main", Array<String>::class.java)
main.invoke(
null,
arrayOf(
// h2 driver dir
"-l",
libsDir.absolutePathString(),
// from version
"-f",
h2Old,
// to version
"-t",
h2New,
// user
"-u",
"",
// password
"-p",
"",
// database.mv.db
"-d",
mvStore.absolutePathString(),
// database backup in SQL
"-s",
script.absolutePathString(),
),
)
try {
main.invoke(
null,
arrayOf(
// h2 driver dir
"-l",
libsDir.absolutePathString(),
// from version
"-f",
h2Old,
// to version
"-t",
h2New,
// user
"-u",
"",
// password
"-p",
"",
// database.mv.db
"-d",
mvStore.absolutePathString(),
// database backup in SQL
"-s",
script.absolutePathString(),
),
)
} catch (e: Exception) {
// Modify raw .sql file as needed for compatibility
if (e.stackTraceToString().contains("Unknown data type: \"DATETIME\"; SQL statement:") && script.exists()) {
script.bufferedReader().use { reader ->
modifiedScript.bufferedWriter().use { writer ->
reader.forEachLine { line ->
writer.write(
line.replace(
" \"EXECUTED_AT\" DATETIME(9) NOT NULL",
" \"EXECUTED_AT\" TIMESTAMP(9) NOT NULL",
),
)
writer.newLine()
}
}
}
main.invoke(
null,
arrayOf(
// h2 driver dir
"-l",
libsDir.absolutePathString(),
// from version
"-f",
h2Old,
// to version
"-t",
h2New,
// user
"-u",
"",
// password
"-p",
"",
// database.mv.db
"-d",
modifiedScript.absolutePathString(),
),
)
} else {
throw e
}
}
}
}
}

View File

@@ -10,17 +10,10 @@ package suwayomi.tachidesk.server.database.migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.SQLMigration
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
@Suppress("ClassName", "unused")
class M0023_CategoryMetaRefFix : SQLMigration() {
fun String.toSqlName(): String =
TransactionManager.defaultDatabase!!.identifierManager.let {
it.quoteIfNecessary(
it.inProperCase(this),
)
}
private val CategoryMetaTable by lazy { "CategoryMeta".toSqlName() }
private val CategoryRefColumn by lazy { "category_ref".toSqlName() }
private val CategoryTable by lazy { "Category".toSqlName() }

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.server.database.migration
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.SQLMigration
import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
@Suppress("ClassName", "unused")
class M0049_FixDuplicatedMetas : SQLMigration() {
@@ -15,7 +16,7 @@ class M0049_FixDuplicatedMetas : SQLMigration() {
table: String,
refColumn: String? = null,
): String {
val groupBy = listOfNotNull(refColumn, "KEY").joinToString(", ")
val groupBy = listOfNotNull(refColumn, "KEY".toSqlName()).joinToString(", ")
return """
DELETE FROM $table
@@ -30,10 +31,11 @@ class M0049_FixDuplicatedMetas : SQLMigration() {
""".trimIndent()
}
override val sql: String =
override val sql: String by lazy {
createMigrationForTable("CATEGORYMETA", "CATEGORY_REF") +
createMigrationForTable("CHAPTERMETA", "CHAPTER_REF") +
createMigrationForTable("GLOBALMETA") +
createMigrationForTable("MANGAMETA", "MANGA_REF") +
createMigrationForTable("SOURCEMETA", "SOURCE_REF")
}
}

View File

@@ -0,0 +1,38 @@
package suwayomi.tachidesk.server.database.migration
/*
* Copyright (C) Contributors to the Suwayomi project
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import de.neonew.exposed.migrations.helpers.SQLMigration
import suwayomi.tachidesk.graphql.types.DatabaseType
import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
import suwayomi.tachidesk.server.serverConfig
@Suppress("ClassName", "unused")
class M0055_RenameMetaKeys : SQLMigration() {
fun postgresRename(table: String): String =
"ALTER TABLE $table " +
"RENAME COLUMN " + "KEY".toSqlName() + " TO META_KEY;"
fun h2Rename(table: String): String =
"ALTER TABLE $table " +
"ALTER COLUMN " + "KEY".toSqlName() + " RENAME TO META_KEY;"
fun createRenameMigration(table: String): String =
when (serverConfig.databaseType.value) {
DatabaseType.H2 -> h2Rename(table.toSqlName())
DatabaseType.POSTGRESQL -> postgresRename(table.toSqlName())
}
override val sql: String by lazy {
createRenameMigration("CATEGORYMETA") +
createRenameMigration("CHAPTERMETA") +
createRenameMigration("GLOBALMETA") +
createRenameMigration("MANGAMETA") +
createRenameMigration("SOURCEMETA")
}
}

View File

@@ -1,17 +1,9 @@
package suwayomi.tachidesk.server.database.migration.helpers
import de.neonew.exposed.migrations.helpers.SQLMigration
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
import suwayomi.tachidesk.graphql.types.DatabaseType
import suwayomi.tachidesk.server.serverConfig
fun String.toSqlName(): String =
TransactionManager.current().db.identifierManager.let {
it.quoteIfNecessary(
it.inProperCase(this),
)
}
abstract class RenameFieldMigration(
tableName: String,
originalName: String,

View File

@@ -0,0 +1,10 @@
package suwayomi.tachidesk.server.database.migration.helpers
import org.jetbrains.exposed.v1.jdbc.transactions.TransactionManager
fun String.toSqlName(): String =
TransactionManager.current().db.identifierManager.let {
it.quoteIfNecessary(
it.inProperCase(this),
)
}

View File

@@ -21,6 +21,10 @@ enum class ExitCode(
WebUISetupFailure(3),
ConfigMigrationMisconfiguredFailure(4),
DbMigrationFailure(5),
SetupConfFileFailed(6),
LocalSourceIconCopyFailure(7),
LocalSourceSetupFailure(8),
MigrationsRunFailure(9),
}
fun shutdownApp(exitCode: ExitCode) {