diff --git a/examples/kotlin/.gitignore b/examples/kotlin/.gitignore new file mode 100644 index 0000000000..fbb16c8de7 --- /dev/null +++ b/examples/kotlin/.gitignore @@ -0,0 +1,4 @@ +/.gradle/ +/.idea/ +/build/ +/out/ diff --git a/examples/kotlin/README.md b/examples/kotlin/README.md new file mode 100644 index 0000000000..ed910aa966 --- /dev/null +++ b/examples/kotlin/README.md @@ -0,0 +1,17 @@ +# 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 +./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/kotlin/build.gradle b/examples/kotlin/build.gradle new file mode 100644 index 0000000000..f20b3ae51a --- /dev/null +++ b/examples/kotlin/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.3.60' +} + +group 'com.example' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.postgresql:postgresql:42.2.9' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.1' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1' +} + +test { + useJUnitPlatform() +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} \ No newline at end of file diff --git a/examples/kotlin/gradle.properties b/examples/kotlin/gradle.properties new file mode 100644 index 0000000000..29e08e8ca8 --- /dev/null +++ b/examples/kotlin/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/examples/kotlin/gradle/wrapper/gradle-wrapper.jar b/examples/kotlin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..87b738cbd0 Binary files /dev/null and b/examples/kotlin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/examples/kotlin/gradle/wrapper/gradle-wrapper.properties b/examples/kotlin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..3cdb37dd29 --- /dev/null +++ b/examples/kotlin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Jan 25 10:45:34 EST 2020 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/examples/kotlin/gradlew b/examples/kotlin/gradlew new file mode 100755 index 0000000000..af6708ff22 --- /dev/null +++ b/examples/kotlin/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/kotlin/gradlew.bat b/examples/kotlin/gradlew.bat new file mode 100644 index 0000000000..6d57edc706 --- /dev/null +++ b/examples/kotlin/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/kotlin/settings.gradle b/examples/kotlin/settings.gradle new file mode 100644 index 0000000000..d51bf78f7f --- /dev/null +++ b/examples/kotlin/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'dbtest' + diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/Models.kt b/examples/kotlin/src/main/kotlin/com/example/authors/Models.kt new file mode 100644 index 0000000000..916a88d7c8 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/authors/Models.kt @@ -0,0 +1,10 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.authors + +data class Author ( + val id: Long, + val name: String, + val bio: String? +) + diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt new file mode 100644 index 0000000000..276de49970 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt @@ -0,0 +1,109 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.authors + +import java.sql.Connection +import java.sql.SQLException + +const val createAuthor = """-- name: createAuthor :one +INSERT INTO authors ( + name, bio +) VALUES ( + ?, ? +) +RETURNING id, name, bio +""" + +data class CreateAuthorParams ( + val name: String, + val bio: String? +) + +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) { + + @Throws(SQLException::class) + fun createAuthor(arg: CreateAuthorParams): Author { + val stmt = conn.prepareStatement(createAuthor) + stmt.setString(1, arg.name) + stmt.setString(2, arg.bio) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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) + fun deleteAuthor(id: Long) { + val stmt = conn.prepareStatement(deleteAuthor) + stmt.setLong(1, id) + + stmt.execute() + stmt.close() + } + + @Throws(SQLException::class) + fun getAuthor(id: Long): Author { + val stmt = conn.prepareStatement(getAuthor) + stmt.setLong(1, id) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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) + fun listAuthors(): List { + val stmt = conn.prepareStatement(listAuthors) + + return stmt.executeQuery().use { results -> + val ret = mutableListOf() + while (results.next()) { + ret.add(Author( + results.getLong(1), + results.getString(2), + results.getString(3) + )) + } + ret + } + } + +} + diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt new file mode 100644 index 0000000000..46b71b248a --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt @@ -0,0 +1,27 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.booktest.postgresql + +import java.time.OffsetDateTime + +enum class BookType(val value: String) { + FICTION("FICTION"), + NONFICTION("NONFICTION") +} + +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: Array +) + diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt new file mode 100644 index 0000000000..e7d6d4764c --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt @@ -0,0 +1,292 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.booktest.postgresql + +import java.sql.Connection +import java.sql.SQLException +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: Array +) + +const val booksByTitleYear = """-- name: booksByTitleYear :many +SELECT book_id, author_id, isbn, booktype, title, year, available, tags FROM books +WHERE title = ? AND year = ? +""" + +data class BooksByTitleYearParams ( + val title: String, + val year: Int +) + +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, + booktype, + title, + year, + available, + tags +) VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ? +) +RETURNING book_id, author_id, isbn, booktype, title, year, available, tags +""" + +data class CreateBookParams ( + val authorId: Int, + val isbn: String, + val booktype: BookType, + val title: String, + val year: Int, + val available: OffsetDateTime, + val tags: Array +) + +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, booktype, title, year, available, tags FROM books +WHERE book_id = ? +""" + +const val updateBook = """-- name: updateBook :exec +UPDATE books +SET title = ?, tags = ? +WHERE book_id = ? +""" + +data class UpdateBookParams ( + val title: String, + val tags: Array, + val bookId: Int +) + +const val updateBookISBN = """-- name: updateBookISBN :exec +UPDATE books +SET title = ?, tags = ?, isbn = ? +WHERE book_id = ? +""" + +data class UpdateBookISBNParams ( + val title: String, + val tags: Array, + val bookId: Int, + val isbn: String +) + +class QueriesImpl(private val conn: Connection) { + + @Throws(SQLException::class) + fun booksByTags(dollar_1: Array): List { + val stmt = conn.prepareStatement(booksByTags) + stmt.setArray(1, conn.createArrayOf("pg_catalog.varchar", dollar_1)) + + return stmt.executeQuery().use { results -> + 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 + )) + } + ret + } + } + + @Throws(SQLException::class) + fun booksByTitleYear(arg: BooksByTitleYearParams): List { + val stmt = conn.prepareStatement(booksByTitleYear) + stmt.setString(1, arg.title) + stmt.setInt(2, arg.year) + + return stmt.executeQuery().use { results -> + val ret = mutableListOf() + while (results.next()) { + ret.add(Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BookType.valueOf(results.getString(4)), + results.getString(5), + results.getInt(6), + results.getObject(7, OffsetDateTime::class.java), + results.getArray(8).array as Array + )) + } + ret + } + } + + @Throws(SQLException::class) + fun createAuthor(name: String): Author { + val stmt = conn.prepareStatement(createAuthor) + stmt.setString(1, name) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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) + fun createBook(arg: CreateBookParams): Book { + val stmt = conn.prepareStatement(createBook) + stmt.setInt(1, arg.authorId) + stmt.setString(2, arg.isbn) + stmt.setString(3, arg.booktype.value) + stmt.setString(4, arg.title) + stmt.setInt(5, arg.year) + stmt.setObject(6, arg.available) + stmt.setArray(7, conn.createArrayOf("pg_catalog.varchar", arg.tags)) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + val ret = Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BookType.valueOf(results.getString(4)), + results.getString(5), + results.getInt(6), + results.getObject(7, OffsetDateTime::class.java), + results.getArray(8).array as Array + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + fun deleteBook(bookId: Int) { + val stmt = conn.prepareStatement(deleteBook) + stmt.setInt(1, bookId) + + stmt.execute() + stmt.close() + } + + @Throws(SQLException::class) + fun getAuthor(authorId: Int): Author { + val stmt = conn.prepareStatement(getAuthor) + stmt.setInt(1, authorId) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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) + fun getBook(bookId: Int): Book { + val stmt = conn.prepareStatement(getBook) + stmt.setInt(1, bookId) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + val ret = Book( + results.getInt(1), + results.getInt(2), + results.getString(3), + BookType.valueOf(results.getString(4)), + results.getString(5), + results.getInt(6), + results.getObject(7, OffsetDateTime::class.java), + results.getArray(8).array as Array + ) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + fun updateBook(arg: UpdateBookParams) { + val stmt = conn.prepareStatement(updateBook) + stmt.setString(1, arg.title) + stmt.setArray(2, conn.createArrayOf("pg_catalog.varchar", arg.tags)) + stmt.setInt(3, arg.bookId) + + stmt.execute() + stmt.close() + } + + @Throws(SQLException::class) + fun updateBookISBN(arg: UpdateBookISBNParams) { + val stmt = conn.prepareStatement(updateBookISBN) + stmt.setString(1, arg.title) + stmt.setArray(2, conn.createArrayOf("pg_catalog.varchar", arg.tags)) + stmt.setInt(3, arg.bookId) + stmt.setString(4, arg.isbn) + + stmt.execute() + stmt.close() + } + +} + diff --git a/examples/kotlin/src/main/kotlin/com/example/jets/Models.kt b/examples/kotlin/src/main/kotlin/com/example/jets/Models.kt new file mode 100644 index 0000000000..d2bced3778 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/jets/Models.kt @@ -0,0 +1,27 @@ +// Code generated by sqlc. DO NOT EDIT. + +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/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt new file mode 100644 index 0000000000..35f60b681c --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt @@ -0,0 +1,64 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.jets + +import java.sql.Connection +import java.sql.SQLException + +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) { + + @Throws(SQLException::class) + fun countPilots(): Long { + val stmt = conn.prepareStatement(countPilots) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + val ret = results.getLong(1) + if (results.next()) { + throw SQLException("expected one row in result set, but got many") + } + ret + } + } + + @Throws(SQLException::class) + fun deletePilot(id: Int) { + val stmt = conn.prepareStatement(deletePilot) + stmt.setInt(1, id) + + stmt.execute() + stmt.close() + } + + @Throws(SQLException::class) + fun listPilots(): List { + val stmt = conn.prepareStatement(listPilots) + + return stmt.executeQuery().use { results -> + val ret = mutableListOf() + while (results.next()) { + ret.add(Pilot( + results.getInt(1), + results.getString(2) + )) + } + ret + } + } + +} + diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/Models.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/Models.kt new file mode 100644 index 0000000000..6b7f38d395 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/Models.kt @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.ondeck + +import java.time.LocalDateTime + +// Venues can be either open or closed +enum class Status(val value: String) { + OPEN("op!en"), + CLOSED("clo@sed") +} + +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: Array, + // This value appears in public URLs + val slug: String, + val name: String, + val city: String, + val spotifyPlaylist: String, + val songkickId: String?, + val tags: Array, + val createdAt: LocalDateTime +) + diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/Queries.kt new file mode 100644 index 0000000000..fa540fc82e --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/Queries.kt @@ -0,0 +1,41 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.ondeck + +import java.sql.Connection +import java.sql.SQLException +import java.time.LocalDateTime + +interface Queries { + @Throws(SQLException::class) + fun createCity(arg: CreateCityParams): City + + @Throws(SQLException::class) + fun createVenue(arg: CreateVenueParams): Int + + @Throws(SQLException::class) + fun deleteVenue(slug: String) + + @Throws(SQLException::class) + fun getCity(slug: String): City + + @Throws(SQLException::class) + fun getVenue(arg: GetVenueParams): Venue + + @Throws(SQLException::class) + fun listCities(): List + + @Throws(SQLException::class) + fun listVenues(city: String): List + + @Throws(SQLException::class) + fun updateCityName(arg: UpdateCityNameParams) + + @Throws(SQLException::class) + fun updateVenueName(arg: UpdateVenueNameParams): Int + + @Throws(SQLException::class) + fun venueCountByCity(): List + +} + diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/QueriesImpl.kt new file mode 100644 index 0000000000..4da97fec32 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/QueriesImpl.kt @@ -0,0 +1,322 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.ondeck + +import java.sql.Connection +import java.sql.SQLException +import java.time.LocalDateTime + +const val createCity = """-- name: createCity :one +INSERT INTO city ( + name, + slug +) VALUES ( + ?, + ? +) RETURNING slug, name +""" + +data class CreateCityParams ( + val name: String, + val slug: String +) + +const val createVenue = """-- name: createVenue :one +INSERT INTO venue ( + slug, + name, + city, + created_at, + spotify_playlist, + status, + statuses, + tags +) VALUES ( + ?, + ?, + ?, + NOW(), + ?, + ?, + ?, + ? +) RETURNING id +""" + +data class CreateVenueParams ( + val slug: String, + val name: String, + val city: String, + val spotifyPlaylist: String, + val status: Status, + val statuses: Array, + val tags: Array +) + +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 = ? +""" + +data class GetVenueParams ( + val slug: String, + val city: String +) + +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 = ? +""" + +data class UpdateCityNameParams ( + val slug: String, + val name: String +) + +const val updateVenueName = """-- name: updateVenueName :one +UPDATE venue +SET name = ? +WHERE slug = ? +RETURNING id +""" + +data class UpdateVenueNameParams ( + val slug: String, + val name: String +) + +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(arg: CreateCityParams): City { + val stmt = conn.prepareStatement(createCity) + stmt.setString(1, arg.name) + stmt.setString(2, arg.slug) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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(arg: CreateVenueParams): Int { + val stmt = conn.prepareStatement(createVenue) + stmt.setString(1, arg.slug) + stmt.setString(2, arg.name) + stmt.setString(3, arg.city) + stmt.setString(4, arg.spotifyPlaylist) + stmt.setString(5, arg.status.value) + stmt.setArray(6, conn.createArrayOf("status", arg.statuses.map { v -> v.value }.toTypedArray())) + stmt.setArray(7, conn.createArrayOf("text", arg.tags)) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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) { + val stmt = conn.prepareStatement(deleteVenue) + stmt.setString(1, slug) + + stmt.execute() + stmt.close() + } + + @Throws(SQLException::class) + override fun getCity(slug: String): City { + val stmt = conn.prepareStatement(getCity) + stmt.setString(1, slug) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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(arg: GetVenueParams): Venue { + val stmt = conn.prepareStatement(getVenue) + stmt.setString(1, arg.slug) + stmt.setString(2, arg.city) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + val ret = Venue( + results.getInt(1), + Status.valueOf(results.getString(2)), + (results.getArray(3).array as Array).map { v -> Status.valueOf(v) }.toTypedArray(), + results.getString(4), + results.getString(5), + results.getString(6), + results.getString(7), + results.getString(8), + results.getArray(9).array as Array, + 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 { + val stmt = conn.prepareStatement(listCities) + + return stmt.executeQuery().use { results -> + 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 { + val stmt = conn.prepareStatement(listVenues) + stmt.setString(1, city) + + return stmt.executeQuery().use { results -> + val ret = mutableListOf() + while (results.next()) { + ret.add(Venue( + results.getInt(1), + Status.valueOf(results.getString(2)), + (results.getArray(3).array as Array).map { v -> Status.valueOf(v) }.toTypedArray(), + results.getString(4), + results.getString(5), + results.getString(6), + results.getString(7), + results.getString(8), + results.getArray(9).array as Array, + results.getObject(10, LocalDateTime::class.java) + )) + } + ret + } + } + + @Throws(SQLException::class) + override fun updateCityName(arg: UpdateCityNameParams) { + val stmt = conn.prepareStatement(updateCityName) + stmt.setString(1, arg.slug) + stmt.setString(2, arg.name) + + stmt.execute() + stmt.close() + } + + @Throws(SQLException::class) + override fun updateVenueName(arg: UpdateVenueNameParams): Int { + val stmt = conn.prepareStatement(updateVenueName) + stmt.setString(1, arg.slug) + stmt.setString(2, arg.name) + + return stmt.executeQuery().use { results -> + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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 { + val stmt = conn.prepareStatement(venueCountByCity) + + return stmt.executeQuery().use { results -> + val ret = mutableListOf() + while (results.next()) { + ret.add(VenueCountByCityRow( + results.getString(1), + results.getLong(2) + )) + } + ret + } + } + +} + diff --git a/examples/kotlin/src/main/resources/query.sql b/examples/kotlin/src/main/resources/query.sql new file mode 100644 index 0000000000..75e38b2caf --- /dev/null +++ b/examples/kotlin/src/main/resources/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/kotlin/src/main/resources/schema.sql b/examples/kotlin/src/main/resources/schema.sql new file mode 100644 index 0000000000..b4fad78497 --- /dev/null +++ b/examples/kotlin/src/main/resources/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE authors ( + id BIGSERIAL PRIMARY KEY, + name text NOT NULL, + bio text +); diff --git a/examples/kotlin/src/test/kotlin/com/example/authors/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/authors/QueriesImplTest.kt new file mode 100644 index 0000000000..5bb4994848 --- /dev/null +++ b/examples/kotlin/src/test/kotlin/com/example/authors/QueriesImplTest.kt @@ -0,0 +1,86 @@ +package com.example.authors + +import org.junit.jupiter.api.AfterEach +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.nio.file.Files +import java.nio.file.Paths +import java.sql.Connection +import java.sql.DriverManager + +const val schema = "dinosql_test" + +class QueriesImplTest { + lateinit var schemaConn: Connection + lateinit var conn: Connection + + @BeforeEach + fun setup() { + val user = System.getenv("PG_USER") ?: "postgres" + val port = System.getenv("PG_PORT") ?: "5432" + val host = System.getenv("PG_HOST") ?: "127.0.0.1" + val db = System.getenv("PG_DB") ?: "postgres" + val pass = System.getenv("PG_PASS") ?: "postgres" + val url = "jdbc:postgresql://$host:$port/$db?user=$user&password=$pass&sslmode=disable" + println("db: $url") + + schemaConn = DriverManager.getConnection(url) + schemaConn.createStatement().execute("CREATE SCHEMA $schema") + + conn = DriverManager.getConnection("$url¤tSchema=$schema") + val stmt = Files.readString(Paths.get("src/main/resources/schema.sql")) + conn.createStatement().execute(stmt) + } + + @AfterEach + fun teardown() { + schemaConn.createStatement().execute("DROP SCHEMA $schema CASCADE") + } + + @Test + fun testCreateAuthor() { + val db = QueriesImpl(conn) + + val initialAuthors = db.listAuthors() + assert(initialAuthors.isEmpty()) + + val params = CreateAuthorParams( + name = "Brian Kernighan", + bio = "Co-author of The C Programming Language and The Go Programming Language" + ) + val insertedAuthor = db.createAuthor(params) + val expectedAuthor = Author(insertedAuthor.id, params.name, params.bio) + assertEquals(expectedAuthor, insertedAuthor) + + val fetchedAuthor = db.getAuthor(insertedAuthor.id) + assertEquals(expectedAuthor, fetchedAuthor) + + val listedAuthors = db.listAuthors() + assertEquals(1, listedAuthors.size) + assertEquals(expectedAuthor, listedAuthors[0]) + } + + @Test + fun testNull() { + val db = QueriesImpl(conn) + + val initialAuthors = db.listAuthors() + assert(initialAuthors.isEmpty()) + + val params = CreateAuthorParams( + name = "Brian Kernighan", + bio = null + ) + val insertedAuthor = db.createAuthor(params) + val expectedAuthor = Author(insertedAuthor.id, params.name, params.bio) + assertEquals(expectedAuthor, insertedAuthor) + + val fetchedAuthor = db.getAuthor(insertedAuthor.id) + assertEquals(expectedAuthor, fetchedAuthor) + + val listedAuthors = db.listAuthors() + assertEquals(1, listedAuthors.size) + assertEquals(expectedAuthor, listedAuthors[0]) + } +} diff --git a/examples/sqlc.json b/examples/sqlc.json index 9662784e93..98f3d5fdac 100644 --- a/examples/sqlc.json +++ b/examples/sqlc.json @@ -35,6 +35,39 @@ "schema": "booktest/mysql/schema.sql", "queries": "booktest/mysql/query.sql", "engine": "mysql" + }, + { + "name": "com.example.authors", + "path": "kotlin/src/main/kotlin/com/example/authors", + "schema": "kotlin/src/main/resources/schema.sql", + "queries": "kotlin/src/main/resources/query.sql", + "engine": "postgresql", + "language": "kotlin" + }, + { + "name": "com.example.ondeck", + "path": "kotlin/src/main/kotlin/com/example/ondeck", + "schema": "ondeck/schema", + "queries": "ondeck/query", + "engine": "postgresql", + "emit_interface": true, + "language": "kotlin" + }, + { + "name": "com.example.jets", + "path": "kotlin/src/main/kotlin/com/example/jets", + "schema": "jets/schema.sql", + "queries": "jets/query-building.sql", + "engine": "postgresql", + "language": "kotlin" + }, + { + "name": "com.example.booktest.postgresql", + "path": "kotlin/src/main/kotlin/com/example/booktest/postgresql", + "schema": "booktest/postgresql/schema.sql", + "queries": "booktest/postgresql/query.sql", + "engine": "postgresql", + "language": "kotlin" } ] } diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index 487deb07ef..3e4f7fa301 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -9,11 +9,11 @@ import ( "os/exec" "path/filepath" - "github.com/kyleconroy/sqlc/internal/dinosql" - "github.com/davecgh/go-spew/spew" pg "github.com/lfittl/pg_query_go" "github.com/spf13/cobra" + + "github.com/kyleconroy/sqlc/internal/dinosql" ) // Do runs the command logic. diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index 2ff3101cf0..cc3ad0769f 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -117,7 +117,7 @@ func Generate(dir string, stderr io.Writer) (map[string]string, error) { case dinosql.LanguageKotlin: ktRes, ok := result.(dinosql.KtGenerateable) if !ok { - err = fmt.Errorf("Kotlin not supported") + err = fmt.Errorf("kotlin not supported for engine %s", pkg.Engine) break } files, err = dinosql.KtGenerate(ktRes, combo)