generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: provide Kotlin API for db connections
- Loading branch information
Showing
5 changed files
with
184 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package ftl.dbtest | ||
|
||
import xyz.block.ftl.Context | ||
import xyz.block.ftl.Verb | ||
import xyz.block.ftl.Database | ||
|
||
data class DbRequest(val data: String?) | ||
data class DbResponse(val message: String? = "ok") | ||
|
||
val db = Database("testdb") | ||
|
||
class DbTest { | ||
@Verb | ||
fun create(context: Context, req: DbRequest): DbResponse { | ||
persistRequest(req) | ||
return DbResponse() | ||
} | ||
|
||
fun persistRequest(req: DbRequest) { | ||
db.conn { | ||
it.prepareStatement( | ||
""" | ||
CREATE TABLE IF NOT EXISTS requests | ||
( | ||
data TEXT, | ||
created_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc'), | ||
updated_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() AT TIME ZONE 'utc') | ||
); | ||
""" | ||
).execute() | ||
it.prepareStatement("INSERT INTO requests (data) VALUES ('${req.data}');") | ||
.execute() | ||
} | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
kotlin-runtime/ftl-runtime/src/main/kotlin/xyz/block/ftl/Database.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package xyz.block.ftl | ||
|
||
import xyz.block.ftl.logging.Logging | ||
import java.net.URI | ||
import java.sql.Connection | ||
import java.sql.DriverManager | ||
|
||
private const val FTL_DSN_VAR_PREFIX = "FTL_POSTGRES_DSN" | ||
|
||
/** | ||
* `Database` is a simple wrapper around the JDBC driver manager that provides a connection to the database specified | ||
* by the FTL_POSTGRES_DSN_<moduleName>_<dbName> environment variable. | ||
*/ | ||
class Database(private val name: String) { | ||
private val logger = Logging.logger(Database::class) | ||
private val moduleName: String = Thread.currentThread().stackTrace[2]?.let { | ||
val components = it.className.split(".") | ||
require(components.first() == "ftl") { | ||
"Expected Database to be declared in package ftl.<module>, but was $it" | ||
} | ||
|
||
return@let components[1] | ||
} ?: throw IllegalStateException("Could not determine module name from Database declaration") | ||
|
||
fun <R> conn(block: (c: Connection) -> R): R { | ||
return try { | ||
val envVar = listOf(FTL_DSN_VAR_PREFIX, moduleName, name).joinToString("_") | ||
val dsn = System.getenv(envVar) | ||
require(dsn != null) { "$envVar environment variable not set" } | ||
|
||
DriverManager.getConnection(dsnToJdbcUrl(dsn)).use { | ||
block(it) | ||
} | ||
} catch (e: Exception) { | ||
logger.error("Could not connect to database", e) | ||
throw e | ||
} | ||
} | ||
|
||
private fun dsnToJdbcUrl(dsn: String): String { | ||
val uri = URI(dsn) | ||
val scheme = uri.scheme ?: throw IllegalArgumentException("Missing scheme in DSN.") | ||
val userInfo = uri.userInfo?.split(":") ?: throw IllegalArgumentException("Missing userInfo in DSN.") | ||
val user = userInfo.firstOrNull() ?: throw IllegalArgumentException("Missing user in userInfo.") | ||
val password = if (userInfo.size > 1) userInfo[1] else "" | ||
val host = uri.host ?: throw IllegalArgumentException("Missing host in DSN.") | ||
val port = if (uri.port != -1) uri.port.toString() else throw IllegalArgumentException("Missing port in DSN.") | ||
val database = uri.path.trimStart('/') | ||
val parameters = uri.query?.replace("&", "?") ?: "" | ||
|
||
val jdbcScheme = when (scheme) { | ||
"postgres" -> "jdbc:postgresql" | ||
else -> throw IllegalArgumentException("Unsupported scheme: $scheme") | ||
} | ||
|
||
val jdbcUrl = "$jdbcScheme://$host:$port/$database?$parameters" | ||
return if (user.isNotBlank() && password.isNotBlank()) { | ||
"$jdbcUrl&user=$user&password=$password" | ||
} else { | ||
jdbcUrl | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters