diff --git a/detekt/baseline.xml b/detekt/baseline.xml index 48731680cd8..438b722e59e 100644 --- a/detekt/baseline.xml +++ b/detekt/baseline.xml @@ -128,6 +128,8 @@ ArgumentListWrapping:GetPaginatedFlowOfMessagesBySearchQueryAndConversationIdUseCase.kt$GetPaginatedFlowOfMessagesBySearchQueryAndConversationIdUseCase$( searchQuery, conversationId, pagingConfig, startingOffset ) ArgumentListWrapping:GetSessionsUseCase.kt$GetSessionsUseCase$( { when (it) { StorageFailure.DataNotFound -> GetAllSessionsResult.Failure.NoSessionFound is StorageFailure.Generic -> GetAllSessionsResult.Failure.Generic(it) } }, { GetAllSessionsResult.Success(it) } ) ArgumentListWrapping:GetUserInfoUseCase.kt$GetUserInfoUseCaseImpl$( { GetUserInfoResult.Failure }, { team -> GetUserInfoResult.Success(otherUser, team) }) + ArgumentListWrapping:GlobalDBBaseTest.kt$GlobalDBBaseTest$( PlatformDatabaseData(StorageData.FileBacked(storePath)), StandardTestDispatcher(), null, false ) + ArgumentListWrapping:GlobalDatabase.kt$( identifier = Int.MIN_VALUE, sql = """SELECT table_name FROM information_schema.tables where table_name = 'accounts'""", mapper = { if (it.next().value) { val result = it.getString(0) println("THE RESULT: $result") app.cash.sqldelight.db.QueryResult.Value(result?.isNotEmpty()) } else { app.cash.sqldelight.db.QueryResult.Value(false) } }, parameters = 0 ) ArgumentListWrapping:GuestRoomConfigHandler.kt$GuestRoomConfigHandler$( { false }, { it.isGuestRoomLinkEnabled != status } ) ArgumentListWrapping:HandleExternalRequestAction.kt$( """ Hey there, I hope you're doing well. I've got a bit of a craving for bananas, and I was wondering if you might be able to share a few with me? It would mean a lot. 😊 Thanks a bunch, A friendly monkey 🍌🐡 """.trimIndent(), """ Yo, I'm in need of some bananas, my friend. Can you hook me up? I'd appreciate it big time. Respect, A neutral monkey 🍌 """.trimIndent(), """ Listen up, I ain't messin' around. I want them bananas, and I want 'em now. You better deliver or there'll be consequences. No games, An evil monkey πŸŒπŸ‘ΏπŸ’€ """.trimIndent() ) ArgumentListWrapping:InstanceService.kt$InstanceService$( instanceRequest.email, instanceRequest.password, true, secondFactorVerificationCode = instanceRequest.verificationCode ) @@ -307,6 +309,7 @@ CommentSpacing:UserDAOTest.kt$UserDAOTest$//given EmptyFunctionBlock:FileTestHelper.kt$FileTestHelper${ } EmptyKtFile:FetchApiVersionUseCaseTest.kt$.FetchApiVersionUseCaseTest.kt + Filename:BooleanExt.kt$com.wire.kalium.persistence.dao.BooleanExt.kt Filename:ConversationStatus.kt$com.wire.kalium.logic.data.conversation.ConversationStatus.kt Filename:FileUtilTest.kt$com.wire.kalium.util.FileUtilTest.kt Filename:GetOtherUserClientsUseCaseTest.kt$com.wire.kalium.logic.feature.client.GetOtherUserClientsUseCaseTest.kt @@ -401,6 +404,7 @@ LongParameterList:ToggleReactionUseCase.kt$ToggleReactionUseCase$( clientId: ClientId, conversationId: ConversationId, date: String, messageId: String, removedReaction: String, currentReactions: UserReactions ) MagicNumber:ConversationStatus.kt$MutedConversationStatus.AllMuted$3 MagicNumber:GlobalCallManager.kt$LogHandlerImpl$3 + MagicNumber:GlobalDatabase.kt$3 MagicNumber:NetworkUtils.kt$300 MagicNumber:NetworkUtils.kt$399 MagicNumber:NetworkUtils.kt$400 @@ -411,10 +415,6 @@ MatchingDeclarationName:ConversationStatus.kt$MutedConversationStatus MatchingDeclarationName:CryptoboxCRUDStore.module_@wireapp_cryptobox.kt$CryptoboxCRUDStore : PreKeyStore MatchingDeclarationName:CryptoboxSession.module_@wireapp_cryptobox.kt$CryptoboxSession - MatchingDeclarationName:DriverBuilder.android.kt$DriverBuilder - MatchingDeclarationName:DriverBuilder.apple.kt$DriverBuilder - MatchingDeclarationName:DriverBuilder.js.kt$DriverBuilder - MatchingDeclarationName:DriverBuilder.jvm.kt$DriverBuilder MatchingDeclarationName:Encoder.module_@wireapp_cbor.kt$Encoder MatchingDeclarationName:FileUtilTest.kt$FileTestHelper MatchingDeclarationName:GetOtherUserClientsUseCaseTest.kt$ObserveClientsByUserIdUseCaseTest @@ -939,7 +939,7 @@ UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(libs.apacheTika) } } UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(libs.coroutines.core) } } UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(libs.ktor.okHttp) implementation(libs.okhttp.loggingInterceptor) } } - UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(libs.sqldelight.jvmDriver) implementation(libs.sqlite.xerialDriver) } } + UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(libs.sqldelight.jvmDriver) implementation(libs.sqldelight.jdbcDriver) implementation(libs.sqlite.xerialDriver) implementation(libs.hikaricp) implementation(libs.postgres.driver) implementation(libs.testContainers.postgres) } } UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(project(":logic")) implementation(project(":calling")) } } UnusedPrivateProperty:build.gradle.kts$val jvmTest by getting UnusedPrivateProperty:build.gradle.kts$val jvmTest by getting { dependencies { implementation(libs.konsist) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f8d462c7a8a..5f4f534fd96 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -181,6 +181,7 @@ sqldelight-androidxPaging = { module = "app.cash.sqldelight:androidx-paging3-ext sqldelight-nativeDriver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" } sqldelight-jvmDriver = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-jsDriver = { module = "app.cash.sqldelight:web-worker-driver", version.ref = "sqldelight" } +sqldelight-jdbcDriver = { module = "app.cash.sqldelight:jdbc-driver", version.ref = "sqldelight" } sqldelight-primitiveAdapters = { module = "app.cash.sqldelight:primitive-adapters", version.ref = "sqldelight" } sqldelight-dialect = { module = "app.cash.sqldelight:sqlite-3-38-dialect", version.ref = "sqldelight" } sqldelight-postgres = { module = "app.cash.sqldelight:postgresql-dialect", version.ref = "sqldelight" } @@ -189,6 +190,8 @@ sqldelight-async = { module = "app.cash.sqldelight:async-extensions", version.re r2dbc-postgres = { module = "org.postgresql:r2dbc-postgresql", version.ref = "postgres" } r2dbc-spi = { module = "io.r2dbc:r2dbc-spi", version.ref = "r2dbc" } sqlite-xerialDriver = { module = "org.xerial:sqlite-jdbc", version.ref = "xerialDriver" } +hikaricp = { module = "com.zaxxer:HikariCP", version = "5.0.1" } +postgres-driver = { module = "org.postgresql:postgresql", version = "42.5.4" } # mocks and testing mockative-runtime = { module = "io.mockative:mockative", version.ref = "mockative" } @@ -196,6 +199,7 @@ mockative-processor = { module = "io.mockative:mockative-processor", version.ref turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } mockk = { module = "io.mockk:mockk", version.ref = "mockk" } konsist = { module = "com.lemonappdev:konsist", version.ref = "konsist" } +testContainers-postgres = { module = "org.testcontainers:postgresql", version = "1.19.6" } # detekt detekt-cli = { module = "io.gitlab.arturbosch.detekt:detekt-cli", version.ref = "detekt" } diff --git a/persistence/build.gradle.kts b/persistence/build.gradle.kts index fcdaab644cb..80a60f13125 100644 --- a/persistence/build.gradle.kts +++ b/persistence/build.gradle.kts @@ -87,7 +87,11 @@ kotlin { val jvmMain by getting { dependencies { implementation(libs.sqldelight.jvmDriver) + implementation(libs.sqldelight.jdbcDriver) implementation(libs.sqlite.xerialDriver) + implementation(libs.hikaricp) + implementation(libs.postgres.driver) + implementation(libs.testContainers.postgres) } } val jvmTest by getting diff --git a/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index cfbb214b97b..a8a38839556 100644 --- a/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/androidInstrumentedTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -34,7 +34,7 @@ actual abstract class GlobalDBBaseTest { context.deleteDatabase(FileNameUtil.globalDBName()) } - actual fun createDatabase(): GlobalDatabaseBuilder = globalDatabaseProvider( + actual fun createDatabase(platformDatabaseData: PlatformDatabaseData): GlobalDatabaseBuilder = globalDatabaseProvider( platformDatabaseData = PlatformDatabaseData(ApplicationProvider.getApplicationContext()), queriesContext = KaliumDispatcherImpl.unconfined, passphrase = GlobalDatabaseSecret("test_db_secret".toByteArray()), diff --git a/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index edb29062eb1..0e1776e07cf 100644 --- a/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/androidUnitTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -19,13 +19,14 @@ package com.wire.kalium.persistence import com.wire.kalium.persistence.db.GlobalDatabaseBuilder +import com.wire.kalium.persistence.db.PlatformDatabaseData actual abstract class GlobalDBBaseTest { actual fun deleteDatabase() { TODO("Not yet implemented") } - actual fun createDatabase(): GlobalDatabaseBuilder { + actual fun createDatabase(platformDatabaseData: PlatformDatabaseData): GlobalDatabaseBuilder { TODO("Not yet implemented") } } diff --git a/persistence/src/appleTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/appleTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index 48d3e69d0d2..4d6239a66ad 100644 --- a/persistence/src/appleTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/appleTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -37,7 +37,7 @@ actual abstract class GlobalDBBaseTest { deleteDatabase(FileNameUtil.globalDBName(), storePath) } - actual fun createDatabase(): GlobalDatabaseBuilder { + actual fun createDatabase(platformDatabaseData: PlatformDatabaseData): GlobalDatabaseBuilder { return globalDatabaseProvider( PlatformDatabaseData(StorageData.FileBacked(storePath)), StandardTestDispatcher(), null, false ) diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/Accounts.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/Accounts.sq index 3130400058e..e6fff03cbaf 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/Accounts.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/Accounts.sq @@ -1,7 +1,9 @@ import com.wire.kalium.persistence.dao.ManagedByEntity; import com.wire.kalium.persistence.dao.QualifiedIDEntity; import com.wire.kalium.persistence.model.LogoutReason; +import com.wire.kalium.persistence.dao.BooleanEntity; import kotlin.Boolean; +import kotlin.Int; CREATE TABLE Accounts ( id TEXT AS QualifiedIDEntity PRIMARY KEY NOT NULL, @@ -10,12 +12,12 @@ CREATE TABLE Accounts ( tenant TEXT, server_config_id TEXT NOT NULL, logout_reason TEXT AS LogoutReason, - isPersistentWebSocketEnabled INTEGER AS Boolean NOT NULL DEFAULT 0, + isPersistentWebSocketEnabled INTEGER AS BooleanEntity NOT NULL DEFAULT 0, managed_by TEXT AS ManagedByEntity ); insertOrReplace: -INSERT OR REPLACE INTO Accounts (id, scim_external_id, subject, tenant, server_config_id, logout_reason, isPersistentWebSocketEnabled) +INSERT INTO Accounts (id, scim_external_id, subject, tenant, server_config_id, logout_reason, isPersistentWebSocketEnabled) VALUES (:id, :scimExternalId, :subject, :tenant, :serverConfigId, :logoutReason,:isPersistentWebSocketEnabled); delete: diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/CurrentAccount.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/CurrentAccount.sq index d1a8b6ac658..a2c48892a22 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/CurrentAccount.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/CurrentAccount.sq @@ -7,7 +7,7 @@ CREATE TABLE CurrentAccount ( ); update: -INSERT OR REPLACE INTO CurrentAccount (id, user_id) VALUES (1, :user_id); +INSERT INTO CurrentAccount (id, user_id) VALUES (1, :user_id); currentAccountInfo: SELECT id, logout_reason FROM Accounts WHERE id = (SELECT user_id FROM CurrentAccount WHERE id = 1); diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/GlobalDatabaseProperties.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/GlobalDatabaseProperties.sq index 7fbdb5dbd98..e69de29bb2d 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/GlobalDatabaseProperties.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/GlobalDatabaseProperties.sq @@ -1,3 +0,0 @@ - -enableForeignKeyContraints: -PRAGMA foreign_keys = 1; diff --git a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq index b09efc6e29a..b1249a12ce2 100644 --- a/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq +++ b/persistence/src/commonMain/db_global/com/wire/kalium/persistence/ServerConfiguration.sq @@ -1,5 +1,6 @@ import kotlin.Boolean; import kotlin.Int; +import com.wire.kalium.persistence.dao.BooleanEntity; CREATE TABLE ServerConfiguration ( id TEXT PRIMARY KEY NOT NULL, @@ -10,12 +11,12 @@ CREATE TABLE ServerConfiguration ( blackListUrl TEXT NOT NULL, teamsUrl TEXT NOT NULL, websiteUrl TEXT NOT NULL, - isOnPremises INTEGER AS Boolean NOT NULL, + isOnPremises INTEGER AS BooleanEntity NOT NULL, domain TEXT, commonApiVersion INTEGER AS Int NOT NULL, - federation INTEGER AS Boolean NOT NULL, + federation INTEGER AS BooleanEntity NOT NULL, apiProxyHost TEXT, - apiProxyNeedsAuthentication INTEGER AS Boolean, + apiProxyNeedsAuthentication INTEGER AS BooleanEntity, apiProxyPort INTEGER AS Int, lastBlackListCheck TEXT, CONSTRAINT server_config_unique UNIQUE (title, apiBaseUrl, webSocketBaseUrl, domain, apiProxyHost, apiProxyPort) @@ -25,7 +26,7 @@ deleteById: DELETE FROM ServerConfiguration WHERE id = ?; insert: -INSERT OR FAIL INTO ServerConfiguration(id, apiBaseUrl, accountBaseUrl, webSocketBaseUrl, blackListUrl, teamsUrl, websiteUrl, title, isOnPremises, federation, domain, commonApiVersion, apiProxyHost, apiProxyNeedsAuthentication, apiProxyPort) +INSERT INTO ServerConfiguration(id, apiBaseUrl, accountBaseUrl, webSocketBaseUrl, blackListUrl, teamsUrl, websiteUrl, title, isOnPremises, federation, domain, commonApiVersion, apiProxyHost, apiProxyNeedsAuthentication, apiProxyPort) VALUES( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?,?,?); updateApiVersion: diff --git a/persistence/src/commonMain/db_global/migrations/3.sqm b/persistence/src/commonMain/db_global/migrations/3.sqm index 2c31ce86111..3527f9c7b71 100644 --- a/persistence/src/commonMain/db_global/migrations/3.sqm +++ b/persistence/src/commonMain/db_global/migrations/3.sqm @@ -1,4 +1,4 @@ -import kotlin.Boolean; +import com.wire.kalium.persistence.dao.BooleanEntity; ALTER TABLE Accounts -ADD COLUMN isPersistentWebSocketEnabled INTEGER AS Boolean NOT NULL DEFAULT(0); +ADD COLUMN isPersistentWebSocketEnabled INTEGER AS BooleanEntity NOT NULL DEFAULT(0); diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/CrossCompatBooleanAdapter.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/CrossCompatBooleanAdapter.kt new file mode 100644 index 00000000000..68fda79abb4 --- /dev/null +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/adapter/CrossCompatBooleanAdapter.kt @@ -0,0 +1,38 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.adapter + +import app.cash.sqldelight.ColumnAdapter +import com.wire.kalium.persistence.dao.BooleanEntity + +internal object CrossCompatBooleanAdapter : ColumnAdapter { + override fun decode(databaseValue: Long?): BooleanEntity { + return databaseValue == 1L + } + + override fun encode(value: BooleanEntity): Long { + return value.toLong() + } +} + +fun Boolean.toLong(): Long { + return when (this) { + true -> 1 + false -> 0 + } +} diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/BooleanExt.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/BooleanExt.kt new file mode 100644 index 00000000000..20df1cc3716 --- /dev/null +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/BooleanExt.kt @@ -0,0 +1,20 @@ +/* + * Wire + * Copyright (C) 2024 Wire Swiss GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package com.wire.kalium.persistence.dao + +typealias BooleanEntity = Boolean diff --git a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabaseBuilder.kt b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabaseBuilder.kt index 180f339779c..e5e1b562b90 100644 --- a/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabaseBuilder.kt +++ b/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabaseBuilder.kt @@ -18,6 +18,7 @@ package com.wire.kalium.persistence.db +import app.cash.sqldelight.ColumnAdapter import app.cash.sqldelight.EnumColumnAdapter import app.cash.sqldelight.adapter.primitive.IntColumnAdapter import app.cash.sqldelight.db.SqlDriver @@ -25,8 +26,10 @@ import com.wire.kalium.persistence.Accounts import com.wire.kalium.persistence.CurrentAccount import com.wire.kalium.persistence.GlobalDatabase import com.wire.kalium.persistence.ServerConfiguration +import com.wire.kalium.persistence.adapter.CrossCompatBooleanAdapter import com.wire.kalium.persistence.adapter.LogoutReasonAdapter import com.wire.kalium.persistence.adapter.QualifiedIDAdapter +import com.wire.kalium.persistence.dao.BooleanEntity import com.wire.kalium.persistence.daokaliumdb.AccountsDAO import com.wire.kalium.persistence.daokaliumdb.AccountsDAOImpl import com.wire.kalium.persistence.daokaliumdb.ServerConfigurationDAO @@ -49,12 +52,16 @@ class GlobalDatabaseBuilder internal constructor( sqlDriver, ServerConfigurationAdapter = ServerConfiguration.Adapter( commonApiVersionAdapter = IntColumnAdapter, - apiProxyPortAdapter = IntColumnAdapter + isOnPremisesAdapter = CrossCompatBooleanAdapter as ColumnAdapter, + apiProxyPortAdapter = IntColumnAdapter, + federationAdapter = CrossCompatBooleanAdapter as ColumnAdapter, + apiProxyNeedsAuthenticationAdapter = CrossCompatBooleanAdapter as ColumnAdapter ), AccountsAdapter = Accounts.Adapter( idAdapter = QualifiedIDAdapter, logout_reasonAdapter = LogoutReasonAdapter, - managed_byAdapter = EnumColumnAdapter() + managed_byAdapter = EnumColumnAdapter(), + isPersistentWebSocketEnabledAdapter = CrossCompatBooleanAdapter as ColumnAdapter ), CurrentAccountAdapter = CurrentAccount.Adapter( user_idAdapter = QualifiedIDAdapter @@ -62,7 +69,7 @@ class GlobalDatabaseBuilder internal constructor( ) init { - database.globalDatabasePropertiesQueries.enableForeignKeyContraints() + // database.globalDatabasePropertiesQueries.enableForeignKeyContraints() } val serverConfigurationDAO: ServerConfigurationDAO diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index e4b88696644..43ec5e146a2 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -19,8 +19,9 @@ package com.wire.kalium.persistence import com.wire.kalium.persistence.db.GlobalDatabaseBuilder +import com.wire.kalium.persistence.db.PlatformDatabaseData expect abstract class GlobalDBBaseTest() { fun deleteDatabase() - fun createDatabase(): GlobalDatabaseBuilder + fun createDatabase(platformDatabaseData: PlatformDatabaseData): GlobalDatabaseBuilder } diff --git a/persistence/src/jsTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/jsTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index edb29062eb1..0e1776e07cf 100644 --- a/persistence/src/jsTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/jsTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -19,13 +19,14 @@ package com.wire.kalium.persistence import com.wire.kalium.persistence.db.GlobalDatabaseBuilder +import com.wire.kalium.persistence.db.PlatformDatabaseData actual abstract class GlobalDBBaseTest { actual fun deleteDatabase() { TODO("Not yet implemented") } - actual fun createDatabase(): GlobalDatabaseBuilder { + actual fun createDatabase(platformDatabaseData: PlatformDatabaseData): GlobalDatabaseBuilder { TODO("Not yet implemented") } } diff --git a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabase.kt b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabase.kt index 616d00e93af..09ec2bb4321 100644 --- a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabase.kt +++ b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/GlobalDatabase.kt @@ -18,10 +18,15 @@ package com.wire.kalium.persistence.db +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.jdbc.asJdbcDriver import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver import com.wire.kalium.persistence.GlobalDatabase import com.wire.kalium.persistence.util.FileNameUtil +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource import kotlinx.coroutines.CoroutineDispatcher +import javax.sql.DataSource actual fun globalDatabaseProvider( platformDatabaseData: PlatformDatabaseData, @@ -29,31 +34,34 @@ actual fun globalDatabaseProvider( passphrase: GlobalDatabaseSecret?, enableWAL: Boolean, ): GlobalDatabaseBuilder { - val storageData = platformDatabaseData.storageData - if (storageData is StorageData.InMemory) { - return createGlobalInMemoryDatabase(queriesContext) - } - - if (storageData !is StorageData.FileBacked) { - throw IllegalStateException("Unsupported storage data type: $storageData") - } - if (passphrase != null) { throw NotImplementedError("Encrypted DB is not supported on JVM") } - val databasePath = storageData.file.resolve(FileNameUtil.globalDBName()) - val databaseExists = databasePath.exists() - - // Make sure all intermediate directories exist - storageData.file.mkdirs() - val driver = databaseDriver("jdbc:sqlite:${databasePath.absolutePath}") { - isWALEnabled = enableWAL - areForeignKeyConstraintsEnforced = true - } + val driver = when (val storageData = platformDatabaseData.storageData) { + is StorageData.InMemory -> return createGlobalInMemoryDatabase(queriesContext) + is StorageData.FileBacked -> { + val databasePath = storageData.file.resolve(FileNameUtil.globalDBName()) + val databaseExists = databasePath.exists() + storageData.file.mkdirs() + val driver = databaseDriver("jdbc:sqlite:${databasePath.absolutePath}") { + isWALEnabled = enableWAL + areForeignKeyConstraintsEnforced = true + } + if (!databaseExists) { + GlobalDatabase.Schema.create(driver) + } + driver + } - if (!databaseExists) { - GlobalDatabase.Schema.create(driver) + is StorageData.Postgres -> { + val driver = createDataSource(storageData).asJdbcDriver() + val databaseExists = driver.databaseExists() + if (!databaseExists) { + GlobalDatabase.Schema.create(driver) + } + driver + } } return GlobalDatabaseBuilder(driver, platformDatabaseData, queriesContext) @@ -71,3 +79,37 @@ fun createGlobalInMemoryDatabase(dispatcher: CoroutineDispatcher): GlobalDatabas GlobalDatabase.Schema.create(driver) return GlobalDatabaseBuilder(driver, PlatformDatabaseData(StorageData.InMemory), dispatcher) } + +private fun createDataSource(storageData: StorageData.Postgres): DataSource { + val dataSourceConfig = HikariConfig().apply { + val poolSize = 3 + driverClassName = "org.postgresql.Driver" + jdbcUrl = storageData.uri + username = storageData.username + password = storageData.password + maximumPoolSize = poolSize + isAutoCommit = true + transactionIsolation = "TRANSACTION_REPEATABLE_READ" + validate() + } + return HikariDataSource(dataSourceConfig) +} + +fun SqlDriver.databaseExists(): Boolean { + val result = executeQuery( + identifier = Int.MIN_VALUE, sql = """SELECT table_name FROM information_schema.tables where table_name = 'accounts'""", + mapper = { + if (it.next().value) { + val result = it.getString(0) + println("THE RESULT: $result") + app.cash.sqldelight.db.QueryResult.Value(result?.isNotEmpty()) + } else { + app.cash.sqldelight.db.QueryResult.Value(false) + } + + }, + parameters = 0 + ) + println("Exists: ${result.value}") + return result.value ?: false +} diff --git a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/PlatformDatabaseData.kt b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/PlatformDatabaseData.kt index 2cf574d3f33..50834db385e 100644 --- a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/PlatformDatabaseData.kt +++ b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/PlatformDatabaseData.kt @@ -29,6 +29,7 @@ actual data class PlatformDatabaseData( sealed interface StorageData { data class FileBacked(val file: File) : StorageData data object InMemory : StorageData + data class Postgres(val uri: String, val username: String, val password: String) : StorageData } fun databaseDriver(uri: String, config: DriverConfigurationBuilder.() -> Unit = {}): SqlDriver { diff --git a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt index cf5b97a23e0..c9f7587a387 100644 --- a/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt +++ b/persistence/src/jvmMain/kotlin/com/wire/kalium/persistence/db/UserDatabase.kt @@ -117,4 +117,5 @@ internal actual fun nuke( ): Boolean = when (val storageData = platformDatabaseData.storageData) { StorageData.InMemory -> clearInMemoryDatabase(userId) is StorageData.FileBacked -> storageData.file.resolve(DATABASE_NAME).delete() + is StorageData.Postgres -> throw NotImplementedError("RDBMS nuke is not supported on JVM, truncate by query") } diff --git a/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt b/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt index 64f8f267ed1..21c716207fa 100644 --- a/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt +++ b/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/GlobalDBBaseTest.kt @@ -20,25 +20,24 @@ package com.wire.kalium.persistence import com.wire.kalium.persistence.db.GlobalDatabaseBuilder import com.wire.kalium.persistence.db.PlatformDatabaseData -import com.wire.kalium.persistence.db.StorageData import com.wire.kalium.persistence.db.globalDatabaseProvider -import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestDispatcher +import kotlinx.coroutines.test.UnconfinedTestDispatcher import java.nio.file.Files actual abstract class GlobalDBBaseTest { - private val dispatcher: TestDispatcher = StandardTestDispatcher() - private val databaseFile = Files.createTempDirectory("test-storage").toFile().resolve("test-kalium.db") + @OptIn(ExperimentalCoroutinesApi::class) + private val dispatcher: TestDispatcher = UnconfinedTestDispatcher() + val databaseFile = Files.createTempDirectory("test-storage").toFile().resolve("test-kalium.db") actual fun deleteDatabase() { databaseFile.delete() } - actual fun createDatabase(): GlobalDatabaseBuilder { + actual fun createDatabase(platformDatabaseData: PlatformDatabaseData): GlobalDatabaseBuilder { return globalDatabaseProvider( - platformDatabaseData = PlatformDatabaseData( - StorageData.FileBacked(databaseFile) - ), + platformDatabaseData = platformDatabaseData, queriesContext = dispatcher, passphrase = null, enableWAL = false diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt b/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt similarity index 97% rename from persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt rename to persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt index 5ab0fe61e87..05df3cabefc 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt +++ b/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/daokaliumdb/ServerConfigurationDAOTest.kt @@ -22,6 +22,8 @@ package com.wire.kalium.persistence.daokaliumdb import com.wire.kalium.persistence.GlobalDBBaseTest import com.wire.kalium.persistence.db.GlobalDatabaseBuilder +import com.wire.kalium.persistence.db.PlatformDatabaseData +import com.wire.kalium.persistence.db.StorageData import com.wire.kalium.persistence.model.ServerConfigEntity import com.wire.kalium.persistence.utils.stubs.newServerConfig import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -45,7 +47,7 @@ class ServerConfigurationDAOTest : GlobalDBBaseTest() { @BeforeTest fun setup() { - globalDatabaseBuilder = createDatabase() + globalDatabaseBuilder = createDatabase(PlatformDatabaseData(StorageData.FileBacked(databaseFile))) } @AfterTest diff --git a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt b/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt similarity index 94% rename from persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt rename to persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt index 31ba21d5bb5..add0815979b 100644 --- a/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt +++ b/persistence/src/jvmTest/kotlin/com/wire/kalium/persistence/globalDB/AccountsDAOTest.kt @@ -26,11 +26,15 @@ import com.wire.kalium.persistence.daokaliumdb.FullAccountEntity import com.wire.kalium.persistence.daokaliumdb.PersistentWebSocketStatusEntity import com.wire.kalium.persistence.daokaliumdb.ServerConfigurationDAO import com.wire.kalium.persistence.db.GlobalDatabaseBuilder +import com.wire.kalium.persistence.db.PlatformDatabaseData +import com.wire.kalium.persistence.db.StorageData import com.wire.kalium.persistence.model.LogoutReason import com.wire.kalium.persistence.model.ServerConfigEntity import com.wire.kalium.persistence.model.SsoIdEntity import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.testcontainers.containers.PostgreSQLContainer import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertEquals @@ -39,12 +43,23 @@ import kotlin.test.assertNull @OptIn(ExperimentalCoroutinesApi::class) class AccountsDAOTest : GlobalDBBaseTest() { + @JvmField + @Rule + var postgresContainer: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:15-alpine") + lateinit var globalDatabaseBuilder: GlobalDatabaseBuilder @BeforeTest fun setUp() = runTest { - deleteDatabase() - globalDatabaseBuilder = createDatabase() + globalDatabaseBuilder = createDatabase( + PlatformDatabaseData( + StorageData.Postgres( + postgresContainer.jdbcUrl, + postgresContainer.username, + postgresContainer.password, + ) + ) + ) with(SERVER_CONFIG) { globalDatabaseBuilder.serverConfigurationDAO.insert(