diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2779fd5..6cc2d41 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,16 +1,21 @@ -name: make +name: go on: push: branches: - main pull_request: jobs: - build: + test: name: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v4 with: - go-version: '1.21.0' + go-version: '1.21' + - uses: sqlc-dev/setup-sqlc@v4 + with: + sqlc-version: '1.23.0' - run: make + - run: sqlc diff + working-directory: examples \ No newline at end of file diff --git a/.github/workflows/db.yml b/.github/workflows/db.yml new file mode 100644 index 0000000..6debc50 --- /dev/null +++ b/.github/workflows/db.yml @@ -0,0 +1,52 @@ +name: db +on: + push: + branches: + - main + pull_request: +jobs: + + build: + name: test + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:11 + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + # needed because the postgres container does not provide a healthcheck + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + mysql: + image: mysql:8 + env: + MYSQL_ROOT_PASSWORD: mysecretpassword + MYSQL_DATABASE: mysql + ports: + - 3306:3306 + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v3 + with: + distribution: 'adopt' + java-version: '11' + - uses: eskatos/gradle-command-action@v2 + env: + PG_USER: postgres + PG_HOST: localhost + PG_DATABASE: postgres + PG_PASSWORD: postgres + PG_PORT: ${{ job.services.postgres.ports['5432'] }} + MYSQL_DATABASE: mysql + MYSQL_HOST: localhost + MYSQL_PORT: ${{ job.services.mysql.ports['3306'] }} + MYSQL_ROOT_PASSWORD: mysecretpassword + with: + build-root-directory: examples + wrapper-directory: examples + arguments: test --scan diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5e82d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin \ No newline at end of file diff --git a/Makefile b/Makefile index e91a8ba..c4386a7 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,19 @@ -all: sqlc-gen-kotlin sqlc-gen-kotlin.wasm +.PHONY: build test -sqlc-gen-kotlin: - cd plugin && go build -o ~/bin/sqlc-gen-kotlin ./main.go +build: + go build ./... -sqlc-gen-kotlin.wasm: - cd plugin && GOOS=wasip1 GOARCH=wasm go build -o sqlc-gen-kotlin.wasm main.go - openssl sha256 plugin/sqlc-gen-kotlin.wasm +test: + go test ./... + +all: bin/sqlc-gen-kotlin bin/sqlc-gen-kotlin.wasm + +bin/sqlc-gen-kotlin: bin go.mod go.sum $(wildcard **/*.go) + cd plugin && go build -o ../bin/sqlc-gen-kotlin ./main.go + +bin/sqlc-gen-kotlin.wasm: bin/sqlc-gen-kotlin + cd plugin && GOOS=wasip1 GOARCH=wasm go build -o ../bin/sqlc-gen-kotlin.wasm main.go + +bin: + mkdir -p bin diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..fbb16c8 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,4 @@ +/.gradle/ +/.idea/ +/build/ +/out/ diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..717425c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,18 @@ +# Kotlin examples + +This is a Kotlin gradle project configured to compile and test all examples. Currently tests have only been written for the `authors` example. + +To run tests: + +```shell script +docker run --name dinosql-postgres -d -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=mysecretpassword -e POSTGRES_DB=postgres -p 5432:5432 postgres:11 +./gradlew clean test +``` + +The project can be easily imported into Intellij. + +1. Install Java if you don't already have it +1. Download Intellij IDEA Community Edition +1. In the "Welcome" modal, click "Import Project" +1. Open the `build.gradle` file adjacent to this README file +1. Wait for Intellij to sync the gradle modules and complete indexing diff --git a/examples/build.gradle b/examples/build.gradle new file mode 100644 index 0000000..0dfec90 --- /dev/null +++ b/examples/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.60' +} + +group 'com.example' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'mysql:mysql-connector-java:8.0.22' + implementation 'org.postgresql:postgresql:42.2.9' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0' +} + +test { + useJUnitPlatform() +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + +buildScan { + termsOfServiceUrl = "https://gradle.com/terms-of-service" + termsOfServiceAgree = "yes" +} diff --git a/examples/gradle.properties b/examples/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/examples/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/examples/gradle/wrapper/gradle-wrapper.jar b/examples/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..87b738c Binary files /dev/null and b/examples/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/gradle/wrapper/gradle-wrapper.properties b/examples/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..fd68b0d --- /dev/null +++ b/examples/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 25 10:45:34 EST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/examples/gradlew b/examples/gradlew new file mode 100755 index 0000000..af6708f --- /dev/null +++ b/examples/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +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. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/examples/gradlew.bat b/examples/gradlew.bat new file mode 100644 index 0000000..6d57edc --- /dev/null +++ b/examples/gradlew.bat @@ -0,0 +1,84 @@ +@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=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/examples/settings.gradle b/examples/settings.gradle new file mode 100644 index 0000000..5a09465 --- /dev/null +++ b/examples/settings.gradle @@ -0,0 +1,5 @@ +plugins { + id("com.gradle.enterprise").version("3.1.1") +} + +rootProject.name = 'dbtest' diff --git a/examples/sqlc.yaml b/examples/sqlc.yaml new file mode 100644 index 0000000..8a129bf --- /dev/null +++ b/examples/sqlc.yaml @@ -0,0 +1,63 @@ +version: '2' +plugins: +- name: kt + wasm: + url: https://downloads.sqlc.dev/plugin/sqlc-gen-kotlin_1.2.0.wasm + sha256: 22b437ecaea66417bbd3b958339d9868ba89368ce542c936c37305acf373104b +sql: +- schema: src/main/resources/authors/postgresql/schema.sql + queries: src/main/resources/authors/postgresql/query.sql + engine: postgresql + codegen: + - out: src/main/kotlin/com/example/authors/postgresql + plugin: kt + options: + package: com.example.authors.postgresql +- schema: src/main/resources/ondeck/postgresql/schema + queries: src/main/resources/ondeck/postgresql/query + engine: postgresql + codegen: + - out: src/main/kotlin/com/example/ondeck/postgresql + plugin: kt + options: + package: com.example.ondeck.postgresql +- schema: src/main/resources/jets/postgresql/schema.sql + queries: src/main/resources/jets/postgresql/query-building.sql + engine: postgresql + codegen: + - plugin: kt + out: src/main/kotlin/com/example/jets + options: + package: com.example.jets +- schema: src/main/resources/booktest/postgresql/schema.sql + queries: src/main/resources/booktest/postgresql/query.sql + engine: postgresql + codegen: + - out: src/main/kotlin/com/example/booktest/postgresql + plugin: kt + options: + package: com.example.booktest.postgresql +- schema: src/main/resources/authors/mysql/schema.sql + queries: src/main/resources/authors/mysql/query.sql + engine: mysql + codegen: + - out: src/main/kotlin/com/example/authors/mysql + plugin: kt + options: + package: com.example.authors.mysql +- schema: src/main/resources/booktest/mysql/schema.sql + queries: src/main/resources/booktest/mysql/query.sql + engine: mysql + codegen: + - out: src/main/kotlin/com/example/booktest/mysql + plugin: kt + options: + package: com.example.booktest.mysql +- schema: src/main/resources/ondeck/mysql/schema + queries: src/main/resources/ondeck/mysql/query + engine: mysql + codegen: + - out: src/main/kotlin/com/example/ondeck/mysql + plugin: kt + options: + package: com.example.ondeck.mysql diff --git a/examples/src/main/kotlin/com/example/authors/mysql/Models.kt b/examples/src/main/kotlin/com/example/authors/mysql/Models.kt new file mode 100644 index 0000000..9be77d5 --- /dev/null +++ b/examples/src/main/kotlin/com/example/authors/mysql/Models.kt @@ -0,0 +1,12 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.authors.mysql + +data class Author ( + val id: Long, + val name: String, + val bio: String? +) + diff --git a/examples/src/main/kotlin/com/example/authors/mysql/Queries.kt b/examples/src/main/kotlin/com/example/authors/mysql/Queries.kt new file mode 100644 index 0000000..0b42198 --- /dev/null +++ b/examples/src/main/kotlin/com/example/authors/mysql/Queries.kt @@ -0,0 +1,25 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.authors.mysql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement + +interface Queries { + @Throws(SQLException::class) + fun createAuthor(name: String, bio: String?): Long + + @Throws(SQLException::class) + fun deleteAuthor(id: Long) + + @Throws(SQLException::class) + fun getAuthor(id: Long): Author? + + @Throws(SQLException::class) + fun listAuthors(): List + +} + diff --git a/examples/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt b/examples/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt new file mode 100644 index 0000000..4b85917 --- /dev/null +++ b/examples/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt @@ -0,0 +1,100 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.authors.mysql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement + +const val createAuthor = """-- name: createAuthor :execresult +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +) +""" + +const val deleteAuthor = """-- name: deleteAuthor :exec +DELETE FROM authors +WHERE id = ? +""" + +const val getAuthor = """-- name: getAuthor :one +SELECT id, name, bio FROM authors +WHERE id = ? LIMIT 1 +""" + +const val listAuthors = """-- name: listAuthors :many +SELECT id, name, bio FROM authors +ORDER BY name +""" + +class QueriesImpl(private val conn: Connection) : Queries { + + @Throws(SQLException::class) + override fun createAuthor(name: String, bio: String?): Long { + return conn.prepareStatement(createAuthor, Statement.RETURN_GENERATED_KEYS).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, bio) + + stmt.execute() + + val results = stmt.generatedKeys + if (!results.next()) { + throw SQLException("no generated key returned") + } + results.getLong(1) + } + } + + @Throws(SQLException::class) + override fun deleteAuthor(id: Long) { + conn.prepareStatement(deleteAuthor).use { stmt -> + stmt.setLong(1, id) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun getAuthor(id: Long): Author? { + return conn.prepareStatement(getAuthor).use { stmt -> + stmt.setLong(1, id) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Author( + results.getLong(1), + results.getString(2), + results.getString(3) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun listAuthors(): List { + return conn.prepareStatement(listAuthors).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Author( + results.getLong(1), + results.getString(2), + results.getString(3) + )) + } + ret + } + } + +} + diff --git a/examples/src/main/kotlin/com/example/authors/postgresql/Models.kt b/examples/src/main/kotlin/com/example/authors/postgresql/Models.kt new file mode 100644 index 0000000..cbabdb1 --- /dev/null +++ b/examples/src/main/kotlin/com/example/authors/postgresql/Models.kt @@ -0,0 +1,12 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.authors.postgresql + +data class Author ( + val id: Long, + val name: String, + val bio: String? +) + diff --git a/examples/src/main/kotlin/com/example/authors/postgresql/Queries.kt b/examples/src/main/kotlin/com/example/authors/postgresql/Queries.kt new file mode 100644 index 0000000..2b46cdd --- /dev/null +++ b/examples/src/main/kotlin/com/example/authors/postgresql/Queries.kt @@ -0,0 +1,25 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.authors.postgresql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement + +interface Queries { + @Throws(SQLException::class) + fun createAuthor(name: String, bio: String?): Author? + + @Throws(SQLException::class) + fun deleteAuthor(id: Long) + + @Throws(SQLException::class) + fun getAuthor(id: Long): Author? + + @Throws(SQLException::class) + fun listAuthors(): List + +} + diff --git a/examples/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt b/examples/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt new file mode 100644 index 0000000..3ef6a8b --- /dev/null +++ b/examples/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt @@ -0,0 +1,107 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.authors.postgresql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement + +const val createAuthor = """-- name: createAuthor :one +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +) +RETURNING id, name, bio +""" + +const val deleteAuthor = """-- name: deleteAuthor :exec +DELETE FROM authors +WHERE id = ? +""" + +const val getAuthor = """-- name: getAuthor :one +SELECT id, name, bio FROM authors +WHERE id = ? LIMIT 1 +""" + +const val listAuthors = """-- name: listAuthors :many +SELECT id, name, bio FROM authors +ORDER BY name +""" + +class QueriesImpl(private val conn: Connection) : Queries { + + @Throws(SQLException::class) + override fun createAuthor(name: String, bio: String?): Author? { + return conn.prepareStatement(createAuthor).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, bio) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Author( + results.getLong(1), + results.getString(2), + results.getString(3) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun deleteAuthor(id: Long) { + conn.prepareStatement(deleteAuthor).use { stmt -> + stmt.setLong(1, id) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun getAuthor(id: Long): Author? { + return conn.prepareStatement(getAuthor).use { stmt -> + stmt.setLong(1, id) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Author( + results.getLong(1), + results.getString(2), + results.getString(3) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun listAuthors(): List { + return conn.prepareStatement(listAuthors).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Author( + results.getLong(1), + results.getString(2), + results.getString(3) + )) + } + ret + } + } + +} + diff --git a/examples/src/main/kotlin/com/example/booktest/mysql/Models.kt b/examples/src/main/kotlin/com/example/booktest/mysql/Models.kt new file mode 100644 index 0000000..fe69ac9 --- /dev/null +++ b/examples/src/main/kotlin/com/example/booktest/mysql/Models.kt @@ -0,0 +1,34 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.booktest.mysql + +import java.time.LocalDateTime + +enum class BooksBookType(val value: String) { + FICTION("FICTION"), + NONFICTION("NONFICTION"); + + companion object { + private val map = BooksBookType.values().associateBy(BooksBookType::value) + fun lookup(value: String) = map[value] + } +} + +data class Author ( + val authorId: Int, + val name: String +) + +data class Book ( + val bookId: Int, + val authorId: Int, + val isbn: String, + val bookType: BooksBookType, + val title: String, + val yr: Int, + val available: LocalDateTime, + val tags: String +) + diff --git a/examples/src/main/kotlin/com/example/booktest/mysql/Queries.kt b/examples/src/main/kotlin/com/example/booktest/mysql/Queries.kt new file mode 100644 index 0000000..0433128 --- /dev/null +++ b/examples/src/main/kotlin/com/example/booktest/mysql/Queries.kt @@ -0,0 +1,58 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.booktest.mysql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.time.LocalDateTime + +interface Queries { + @Throws(SQLException::class) + fun booksByTags(tags: String): List + + @Throws(SQLException::class) + fun booksByTitleYear(title: String, yr: Int): List + + @Throws(SQLException::class) + fun createAuthor(name: String): Long + + @Throws(SQLException::class) + fun createBook( + authorId: Int, + isbn: String, + bookType: BooksBookType, + title: String, + yr: Int, + available: LocalDateTime, + tags: String): Long + + @Throws(SQLException::class) + fun deleteAuthorBeforeYear(yr: Int, authorId: Int) + + @Throws(SQLException::class) + fun deleteBook(bookId: Int) + + @Throws(SQLException::class) + fun getAuthor(authorId: Int): Author? + + @Throws(SQLException::class) + fun getBook(bookId: Int): Book? + + @Throws(SQLException::class) + fun updateBook( + title: String, + tags: String, + bookId: Int) + + @Throws(SQLException::class) + fun updateBookISBN( + title: String, + tags: String, + isbn: String, + bookId: Int) + +} + diff --git a/examples/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt b/examples/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt new file mode 100644 index 0000000..3589f1a --- /dev/null +++ b/examples/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt @@ -0,0 +1,278 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.booktest.mysql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.time.LocalDateTime + +const val booksByTags = """-- name: booksByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ? +""" + +data class BooksByTagsRow ( + val bookId: Int, + val title: String, + val name: String?, + val isbn: String, + val tags: String +) + +const val booksByTitleYear = """-- name: booksByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE title = ? AND yr = ? +""" + +const val createAuthor = """-- name: createAuthor :execresult +INSERT INTO authors (name) VALUES (?) +""" + +const val createBook = """-- name: createBook :execresult +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +""" + +const val deleteAuthorBeforeYear = """-- name: deleteAuthorBeforeYear :exec +DELETE FROM books +WHERE yr < ? AND author_id = ? +""" + +const val deleteBook = """-- name: deleteBook :exec +DELETE FROM books +WHERE book_id = ? +""" + +const val getAuthor = """-- name: getAuthor :one +SELECT author_id, name FROM authors +WHERE author_id = ? +""" + +const val getBook = """-- name: getBook :one +SELECT book_id, author_id, isbn, book_type, title, yr, available, tags FROM books +WHERE book_id = ? +""" + +const val updateBook = """-- name: updateBook :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +""" + +const val updateBookISBN = """-- name: updateBookISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +""" + +class QueriesImpl(private val conn: Connection) : Queries { + + @Throws(SQLException::class) + override fun booksByTags(tags: String): List { + return conn.prepareStatement(booksByTags).use { stmt -> + stmt.setString(1, tags) + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(BooksByTagsRow( + results.getInt(1), + results.getString(2), + results.getString(3), + results.getString(4), + results.getString(5) + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun booksByTitleYear(title: String, yr: Int): List { + return conn.prepareStatement(booksByTitleYear).use { stmt -> + stmt.setString(1, title) + stmt.setInt(2, yr) + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BooksBookType.lookup(results.getString(4))!!, + results.getString(5), + results.getInt(6), + results.getObject(7, LocalDateTime::class.java), + results.getString(8) + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun createAuthor(name: String): Long { + return conn.prepareStatement(createAuthor, Statement.RETURN_GENERATED_KEYS).use { stmt -> + stmt.setString(1, name) + + stmt.execute() + + val results = stmt.generatedKeys + if (!results.next()) { + throw SQLException("no generated key returned") + } + results.getLong(1) + } + } + + @Throws(SQLException::class) + override fun createBook( + authorId: Int, + isbn: String, + bookType: BooksBookType, + title: String, + yr: Int, + available: LocalDateTime, + tags: String): Long { + return conn.prepareStatement(createBook, Statement.RETURN_GENERATED_KEYS).use { stmt -> + stmt.setInt(1, authorId) + stmt.setString(2, isbn) + stmt.setString(3, bookType.value) + stmt.setString(4, title) + stmt.setInt(5, yr) + stmt.setObject(6, available) + stmt.setString(7, tags) + + stmt.execute() + + val results = stmt.generatedKeys + if (!results.next()) { + throw SQLException("no generated key returned") + } + results.getLong(1) + } + } + + @Throws(SQLException::class) + override fun deleteAuthorBeforeYear(yr: Int, authorId: Int) { + conn.prepareStatement(deleteAuthorBeforeYear).use { stmt -> + stmt.setInt(1, yr) + stmt.setInt(2, authorId) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun deleteBook(bookId: Int) { + conn.prepareStatement(deleteBook).use { stmt -> + stmt.setInt(1, bookId) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun getAuthor(authorId: Int): Author? { + return conn.prepareStatement(getAuthor).use { stmt -> + stmt.setInt(1, authorId) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Author( + results.getInt(1), + results.getString(2) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun getBook(bookId: Int): Book? { + return conn.prepareStatement(getBook).use { stmt -> + stmt.setInt(1, bookId) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BooksBookType.lookup(results.getString(4))!!, + results.getString(5), + results.getInt(6), + results.getObject(7, LocalDateTime::class.java), + results.getString(8) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun updateBook( + title: String, + tags: String, + bookId: Int) { + conn.prepareStatement(updateBook).use { stmt -> + stmt.setString(1, title) + stmt.setString(2, tags) + stmt.setInt(3, bookId) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun updateBookISBN( + title: String, + tags: String, + isbn: String, + bookId: Int) { + conn.prepareStatement(updateBookISBN).use { stmt -> + stmt.setString(1, title) + stmt.setString(2, tags) + stmt.setString(3, isbn) + stmt.setInt(4, bookId) + + stmt.execute() + } + } + +} + diff --git a/examples/src/main/kotlin/com/example/booktest/postgresql/Models.kt b/examples/src/main/kotlin/com/example/booktest/postgresql/Models.kt new file mode 100644 index 0000000..e77993a --- /dev/null +++ b/examples/src/main/kotlin/com/example/booktest/postgresql/Models.kt @@ -0,0 +1,34 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.booktest.postgresql + +import java.time.OffsetDateTime + +enum class BookType(val value: String) { + FICTION("FICTION"), + NONFICTION("NONFICTION"); + + companion object { + private val map = BookType.values().associateBy(BookType::value) + fun lookup(value: String) = map[value] + } +} + +data class Author ( + val authorId: Int, + val name: String +) + +data class Book ( + val bookId: Int, + val authorId: Int, + val isbn: String, + val bookType: BookType, + val title: String, + val year: Int, + val available: OffsetDateTime, + val tags: List +) + diff --git a/examples/src/main/kotlin/com/example/booktest/postgresql/Queries.kt b/examples/src/main/kotlin/com/example/booktest/postgresql/Queries.kt new file mode 100644 index 0000000..cfeec4d --- /dev/null +++ b/examples/src/main/kotlin/com/example/booktest/postgresql/Queries.kt @@ -0,0 +1,56 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.booktest.postgresql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.sql.Types +import java.time.OffsetDateTime + +interface Queries { + @Throws(SQLException::class) + fun booksByTags(dollar1: List): List + + @Throws(SQLException::class) + fun booksByTitleYear(title: String, year: Int): List + + @Throws(SQLException::class) + fun createAuthor(name: String): Author? + + @Throws(SQLException::class) + fun createBook( + authorId: Int, + isbn: String, + bookType: BookType, + title: String, + year: Int, + available: OffsetDateTime, + tags: List): Book? + + @Throws(SQLException::class) + fun deleteBook(bookId: Int) + + @Throws(SQLException::class) + fun getAuthor(authorId: Int): Author? + + @Throws(SQLException::class) + fun getBook(bookId: Int): Book? + + @Throws(SQLException::class) + fun updateBook( + title: String, + tags: List, + bookId: Int) + + @Throws(SQLException::class) + fun updateBookISBN( + title: String, + tags: List, + isbn: String, + bookId: Int) + +} + diff --git a/examples/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt b/examples/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt new file mode 100644 index 0000000..e6f10eb --- /dev/null +++ b/examples/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt @@ -0,0 +1,282 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.booktest.postgresql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.sql.Types +import java.time.OffsetDateTime + +const val booksByTags = """-- name: booksByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && ?::varchar[] +""" + +data class BooksByTagsRow ( + val bookId: Int, + val title: String, + val name: String?, + val isbn: String, + val tags: List +) + +const val booksByTitleYear = """-- name: booksByTitleYear :many +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE title = ? AND year = ? +""" + +const val createAuthor = """-- name: createAuthor :one +INSERT INTO authors (name) VALUES (?) +RETURNING author_id, name +""" + +const val createBook = """-- name: createBook :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +RETURNING book_id, author_id, isbn, book_type, title, year, available, tags +""" + +const val deleteBook = """-- name: deleteBook :exec +DELETE FROM books +WHERE book_id = ? +""" + +const val getAuthor = """-- name: getAuthor :one +SELECT author_id, name FROM authors +WHERE author_id = ? +""" + +const val getBook = """-- name: getBook :one +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books +WHERE book_id = ? +""" + +const val updateBook = """-- name: updateBook :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +""" + +const val updateBookISBN = """-- name: updateBookISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +""" + +class QueriesImpl(private val conn: Connection) : Queries { + + @Throws(SQLException::class) + override fun booksByTags(dollar1: List): List { + return conn.prepareStatement(booksByTags).use { stmt -> + stmt.setArray(1, conn.createArrayOf("pg_catalog.varchar", dollar1.toTypedArray())) + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(BooksByTagsRow( + results.getInt(1), + results.getString(2), + results.getString(3), + results.getString(4), + (results.getArray(5).array as Array).toList() + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun booksByTitleYear(title: String, year: Int): List { + return conn.prepareStatement(booksByTitleYear).use { stmt -> + stmt.setString(1, title) + stmt.setInt(2, year) + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BookType.lookup(results.getString(4))!!, + results.getString(5), + results.getInt(6), + results.getObject(7, OffsetDateTime::class.java), + (results.getArray(8).array as Array).toList() + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun createAuthor(name: String): Author? { + return conn.prepareStatement(createAuthor).use { stmt -> + stmt.setString(1, name) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Author( + results.getInt(1), + results.getString(2) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun createBook( + authorId: Int, + isbn: String, + bookType: BookType, + title: String, + year: Int, + available: OffsetDateTime, + tags: List): Book? { + return conn.prepareStatement(createBook).use { stmt -> + stmt.setInt(1, authorId) + stmt.setString(2, isbn) + stmt.setObject(3, bookType.value, Types.OTHER) + stmt.setString(4, title) + stmt.setInt(5, year) + stmt.setObject(6, available) + stmt.setArray(7, conn.createArrayOf("pg_catalog.varchar", tags.toTypedArray())) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BookType.lookup(results.getString(4))!!, + results.getString(5), + results.getInt(6), + results.getObject(7, OffsetDateTime::class.java), + (results.getArray(8).array as Array).toList() + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun deleteBook(bookId: Int) { + conn.prepareStatement(deleteBook).use { stmt -> + stmt.setInt(1, bookId) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun getAuthor(authorId: Int): Author? { + return conn.prepareStatement(getAuthor).use { stmt -> + stmt.setInt(1, authorId) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Author( + results.getInt(1), + results.getString(2) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun getBook(bookId: Int): Book? { + return conn.prepareStatement(getBook).use { stmt -> + stmt.setInt(1, bookId) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BookType.lookup(results.getString(4))!!, + results.getString(5), + results.getInt(6), + results.getObject(7, OffsetDateTime::class.java), + (results.getArray(8).array as Array).toList() + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun updateBook( + title: String, + tags: List, + bookId: Int) { + conn.prepareStatement(updateBook).use { stmt -> + stmt.setString(1, title) + stmt.setArray(2, conn.createArrayOf("pg_catalog.varchar", tags.toTypedArray())) + stmt.setInt(3, bookId) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun updateBookISBN( + title: String, + tags: List, + isbn: String, + bookId: Int) { + conn.prepareStatement(updateBookISBN).use { stmt -> + stmt.setString(1, title) + stmt.setArray(2, conn.createArrayOf("pg_catalog.varchar", tags.toTypedArray())) + stmt.setString(3, isbn) + stmt.setInt(4, bookId) + + stmt.execute() + } + } + +} + diff --git a/examples/src/main/kotlin/com/example/jets/Models.kt b/examples/src/main/kotlin/com/example/jets/Models.kt new file mode 100644 index 0000000..9505c32 --- /dev/null +++ b/examples/src/main/kotlin/com/example/jets/Models.kt @@ -0,0 +1,29 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.jets + +data class Jet ( + val id: Int, + val pilotId: Int, + val age: Int, + val name: String, + val color: String +) + +data class Language ( + val id: Int, + val language: String +) + +data class Pilot ( + val id: Int, + val name: String +) + +data class PilotLanguage ( + val pilotId: Int, + val languageId: Int +) + diff --git a/examples/src/main/kotlin/com/example/jets/Queries.kt b/examples/src/main/kotlin/com/example/jets/Queries.kt new file mode 100644 index 0000000..0c4892a --- /dev/null +++ b/examples/src/main/kotlin/com/example/jets/Queries.kt @@ -0,0 +1,22 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.jets + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement + +interface Queries { + @Throws(SQLException::class) + fun countPilots(): Long? + + @Throws(SQLException::class) + fun deletePilot(id: Int) + + @Throws(SQLException::class) + fun listPilots(): List + +} + diff --git a/examples/src/main/kotlin/com/example/jets/QueriesImpl.kt b/examples/src/main/kotlin/com/example/jets/QueriesImpl.kt new file mode 100644 index 0000000..a3b1737 --- /dev/null +++ b/examples/src/main/kotlin/com/example/jets/QueriesImpl.kt @@ -0,0 +1,67 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.jets + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement + +const val countPilots = """-- name: countPilots :one +SELECT COUNT(*) FROM pilots +""" + +const val deletePilot = """-- name: deletePilot :exec +DELETE FROM pilots WHERE id = ? +""" + +const val listPilots = """-- name: listPilots :many +SELECT id, name FROM pilots LIMIT 5 +""" + +class QueriesImpl(private val conn: Connection) : Queries { + + @Throws(SQLException::class) + override fun countPilots(): Long? { + return conn.prepareStatement(countPilots).use { stmt -> + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = results.getLong(1) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun deletePilot(id: Int) { + conn.prepareStatement(deletePilot).use { stmt -> + stmt.setInt(1, id) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun listPilots(): List { + return conn.prepareStatement(listPilots).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Pilot( + results.getInt(1), + results.getString(2) + )) + } + ret + } + } + +} + diff --git a/examples/src/main/kotlin/com/example/ondeck/mysql/Models.kt b/examples/src/main/kotlin/com/example/ondeck/mysql/Models.kt new file mode 100644 index 0000000..38f26a5 --- /dev/null +++ b/examples/src/main/kotlin/com/example/ondeck/mysql/Models.kt @@ -0,0 +1,40 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.ondeck.mysql + +import java.sql.Timestamp +import java.time.Instant + +enum class VenueStatus(val value: String) { + OPEN("open"), + CLOSED("closed"); + + companion object { + private val map = VenueStatus.values().associateBy(VenueStatus::value) + fun lookup(value: String) = map[value] + } +} + +data class City ( + val slug: String, + val name: String +) + +// Venues are places where muisc happens +data class Venue ( + val id: Long, + // Venues can be either open or closed + val status: VenueStatus, + val statuses: String?, + // This value appears in public URLs + val slug: String, + val name: String, + val city: String, + val spotifyPlaylist: String, + val songkickId: String?, + val tags: String?, + val createdAt: Instant +) + diff --git a/examples/src/main/kotlin/com/example/ondeck/mysql/Queries.kt b/examples/src/main/kotlin/com/example/ondeck/mysql/Queries.kt new file mode 100644 index 0000000..39a7a1b --- /dev/null +++ b/examples/src/main/kotlin/com/example/ondeck/mysql/Queries.kt @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.ondeck.mysql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.sql.Timestamp +import java.time.Instant + +interface Queries { + @Throws(SQLException::class) + fun createCity(name: String, slug: String) + + @Throws(SQLException::class) + fun createVenue( + slug: String, + name: String, + city: String, + spotifyPlaylist: String, + status: VenueStatus, + statuses: String?, + tags: String?): Long + + @Throws(SQLException::class) + fun deleteVenue(slug: String, slug_2: String) + + @Throws(SQLException::class) + fun getCity(slug: String): City? + + @Throws(SQLException::class) + fun getVenue(slug: String, city: String): Venue? + + @Throws(SQLException::class) + fun listCities(): List + + @Throws(SQLException::class) + fun listVenues(city: String): List + + @Throws(SQLException::class) + fun updateCityName(name: String, slug: String) + + @Throws(SQLException::class) + fun updateVenueName(name: String, slug: String) + + @Throws(SQLException::class) + fun venueCountByCity(): List + +} + diff --git a/examples/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt b/examples/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt new file mode 100644 index 0000000..cb364c8 --- /dev/null +++ b/examples/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt @@ -0,0 +1,278 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.ondeck.mysql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.sql.Timestamp +import java.time.Instant + +const val createCity = """-- name: createCity :exec +INSERT INTO city ( + name, + slug +) VALUES ( + ?, + ? +) +""" + +const val createVenue = """-- name: createVenue :execresult +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + statuses, + tags +) VALUES ( + ?, + ?, + ?, + NOW(), + ?, + ?, + ?, + ? +) +""" + +const val deleteVenue = """-- name: deleteVenue :exec +DELETE FROM venue +WHERE slug = ? AND slug = ? +""" + +const val getCity = """-- name: getCity :one +SELECT slug, name +FROM city +WHERE slug = ? +""" + +const val getVenue = """-- name: getVenue :one +SELECT id, status, statuses, slug, name, city, spotify_playlist, songkick_id, tags, created_at +FROM venue +WHERE slug = ? AND city = ? +""" + +const val listCities = """-- name: listCities :many +SELECT slug, name +FROM city +ORDER BY name +""" + +const val listVenues = """-- name: listVenues :many +SELECT id, status, statuses, slug, name, city, spotify_playlist, songkick_id, tags, created_at +FROM venue +WHERE city = ? +ORDER BY name +""" + +const val updateCityName = """-- name: updateCityName :exec +UPDATE city +SET name = ? +WHERE slug = ? +""" + +const val updateVenueName = """-- name: updateVenueName :exec +UPDATE venue +SET name = ? +WHERE slug = ? +""" + +const val venueCountByCity = """-- name: venueCountByCity :many +SELECT + city, + count(*) +FROM venue +GROUP BY 1 +ORDER BY 1 +""" + +data class VenueCountByCityRow ( + val city: String, + val count: Long +) + +class QueriesImpl(private val conn: Connection) : Queries { + + @Throws(SQLException::class) + override fun createCity(name: String, slug: String) { + conn.prepareStatement(createCity).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, slug) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun createVenue( + slug: String, + name: String, + city: String, + spotifyPlaylist: String, + status: VenueStatus, + statuses: String?, + tags: String?): Long { + return conn.prepareStatement(createVenue, Statement.RETURN_GENERATED_KEYS).use { stmt -> + stmt.setString(1, slug) + stmt.setString(2, name) + stmt.setString(3, city) + stmt.setString(4, spotifyPlaylist) + stmt.setString(5, status.value) + stmt.setString(6, statuses) + stmt.setString(7, tags) + + stmt.execute() + + val results = stmt.generatedKeys + if (!results.next()) { + throw SQLException("no generated key returned") + } + results.getLong(1) + } + } + + @Throws(SQLException::class) + override fun deleteVenue(slug: String, slug_2: String) { + conn.prepareStatement(deleteVenue).use { stmt -> + stmt.setString(1, slug) + stmt.setString(2, slug_2) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun getCity(slug: String): City? { + return conn.prepareStatement(getCity).use { stmt -> + stmt.setString(1, slug) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = City( + results.getString(1), + results.getString(2) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun getVenue(slug: String, city: String): Venue? { + return conn.prepareStatement(getVenue).use { stmt -> + stmt.setString(1, slug) + stmt.setString(2, city) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Venue( + results.getLong(1), + VenueStatus.lookup(results.getString(2))!!, + results.getString(3), + results.getString(4), + results.getString(5), + results.getString(6), + results.getString(7), + results.getString(8), + results.getString(9), + results.getTimestamp(10).toInstant() + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun listCities(): List { + return conn.prepareStatement(listCities).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(City( + results.getString(1), + results.getString(2) + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun listVenues(city: String): List { + return conn.prepareStatement(listVenues).use { stmt -> + stmt.setString(1, city) + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Venue( + results.getLong(1), + VenueStatus.lookup(results.getString(2))!!, + results.getString(3), + results.getString(4), + results.getString(5), + results.getString(6), + results.getString(7), + results.getString(8), + results.getString(9), + results.getTimestamp(10).toInstant() + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun updateCityName(name: String, slug: String) { + conn.prepareStatement(updateCityName).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, slug) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun updateVenueName(name: String, slug: String) { + conn.prepareStatement(updateVenueName).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, slug) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun venueCountByCity(): List { + return conn.prepareStatement(venueCountByCity).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(VenueCountByCityRow( + results.getString(1), + results.getLong(2) + )) + } + ret + } + } + +} + diff --git a/examples/src/main/kotlin/com/example/ondeck/postgresql/Models.kt b/examples/src/main/kotlin/com/example/ondeck/postgresql/Models.kt new file mode 100644 index 0000000..d0be9b8 --- /dev/null +++ b/examples/src/main/kotlin/com/example/ondeck/postgresql/Models.kt @@ -0,0 +1,39 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.ondeck.postgresql + +import java.time.LocalDateTime + +// Venues can be either open or closed +enum class Status(val value: String) { + OPEN("op!en"), + CLOSED("clo@sed"); + + companion object { + private val map = Status.values().associateBy(Status::value) + fun lookup(value: String) = map[value] + } +} + +data class City ( + val slug: String, + val name: String +) + +// Venues are places where muisc happens +data class Venue ( + val id: Int, + val status: Status, + val statuses: List, + // This value appears in public URLs + val slug: String, + val name: String, + val city: String, + val spotifyPlaylist: String, + val songkickId: String?, + val tags: List, + val createdAt: LocalDateTime +) + diff --git a/examples/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt b/examples/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt new file mode 100644 index 0000000..ffd5759 --- /dev/null +++ b/examples/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.ondeck.postgresql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.sql.Types +import java.time.LocalDateTime + +interface Queries { + @Throws(SQLException::class) + fun createCity(name: String, slug: String): City? + + @Throws(SQLException::class) + fun createVenue( + slug: String, + name: String, + city: String, + spotifyPlaylist: String, + status: Status, + statuses: List, + tags: List): Int? + + @Throws(SQLException::class) + fun deleteVenue(slug: String) + + @Throws(SQLException::class) + fun getCity(slug: String): City? + + @Throws(SQLException::class) + fun getVenue(slug: String, city: String): Venue? + + @Throws(SQLException::class) + fun listCities(): List + + @Throws(SQLException::class) + fun listVenues(city: String): List + + @Throws(SQLException::class) + fun updateCityName(name: String, slug: String) + + @Throws(SQLException::class) + fun updateVenueName(name: String, slug: String): Int? + + @Throws(SQLException::class) + fun venueCountByCity(): List + +} + diff --git a/examples/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt b/examples/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt new file mode 100644 index 0000000..fb927cf --- /dev/null +++ b/examples/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt @@ -0,0 +1,304 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.23.0 + +package com.example.ondeck.postgresql + +import java.sql.Connection +import java.sql.SQLException +import java.sql.Statement +import java.sql.Types +import java.time.LocalDateTime + +const val createCity = """-- name: createCity :one +INSERT INTO city ( + name, + slug +) VALUES ( + ?, + ? +) RETURNING slug, name +""" + +const val createVenue = """-- name: createVenue :one +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + statuses, + tags +) VALUES ( + ?, + ?, + ?, + NOW(), + ?, + ?, + ?, + ? +) RETURNING id +""" + +const val deleteVenue = """-- name: deleteVenue :exec +DELETE FROM venue +WHERE slug = ? AND slug = ? +""" + +const val getCity = """-- name: getCity :one +SELECT slug, name +FROM city +WHERE slug = ? +""" + +const val getVenue = """-- name: getVenue :one +SELECT id, status, statuses, slug, name, city, spotify_playlist, songkick_id, tags, created_at +FROM venue +WHERE slug = ? AND city = ? +""" + +const val listCities = """-- name: listCities :many +SELECT slug, name +FROM city +ORDER BY name +""" + +const val listVenues = """-- name: listVenues :many +SELECT id, status, statuses, slug, name, city, spotify_playlist, songkick_id, tags, created_at +FROM venue +WHERE city = ? +ORDER BY name +""" + +const val updateCityName = """-- name: updateCityName :exec +UPDATE city +SET name = ? +WHERE slug = ? +""" + +const val updateVenueName = """-- name: updateVenueName :one +UPDATE venue +SET name = ? +WHERE slug = ? +RETURNING id +""" + +const val venueCountByCity = """-- name: venueCountByCity :many +SELECT + city, + count(*) +FROM venue +GROUP BY 1 +ORDER BY 1 +""" + +data class VenueCountByCityRow ( + val city: String, + val count: Long +) + +class QueriesImpl(private val conn: Connection) : Queries { + +// Create a new city. The slug must be unique. +// This is the second line of the comment +// This is the third line + + @Throws(SQLException::class) + override fun createCity(name: String, slug: String): City? { + return conn.prepareStatement(createCity).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, slug) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = City( + results.getString(1), + results.getString(2) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun createVenue( + slug: String, + name: String, + city: String, + spotifyPlaylist: String, + status: Status, + statuses: List, + tags: List): Int? { + return conn.prepareStatement(createVenue).use { stmt -> + stmt.setString(1, slug) + stmt.setString(2, name) + stmt.setString(3, city) + stmt.setString(4, spotifyPlaylist) + stmt.setObject(5, status.value, Types.OTHER) + stmt.setArray(6, conn.createArrayOf("status", statuses.map { v -> v.value }.toTypedArray())) + stmt.setArray(7, conn.createArrayOf("text", tags.toTypedArray())) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = results.getInt(1) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun deleteVenue(slug: String) { + conn.prepareStatement(deleteVenue).use { stmt -> + stmt.setString(1, slug) + stmt.setString(2, slug) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun getCity(slug: String): City? { + return conn.prepareStatement(getCity).use { stmt -> + stmt.setString(1, slug) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = City( + results.getString(1), + results.getString(2) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun getVenue(slug: String, city: String): Venue? { + return conn.prepareStatement(getVenue).use { stmt -> + stmt.setString(1, slug) + stmt.setString(2, city) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = Venue( + results.getInt(1), + Status.lookup(results.getString(2))!!, + (results.getArray(3).array as Array).map { v -> Status.lookup(v)!! }.toList(), + results.getString(4), + results.getString(5), + results.getString(6), + results.getString(7), + results.getString(8), + (results.getArray(9).array as Array).toList(), + results.getObject(10, LocalDateTime::class.java) + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun listCities(): List { + return conn.prepareStatement(listCities).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(City( + results.getString(1), + results.getString(2) + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun listVenues(city: String): List { + return conn.prepareStatement(listVenues).use { stmt -> + stmt.setString(1, city) + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(Venue( + results.getInt(1), + Status.lookup(results.getString(2))!!, + (results.getArray(3).array as Array).map { v -> Status.lookup(v)!! }.toList(), + results.getString(4), + results.getString(5), + results.getString(6), + results.getString(7), + results.getString(8), + (results.getArray(9).array as Array).toList(), + results.getObject(10, LocalDateTime::class.java) + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun updateCityName(name: String, slug: String) { + conn.prepareStatement(updateCityName).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, slug) + + stmt.execute() + } + } + + @Throws(SQLException::class) + override fun updateVenueName(name: String, slug: String): Int? { + return conn.prepareStatement(updateVenueName).use { stmt -> + stmt.setString(1, name) + stmt.setString(2, slug) + + val results = stmt.executeQuery() + if (!results.next()) { + return null + } + val ret = results.getInt(1) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + override fun venueCountByCity(): List { + return conn.prepareStatement(venueCountByCity).use { stmt -> + + val results = stmt.executeQuery() + val ret = mutableListOf() + while (results.next()) { + ret.add(VenueCountByCityRow( + results.getString(1), + results.getLong(2) + )) + } + ret + } + } + +} + diff --git a/examples/src/main/resources/authors/mysql/query.sql b/examples/src/main/resources/authors/mysql/query.sql new file mode 100644 index 0000000..c3b5866 --- /dev/null +++ b/examples/src/main/resources/authors/mysql/query.sql @@ -0,0 +1,18 @@ +/* name: GetAuthor :one */ +SELECT * FROM authors +WHERE id = ? LIMIT 1; + +/* name: ListAuthors :many */ +SELECT * FROM authors +ORDER BY name; + +/* name: CreateAuthor :execresult */ +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +); + +/* name: DeleteAuthor :exec */ +DELETE FROM authors +WHERE id = ?; diff --git a/examples/src/main/resources/authors/mysql/schema.sql b/examples/src/main/resources/authors/mysql/schema.sql new file mode 100644 index 0000000..581ecfe --- /dev/null +++ b/examples/src/main/resources/authors/mysql/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE authors ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + name text NOT NULL, + bio text +); diff --git a/examples/src/main/resources/authors/postgresql/query.sql b/examples/src/main/resources/authors/postgresql/query.sql new file mode 100644 index 0000000..75e38b2 --- /dev/null +++ b/examples/src/main/resources/authors/postgresql/query.sql @@ -0,0 +1,19 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = $1 LIMIT 1; + +-- name: ListAuthors :many +SELECT * FROM authors +ORDER BY name; + +-- name: CreateAuthor :one +INSERT INTO authors ( + name, bio +) VALUES ( + $1, $2 +) +RETURNING *; + +-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = $1; diff --git a/examples/src/main/resources/authors/postgresql/schema.sql b/examples/src/main/resources/authors/postgresql/schema.sql new file mode 100644 index 0000000..b4fad78 --- /dev/null +++ b/examples/src/main/resources/authors/postgresql/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE authors ( + id BIGSERIAL PRIMARY KEY, + name text NOT NULL, + bio text +); diff --git a/examples/src/main/resources/booktest/mysql/query.sql b/examples/src/main/resources/booktest/mysql/query.sql new file mode 100644 index 0000000..a455ad4 --- /dev/null +++ b/examples/src/main/resources/booktest/mysql/query.sql @@ -0,0 +1,63 @@ +/* name: GetAuthor :one */ +SELECT * FROM authors +WHERE author_id = ?; + +/* name: GetBook :one */ +SELECT * FROM books +WHERE book_id = ?; + +/* name: DeleteBook :exec */ +DELETE FROM books +WHERE book_id = ?; + +/* name: BooksByTitleYear :many */ +SELECT * FROM books +WHERE title = ? AND yr = ?; + +/* name: BooksByTags :many */ +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags = ?; + +/* name: CreateAuthor :execresult */ +INSERT INTO authors (name) VALUES (?); + +/* name: CreateBook :execresult */ +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + yr, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +); + +/* name: UpdateBook :exec */ +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ?; + +/* name: UpdateBookISBN :exec */ +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ?; + +/* name: DeleteAuthorBeforeYear :exec */ +DELETE FROM books +WHERE yr < ? AND author_id = ?; +-- WHERE yr < sqlc.arg(min_publish_year) AND author_id = ?; diff --git a/examples/src/main/resources/booktest/mysql/schema.sql b/examples/src/main/resources/booktest/mysql/schema.sql new file mode 100644 index 0000000..e457da9 --- /dev/null +++ b/examples/src/main/resources/booktest/mysql/schema.sql @@ -0,0 +1,26 @@ +CREATE TABLE authors ( + author_id integer NOT NULL AUTO_INCREMENT PRIMARY KEY, + name text NOT NULL +) ENGINE=InnoDB; + +CREATE INDEX authors_name_idx ON authors(name(255)); + +CREATE TABLE books ( + book_id integer NOT NULL AUTO_INCREMENT PRIMARY KEY, + author_id integer NOT NULL, + isbn varchar(255) NOT NULL DEFAULT '' UNIQUE, + book_type ENUM('FICTION', 'NONFICTION') NOT NULL DEFAULT 'FICTION', + title text NOT NULL, + yr integer NOT NULL DEFAULT 2000, + available datetime NOT NULL DEFAULT NOW(), + tags text NOT NULL + -- CONSTRAINT FOREIGN KEY (author_id) REFERENCES authors(author_id) +) ENGINE=InnoDB; + +CREATE INDEX books_title_idx ON books(title(255), yr); + +/* +CREATE FUNCTION say_hello(s text) RETURNS text + DETERMINISTIC + RETURN CONCAT('hello ', s); +*/ diff --git a/examples/src/main/resources/booktest/postgresql/query.sql b/examples/src/main/resources/booktest/postgresql/query.sql new file mode 100644 index 0000000..194897a --- /dev/null +++ b/examples/src/main/resources/booktest/postgresql/query.sql @@ -0,0 +1,60 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE author_id = $1; + +-- name: GetBook :one +SELECT * FROM books +WHERE book_id = $1; + +-- name: DeleteBook :exec +DELETE FROM books +WHERE book_id = $1; + +-- name: BooksByTitleYear :many +SELECT * FROM books +WHERE title = $1 AND year = $2; + +-- name: BooksByTags :many +SELECT + book_id, + title, + name, + isbn, + tags +FROM books +LEFT JOIN authors ON books.author_id = authors.author_id +WHERE tags && $1::varchar[]; + +-- name: CreateAuthor :one +INSERT INTO authors (name) VALUES ($1) +RETURNING *; + +-- name: CreateBook :one +INSERT INTO books ( + author_id, + isbn, + book_type, + title, + year, + available, + tags +) VALUES ( + $1, + $2, + $3, + $4, + $5, + $6, + $7 +) +RETURNING *; + +-- name: UpdateBook :exec +UPDATE books +SET title = $1, tags = $2 +WHERE book_id = $3; + +-- name: UpdateBookISBN :exec +UPDATE books +SET title = $1, tags = $2, isbn = $4 +WHERE book_id = $3; diff --git a/examples/src/main/resources/booktest/postgresql/schema.sql b/examples/src/main/resources/booktest/postgresql/schema.sql new file mode 100644 index 0000000..2beecab --- /dev/null +++ b/examples/src/main/resources/booktest/postgresql/schema.sql @@ -0,0 +1,32 @@ +CREATE TABLE authors ( + author_id SERIAL PRIMARY KEY, + name text NOT NULL DEFAULT '' +); + +CREATE INDEX authors_name_idx ON authors(name); + +CREATE TYPE book_type AS ENUM ( + 'FICTION', + 'NONFICTION' +); + +CREATE TABLE books ( + book_id SERIAL PRIMARY KEY, + author_id integer NOT NULL REFERENCES authors(author_id), + isbn text NOT NULL DEFAULT '' UNIQUE, + book_type book_type NOT NULL DEFAULT 'FICTION', + title text NOT NULL DEFAULT '', + year integer NOT NULL DEFAULT 2000, + available timestamp with time zone NOT NULL DEFAULT 'NOW()', + tags varchar[] NOT NULL DEFAULT '{}' +); + +CREATE INDEX books_title_idx ON books(title, year); + +CREATE FUNCTION say_hello(text) RETURNS text AS $$ +BEGIN + RETURN CONCAT('hello ', $1); +END; +$$ LANGUAGE plpgsql; + +CREATE INDEX books_title_lower_idx ON books(title); diff --git a/examples/src/main/resources/jets/postgresql/query-building.sql b/examples/src/main/resources/jets/postgresql/query-building.sql new file mode 100644 index 0000000..ede8952 --- /dev/null +++ b/examples/src/main/resources/jets/postgresql/query-building.sql @@ -0,0 +1,8 @@ +-- name: CountPilots :one +SELECT COUNT(*) FROM pilots; + +-- name: ListPilots :many +SELECT * FROM pilots LIMIT 5; + +-- name: DeletePilot :exec +DELETE FROM pilots WHERE id = $1; diff --git a/examples/src/main/resources/jets/postgresql/schema.sql b/examples/src/main/resources/jets/postgresql/schema.sql new file mode 100644 index 0000000..2cc4aca --- /dev/null +++ b/examples/src/main/resources/jets/postgresql/schema.sql @@ -0,0 +1,35 @@ +CREATE TABLE pilots ( + id integer NOT NULL, + name text NOT NULL +); + +ALTER TABLE pilots ADD CONSTRAINT pilot_pkey PRIMARY KEY (id); + +CREATE TABLE jets ( + id integer NOT NULL, + pilot_id integer NOT NULL, + age integer NOT NULL, + name text NOT NULL, + color text NOT NULL +); + +ALTER TABLE jets ADD CONSTRAINT jet_pkey PRIMARY KEY (id); +ALTER TABLE jets ADD CONSTRAINT jet_pilots_fkey FOREIGN KEY (pilot_id) REFERENCES pilots(id); + +CREATE TABLE languages ( + id integer NOT NULL, + language text NOT NULL +); + +ALTER TABLE languages ADD CONSTRAINT language_pkey PRIMARY KEY (id); + +-- Join table +CREATE TABLE pilot_languages ( + pilot_id integer NOT NULL, + language_id integer NOT NULL +); + +-- Composite primary key +ALTER TABLE pilot_languages ADD CONSTRAINT pilot_language_pkey PRIMARY KEY (pilot_id, language_id); +ALTER TABLE pilot_languages ADD CONSTRAINT pilot_language_pilots_fkey FOREIGN KEY (pilot_id) REFERENCES pilots(id); +ALTER TABLE pilot_languages ADD CONSTRAINT pilot_language_languages_fkey FOREIGN KEY (language_id) REFERENCES languages(id); diff --git a/examples/src/main/resources/ondeck/mysql/query/city.sql b/examples/src/main/resources/ondeck/mysql/query/city.sql new file mode 100644 index 0000000..c387e9d --- /dev/null +++ b/examples/src/main/resources/ondeck/mysql/query/city.sql @@ -0,0 +1,23 @@ +/* name: ListCities :many */ +SELECT * +FROM city +ORDER BY name; + +/* name: GetCity :one */ +SELECT * +FROM city +WHERE slug = ?; + +/* name: CreateCity :exec */ +INSERT INTO city ( + name, + slug +) VALUES ( + ?, + ? +); + +/* name: UpdateCityName :exec */ +UPDATE city +SET name = ? +WHERE slug = ?; diff --git a/examples/src/main/resources/ondeck/mysql/query/venue.sql b/examples/src/main/resources/ondeck/mysql/query/venue.sql new file mode 100644 index 0000000..a1dd7a1 --- /dev/null +++ b/examples/src/main/resources/ondeck/mysql/query/venue.sql @@ -0,0 +1,48 @@ +/* name: ListVenues :many */ +SELECT * +FROM venue +WHERE city = ? +ORDER BY name; + +/* name: DeleteVenue :exec */ +DELETE FROM venue +WHERE slug = ? AND slug = ?; + +/* name: GetVenue :one */ +SELECT * +FROM venue +WHERE slug = ? AND city = ?; + +/* name: CreateVenue :execresult */ +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + statuses, + tags +) VALUES ( + ?, + ?, + ?, + NOW(), + ?, + ?, + ?, + ? +); + +/* name: UpdateVenueName :exec */ +UPDATE venue +SET name = ? +WHERE slug = ?; + +/* name: VenueCountByCity :many */ +SELECT + city, + count(*) +FROM venue +GROUP BY 1 +ORDER BY 1; diff --git a/examples/src/main/resources/ondeck/mysql/schema/0001_city.sql b/examples/src/main/resources/ondeck/mysql/schema/0001_city.sql new file mode 100644 index 0000000..6be35d1 --- /dev/null +++ b/examples/src/main/resources/ondeck/mysql/schema/0001_city.sql @@ -0,0 +1,4 @@ +CREATE TABLE city ( + slug varchar(255) PRIMARY KEY, + name text NOT NULL +) diff --git a/examples/src/main/resources/ondeck/mysql/schema/0002_venue.sql b/examples/src/main/resources/ondeck/mysql/schema/0002_venue.sql new file mode 100644 index 0000000..4fc842c --- /dev/null +++ b/examples/src/main/resources/ondeck/mysql/schema/0002_venue.sql @@ -0,0 +1,12 @@ +CREATE TABLE venues ( + id SERIAL primary key, + dropped text, + status ENUM('open', 'closed') not null COMMENT 'Venues can be either open or closed', + statuses text, -- status[], + slug text not null COMMENT 'This value appears in public URLs', + name varchar(255) not null, + city text not null references city(slug), + spotify_playlist varchar(255) not null, + songkick_id text, + tags text -- text[] +) COMMENT='Venues are places where muisc happens'; diff --git a/examples/src/main/resources/ondeck/mysql/schema/0003_add_column.sql b/examples/src/main/resources/ondeck/mysql/schema/0003_add_column.sql new file mode 100644 index 0000000..9b334bc --- /dev/null +++ b/examples/src/main/resources/ondeck/mysql/schema/0003_add_column.sql @@ -0,0 +1,3 @@ +ALTER TABLE venues RENAME TO venue; +ALTER TABLE venue ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT NOW(); +ALTER TABLE venue DROP COLUMN dropped; diff --git a/examples/src/main/resources/ondeck/postgresql/query/city.sql b/examples/src/main/resources/ondeck/postgresql/query/city.sql new file mode 100644 index 0000000..f34dc99 --- /dev/null +++ b/examples/src/main/resources/ondeck/postgresql/query/city.sql @@ -0,0 +1,26 @@ +-- name: ListCities :many +SELECT * +FROM city +ORDER BY name; + +-- name: GetCity :one +SELECT * +FROM city +WHERE slug = $1; + +-- name: CreateCity :one +-- Create a new city. The slug must be unique. +-- This is the second line of the comment +-- This is the third line +INSERT INTO city ( + name, + slug +) VALUES ( + $1, + $2 +) RETURNING *; + +-- name: UpdateCityName :exec +UPDATE city +SET name = $2 +WHERE slug = $1; diff --git a/examples/src/main/resources/ondeck/postgresql/query/venue.sql b/examples/src/main/resources/ondeck/postgresql/query/venue.sql new file mode 100644 index 0000000..8c6bd02 --- /dev/null +++ b/examples/src/main/resources/ondeck/postgresql/query/venue.sql @@ -0,0 +1,49 @@ +-- name: ListVenues :many +SELECT * +FROM venue +WHERE city = $1 +ORDER BY name; + +-- name: DeleteVenue :exec +DELETE FROM venue +WHERE slug = $1 AND slug = $1; + +-- name: GetVenue :one +SELECT * +FROM venue +WHERE slug = $1 AND city = $2; + +-- name: CreateVenue :one +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + statuses, + tags +) VALUES ( + $1, + $2, + $3, + NOW(), + $4, + $5, + $6, + $7 +) RETURNING id; + +-- name: UpdateVenueName :one +UPDATE venue +SET name = $2 +WHERE slug = $1 +RETURNING id; + +-- name: VenueCountByCity :many +SELECT + city, + count(*) +FROM venue +GROUP BY 1 +ORDER BY 1; diff --git a/examples/src/main/resources/ondeck/postgresql/schema/0001_city.sql b/examples/src/main/resources/ondeck/postgresql/schema/0001_city.sql new file mode 100644 index 0000000..af38f16 --- /dev/null +++ b/examples/src/main/resources/ondeck/postgresql/schema/0001_city.sql @@ -0,0 +1,4 @@ +CREATE TABLE city ( + slug text PRIMARY KEY, + name text NOT NULL +) diff --git a/examples/src/main/resources/ondeck/postgresql/schema/0002_venue.sql b/examples/src/main/resources/ondeck/postgresql/schema/0002_venue.sql new file mode 100644 index 0000000..940de7a --- /dev/null +++ b/examples/src/main/resources/ondeck/postgresql/schema/0002_venue.sql @@ -0,0 +1,18 @@ +CREATE TYPE status AS ENUM ('op!en', 'clo@sed'); +COMMENT ON TYPE status IS 'Venues can be either open or closed'; + +CREATE TABLE venues ( + id SERIAL primary key, + dropped text, + status status not null, + statuses status[], + slug text not null, + name varchar(255) not null, + city text not null references city(slug), + spotify_playlist varchar not null, + songkick_id text, + tags text[] +); +COMMENT ON TABLE venues IS 'Venues are places where muisc happens'; +COMMENT ON COLUMN venues.slug IS 'This value appears in public URLs'; + diff --git a/examples/src/main/resources/ondeck/postgresql/schema/0003_add_column.sql b/examples/src/main/resources/ondeck/postgresql/schema/0003_add_column.sql new file mode 100644 index 0000000..9b334bc --- /dev/null +++ b/examples/src/main/resources/ondeck/postgresql/schema/0003_add_column.sql @@ -0,0 +1,3 @@ +ALTER TABLE venues RENAME TO venue; +ALTER TABLE venue ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT NOW(); +ALTER TABLE venue DROP COLUMN dropped; diff --git a/examples/src/test/kotlin/com/example/authors/mysql/QueriesImplTest.kt b/examples/src/test/kotlin/com/example/authors/mysql/QueriesImplTest.kt new file mode 100644 index 0000000..3cb7d78 --- /dev/null +++ b/examples/src/test/kotlin/com/example/authors/mysql/QueriesImplTest.kt @@ -0,0 +1,65 @@ +package com.example.authors.mysql + +import com.example.dbtest.MysqlDbTestExtension +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class QueriesImplTest() { + + companion object { + @JvmField + @RegisterExtension + val dbtest = MysqlDbTestExtension("src/main/resources/authors/mysql/schema.sql") + } + + @Test + fun testCreateAuthor() { + val db = QueriesImpl(dbtest.getConnection()) + + val initialAuthors = db.listAuthors() + assert(initialAuthors.isEmpty()) + + val name = "Brian Kernighan" + val bio = "Co-author of The C Programming Language and The Go Programming Language" + val id = db.createAuthor( + name = name, + bio = bio + ) + assertEquals(id, 1) + val expectedAuthor = Author(id, name, bio) + + val fetchedAuthor = db.getAuthor(id) + assertEquals(expectedAuthor, fetchedAuthor) + + val listedAuthors = db.listAuthors() + assertEquals(1, listedAuthors.size) + assertEquals(expectedAuthor, listedAuthors[0]) + + val id2 = db.createAuthor( + name = name, + bio = bio + ) + assertEquals(id2, 2) + } + + @Test + fun testNull() { + val db = QueriesImpl(dbtest.getConnection()) + + val initialAuthors = db.listAuthors() + assert(initialAuthors.isEmpty()) + + val name = "Brian Kernighan" + val bio = null + val id = db.createAuthor(name, bio) + val expectedAuthor = Author(id, name, bio) + + val fetchedAuthor = db.getAuthor(id) + assertEquals(expectedAuthor, fetchedAuthor) + + val listedAuthors = db.listAuthors() + assertEquals(1, listedAuthors.size) + assertEquals(expectedAuthor, listedAuthors[0]) + } +} diff --git a/examples/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt b/examples/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt new file mode 100644 index 0000000..86c24fb --- /dev/null +++ b/examples/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt @@ -0,0 +1,60 @@ +package com.example.authors.postgresql + +import com.example.dbtest.PostgresDbTestExtension +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class QueriesImplTest() { + + companion object { + @JvmField + @RegisterExtension + val dbtest = PostgresDbTestExtension("src/main/resources/authors/postgresql/schema.sql") + } + + @Test + fun testCreateAuthor() { + val db = QueriesImpl(dbtest.getConnection()) + + val initialAuthors = db.listAuthors() + assert(initialAuthors.isEmpty()) + + val name = "Brian Kernighan" + val bio = "Co-author of The C Programming Language and The Go Programming Language" + val insertedAuthor = db.createAuthor( + name = name, + bio = bio + )!! + val expectedAuthor = Author(insertedAuthor.id, name, bio) + Assertions.assertEquals(expectedAuthor, insertedAuthor) + + val fetchedAuthor = db.getAuthor(insertedAuthor.id) + Assertions.assertEquals(expectedAuthor, fetchedAuthor) + + val listedAuthors = db.listAuthors() + Assertions.assertEquals(1, listedAuthors.size) + Assertions.assertEquals(expectedAuthor, listedAuthors[0]) + } + + @Test + fun testNull() { + val db = QueriesImpl(dbtest.getConnection()) + + val initialAuthors = db.listAuthors() + assert(initialAuthors.isEmpty()) + + val name = "Brian Kernighan" + val bio = null + val insertedAuthor = db.createAuthor(name, bio)!! + val expectedAuthor = Author(insertedAuthor.id, name, bio) + Assertions.assertEquals(expectedAuthor, insertedAuthor) + + val fetchedAuthor = db.getAuthor(insertedAuthor.id) + Assertions.assertEquals(expectedAuthor, fetchedAuthor) + + val listedAuthors = db.listAuthors() + Assertions.assertEquals(1, listedAuthors.size) + Assertions.assertEquals(expectedAuthor, listedAuthors[0]) + } +} \ No newline at end of file diff --git a/examples/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt b/examples/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt new file mode 100644 index 0000000..f69cd85 --- /dev/null +++ b/examples/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt @@ -0,0 +1,98 @@ +package com.example.booktest.mysql + +import com.example.dbtest.MysqlDbTestExtension +import com.example.dbtest.PostgresDbTestExtension +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter + +class QueriesImplTest { + companion object { + @JvmField @RegisterExtension val dbtest = MysqlDbTestExtension("src/main/resources/booktest/mysql/schema.sql") + } + + @Test + fun testQueries() { + val conn = dbtest.getConnection() + val db = QueriesImpl(conn) + val authorId = db.createAuthor("Unknown Master") + val author = db.getAuthor(authorId.toInt())!! + + // Start a transaction + conn.autoCommit = false + db.createBook( + authorId = author.authorId, + isbn = "1", + title = "my book title", + bookType = BooksBookType.NONFICTION, + yr = 2016, + available = LocalDateTime.now(), + tags = "" + ) + + val b1Id = db.createBook( + authorId = author.authorId, + isbn = "2", + title = "the second book", + bookType = BooksBookType.NONFICTION, + yr = 2016, + available = LocalDateTime.now(), + tags = listOf("cool", "unique").joinToString(",") + ) + + db.updateBook( + bookId = b1Id.toInt(), + title = "changed second title", + tags = listOf("cool", "disastor").joinToString(",") + ) + + val b3Id = db.createBook( + authorId = author.authorId, + isbn = "3", + title = "the third book", + bookType = BooksBookType.NONFICTION, + yr = 2001, + available = LocalDateTime.now(), + tags = listOf("cool").joinToString(",") + ) + + db.createBook( + authorId = author.authorId, + isbn = "4", + title = "4th place finisher", + bookType = BooksBookType.NONFICTION, + yr = 2011, + available = LocalDateTime.now(), + tags = listOf("other").joinToString(",") + ) + + // Commit transaction + conn.commit() + conn.autoCommit = true + + db.updateBookISBN( + bookId = b3Id.toInt(), + isbn = "NEW ISBN", + title = "never ever gonna finish, a quatrain", + tags = listOf("someother").joinToString(",") + ) + + val books0 = db.booksByTitleYear("my book title", 2016) + + val formatter = DateTimeFormatter.ISO_DATE_TIME + for (book in books0) { + println("Book ${book.bookId} (${book.bookType}): ${book.title} available: ${book.available.format(formatter)}") + val author2 = db.getAuthor(book.authorId)!! + println("Book ${book.bookId} author: ${author2.name}") + } + + // find a book with either "cool" or "other" tag + println("---------\\nTag search results:\\n") + val res = db.booksByTags(listOf("cool", "other", "someother").joinToString(",")) + for (ab in res) { + println("Book ${ab.bookId}: '${ab.title}', Author: '${ab.name}', ISBN: '${ab.isbn}' Tags: '${ab.tags.toList()}'") + } + } +} \ No newline at end of file diff --git a/examples/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt b/examples/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt new file mode 100644 index 0000000..1a43161 --- /dev/null +++ b/examples/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt @@ -0,0 +1,97 @@ +package com.example.booktest.postgresql + +import com.example.dbtest.PostgresDbTestExtension +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension +import java.time.OffsetDateTime +import java.time.format.DateTimeFormatter + +class QueriesImplTest { + companion object { + @JvmField @RegisterExtension val dbtest = PostgresDbTestExtension("src/main/resources/booktest/postgresql/schema.sql") + } + + @Test + fun testQueries() { + val conn = dbtest.getConnection() + val db = QueriesImpl(conn) + val author = db.createAuthor("Unknown Master")!! + + // Start a transaction + conn.autoCommit = false + db.createBook( + authorId = author.authorId, + isbn = "1", + title = "my book title", + bookType = BookType.NONFICTION, + year = 2016, + available = OffsetDateTime.now(), + tags = listOf() + ) + + val b1 = db.createBook( + authorId = author.authorId, + isbn = "2", + title = "the second book", + bookType = BookType.NONFICTION, + year = 2016, + available = OffsetDateTime.now(), + tags = listOf("cool", "unique") + )!! + + db.updateBook( + bookId = b1.bookId, + title = "changed second title", + tags = listOf("cool", "disastor") + ) + + val b3 = db.createBook( + authorId = author.authorId, + isbn = "3", + title = "the third book", + bookType = BookType.NONFICTION, + year = 2001, + available = OffsetDateTime.now(), + tags = listOf("cool") + )!! + + db.createBook( + authorId = author.authorId, + isbn = "4", + title = "4th place finisher", + bookType = BookType.NONFICTION, + year = 2011, + available = OffsetDateTime.now(), + tags = listOf("other") + ) + + // Commit transaction + conn.commit() + conn.autoCommit = true + + // ISBN update fails because parameters are not in sequential order. After changing $N to ?, ordering is lost, + // and the parameters are filled into the wrong slots. + db.updateBookISBN( + bookId = b3.bookId, + isbn = "NEW ISBN", + title = "never ever gonna finish, a quatrain", + tags = listOf("someother") + ) + + val books0 = db.booksByTitleYear("my book title", 2016) + + val formatter = DateTimeFormatter.ISO_DATE_TIME + for (book in books0) { + println("Book ${book.bookId} (${book.bookType}): ${book.title} available: ${book.available.format(formatter)}") + val author2 = db.getAuthor(book.authorId)!! + println("Book ${book.bookId} author: ${author2.name}") + } + + // find a book with either "cool" or "other" tag + println("---------\\nTag search results:\\n") + val res = db.booksByTags(listOf("cool", "other", "someother")) + for (ab in res) { + println("Book ${ab.bookId}: '${ab.title}', Author: '${ab.name}', ISBN: '${ab.isbn}' Tags: '${ab.tags.toList()}'") + } + } +} \ No newline at end of file diff --git a/examples/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt b/examples/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt new file mode 100644 index 0000000..243a441 --- /dev/null +++ b/examples/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt @@ -0,0 +1,46 @@ +package com.example.dbtest + +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import java.nio.file.Files +import java.nio.file.Paths +import java.sql.Connection +import java.sql.DriverManager +import kotlin.streams.toList + + +class MysqlDbTestExtension(private val migrationsPath: String) : BeforeEachCallback, AfterEachCallback { + val user = System.getenv("MYSQL_USER") ?: "root" + val pass = System.getenv("MYSQL_ROOT_PASSWORD") ?: "mysecretpassword" + val host = System.getenv("MYSQL_HOST") ?: "127.0.0.1" + val port = System.getenv("MYSQL_PORT") ?: "3306" + val mainDb = System.getenv("MYSQL_DATABASE") ?: "dinotest" + val testDb = "sqltest_mysql" + + override fun beforeEach(context: ExtensionContext) { + getConnection(mainDb).createStatement().execute("CREATE DATABASE $testDb") + val path = Paths.get(migrationsPath) + val migrations = if (Files.isDirectory(path)) { + Files.list(path).filter { it.toString().endsWith(".sql") }.sorted().map { Files.readString(it) }.toList() + } else { + listOf(Files.readString(path)) + } + migrations.forEach { + getConnection().createStatement().execute(it) + } + } + + override fun afterEach(context: ExtensionContext) { + getConnection(mainDb).createStatement().execute("DROP DATABASE $testDb") + } + + private fun getConnection(db: String): Connection { + val url = "jdbc:mysql://$host:$port/$db?user=$user&password=$pass&allowMultiQueries=true" + return DriverManager.getConnection(url) + } + + fun getConnection(): Connection { + return getConnection(testDb) + } +} \ No newline at end of file diff --git a/examples/src/test/kotlin/com/example/dbtest/PostgresDbTestExtension.kt b/examples/src/test/kotlin/com/example/dbtest/PostgresDbTestExtension.kt new file mode 100644 index 0000000..55a7d6f --- /dev/null +++ b/examples/src/test/kotlin/com/example/dbtest/PostgresDbTestExtension.kt @@ -0,0 +1,51 @@ +package com.example.dbtest + +import org.junit.jupiter.api.extension.AfterEachCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import java.nio.file.Files +import java.nio.file.Paths +import java.sql.Connection +import java.sql.DriverManager +import kotlin.streams.toList + +class PostgresDbTestExtension(private val migrationsPath: String) : BeforeEachCallback, AfterEachCallback { + private val schemaConn: Connection + private val url: String + + companion object { + const val schema = "dinosql_test" + } + + init { + val user = System.getenv("PG_USER") ?: "postgres" + val pass = System.getenv("PG_PASSWORD") ?: "mysecretpassword" + val host = System.getenv("PG_HOST") ?: "127.0.0.1" + val port = System.getenv("PG_PORT") ?: "5432" + val db = System.getenv("PG_DATABASE") ?: "dinotest" + url = "jdbc:postgresql://$host:$port/$db?user=$user&password=$pass&sslmode=disable" + + schemaConn = DriverManager.getConnection(url) + } + + override fun beforeEach(context: ExtensionContext) { + schemaConn.createStatement().execute("CREATE SCHEMA $schema") + val path = Paths.get(migrationsPath) + val migrations = if (Files.isDirectory(path)) { + Files.list(path).filter{ it.toString().endsWith(".sql")}.sorted().map { Files.readString(it) }.toList() + } else { + listOf(Files.readString(path)) + } + migrations.forEach { + getConnection().createStatement().execute(it) + } + } + + override fun afterEach(context: ExtensionContext) { + schemaConn.createStatement().execute("DROP SCHEMA $schema CASCADE") + } + + fun getConnection(): Connection { + return DriverManager.getConnection("$url¤tSchema=$schema") + } +} \ No newline at end of file diff --git a/examples/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt b/examples/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt new file mode 100644 index 0000000..27fac23 --- /dev/null +++ b/examples/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt @@ -0,0 +1,50 @@ +package com.example.ondeck.mysql + +import com.example.dbtest.MysqlDbTestExtension +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class QueriesImplTest { + companion object { + @JvmField + @RegisterExtension + val dbtest = MysqlDbTestExtension("src/main/resources/ondeck/mysql/schema") + } + + @Test + fun testQueries() { + val q = QueriesImpl(dbtest.getConnection()) + q.createCity( + slug = "san-francisco", + name = "San Francisco" + ) + val city = q.listCities()[0] + val venueId = q.createVenue( + slug = "the-fillmore", + name = "The Fillmore", + city = city.slug, + spotifyPlaylist = "spotify=uri", + status = VenueStatus.OPEN, + statuses = listOf(VenueStatus.OPEN, VenueStatus.CLOSED).joinToString(","), + tags = listOf("rock", "punk").joinToString(",") + ) + val venue = q.getVenue( + slug = "the-fillmore", + city = city.slug + )!! + assertEquals(venueId, venue.id) + + assertEquals(city, q.getCity(city.slug)) + assertEquals(listOf(VenueCountByCityRow(city.slug, 1)), q.venueCountByCity()) + assertEquals(listOf(city), q.listCities()) + assertEquals(listOf(venue), q.listVenues(city.slug)) + + q.updateCityName(slug = city.slug, name = "SF") + q.updateVenueName(slug = venue.slug, name = "Fillmore") + val fresh = q.getVenue(venue.slug, city.slug)!! + assertEquals("Fillmore", fresh.name) + + q.deleteVenue(venue.slug, venue.slug) + } +} diff --git a/examples/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt b/examples/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt new file mode 100644 index 0000000..990f439 --- /dev/null +++ b/examples/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt @@ -0,0 +1,46 @@ +package com.example.ondeck.postgresql + +import com.example.dbtest.PostgresDbTestExtension +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class QueriesImplTest { + companion object { + @JvmField @RegisterExtension val dbtest = PostgresDbTestExtension("src/main/resources/ondeck/postgresql/schema") + } + + @Test + fun testQueries() { + val q = QueriesImpl(dbtest.getConnection()) + val city = q.createCity( + slug = "san-francisco", + name = "San Francisco" + )!! + val venueId = q.createVenue( + slug = "the-fillmore", + name = "The Fillmore", + city = city.slug, + spotifyPlaylist = "spotify=uri", + status = Status.OPEN, + statuses = listOf(Status.OPEN, Status.CLOSED), + tags = listOf("rock", "punk") + ) + val venue = q.getVenue( + slug = "the-fillmore", + city = city.slug + )!! + assertEquals(venueId, venue.id) + + assertEquals(city, q.getCity(city.slug)) + assertEquals(listOf(VenueCountByCityRow(city.slug, 1)), q.venueCountByCity()) + assertEquals(listOf(city), q.listCities()) + assertEquals(listOf(venue), q.listVenues(city.slug)) + + q.updateCityName(slug = city.slug, name = "SF") + val id = q.updateVenueName(slug = venue.slug, name = "Fillmore") + assertEquals(venue.id, id) + + q.deleteVenue(venue.slug) + } +}