diff --git a/.gitignore b/.gitignore index 5d75b3c..7986ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -64,4 +64,5 @@ bin/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store +local.properties diff --git a/build.gradle.kts b/build.gradle.kts index acf2e1b..0e76c6b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,10 @@ plugins { kotlin("jvm") version "1.9.0" + + // SQLDelight - Generates typesafe Kotlin APIs from SQL + // https://cashapp.github.io/sqldelight/2.0.0/ + id("app.cash.sqldelight") version "2.0.0" + application } @@ -7,10 +12,45 @@ group = "dev.hossain.postgresqldelight" version = "1.0-SNAPSHOT" repositories { + google() mavenCentral() } +// SQLDelight - Generates typesafe Kotlin APIs from SQL +// https://github.com/cashapp/sqldelight +sqldelight { + databases { + create("SportsDatabase") { + packageName.set("dev.hossain.postgresqldelight") + // https://cashapp.github.io/sqldelight/2.0.0/jvm_postgresql/ + dialect("app.cash.sqldelight:postgresql-dialect:2.0.0") + } + } +} + +java { + toolchain.languageVersion.set(JavaLanguageVersion.of(19)) +} + dependencies { + // PostgreSQL is a powerful object-relational database system + // https://www.postgresql.org/ + // https://mvnrepository.com/artifact/org.postgresql/postgresql + implementation("org.postgresql:postgresql:42.6.0") + + // 光 HikariCP・A solid, high-performance, JDBC connection pool at last. + // https://github.com/brettwooldridge/HikariCP#artifacts + // https://www.baeldung.com/hikaricp + implementation("com.zaxxer:HikariCP:5.0.1") + + // SQLDelight - Generates typesafe Kotlin APIs from SQL + // https://cashapp.github.io/sqldelight/2.0.0/jvm_postgresql + implementation("app.cash.sqldelight:jdbc-driver:2.0.0") + + // Faker - Generates fake data for testing or populating a development database. + // https://github.com/blocoio/faker + implementation("io.github.serpro69:kotlin-faker:1.14.0") + testImplementation(kotlin("test")) } diff --git a/sample-local.properties b/sample-local.properties new file mode 100644 index 0000000..3f83a94 --- /dev/null +++ b/sample-local.properties @@ -0,0 +1,8 @@ +## Rename this file to `local.properties` +## These properties are loaded via `AppConfigLoader` +# ---------------------------------------------------- + +## Data based properties +db_name=YourDbName +db_username=your_db_username +db_password=your_db_password \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt deleted file mode 100644 index f2a59b6..0000000 --- a/src/main/kotlin/Main.kt +++ /dev/null @@ -1,7 +0,0 @@ -fun main(args: Array) { - println("Hello World!") - - // Try adding program arguments via Run/Debug configuration. - // Learn more about running applications: https://www.jetbrains.com/help/idea/running-applications.html. - println("Program arguments: ${args.joinToString()}") -} \ No newline at end of file diff --git a/src/main/kotlin/dev/hossain/postgresqldelight/AppConfig.kt b/src/main/kotlin/dev/hossain/postgresqldelight/AppConfig.kt new file mode 100644 index 0000000..5ca8d5a --- /dev/null +++ b/src/main/kotlin/dev/hossain/postgresqldelight/AppConfig.kt @@ -0,0 +1,3 @@ +package dev.hossain.postgresqldelight + +data class AppConfig(val dbName: String, val dbUsername: String, val dbPassword: String) diff --git a/src/main/kotlin/dev/hossain/postgresqldelight/AppConfigLoader.kt b/src/main/kotlin/dev/hossain/postgresqldelight/AppConfigLoader.kt new file mode 100644 index 0000000..209a58d --- /dev/null +++ b/src/main/kotlin/dev/hossain/postgresqldelight/AppConfigLoader.kt @@ -0,0 +1,18 @@ +package dev.hossain.postgresqldelight + +import java.io.File +import java.util.* + +class AppConfigLoader { + /** + * Loads app config from local properties file. + */ + fun loadAppConfig(): AppConfig { + val properties = Properties().apply { load(File("local.properties").inputStream()) } + return AppConfig( + dbName = properties.getProperty("db_name"), + dbUsername = properties.getProperty("db_username"), + dbPassword = properties.getProperty("db_password"), + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/hossain/postgresqldelight/Main.kt b/src/main/kotlin/dev/hossain/postgresqldelight/Main.kt new file mode 100644 index 0000000..823bb0b --- /dev/null +++ b/src/main/kotlin/dev/hossain/postgresqldelight/Main.kt @@ -0,0 +1,52 @@ +package dev.hossain.postgresqldelight + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.jdbc.asJdbcDriver +import io.github.serpro69.kfaker.Faker +import io.github.serpro69.kfaker.faker +import javax.sql.DataSource +import kotlin.math.absoluteValue +import kotlin.random.Random + + +fun main(args: Array) { + println("Begin SQLDelight 2.0 with PostgreSQL Sample!") + val appConfigLoader = AppConfigLoader() + val sportsRepository = SportsRepository() + val dataSource: DataSource = sportsRepository.getSource(appConfigLoader.loadAppConfig()) + + + val driver: SqlDriver = dataSource.asJdbcDriver() + SportsDatabase.Schema.create(driver) + + testDriveDatabase(driver, faker { }) +} + + +/** + * @param driver the [SqlDriver] required to create the database. + * @param faker Fake data generator. + */ +fun testDriveDatabase(driver: SqlDriver, faker: Faker) { + val database = SportsDatabase(driver) + + val playerQueries: PlayerQueries = database.playerQueries + + // Show all players + println(playerQueries.selectAll().executeAsList()) + + + // Uses query param to insert data + playerQueries.insert( + player_number = Random.nextInt().absoluteValue, + full_name = faker.name.name() + ) + println(playerQueries.selectAll().executeAsList()) + + // Uses full data object to insert data + val player = HockeyPlayer( + player_number = Random.nextInt().absoluteValue, + full_name = faker.name.name() + ) + playerQueries.insertFullPlayerObject(player) +} \ No newline at end of file diff --git a/src/main/kotlin/dev/hossain/postgresqldelight/SportsRepository.kt b/src/main/kotlin/dev/hossain/postgresqldelight/SportsRepository.kt new file mode 100644 index 0000000..dd97cdb --- /dev/null +++ b/src/main/kotlin/dev/hossain/postgresqldelight/SportsRepository.kt @@ -0,0 +1,21 @@ +package dev.hossain.postgresqldelight + +import com.zaxxer.hikari.HikariConfig +import com.zaxxer.hikari.HikariDataSource +import javax.sql.DataSource + +class SportsRepository constructor( + +) { + + fun getSource(appConfig: AppConfig): DataSource { + val hikariConfig = HikariConfig() + // https://jdbc.postgresql.org/documentation/use/ + hikariConfig.setJdbcUrl("jdbc:postgresql://192.168.2.32:5432/${appConfig.dbName}") + hikariConfig.driverClassName = "org.postgresql.Driver" + hikariConfig.username = appConfig.dbUsername + hikariConfig.password = appConfig.dbPassword + + return HikariDataSource(hikariConfig) + } +} \ No newline at end of file diff --git a/src/main/sqldelight/dev/hossain/postgresqldelight/Player.sq b/src/main/sqldelight/dev/hossain/postgresqldelight/Player.sq new file mode 100644 index 0000000..5deae7e --- /dev/null +++ b/src/main/sqldelight/dev/hossain/postgresqldelight/Player.sq @@ -0,0 +1,25 @@ +-- Source: https://cashapp.github.io/sqldelight/2.0.0/jvm_postgresql/#fresh-schema +CREATE TABLE IF NOT EXISTS hockeyPlayer ( + player_number INTEGER PRIMARY KEY NOT NULL, + full_name TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS hockeyPlayer_full_name ON hockeyPlayer(full_name); + + +selectAll: +SELECT * +FROM hockeyPlayer; + +insert: +INSERT INTO hockeyPlayer(player_number, full_name) +VALUES (?, ?); + +insertFullPlayerObject: +INSERT INTO hockeyPlayer(player_number, full_name) +VALUES ?; + + +-- tableExists: +-- https://database.guide/5-ways-to-check-if-a-table-exists-in-postgresql/ +-- SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'hockeyPlayer');