diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..32dd675 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,4 @@ +FROM mcr.microsoft.com/devcontainers/java:21 + +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends libpq-dev diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..902f9b0 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,12 @@ +{ + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "ghcr.io/devcontainers/features/sshd:1": {} + }, + + "forwardPorts": [ + 5432 + ] +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..adea18b --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3.8' + +services: + app: + container_name: javadev + # https://youtrack.jetbrains.com/issue/KT-36871/Support-Aarch64-Linux-as-a-host-for-the-Kotlin-Native + platform: "linux/amd64" + build: + context: . + dockerfile: Dockerfile + environment: + POSTGRES_HOSTNAME: postgresdb + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + + volumes: + - ../..:/workspaces:cached + + command: sleep infinity + + network_mode: service:db + + db: + container_name: postgresdb + image: postgres:latest + restart: unless-stopped + healthcheck: + test: [ "CMD-SHELL", "pg_isready" ] + interval: 1s + timeout: 5s + retries: 10 + environment: + POSTGRES_DB: postgres + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + ports: + - "5432:5432" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 99c0223..346adc8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,3 +13,15 @@ updates: assignees: - "hfhbd" rebase-strategy: "disabled" + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: "daily" + assignees: + - "hfhbd" + - package-ecosystem: "docker" + directory: "/.devcontainer" + schedule: + interval: "daily" + assignees: + - "hfhbd" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..993f93e --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,11 @@ +changelog: + categories: + - title: Features + labels: + - '*' + exclude: + labels: + - dependencies + - title: Updated Dependencies + labels: + - dependencies diff --git a/.github/workflows/CD.yml b/.github/workflows/CD.yml index 7451bd9..1e4fad3 100644 --- a/.github/workflows/CD.yml +++ b/.github/workflows/CD.yml @@ -11,20 +11,19 @@ jobs: steps: - name: Set environment for version run: long="${{ github.ref }}"; version=${long#"refs/tags/v"}; echo "version=${version}" >> $GITHUB_ENV - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - uses: Homebrew/actions/setup-homebrew@master id: set-up-homebrew - - uses: gradle/wrapper-validation-action@v1 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: - distribution: 'adopt' - java-version: 17 - - uses: gradle/gradle-build-action@v2 + distribution: 'temurin' + java-version: 21 + - uses: gradle/actions/setup-gradle@v4 - run: brew install libpq - - name: Build with Gradle - run: ./gradlew assemble + env: + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: true - name: Publish - run: ./gradlew -Pversion=$version -Dorg.gradle.parallel=false publish closeAndReleaseStagingRepository + run: ./gradlew -Pversion=$version -Dorg.gradle.parallel=false --no-configuration-cache publish closeAndReleaseStagingRepositories env: ORG_GRADLE_PROJECT_signingKey: ${{ secrets.SIGNING_PRIVATE_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.SIGNING_PASSWORD }} diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 187af54..df0f9d8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -9,22 +9,27 @@ on: jobs: build: runs-on: ${{ matrix.os }} + permissions: + contents: write strategy: matrix: - os: [ 'ubuntu-latest', 'macos-latest' ] + os: [ 'ubuntu-latest', 'ubuntu-24.04', 'macos-latest' ] + + env: + HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: true + steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - uses: Homebrew/actions/setup-homebrew@master id: set-up-homebrew - - uses: gradle/wrapper-validation-action@v1 - run: brew install libpq - - uses: actions/setup-java@v3 - with: - distribution: 'adopt' - java-version: 17 - - uses: gradle/gradle-build-action@v2 - - run: ./gradlew assemble - - uses: ikalnytskyi/action-setup-postgres@v4 + - uses: ikalnytskyi/action-setup-postgres@v7 with: password: password + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - uses: gradle/actions/setup-gradle@v4 + - run: ./gradlew assemble - run: ./gradlew build diff --git a/.github/workflows/Docs.yml b/.github/workflows/Docs.yml new file mode 100644 index 0000000..7410a1a --- /dev/null +++ b/.github/workflows/Docs.yml @@ -0,0 +1,45 @@ +name: Docs + +on: + release: + types: [ created ] + workflow_dispatch: + +concurrency: + group: "docs" + cancel-in-progress: false + +env: + GRADLE_OPTS: -Dorg.gradle.caching=true + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - uses: actions/configure-pages@v5 + - uses: actions/checkout@v5 + - uses: Homebrew/actions/setup-homebrew@master + id: set-up-homebrew + - run: brew install libpq + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - uses: gradle/actions/setup-gradle@v4 + - name: Generate Docs + run: ./gradlew :dokkaHtmlMultiModule --no-configuration-cache + - uses: actions/upload-pages-artifact@v4 + with: + path: build/dokka/htmlMultiModule + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml new file mode 100644 index 0000000..9259ece --- /dev/null +++ b/.github/workflows/dependencies.yml @@ -0,0 +1,32 @@ +name: Dependency review for pull requests + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + dependency-submission: + runs-on: ubuntu-latest + permissions: + contents: write + + env: + GRADLE_OPTS: -Dorg.gradle.caching=true + + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + - uses: gradle/actions/dependency-submission@v4 + + dependency-review: + runs-on: ubuntu-latest + needs: dependency-submission + if: github.event_name == 'pull_request' + + steps: + - uses: actions/dependency-review-action@v4 diff --git a/.gitignore b/.gitignore index e0d53d8..aa238fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .gradle .idea +.kotlin build diff --git a/README.md b/README.md index a938ad8..2d63d9c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Module postgres-native-sqldelight +# PostgreSQL native SQLDelight driver A native Postgres driver using libpq. @@ -6,6 +6,9 @@ You can use the driver with [SQLDelight](https://github.com/cashapp/sqldelight), - [Source code](https://github.com/hfhbd/postgres-native-sqldelight) +> Keep in mind, until now, this is only a single-threaded wrapper over libpq using 1 connection only. There is no +> connection pool nor multithread support (like JDBC or R2DBC). + ## Install You need `libpq` installed and available in your `$PATH`. @@ -13,8 +16,6 @@ You need `libpq` installed and available in your `$PATH`. This package is uploaded to MavenCentral and supports macOS and linuxX64. Windows is currently not supported. -Supported SQLDelight version: 2.0.0-alpha05. - ````kotlin repositories { mavenCentral() @@ -24,7 +25,7 @@ dependencies { implementation("app.softwork:postgres-native-sqldelight-driver:LATEST") } -// optional SQLDelight setup: requires 2.0.0-alpha05 +// optional SQLDelight setup: sqldelight { databases.register("NativePostgres") { dialect("app.softwork:postgres-native-sqldelight-dialect:LATEST") @@ -47,10 +48,12 @@ val driver = PostgresNativeDriver( ) ``` +### Listeners + This driver supports local and remote listeners. -Local listeners only notify this client, ideally for testing or using the database with only one client at a time with -SQLDelight only. -Remote listener support uses `NOTIFY` and `LISTEN`, so you can use this with multiple clients or with existing database +Local listeners only notify this client, ideally for testing or using the database with only one client at a time. +Remote listener support uses `NOTIFY` and `LISTEN`, so you can use this to sync multiple clients or with existing +database triggers. SQLDelight uses and expects the table name as payload, but you can provide a mapper function. @@ -67,35 +70,10 @@ The identifier is used to reuse prepared statements. driver.execute(identifier = null, sql = "INSERT INTO foo VALUES (42)", parameters = 0, binders = null) ``` -It also supports a real lazy cursor or a Flow. The `fetchSize` parameter defines how many rows are fetched at once: +It also supports a real lazy cursor by using a `Flow`. The `fetchSize` parameter defines how many rows are fetched at +once: ```kotlin -val names: List = driver.executeQueryWithNativeCursor( - identifier = null, - sql = "SELECT index, name, bytes FROM foo", - mapper = { cursor -> - // You need to call `next` and use the cursor to return your type, here it is a list. - - // Important, don't leak this cursor, eg by returning a Sequence, - // otherwise the cursor will be closed before fetching rows. - // If you need to use an async iterator, use the `Flow` overload, `executeQueryAsFlow`. - buildList { - while (cursor.next()) { - add( - Simple( - index = cursor.getLong(0)!!.toInt(), - name = cursor.getString(1), - byteArray = cursor.getBytes(2) - ) - ) - } - } - }, - parameters = 0, - fetchSize = 100, - binders = null -) - val namesFlow: Flow = driver.executeQueryAsFlow( identifier = null, sql = "SELECT index, name, bytes FROM foo", @@ -125,34 +103,40 @@ Apache 2 ## Contributing -You need libpq installed: https://formulae.brew.sh/formula/libpq#default +### Devcontainers + +Start the devcontainer, that's it. + +### Local + +#### docker compose + +This is the preferred local option. The `app` service contains the JVM as well as libpq. -You have to add the compiler flags to your path too. -The exact commands depend on your config, but you will get them during installing libpq with homebrew. +#### Manual -Sample commands: +You need to install `libpq`, eg using Homebrew: https://formulae.brew.sh/formula/libpq#default + +For installation using homebrew, the default path is already added. + +Otherwise, you have to add the compiler flags to +the [libpq.def](postgres-native-sqldelight-driver/src/nativeInterop/cinterop/libpq.def). +The exact flags depend on your config, for example: ``` -If you need to have libpq first in your PATH, run: - echo 'export PATH="/home/linuxbrew/.linuxbrew/opt/libpq/bin:$PATH"' >> /home/runner/.bash_profile For compilers to find libpq you may need to set: export LDFLAGS="-L/home/linuxbrew/.linuxbrew/opt/libpq/lib" export CPPFLAGS="-I/home/linuxbrew/.linuxbrew/opt/libpq/include" ``` -### Testing - -If you install libpq with homebrew, it will install the platform-specific artifact. +##### Testing -| Host | Supported test targets | -|-------------|------------------------| -| linux x64 | linux x64 | -| macOS x64 | macOS x64 | -| macOS arm64 | macOS arm64 | +If you installed libpq with homebrew, it will install the platform-specific artifact. To test other platforms, eg. linux x64 on macOS, you need to install the platform-specific libpq of linux x64 too. To start the postgres instance, you can use docker: -``` + +```sh docker run -e POSTGRES_PASSWORD=password -p 5432:5432 postgres ``` diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts deleted file mode 100644 index cb218f1..0000000 --- a/build-logic/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -plugins { - `kotlin-dsl` -} - -dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0") - implementation("com.alecstrong:grammar-kit-composer:0.1.11") - implementation("io.github.gradle-nexus:publish-plugin:1.1.0") - implementation("org.jetbrains.kotlinx:binary-compatibility-validator:0.12.1") - implementation("app.cash.sqldelight:gradle-plugin:2.0.0-alpha05") - implementation("app.cash.licensee:licensee-gradle-plugin:1.6.0") -} - -kotlin { - jvmToolchain { - languageVersion.set(JavaLanguageVersion.of(17)) - } -} - -gradlePlugin { - plugins { - register("MyRepos") { - id = "MyRepos" - implementationClass = "MyRepos" - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts index 18aa25b..b8e4a83 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,14 @@ plugins { - io.github.`gradle-nexus`.`publish-plugin` + id("io.github.gradle-nexus.publish-plugin") + id("org.jetbrains.dokka") +} + +tasks.dokkaHtmlMultiModule { + includes.from("README.md") } nexusPublishing { - repositories { + this.repositories { sonatype { nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) diff --git a/gradle.properties b/gradle.properties index 8c355b5..35f98a5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,5 +3,7 @@ kotlin.mpp.enableCInteropCommonization=true org.gradle.parallel=true org.gradle.jvmargs=-Xmx2048m +org.gradle.configuration-cache=true +org.gradle.configureondemand=true group=app.softwork diff --git a/gradle/build-logic/build.gradle.kts b/gradle/build-logic/build.gradle.kts new file mode 100644 index 0000000..003cb81 --- /dev/null +++ b/gradle/build-logic/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `kotlin-dsl` +} + +dependencies { + implementation(libs.plugins.kotlin.jvm.toDep()) + implementation(libs.plugins.kotlin.serialization.toDep()) + implementation(libs.plugins.grammarKit.toDep()) + implementation(libs.plugins.publish.toDep()) + implementation(libs.plugins.binary.toDep()) + implementation(libs.plugins.sqldelight.toDep()) + implementation(libs.plugins.licensee.toDep()) + implementation(libs.plugins.dokka.toDep()) +} + +fun Provider.toDep() = map { + "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" +} + +kotlin.jvmToolchain(17) diff --git a/build-logic/settings.gradle.kts b/gradle/build-logic/settings.gradle.kts similarity index 54% rename from build-logic/settings.gradle.kts rename to gradle/build-logic/settings.gradle.kts index 76a0431..10af82a 100644 --- a/build-logic/settings.gradle.kts +++ b/gradle/build-logic/settings.gradle.kts @@ -4,6 +4,13 @@ dependencyResolutionManagement { mavenCentral() gradlePluginPortal() } + versionCatalogs.register("libs") { + from(files("../libs.versions.toml")) + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" } rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/MyRepos.kt b/gradle/build-logic/src/main/kotlin/MyRepos.kt similarity index 52% rename from build-logic/src/main/kotlin/MyRepos.kt rename to gradle/build-logic/src/main/kotlin/MyRepos.kt index 4dbc4c6..bc41370 100644 --- a/build-logic/src/main/kotlin/MyRepos.kt +++ b/gradle/build-logic/src/main/kotlin/MyRepos.kt @@ -1,17 +1,5 @@ -import org.gradle.api.* -import org.gradle.api.artifacts.dsl.* -import org.gradle.api.initialization.* -import org.gradle.kotlin.dsl.* - -class MyRepos : Plugin { - override fun apply(settings: Settings) { - settings.dependencyResolutionManagement { - repositories { - repos() - } - } - } -} +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.kotlin.dsl.maven fun RepositoryHandler.repos() { mavenCentral() diff --git a/gradle/build-logic/src/main/kotlin/MyRepos.settings.gradle.kts b/gradle/build-logic/src/main/kotlin/MyRepos.settings.gradle.kts new file mode 100644 index 0000000..964ff0e --- /dev/null +++ b/gradle/build-logic/src/main/kotlin/MyRepos.settings.gradle.kts @@ -0,0 +1,5 @@ +dependencyResolutionManagement { + repositories { + repos() + } +} diff --git a/build-logic/src/main/kotlin/exclude.gradle.kts b/gradle/build-logic/src/main/kotlin/exclude.gradle.kts similarity index 100% rename from build-logic/src/main/kotlin/exclude.gradle.kts rename to gradle/build-logic/src/main/kotlin/exclude.gradle.kts diff --git a/build-logic/src/main/kotlin/publish.gradle.kts b/gradle/build-logic/src/main/kotlin/publish.gradle.kts similarity index 75% rename from build-logic/src/main/kotlin/publish.gradle.kts rename to gradle/build-logic/src/main/kotlin/publish.gradle.kts index 9a09123..92093b8 100644 --- a/build-logic/src/main/kotlin/publish.gradle.kts +++ b/gradle/build-logic/src/main/kotlin/publish.gradle.kts @@ -4,8 +4,8 @@ import org.gradle.kotlin.dsl.* import java.util.* plugins { - `maven-publish` - signing + id("maven-publish") + id("signing") } val emptyJar by tasks.registering(Jar::class) { } @@ -13,8 +13,10 @@ val emptyJar by tasks.registering(Jar::class) { } publishing { publications.configureEach { this as MavenPublication - artifact(emptyJar) { - classifier = "javadoc" + if (project.name != "postgres-native-sqldelight-dialect") { + artifact(emptyJar) { + classifier = "javadoc" + } } pom { name.set("app.softwork Postgres Native Driver and SqlDelight Dialect") @@ -45,8 +47,10 @@ publishing { signing { val signingKey: String? by project val signingPassword: String? by project - useInMemoryPgpKeys(signingKey?.let { String(Base64.getDecoder().decode(it)).trim() }, signingPassword) - sign(publishing.publications) + signingKey?.let { + useInMemoryPgpKeys(String(Base64.getDecoder().decode(it)).trim(), signingPassword) + sign(publishing.publications) + } } // https://youtrack.jetbrains.com/issue/KT-46466 @@ -54,3 +58,10 @@ val signingTasks = tasks.withType() tasks.withType().configureEach { dependsOn(signingTasks) } + +tasks.withType().configureEach { + isPreserveFileTimestamps = false + isReproducibleFileOrder = true + filePermissions {} + dirPermissions {} +} diff --git a/build-logic/src/main/kotlin/repos.gradle.kts b/gradle/build-logic/src/main/kotlin/repos.gradle.kts similarity index 100% rename from build-logic/src/main/kotlin/repos.gradle.kts rename to gradle/build-logic/src/main/kotlin/repos.gradle.kts diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 0000000..63e5bbd --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,2 @@ +#This file is generated by updateDaemonJvm +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..aa6cf70 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,28 @@ +[versions] +kotlin = "2.1.21" +sqldelight = "2.0.2" +idea = "222.4459.24" +coroutines = "1.10.2" + +[libraries] +sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" } +sqldelight-postgresql-dialect = { module = "app.cash.sqldelight:postgresql-dialect", version.ref = "sqldelight" } +sqldelight-dialect-api = { module = "app.cash.sqldelight:dialect-api", version.ref = "sqldelight" } +sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } +sqldelight-compiler-env = { module = "app.cash.sqldelight:compiler-env", version.ref = "sqldelight" } + +coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } +ktor-network = { module = "io.ktor:ktor-network", version = "3.2.1" } +datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.2" } +serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version = "1.8.1" } + +[plugins] +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +grammarKit = { id = "com.alecstrong.grammar.kit.composer", version = "0.1.12" } +publish = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } +binary = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version = "0.17.0" } +sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } +licensee = { id = "app.cash.licensee", version = "1.12.0" } +dokka = { id = "org.jetbrains.dokka", version = "1.9.20" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ccebba7..2c35211 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bdee3c7..09523c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-rc-2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 79a61d4..f5feea6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -83,10 +85,9 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +134,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +201,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..9b42019 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,94 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/postgres-native-sqldelight-dialect/build.gradle.kts b/postgres-native-sqldelight-dialect/build.gradle.kts index 3b9ce7d..e3217e0 100644 --- a/postgres-native-sqldelight-dialect/build.gradle.kts +++ b/postgres-native-sqldelight-dialect/build.gradle.kts @@ -1,11 +1,11 @@ plugins { kotlin("jvm") - com.alecstrong.grammar.kit.composer - org.jetbrains.kotlinx.`binary-compatibility-validator` - app.cash.licensee - repos - publish - exclude + id("com.alecstrong.grammar.kit.composer") + id("org.jetbrains.kotlinx.binary-compatibility-validator") + id("app.cash.licensee") + id("repos") + id("publish") + id("exclude") } java { @@ -13,25 +13,23 @@ java { withSourcesJar() } -val idea = "222.4459.24" - grammarKit { - intellijRelease.set(idea) + intellijRelease.set(libs.versions.idea) } dependencies { - api("app.cash.sqldelight:postgresql-dialect:2.0.0-alpha05") + api(libs.sqldelight.postgresql.dialect) - api("app.cash.sqldelight:dialect-api:2.0.0-alpha05") + api(libs.sqldelight.dialect.api) - compileOnly("com.jetbrains.intellij.platform:analysis-impl:$idea") + compileOnly(libs.sqldelight.compiler.env) - testImplementation("com.jetbrains.intellij.platform:analysis-impl:$idea") testImplementation(kotlin("test")) + testImplementation(libs.sqldelight.compiler.env) } kotlin { - jvmToolchain(11) + jvmToolchain(17) target.compilations.configureEach { kotlinOptions.allWarningsAsErrors = true @@ -51,8 +49,7 @@ licensee { } publishing { - publications.configureEach { - this as MavenPublication + publications.register("maven") { from(components["java"]) } } diff --git a/postgres-native-sqldelight-dialect/src/main/kotlin/app/softwork/sqldelight/postgresdialect/PostgresNativeDialect.kt b/postgres-native-sqldelight-dialect/src/main/kotlin/app/softwork/sqldelight/postgresdialect/PostgresNativeDialect.kt index df8ed4d..4da31ad 100644 --- a/postgres-native-sqldelight-dialect/src/main/kotlin/app/softwork/sqldelight/postgresdialect/PostgresNativeDialect.kt +++ b/postgres-native-sqldelight-dialect/src/main/kotlin/app/softwork/sqldelight/postgresdialect/PostgresNativeDialect.kt @@ -84,9 +84,9 @@ private enum class PostgreSqlType(override val javaType: TypeName): DialectType TIMESTAMP(ClassName("kotlinx.datetime", "LocalDateTime")), TIMESTAMP_TIMEZONE(ClassName("kotlinx.datetime", "Instant")), INTERVAL(ClassName("kotlinx.datetime", "DateTimePeriod")), - UUID(ClassName("kotlinx.uuid", "UUID")); + UUID(ClassName("kotlin.uuid", "Uuid")); - override fun prepareStatementBinder(columnIndex: String, value: CodeBlock): CodeBlock { + override fun prepareStatementBinder(columnIndex: CodeBlock, value: CodeBlock): CodeBlock { return CodeBlock.builder() .add( when (this) { @@ -96,10 +96,10 @@ private enum class PostgreSqlType(override val javaType: TypeName): DialectType TIMESTAMP -> "bindLocalTimestamp" TIMESTAMP_TIMEZONE -> "bindTimestamp" INTERVAL -> "bindInterval" - UUID -> "bindUUID" + UUID -> "bindUuid" } ) - .add("($columnIndex, %L)\n", value) + .add("(%L, %L)\n", columnIndex, value) .build() } @@ -112,7 +112,7 @@ private enum class PostgreSqlType(override val javaType: TypeName): DialectType TIMESTAMP -> "$cursorName.getLocalTimestamp($columnIndex)" TIMESTAMP_TIMEZONE -> "$cursorName.getTimestamp($columnIndex)" INTERVAL -> "$cursorName.getInterval($columnIndex)" - UUID -> "$cursorName.getUUID($columnIndex)" + UUID -> "$cursorName.getUuid($columnIndex)" } ) } diff --git a/postgres-native-sqldelight-driver/api/postgres-native-sqldelight-driver.klib.api b/postgres-native-sqldelight-driver/api/postgres-native-sqldelight-driver.klib.api new file mode 100644 index 0000000..7c06973 --- /dev/null +++ b/postgres-native-sqldelight-driver/api/postgres-native-sqldelight-driver.klib.api @@ -0,0 +1,74 @@ +// Klib ABI Dump +// Targets: [linuxArm64, linuxX64, macosArm64, macosX64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +sealed interface app.softwork.sqldelight.postgresdriver/ListenerSupport { // app.softwork.sqldelight.postgresdriver/ListenerSupport|null[0] + final class Local : app.softwork.sqldelight.postgresdriver/ScopedListenerSupport { // app.softwork.sqldelight.postgresdriver/ListenerSupport.Local|null[0] + constructor (kotlinx.coroutines/CoroutineScope, kotlinx.coroutines.flow/Flow, kotlin.coroutines/SuspendFunction1) // app.softwork.sqldelight.postgresdriver/ListenerSupport.Local.|(kotlinx.coroutines.CoroutineScope;kotlinx.coroutines.flow.Flow;kotlin.coroutines.SuspendFunction1){}[0] + + final val notificationScope // app.softwork.sqldelight.postgresdriver/ListenerSupport.Local.notificationScope|{}notificationScope[0] + final fun (): kotlinx.coroutines/CoroutineScope // app.softwork.sqldelight.postgresdriver/ListenerSupport.Local.notificationScope.|(){}[0] + } + + final class Remote : app.softwork.sqldelight.postgresdriver/ScopedListenerSupport { // app.softwork.sqldelight.postgresdriver/ListenerSupport.Remote|null[0] + constructor (kotlinx.coroutines/CoroutineScope, kotlin/Function1 = ...) // app.softwork.sqldelight.postgresdriver/ListenerSupport.Remote.|(kotlinx.coroutines.CoroutineScope;kotlin.Function1){}[0] + + final val notificationScope // app.softwork.sqldelight.postgresdriver/ListenerSupport.Remote.notificationScope|{}notificationScope[0] + final fun (): kotlinx.coroutines/CoroutineScope // app.softwork.sqldelight.postgresdriver/ListenerSupport.Remote.notificationScope.|(){}[0] + } + + final object Companion { // app.softwork.sqldelight.postgresdriver/ListenerSupport.Companion|null[0] + final fun Local(kotlinx.coroutines/CoroutineScope): app.softwork.sqldelight.postgresdriver/ListenerSupport.Local // app.softwork.sqldelight.postgresdriver/ListenerSupport.Companion.Local|Local(kotlinx.coroutines.CoroutineScope){}[0] + } + + final object None : app.softwork.sqldelight.postgresdriver/ListenerSupport // app.softwork.sqldelight.postgresdriver/ListenerSupport.None|null[0] +} + +final class app.softwork.sqldelight.postgresdriver/PostgresNativeDriver : app.cash.sqldelight.db/SqlDriver { // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver|null[0] + constructor (kotlinx.cinterop/CPointer, app.softwork.sqldelight.postgresdriver/ListenerSupport) // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.|(kotlinx.cinterop.CPointer;app.softwork.sqldelight.postgresdriver.ListenerSupport){}[0] + + final fun <#A1: kotlin/Any?> executeQuery(kotlin/Int?, kotlin/String, kotlin/Function1>, kotlin/Int, kotlin/Function1?): app.cash.sqldelight.db/QueryResult<#A1> // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.executeQuery|executeQuery(kotlin.Int?;kotlin.String;kotlin.Function1>;kotlin.Int;kotlin.Function1?){0§}[0] + final fun <#A1: kotlin/Any?> executeQueryAsFlow(kotlin/Int?, kotlin/String, kotlin.coroutines/SuspendFunction1, kotlin/Int, kotlin/Int = ..., kotlin/Function1?): kotlinx.coroutines.flow/Flow<#A1> // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.executeQueryAsFlow|executeQueryAsFlow(kotlin.Int?;kotlin.String;kotlin.coroutines.SuspendFunction1;kotlin.Int;kotlin.Int;kotlin.Function1?){0§}[0] + final fun addListener(kotlin/Array..., app.cash.sqldelight/Query.Listener) // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.addListener|addListener(kotlin.Array...;app.cash.sqldelight.Query.Listener){}[0] + final fun close() // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.close|close(){}[0] + final fun copy(kotlin.sequences/Sequence): kotlin/Long // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.copy|copy(kotlin.sequences.Sequence){}[0] + final fun currentTransaction(): app.cash.sqldelight/Transacter.Transaction? // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.currentTransaction|currentTransaction(){}[0] + final fun execute(kotlin/Int?, kotlin/String, kotlin/Int, kotlin/Function1?): app.cash.sqldelight.db/QueryResult.Value // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.execute|execute(kotlin.Int?;kotlin.String;kotlin.Int;kotlin.Function1?){}[0] + final fun newTransaction(): app.cash.sqldelight.db/QueryResult.Value // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.newTransaction|newTransaction(){}[0] + final fun notifyListeners(kotlin/Array...) // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.notifyListeners|notifyListeners(kotlin.Array...){}[0] + final fun removeListener(kotlin/Array..., app.cash.sqldelight/Query.Listener) // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver.removeListener|removeListener(kotlin.Array...;app.cash.sqldelight.Query.Listener){}[0] +} + +final class app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement : app.cash.sqldelight.db/SqlPreparedStatement { // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement|null[0] + final fun bindBoolean(kotlin/Int, kotlin/Boolean?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindBoolean|bindBoolean(kotlin.Int;kotlin.Boolean?){}[0] + final fun bindBytes(kotlin/Int, kotlin/ByteArray?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindBytes|bindBytes(kotlin.Int;kotlin.ByteArray?){}[0] + final fun bindDate(kotlin/Int, kotlinx.datetime/LocalDate?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindDate|bindDate(kotlin.Int;kotlinx.datetime.LocalDate?){}[0] + final fun bindDouble(kotlin/Int, kotlin/Double?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindDouble|bindDouble(kotlin.Int;kotlin.Double?){}[0] + final fun bindInterval(kotlin/Int, kotlinx.datetime/DateTimePeriod?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindInterval|bindInterval(kotlin.Int;kotlinx.datetime.DateTimePeriod?){}[0] + final fun bindLocalTimestamp(kotlin/Int, kotlinx.datetime/LocalDateTime?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindLocalTimestamp|bindLocalTimestamp(kotlin.Int;kotlinx.datetime.LocalDateTime?){}[0] + final fun bindLong(kotlin/Int, kotlin/Long?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindLong|bindLong(kotlin.Int;kotlin.Long?){}[0] + final fun bindString(kotlin/Int, kotlin/String?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindString|bindString(kotlin.Int;kotlin.String?){}[0] + final fun bindTime(kotlin/Int, kotlinx.datetime/LocalTime?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindTime|bindTime(kotlin.Int;kotlinx.datetime.LocalTime?){}[0] + final fun bindTimestamp(kotlin/Int, kotlinx.datetime/Instant?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindTimestamp|bindTimestamp(kotlin.Int;kotlinx.datetime.Instant?){}[0] + final fun bindUuid(kotlin/Int, kotlin.uuid/Uuid?) // app.softwork.sqldelight.postgresdriver/PostgresPreparedStatement.bindUuid|bindUuid(kotlin.Int;kotlin.uuid.Uuid?){}[0] +} + +sealed class app.softwork.sqldelight.postgresdriver/PostgresCursor : app.cash.sqldelight.db/SqlCursor { // app.softwork.sqldelight.postgresdriver/PostgresCursor|null[0] + final fun getDate(kotlin/Int): kotlinx.datetime/LocalDate? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getDate|getDate(kotlin.Int){}[0] + final fun getInterval(kotlin/Int): kotlinx.datetime/DateTimePeriod? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getInterval|getInterval(kotlin.Int){}[0] + final fun getLocalTimestamp(kotlin/Int): kotlinx.datetime/LocalDateTime? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getLocalTimestamp|getLocalTimestamp(kotlin.Int){}[0] + final fun getTime(kotlin/Int): kotlinx.datetime/LocalTime? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getTime|getTime(kotlin.Int){}[0] + final fun getTimestamp(kotlin/Int): kotlinx.datetime/Instant? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getTimestamp|getTimestamp(kotlin.Int){}[0] + final fun getUuid(kotlin/Int): kotlin.uuid/Uuid? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getUuid|getUuid(kotlin.Int){}[0] + open fun getBoolean(kotlin/Int): kotlin/Boolean? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getBoolean|getBoolean(kotlin.Int){}[0] + open fun getBytes(kotlin/Int): kotlin/ByteArray? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getBytes|getBytes(kotlin.Int){}[0] + open fun getDouble(kotlin/Int): kotlin/Double? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getDouble|getDouble(kotlin.Int){}[0] + open fun getLong(kotlin/Int): kotlin/Long? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getLong|getLong(kotlin.Int){}[0] + open fun getString(kotlin/Int): kotlin/String? // app.softwork.sqldelight.postgresdriver/PostgresCursor.getString|getString(kotlin.Int){}[0] +} + +final fun app.softwork.sqldelight.postgresdriver/PostgresNativeDriver(kotlin/String, kotlin/String, kotlin/String, kotlin/String, kotlin/Int = ..., kotlin/String? = ..., app.softwork.sqldelight.postgresdriver/ListenerSupport = ...): app.softwork.sqldelight.postgresdriver/PostgresNativeDriver // app.softwork.sqldelight.postgresdriver/PostgresNativeDriver|PostgresNativeDriver(kotlin.String;kotlin.String;kotlin.String;kotlin.String;kotlin.Int;kotlin.String?;app.softwork.sqldelight.postgresdriver.ListenerSupport){}[0] diff --git a/postgres-native-sqldelight-driver/build.gradle.kts b/postgres-native-sqldelight-driver/build.gradle.kts index 8827482..082c2ca 100644 --- a/postgres-native-sqldelight-driver/build.gradle.kts +++ b/postgres-native-sqldelight-driver/build.gradle.kts @@ -2,17 +2,19 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.* plugins { kotlin("multiplatform") - app.cash.licensee - repos - publish + id("org.jetbrains.kotlinx.binary-compatibility-validator") + id("app.cash.licensee") + id("repos") + id("publish") + id("org.jetbrains.dokka") } kotlin { explicitApi() - sourceSets { - configureEach { - languageSettings.progressiveMode = true - } + + compilerOptions { + progressiveMode.set(true) + optIn.add("kotlin.uuid.ExperimentalUuidApi") } fun KotlinNativeTarget.config() { @@ -28,22 +30,22 @@ kotlin { macosArm64 { config() } macosX64 { config() } linuxX64 { config() } + linuxArm64 { config() } // mingwX64 { config() } sourceSets { commonMain { dependencies { - api("io.ktor:ktor-network:2.2.2") - api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") - api("app.cash.sqldelight:runtime:2.0.0-alpha05") - api("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0") - api("app.softwork:kotlinx-uuid-core:0.0.17") + implementation(libs.ktor.network) + api(libs.coroutines.core) + api(libs.sqldelight.runtime) + api(libs.datetime) } } commonTest { dependencies { implementation(kotlin("test")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2") } } } @@ -52,3 +54,21 @@ kotlin { licensee { allow("Apache-2.0") } + +tasks.dokkaHtmlPartial { + dokkaSourceSets.configureEach { + externalDocumentationLink("https://cashapp.github.io/sqldelight/2.0.0/2.x/") + externalDocumentationLink( + url = "https://kotlinlang.org/api/kotlinx-datetime/", + packageListUrl = "https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/package-list", + ) + externalDocumentationLink("https://uuid.softwork.app") + externalDocumentationLink("https://kotlinlang.org/api/kotlinx.coroutines/") + } +} + +apiValidation { + klib { + enabled = true + } +} diff --git a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/ListenerSupport.kt b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/ListenerSupport.kt index d48de01..f68f847 100644 --- a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/ListenerSupport.kt +++ b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/ListenerSupport.kt @@ -30,6 +30,7 @@ public sealed interface ListenerSupport { ) : ScopedListenerSupport { override val notificationScope: CoroutineScope = notificationScope + Job() + @ExperimentalForeignApi internal fun remoteListener(conn: CPointer): Flow = channelFlow { val selector = SelectorManager() diff --git a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/NoCursor.kt b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/NoCursor.kt index ca8e09f..52f157d 100644 --- a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/NoCursor.kt +++ b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/NoCursor.kt @@ -4,6 +4,7 @@ import app.cash.sqldelight.db.* import kotlinx.cinterop.* import libpq.* +@ExperimentalForeignApi internal class NoCursor( result: CPointer ) : PostgresCursor(result), Closeable { @@ -14,12 +15,12 @@ internal class NoCursor( private val maxRowIndex = PQntuples(result) - 1 override var currentRowIndex = -1 - override fun next(): Boolean { + override fun next(): QueryResult.Value { return if (currentRowIndex < maxRowIndex) { currentRowIndex += 1 - true + QueryResult.Value(true) } else { - false + QueryResult.Value(false) } } } diff --git a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresCursor.kt b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresCursor.kt index fb2854c..bf99eff 100644 --- a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresCursor.kt +++ b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresCursor.kt @@ -3,10 +3,10 @@ package app.softwork.sqldelight.postgresdriver import app.cash.sqldelight.db.* import kotlinx.cinterop.* import kotlinx.datetime.* -import kotlinx.uuid.* import libpq.* -import kotlin.time.* +import kotlin.uuid.* +@OptIn(ExperimentalForeignApi::class) public sealed class PostgresCursor( internal var result: CPointer ) : SqlCursor { @@ -67,5 +67,5 @@ public sealed class PostgresCursor( } public fun getInterval(index: Int): DateTimePeriod? = getString(index)?.let { DateTimePeriod.parse(it) } - public fun getUUID(index: Int): UUID? = getString(index)?.toUUID() + public fun getUuid(index: Int): Uuid? = getString(index)?.let { Uuid.parse(it) } } diff --git a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriver.kt b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriver.kt index 02254f2..5b5a75d 100644 --- a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriver.kt +++ b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriver.kt @@ -1,12 +1,17 @@ package app.softwork.sqldelight.postgresdriver -import app.cash.sqldelight.* +import app.cash.sqldelight.Query +import app.cash.sqldelight.Transacter import app.cash.sqldelight.db.* import kotlinx.cinterop.* -import kotlinx.coroutines.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import libpq.* +@OptIn(ExperimentalForeignApi::class) public class PostgresNativeDriver( private val conn: CPointer, private val listenerSupport: ListenerSupport @@ -44,7 +49,7 @@ public class PostgresNativeDriver( } } - override fun addListener(listener: Query.Listener, queryKeys: Array) { + override fun addListener(vararg queryKeys: String, listener: Query.Listener) { when (listenerSupport) { ListenerSupport.None -> return is ListenerSupport.Local -> { @@ -67,7 +72,7 @@ public class PostgresNativeDriver( } } - override fun notifyListeners(queryKeys: Array) { + override fun notifyListeners(vararg queryKeys: String) { when (listenerSupport) { is ListenerSupport.Local -> { listenerSupport.notificationScope.launch { @@ -88,7 +93,7 @@ public class PostgresNativeDriver( } } - override fun removeListener(listener: Query.Listener, queryKeys: Array) { + override fun removeListener(vararg queryKeys: String, listener: Query.Listener) { val queryListeners = listeners[listener] if (queryListeners != null) { if (listenerSupport is ListenerSupport.Remote) { @@ -163,11 +168,21 @@ public class PostgresNativeDriver( private fun preparedStatementExists(identifier: Int): Boolean { val result = - executeQuery(null, "SELECT name FROM pg_prepared_statements WHERE name = '$identifier'", parameters = 0, binders = null, mapper = { - if (it.next()) { - it.getString(0) - } else null - }) + executeQuery( + null, + "SELECT name FROM pg_prepared_statements WHERE name = $1", + parameters = 1, + binders = { + bindString(0, identifier.toString()) + }, + mapper = { + it.next().map { next -> + if (next) { + it.getString(0) + } else null + } + } + ) return result.value != null } @@ -204,10 +219,10 @@ public class PostgresNativeDriver( override fun executeQuery( identifier: Int?, sql: String, - mapper: (SqlCursor) -> R, + mapper: (SqlCursor) -> QueryResult, parameters: Int, binders: (SqlPreparedStatement.() -> Unit)? - ): QueryResult.Value { + ): QueryResult { val preparedStatement = preparedStatement(parameters, binders) val result = if (identifier != null) { checkPreparedStatement(identifier, sql, parameters, preparedStatement) @@ -237,8 +252,7 @@ public class PostgresNativeDriver( } }.check(conn) - val value = NoCursor(result).use(mapper) - return QueryResult.Value(value = value) + return NoCursor(result).use(mapper) } internal companion object { @@ -261,7 +275,7 @@ public class PostgresNativeDriver( private inner class Transaction( override val enclosingTransaction: Transacter.Transaction? ) : Transacter.Transaction() { - override fun endTransaction(successful: Boolean): QueryResult.Unit { + override fun endTransaction(successful: Boolean): QueryResult.Value { if (enclosingTransaction == null) { if (successful) { conn.exec("END") @@ -280,8 +294,8 @@ public class PostgresNativeDriver( * Each element of stdin can be up to 2 GB. */ public fun copy(stdin: Sequence): Long { - for (stdin in stdin) { - val status = PQputCopyData(conn, stdin, stdin.encodeToByteArray().size) + for (std in stdin) { + val status = PQputCopyData(conn, std, std.encodeToByteArray().size) check(status == 1) { conn.error() } @@ -299,14 +313,17 @@ public class PostgresNativeDriver( sql: String, mapper: suspend (PostgresCursor) -> R, parameters: Int, - fetchSize: Int = 1, + fetchSize: Int = 10, binders: (PostgresPreparedStatement.() -> Unit)? ): Flow = flow { val (result, cursorName) = prepareQuery(identifier, sql, parameters, binders) - RealCursor(result, cursorName, conn, fetchSize).use { - while (it.next()) { - emit(mapper(it)) + val cursor = RealCursor(result, cursorName, conn, fetchSize) + try { + while (cursor.next().value) { + emit(mapper(cursor)) } + } finally { + cursor.close() } } @@ -350,41 +367,28 @@ public class PostgresNativeDriver( } }.check(conn) to cursorName } - - @Deprecated( - "Use executeQueryAsFlow instead or enter your use-case in https://github.com/hfhbd/postgres-native-sqldelight/issues/121", - replaceWith = ReplaceWith("executeQueryAsFlow(identifier, sql, mapper, parameters, fetchSize, binders)") - ) - public fun executeQueryWithNativeCursor( - identifier: Int?, - sql: String, - mapper: (PostgresCursor) -> R, - parameters: Int, - fetchSize: Int = 1, - binders: (PostgresPreparedStatement.() -> Unit)? - ): QueryResult.Value { - val (result, cursorName) = prepareQuery(identifier, sql, parameters, binders) - val value = RealCursor(result, cursorName, conn, fetchSize).use(mapper) - return QueryResult.Value(value = value) - } } +@ExperimentalForeignApi private fun CPointer?.error(): String { val errorMessage = PQerrorMessage(this)!!.toKString() PQfinish(this) return errorMessage } +@ExperimentalForeignApi internal fun CPointer?.clear() { PQclear(this) } +@ExperimentalForeignApi internal fun CPointer.exec(sql: String) { val result = PQexec(this, sql) result.check(this) result.clear() } +@ExperimentalForeignApi internal fun CPointer?.check(conn: CPointer): CPointer { val status = PQresultStatus(this) check(status == PGRES_TUPLES_OK || status == PGRES_COMMAND_OK || status == PGRES_COPY_IN) { @@ -393,6 +397,7 @@ internal fun CPointer?.check(conn: CPointer): CPointer.escaped(value: String): String { val cString = PQescapeIdentifier(this, value, value.length.convert()) val escaped = cString!!.toKString() @@ -400,6 +405,7 @@ private fun CPointer.escaped(value: String): String { return escaped } +@OptIn(ExperimentalForeignApi::class) public fun PostgresNativeDriver( host: String, database: String, @@ -418,8 +424,19 @@ public fun PostgresNativeDriver( pwd = password, pgoptions = options ) - require(PQstatus(conn) == ConnStatusType.CONNECTION_OK) { + val status = PQstatus(conn) + if (status == ConnStatusType.CONNECTION_BAD) { + throw IllegalArgumentException(conn.error()) + } + require(status == ConnStatusType.CONNECTION_OK) { conn.error() } return PostgresNativeDriver(conn!!, listenerSupport = listenerSupport) } + +private fun QueryResult.map(action: (T) -> R): QueryResult = when (this) { + is QueryResult.Value -> QueryResult.Value(action(value)) + is QueryResult.AsyncValue -> QueryResult.AsyncValue { + action(await()) + } +} diff --git a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresPreparedStatement.kt b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresPreparedStatement.kt index 50abfd5..238794d 100644 --- a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresPreparedStatement.kt +++ b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/PostgresPreparedStatement.kt @@ -3,10 +3,10 @@ package app.softwork.sqldelight.postgresdriver import app.cash.sqldelight.db.* import kotlinx.cinterop.* import kotlinx.datetime.* -import kotlinx.uuid.* -import kotlin.time.* +import kotlin.uuid.* public class PostgresPreparedStatement internal constructor(private val parameters: Int) : SqlPreparedStatement { + @ExperimentalForeignApi internal fun values(scope: AutofreeScope): CValuesRef> = createValues(parameters) { value = when (val value = _values[it]) { null -> null @@ -80,7 +80,7 @@ public class PostgresPreparedStatement internal constructor(private val paramete bind(index, value?.toString(), intervalOid) } - public fun bindUUID(index: Int, value: UUID?) { + public fun bindUuid(index: Int, value: Uuid?) { bind(index, value?.toString(), uuidOid) } diff --git a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/RealCursor.kt b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/RealCursor.kt index ea2b52d..d4c6b66 100644 --- a/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/RealCursor.kt +++ b/postgres-native-sqldelight-driver/src/commonMain/kotlin/app/softwork/sqldelight/postgresdriver/RealCursor.kt @@ -5,8 +5,9 @@ import kotlinx.cinterop.* import libpq.* /** - * Must be inside a transaction! + * Must be used inside a transaction! */ +@ExperimentalForeignApi internal class RealCursor( result: CPointer, private val name: String, @@ -22,7 +23,7 @@ internal class RealCursor( override var currentRowIndex = -1 private var maxRowIndex = -1 - override fun next(): Boolean { + override fun next(): QueryResult.Value { if (currentRowIndex == maxRowIndex) { currentRowIndex = -1 } @@ -32,7 +33,7 @@ internal class RealCursor( } return if (currentRowIndex < maxRowIndex) { currentRowIndex += 1 - true - } else false + QueryResult.Value(true) + } else QueryResult.Value(false) } } diff --git a/postgres-native-sqldelight-driver/src/nativeInterop/cinterop/libpq.def b/postgres-native-sqldelight-driver/src/nativeInterop/cinterop/libpq.def index 7fe6531..fa38fc1 100644 --- a/postgres-native-sqldelight-driver/src/nativeInterop/cinterop/libpq.def +++ b/postgres-native-sqldelight-driver/src/nativeInterop/cinterop/libpq.def @@ -4,5 +4,5 @@ package = libpq #staticLibraries = libpq.a #libraryPaths = /opt/homebrew/opt/libpq/lib -compilerOpts = -I/home/linuxbrew/.linuxbrew/opt/libpq/include -I/opt/homebrew/opt/libpq/include -I/usr/local/opt/libpq/include -linkerOpts = -L/home/linuxbrew/.linuxbrew/opt/libpq/lib -L/opt/homebrew/opt/libpq/lib -L/usr/local/opt/libpq/lib -lpq +compilerOpts = -I/home/linuxbrew/.linuxbrew/opt/libpq/include -I/opt/homebrew/opt/libpq/include -I/usr/local/opt/libpq/include -I/usr/include/postgresql +linkerOpts = -L/home/linuxbrew/.linuxbrew/opt/libpq/lib -L/opt/homebrew/opt/libpq/lib -L/usr/local/opt/libpq/lib -L/usr/lib64 -L/usr/lib -L/usr/lib/x86_64-linux-gnu -lpq diff --git a/settings.gradle.kts b/settings.gradle.kts index f0943b9..6703776 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,28 +3,31 @@ pluginManagement { mavenCentral() gradlePluginPortal() } - includeBuild("build-logic") + includeBuild("gradle/build-logic") } plugins { id("MyRepos") - id("com.gradle.enterprise") version "3.12.2" + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" + id("com.gradle.develocity") version "4.0.2" } -gradleEnterprise { +develocity { buildScan { - termsOfServiceUrl = "https://gradle.com/terms-of-service" - termsOfServiceAgree = "yes" - if (System.getenv("CI") != null) { - publishAlways() - tag("CI") + termsOfUseUrl.set("https://gradle.com/terms-of-service") + termsOfUseAgree.set("yes") + val isCI = providers.environmentVariable("CI").isPresent + publishing { + onlyIf { isCI } } + tag("CI") } } rootProject.name = "postgres-native-sqldelight" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") +enableFeaturePreview("STABLE_CONFIGURATION_CACHE") include(":postgres-native-sqldelight-driver") include(":postgres-native-sqldelight-dialect") diff --git a/testing-sqldelight/build.gradle.kts b/testing-sqldelight/build.gradle.kts index 1e09026..11128a5 100644 --- a/testing-sqldelight/build.gradle.kts +++ b/testing-sqldelight/build.gradle.kts @@ -2,11 +2,13 @@ import org.jetbrains.kotlin.konan.target.* plugins { kotlin("multiplatform") - app.cash.sqldelight - repos + id("app.cash.sqldelight") } kotlin { + compilerOptions { + optIn.add("kotlin.uuid.ExperimentalUuidApi") + } when (HostManager.host) { KonanTarget.LINUX_X64 -> linuxX64() @@ -19,13 +21,13 @@ kotlin { commonMain { dependencies { implementation(projects.postgresNativeSqldelightDriver) - implementation("app.cash.sqldelight:coroutines-extensions:2.0.0-alpha05") + implementation(libs.sqldelight.coroutines) } } commonTest { dependencies { implementation(kotlin("test")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + implementation(libs.coroutines.test) } } } diff --git a/testing-sqldelight/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeSqldelightDriverTest.kt b/testing-sqldelight/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeSqldelightDriverTest.kt index 1889d24..342377c 100644 --- a/testing-sqldelight/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeSqldelightDriverTest.kt +++ b/testing-sqldelight/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeSqldelightDriverTest.kt @@ -1,22 +1,24 @@ package app.softwork.sqldelight.postgresdriver import app.cash.sqldelight.coroutines.* +import kotlinx.cinterop.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.test.* import kotlinx.datetime.* -import kotlinx.uuid.* +import kotlin.uuid.* +import platform.posix.* import kotlin.test.* import kotlin.time.Duration.Companion.seconds @ExperimentalCoroutinesApi class PostgresNativeSqldelightDriverTest { private val driver = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password" + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password" ) @Test @@ -33,7 +35,7 @@ class PostgresNativeSqldelightDriverTest { timestamp = LocalDateTime(2014, Month.AUGUST, 1, 12, 1, 2, 0), instant = Instant.fromEpochMilliseconds(10L), interval = DateTimePeriod(42, 42, 42, 42, 42, 42, 424242000), - uuid = UUID.NIL + uuid = Uuid.NIL ) queries.create( a = foo.a, @@ -67,7 +69,7 @@ class PostgresNativeSqldelightDriverTest { timestamp = LocalDateTime(2014, Month.AUGUST, 1, 12, 1, 2, 0), instant = Instant.fromEpochMilliseconds(10L), interval = DateTimePeriod(42, 42, 42, 42, 42, 42, 424242000), - uuid = UUID.NIL, + uuid = Uuid.NIL, ) assertEquals(foo, queries.get().executeAsOne()) } @@ -93,24 +95,24 @@ class PostgresNativeSqldelightDriverTest { } @Test - fun remoteListenerTest() = runTest(dispatchTimeoutMs = 10.seconds.inWholeMilliseconds) { + fun remoteListenerTest() = runTest(timeout = 10.seconds) { val client = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password", + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password", listenerSupport = ListenerSupport.Remote(backgroundScope) { it + it } ) val server = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password", + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password", listenerSupport = ListenerSupport.Remote(backgroundScope) { it + it } @@ -127,7 +129,7 @@ class PostgresNativeSqldelightDriverTest { timestamp = LocalDateTime(2014, Month.AUGUST, 1, 12, 1, 2, 0), instant = Instant.fromEpochMilliseconds(10L), interval = DateTimePeriod(42, 42, 42, 42, 42, 42, 424242), - uuid = UUID.NIL + uuid = Uuid.NIL ) val userQueries = db.usersQueries val id = userQueries.insertAndGet("foo", "foo", "foo", "", 42).executeAsOne() @@ -175,13 +177,13 @@ class PostgresNativeSqldelightDriverTest { } @Test - fun localListenerTest() = runTest(dispatchTimeoutMs = 10.seconds.inWholeMilliseconds) { + fun localListenerTest() = runTest(timeout = 10.seconds) { val client = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password", + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password", listenerSupport = ListenerSupport.Local(backgroundScope) ) @@ -196,7 +198,7 @@ class PostgresNativeSqldelightDriverTest { timestamp = LocalDateTime(2014, Month.AUGUST, 1, 12, 1, 2, 0), instant = Instant.fromEpochMilliseconds(10L), interval = DateTimePeriod(42, 42, 42, 42, 42, 42, 424242), - uuid = UUID.NIL + uuid = Uuid.NIL ) val userQueries = db.usersQueries val id = userQueries.insertAndGet("foo", "foo", "foo", "", 42).executeAsOne() @@ -234,3 +236,8 @@ class PostgresNativeSqldelightDriverTest { client.close() } } + +@OptIn(ExperimentalForeignApi::class) +private fun env(name: String): String? { + return getenv(name)?.toKStringFromUtf8()?.takeUnless { it.isEmpty() } +} diff --git a/testing/build.gradle.kts b/testing/build.gradle.kts index 03cc35c..8d14c01 100644 --- a/testing/build.gradle.kts +++ b/testing/build.gradle.kts @@ -2,11 +2,9 @@ import org.jetbrains.kotlin.konan.target.* plugins { kotlin("multiplatform") - repos } kotlin { - when (HostManager.host) { KonanTarget.LINUX_X64 -> linuxX64() KonanTarget.MACOS_ARM64 -> macosArm64() @@ -19,7 +17,7 @@ kotlin { dependencies { implementation(projects.postgresNativeSqldelightDriver) implementation(kotlin("test")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4") + implementation(libs.coroutines.test) } } } diff --git a/testing/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriverTest.kt b/testing/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriverTest.kt index 9a0ddd0..03daa44 100644 --- a/testing/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriverTest.kt +++ b/testing/src/commonTest/kotlin/app/softwork/sqldelight/postgresdriver/PostgresNativeDriverTest.kt @@ -1,23 +1,31 @@ package app.softwork.sqldelight.postgresdriver -import app.cash.sqldelight.* -import kotlinx.coroutines.* +import app.cash.sqldelight.Query +import app.cash.sqldelight.db.QueryResult +import kotlinx.cinterop.* +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.async +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* -import kotlinx.coroutines.test.* +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import platform.posix.* import kotlin.test.* import kotlin.time.Duration.Companion.seconds @ExperimentalCoroutinesApi class PostgresNativeDriverTest { + private val driver = PostgresNativeDriver( + host = env("POSTGRES_HOSTNAME") ?: "localhost", + port = 5432, + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password" + ) + @Test fun simpleTest() = runTest { - val driver = PostgresNativeDriver( - host = "localhost", - port = 5432, - user = "postgres", - database = "postgres", - password = "password" - ) assertEquals(0, driver.execute(null, "DROP TABLE IF EXISTS baz;", parameters = 0).value) assertEquals( 0, @@ -39,11 +47,13 @@ class PostgresNativeDriverTest { }.value assertEquals(2, result) val notPrepared = driver.executeQuery(null, "SELECT * FROM baz LIMIT 1;", parameters = 0, mapper = { - assertTrue(it.next()) - Simple( - index = it.getLong(0)!!.toInt(), - name = it.getString(1), - byteArray = it.getBytes(2) + assertTrue(it.next().value) + QueryResult.Value( + Simple( + index = it.getLong(0)!!.toInt(), + name = it.getString(1), + byteArray = it.getBytes(2) + ) ) }) assertEquals(Simple(0, null, null), notPrepared.value) @@ -52,8 +62,8 @@ class PostgresNativeDriverTest { sql = "SELECT * FROM baz;", parameters = 0, binders = null, mapper = { - buildList { - while (it.next()) { + QueryResult.Value(buildList { + while (it.next().value) { add( Simple( index = it.getLong(0)!!.toInt(), @@ -62,7 +72,7 @@ class PostgresNativeDriverTest { ) ) } - } + }) } ).value @@ -78,49 +88,38 @@ class PostgresNativeDriverTest { ) expect(7) { - val cursorList = driver.executeQueryWithNativeCursor( + val cursorList = driver.executeQueryAsFlow( -99, "SELECT * FROM baz", fetchSize = 4, parameters = 0, binders = null, mapper = { - buildList { - while (it.next()) { - add( - Simple( - index = it.getLong(0)!!.toInt(), - name = it.getString(1), - byteArray = it.getBytes(2) - ) - ) - } - } - }).value - cursorList.size + Simple( + index = it.getLong(0)!!.toInt(), + name = it.getString(1), + byteArray = it.getBytes(2) + ) + }) + cursorList.count() } expect(7) { - val cursorList = driver.executeQueryWithNativeCursor( + val cursorList = driver.executeQueryAsFlow( -5, "SELECT * FROM baz", fetchSize = 1, parameters = 0, binders = null, mapper = { - buildList { - while (it.next()) { - add( - Simple( - index = it.getLong(0)!!.toInt(), - name = it.getString(1), - byteArray = it.getBytes(2) - ) - ) - } - } - }).value - cursorList.size + Simple( + index = it.getLong(0)!!.toInt(), + name = it.getString(1), + byteArray = it.getBytes(2) + + ) + }) + cursorList.count() } val cursorFlow = driver.executeQueryAsFlow( @@ -140,26 +139,20 @@ class PostgresNativeDriverTest { assertEquals(4, cursorFlow.take(4).count()) expect(0) { - val cursorList = driver.executeQueryWithNativeCursor( + val cursorList = driver.executeQueryAsFlow( -100, "SELECT * FROM baz WHERE a = -1", fetchSize = 1, parameters = 0, binders = null, mapper = { - buildList { - while (it.next()) { - add( - Simple( - index = it.getLong(0)!!.toInt(), - name = it.getString(1), - byteArray = it.getBytes(2) - ) - ) - } - } - }).value - cursorList.size + Simple( + index = it.getLong(0)!!.toInt(), + name = it.getString(1), + byteArray = it.getBytes(2) + ) + }) + cursorList.count() } } @@ -192,38 +185,34 @@ class PostgresNativeDriverTest { assertFailsWith { PostgresNativeDriver( host = "wrongHost", - user = "postgres", - database = "postgres", - password = "password" + port = 5432, + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password" ) } assertFailsWith { PostgresNativeDriver( - host = "localhost", - user = "postgres", - database = "postgres", + host = "wrongHost", + port = 5432, + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", password = "wrongPassword" ) } assertFailsWith { PostgresNativeDriver( - host = "localhost", + host = "wrongHost", + port = 5432, user = "wrongUser", - database = "postgres", - password = "password" + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password" ) } } @Test fun copyTest() { - val driver = PostgresNativeDriver( - host = "localhost", - port = 5432, - user = "postgres", - database = "postgres", - password = "password" - ) assertEquals(0, driver.execute(null, "DROP TABLE IF EXISTS copying;", parameters = 0).value) assertEquals(0, driver.execute(null, "CREATE TABLE copying(a int primary key);", parameters = 0).value) driver.execute(-42, "COPY copying FROM STDIN (FORMAT CSV);", 0) @@ -232,11 +221,11 @@ class PostgresNativeDriverTest { assertEquals( listOf(1, 2, 3, 4), driver.executeQuery(null, "SELECT * FROM copying", parameters = 0, binders = null, mapper = { - buildList { - while (it.next()) { + QueryResult.Value(buildList { + while (it.next().value) { add(it.getLong(0)!!.toInt()) } - } + }) }).value ) } @@ -244,49 +233,44 @@ class PostgresNativeDriverTest { @Test fun remoteListenerTest() = runBlocking { val other = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password", + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password", listenerSupport = ListenerSupport.Remote(this) ) val driver = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password", + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password", listenerSupport = ListenerSupport.Remote(this) ) val results = MutableStateFlow(0) - val listener = object : Query.Listener { - override fun queryResultsChanged() { - results.update { it + 1 } - } - } - driver.addListener(listener, arrayOf("foo", "bar")) + val listener = Query.Listener { results.update { it + 1 } } + driver.addListener("foo", "bar", listener = listener) val dbDelay = 2.seconds delay(dbDelay) - other.notifyListeners(arrayOf("foo")) + other.notifyListeners("foo") - other.notifyListeners(arrayOf("foo", "bar")) - other.notifyListeners(arrayOf("bar")) + other.notifyListeners("foo", "bar") + other.notifyListeners("bar") delay(dbDelay) - driver.removeListener(listener, arrayOf("foo", "bar")) - driver.notifyListeners(arrayOf("foo")) - driver.notifyListeners(arrayOf("bar")) + driver.removeListener("foo", "bar", listener = listener) + driver.notifyListeners("foo") + driver.notifyListeners("bar") delay(dbDelay) assertEquals(4, results.value) other.close() - driver.close() } @Test @@ -297,11 +281,11 @@ class PostgresNativeDriverTest { } val driver = PostgresNativeDriver( - host = "localhost", + host = env("POSTGRES_HOSTNAME") ?: "localhost", port = 5432, - user = "postgres", - database = "postgres", - password = "password", + user = env("POSTGRES_USER") ?: "postgres", + database = env("POSTGRES_DB") ?: "postgres", + password = env("POSTGRES_PASSWORD") ?: "password", listenerSupport = ListenerSupport.Local( this, notifications, @@ -311,30 +295,29 @@ class PostgresNativeDriverTest { ) val results = MutableStateFlow(0) - val listener = object : Query.Listener { - override fun queryResultsChanged() { - results.update { it + 1 } - } - } - driver.addListener(listener, arrayOf("foo", "bar")) + val listener = Query.Listener { results.update { it + 1 } } + driver.addListener("foo", "bar", listener = listener) runCurrent() - driver.notifyListeners(arrayOf("foo")) + driver.notifyListeners("foo") runCurrent() - driver.notifyListeners(arrayOf("foo", "bar")) + driver.notifyListeners("foo", "bar") runCurrent() - driver.notifyListeners(arrayOf("bar")) + driver.notifyListeners("bar") runCurrent() - driver.removeListener(listener, arrayOf("foo", "bar")) + driver.removeListener("foo", "bar", listener = listener) runCurrent() - driver.notifyListeners(arrayOf("foo")) + driver.notifyListeners("foo") runCurrent() - driver.notifyListeners(arrayOf("bar")) + driver.notifyListeners("bar") runCurrent() assertEquals(4, results.value) assertEquals(listOf("foo", "foo", "bar", "bar"), notificationList.await()) - - driver.close() } } + +@OptIn(ExperimentalForeignApi::class) +private fun env(name: String): String? { + return getenv(name)?.toKStringFromUtf8()?.takeUnless { it.isEmpty() } +}