From d8a81fa12c26decc2c2ea253843c334ded60830a Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 9 Nov 2020 15:13:12 -0500 Subject: [PATCH 1/3] Kotlin MySQL support Not much changed really. * Added support for `:execresult` * Add Kotlin MySQL examples and get tests working. --- .github/workflows/ci-kotlin.yml | 11 +++ .github/workflows/ci.yml | 2 +- examples/kotlin/build.gradle | 1 + examples/kotlin/sqlc.json | 23 +++-- .../kotlin/com/example/authors/Queries.kt | 1 + .../kotlin/com/example/authors/QueriesImpl.kt | 1 + .../com/example/authors/mysql/Models.kt | 10 ++ .../com/example/authors/mysql/Queries.kt | 23 +++++ .../com/example/authors/mysql/QueriesImpl.kt | 98 +++++++++++++++++++ .../com/example/booktest/postgresql/Models.kt | 2 +- .../example/booktest/postgresql/Queries.kt | 3 +- .../booktest/postgresql/QueriesImpl.kt | 13 +-- .../main/kotlin/com/example/jets/Queries.kt | 1 + .../kotlin/com/example/jets/QueriesImpl.kt | 1 + .../example/ondeck/{ => postgresql}/Models.kt | 2 +- .../ondeck/{ => postgresql}/Queries.kt | 3 +- .../ondeck/{ => postgresql}/QueriesImpl.kt | 3 +- examples/kotlin/src/main/resources/authors | 1 + .../src/main/resources/authors/query.sql | 19 ---- .../src/main/resources/authors/schema.sql | 5 - examples/kotlin/src/main/resources/booktest | 1 + .../resources/booktest/postgresql/query.sql | 60 ------------ .../resources/booktest/postgresql/schema.sql | 37 ------- examples/kotlin/src/main/resources/jets | 1 + .../main/resources/jets/query-building.sql | 8 -- .../kotlin/src/main/resources/jets/schema.sql | 35 ------- examples/kotlin/src/main/resources/ondeck | 1 + .../src/main/resources/ondeck/query/city.sql | 26 ----- .../src/main/resources/ondeck/query/venue.sql | 49 ---------- .../resources/ondeck/schema/0001_city.sql | 4 - .../resources/ondeck/schema/0002_venue.sql | 18 ---- .../ondeck/schema/0003_add_column.sql | 3 - .../authors/{ => mysql}/QueriesImplTest.kt | 27 ++--- .../authors/postgresql/QueriesImplTest.kt | 62 ++++++++++++ .../booktest/postgresql/QueriesImplTest.kt | 14 +-- .../example/dbtest/MysqlDbTestExtension.kt | 46 +++++++++ ...xtension.kt => PostgresDbTestExtension.kt} | 8 +- .../{ => postgresql}/QueriesImplTest.kt | 6 +- internal/cmd/generate.go | 4 +- internal/codegen/kotlin/gen.go | 23 +++++ internal/codegen/kotlin/imports.go | 1 + internal/endtoend/endtoend_test.go | 3 + 42 files changed, 353 insertions(+), 307 deletions(-) create mode 100644 examples/kotlin/src/main/kotlin/com/example/authors/mysql/Models.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt rename examples/kotlin/src/main/kotlin/com/example/ondeck/{ => postgresql}/Models.kt (95%) rename examples/kotlin/src/main/kotlin/com/example/ondeck/{ => postgresql}/Queries.kt (94%) rename examples/kotlin/src/main/kotlin/com/example/ondeck/{ => postgresql}/QueriesImpl.kt (99%) create mode 120000 examples/kotlin/src/main/resources/authors delete mode 100644 examples/kotlin/src/main/resources/authors/query.sql delete mode 100644 examples/kotlin/src/main/resources/authors/schema.sql create mode 120000 examples/kotlin/src/main/resources/booktest delete mode 100644 examples/kotlin/src/main/resources/booktest/postgresql/query.sql delete mode 100644 examples/kotlin/src/main/resources/booktest/postgresql/schema.sql create mode 120000 examples/kotlin/src/main/resources/jets delete mode 100644 examples/kotlin/src/main/resources/jets/query-building.sql delete mode 100644 examples/kotlin/src/main/resources/jets/schema.sql create mode 120000 examples/kotlin/src/main/resources/ondeck delete mode 100644 examples/kotlin/src/main/resources/ondeck/query/city.sql delete mode 100644 examples/kotlin/src/main/resources/ondeck/query/venue.sql delete mode 100644 examples/kotlin/src/main/resources/ondeck/schema/0001_city.sql delete mode 100644 examples/kotlin/src/main/resources/ondeck/schema/0002_venue.sql delete mode 100644 examples/kotlin/src/main/resources/ondeck/schema/0003_add_column.sql rename examples/kotlin/src/test/kotlin/com/example/authors/{ => mysql}/QueriesImplTest.kt (67%) create mode 100644 examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt create mode 100644 examples/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt rename examples/kotlin/src/test/kotlin/com/example/dbtest/{DbTestExtension.kt => PostgresDbTestExtension.kt} (90%) rename examples/kotlin/src/test/kotlin/com/example/ondeck/{ => postgresql}/QueriesImplTest.kt (86%) diff --git a/.github/workflows/ci-kotlin.yml b/.github/workflows/ci-kotlin.yml index 175f91c731..f8a0c59821 100644 --- a/.github/workflows/ci-kotlin.yml +++ b/.github/workflows/ci-kotlin.yml @@ -17,6 +17,13 @@ jobs: - 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@v2 @@ -30,6 +37,10 @@ jobs: 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/kotlin wrapper-directory: examples/kotlin diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1be1a344be..9dc30f5bbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,6 @@ jobs: PG_PORT: ${{ job.services.postgres.ports['5432'] }} MYSQL_DATABASE: mysql MYSQL_HOST: localhost - MYSQL_PORT: ${{ job.services.mysql.ports['5432'] }} + MYSQL_PORT: ${{ job.services.mysql.ports['3306'] }} MYSQL_ROOT_PASSWORD: mysecretpassword diff --git a/examples/kotlin/build.gradle b/examples/kotlin/build.gradle index c29f0389c8..0dfec90887 100644 --- a/examples/kotlin/build.gradle +++ b/examples/kotlin/build.gradle @@ -10,6 +10,7 @@ repositories { } 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' diff --git a/examples/kotlin/sqlc.json b/examples/kotlin/sqlc.json index 00267b1b01..ddd52d572e 100644 --- a/examples/kotlin/sqlc.json +++ b/examples/kotlin/sqlc.json @@ -2,8 +2,8 @@ "version": "2", "sql": [ { - "schema": "src/main/resources/authors/schema.sql", - "queries": "src/main/resources/authors/query.sql", + "schema": "src/main/resources/authors/postgresql/schema.sql", + "queries": "src/main/resources/authors/postgresql/query.sql", "engine": "postgresql", "gen": { "kotlin": { @@ -13,13 +13,13 @@ } }, { - "schema": "src/main/resources/ondeck/schema", - "queries": "src/main/resources/ondeck/query", + "schema": "src/main/resources/ondeck/postgresql/schema", + "queries": "src/main/resources/ondeck/postgresql/query", "engine": "postgresql", "gen": { "kotlin": { - "out": "src/main/kotlin/com/example/ondeck", - "package": "com.example.ondeck" + "out": "src/main/kotlin/com/example/ondeck/postgresql", + "package": "com.example.ondeck.postgresql" } } }, @@ -44,6 +44,17 @@ "package": "com.example.booktest.postgresql" } } + }, + { + "schema": "src/main/resources/authors/mysql/schema.sql", + "queries": "src/main/resources/authors/mysql/query.sql", + "engine": "mysql:beta", + "gen": { + "kotlin": { + "out": "src/main/kotlin/com/example/authors/mysql", + "package": "com.example.authors.mysql" + } + } } ] } diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt index 4dde3eec1f..fc9343998a 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt @@ -4,6 +4,7 @@ package com.example.authors import java.sql.Connection import java.sql.SQLException +import java.sql.Statement interface Queries { @Throws(SQLException::class) diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt index e005d04204..194b0391fa 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt @@ -4,6 +4,7 @@ package com.example.authors import java.sql.Connection import java.sql.SQLException +import java.sql.Statement const val createAuthor = """-- name: createAuthor :one INSERT INTO authors ( diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Models.kt b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Models.kt new file mode 100644 index 0000000000..147ffd7063 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Models.kt @@ -0,0 +1,10 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.authors.mysql + +data class Author ( + val id: Long, + val name: String, + val bio: String? +) + diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt new file mode 100644 index 0000000000..bd9bddb084 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt @@ -0,0 +1,23 @@ +// Code generated by sqlc. DO NOT EDIT. + +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/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt new file mode 100644 index 0000000000..0112297d4d --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt @@ -0,0 +1,98 @@ +// Code generated by sqlc. DO NOT EDIT. + +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()) { + 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) + 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/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt index 066918ee6b..d2dff24e4a 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Models.kt @@ -23,7 +23,7 @@ data class Book ( val bookId: Int, val authorId: Int, val isbn: String, - val booktype: BookType, + val bookType: BookType, val title: String, val year: Int, val available: OffsetDateTime, diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt index addf28a721..41d67eaf08 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt @@ -4,6 +4,7 @@ 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 @@ -21,7 +22,7 @@ interface Queries { fun createBook( authorId: Int, isbn: String, - booktype: BookType, + bookType: BookType, title: String, year: Int, available: OffsetDateTime, 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 index 2c8fd1ed91..19c5bf8033 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt @@ -4,6 +4,7 @@ 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 @@ -28,7 +29,7 @@ data class BooksByTagsRow ( ) const val booksByTitleYear = """-- name: booksByTitleYear :many -SELECT book_id, author_id, isbn, booktype, title, year, available, tags FROM books +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books WHERE title = ? AND year = ? """ @@ -41,7 +42,7 @@ const val createBook = """-- name: createBook :one INSERT INTO books ( author_id, isbn, - booktype, + book_type, title, year, available, @@ -55,7 +56,7 @@ INSERT INTO books ( ?, ? ) -RETURNING book_id, author_id, isbn, booktype, title, year, available, tags +RETURNING book_id, author_id, isbn, book_type, title, year, available, tags """ const val deleteBook = """-- name: deleteBook :exec @@ -69,7 +70,7 @@ WHERE author_id = ? """ const val getBook = """-- name: getBook :one -SELECT book_id, author_id, isbn, booktype, title, year, available, tags FROM books +SELECT book_id, author_id, isbn, book_type, title, year, available, tags FROM books WHERE book_id = ? """ @@ -155,7 +156,7 @@ class QueriesImpl(private val conn: Connection) : Queries { override fun createBook( authorId: Int, isbn: String, - booktype: BookType, + bookType: BookType, title: String, year: Int, available: OffsetDateTime, @@ -163,7 +164,7 @@ class QueriesImpl(private val conn: Connection) : Queries { return conn.prepareStatement(createBook).use { stmt -> stmt.setInt(1, authorId) stmt.setString(2, isbn) - stmt.setObject(3, booktype.value, Types.OTHER) + stmt.setObject(3, bookType.value, Types.OTHER) stmt.setString(4, title) stmt.setInt(5, year) stmt.setObject(6, available) diff --git a/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt index 7437a6f2b0..6371a1fbc6 100644 --- a/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt @@ -4,6 +4,7 @@ package com.example.jets import java.sql.Connection import java.sql.SQLException +import java.sql.Statement interface Queries { @Throws(SQLException::class) diff --git a/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt index 6155469a7d..68b0278ab2 100644 --- a/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt @@ -4,6 +4,7 @@ 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 diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/Models.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Models.kt similarity index 95% rename from examples/kotlin/src/main/kotlin/com/example/ondeck/Models.kt rename to examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Models.kt index e4dd8e7db7..e86b4e8ce4 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/Models.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Models.kt @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. -package com.example.ondeck +package com.example.ondeck.postgresql import java.time.LocalDateTime diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt similarity index 94% rename from examples/kotlin/src/main/kotlin/com/example/ondeck/Queries.kt rename to examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt index 69debb00a7..ddbe5d79b8 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt @@ -1,9 +1,10 @@ // Code generated by sqlc. DO NOT EDIT. -package com.example.ondeck +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 diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt similarity index 99% rename from examples/kotlin/src/main/kotlin/com/example/ondeck/QueriesImpl.kt rename to examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt index 71317222f8..7b79d5dacd 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt @@ -1,9 +1,10 @@ // Code generated by sqlc. DO NOT EDIT. -package com.example.ondeck +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 diff --git a/examples/kotlin/src/main/resources/authors b/examples/kotlin/src/main/resources/authors new file mode 120000 index 0000000000..f391af0e5a --- /dev/null +++ b/examples/kotlin/src/main/resources/authors @@ -0,0 +1 @@ +../../../../authors \ No newline at end of file diff --git a/examples/kotlin/src/main/resources/authors/query.sql b/examples/kotlin/src/main/resources/authors/query.sql deleted file mode 100644 index 75e38b2caf..0000000000 --- a/examples/kotlin/src/main/resources/authors/query.sql +++ /dev/null @@ -1,19 +0,0 @@ --- 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/authors/schema.sql b/examples/kotlin/src/main/resources/authors/schema.sql deleted file mode 100644 index b4fad78497..0000000000 --- a/examples/kotlin/src/main/resources/authors/schema.sql +++ /dev/null @@ -1,5 +0,0 @@ -CREATE TABLE authors ( - id BIGSERIAL PRIMARY KEY, - name text NOT NULL, - bio text -); diff --git a/examples/kotlin/src/main/resources/booktest b/examples/kotlin/src/main/resources/booktest new file mode 120000 index 0000000000..a397d4ae21 --- /dev/null +++ b/examples/kotlin/src/main/resources/booktest @@ -0,0 +1 @@ +../../../../booktest \ No newline at end of file diff --git a/examples/kotlin/src/main/resources/booktest/postgresql/query.sql b/examples/kotlin/src/main/resources/booktest/postgresql/query.sql deleted file mode 100644 index f4537c603e..0000000000 --- a/examples/kotlin/src/main/resources/booktest/postgresql/query.sql +++ /dev/null @@ -1,60 +0,0 @@ --- 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, - booktype, - 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/kotlin/src/main/resources/booktest/postgresql/schema.sql b/examples/kotlin/src/main/resources/booktest/postgresql/schema.sql deleted file mode 100644 index 0816931a81..0000000000 --- a/examples/kotlin/src/main/resources/booktest/postgresql/schema.sql +++ /dev/null @@ -1,37 +0,0 @@ -DROP TABLE IF EXISTS books CASCADE; -DROP TYPE IF EXISTS book_type CASCADE; -DROP TABLE IF EXISTS authors CASCADE; -DROP FUNCTION IF EXISTS say_hello(text) CASCADE; - -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, - booktype 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/kotlin/src/main/resources/jets b/examples/kotlin/src/main/resources/jets new file mode 120000 index 0000000000..24ed880a40 --- /dev/null +++ b/examples/kotlin/src/main/resources/jets @@ -0,0 +1 @@ +../../../../jets \ No newline at end of file diff --git a/examples/kotlin/src/main/resources/jets/query-building.sql b/examples/kotlin/src/main/resources/jets/query-building.sql deleted file mode 100644 index ede8952367..0000000000 --- a/examples/kotlin/src/main/resources/jets/query-building.sql +++ /dev/null @@ -1,8 +0,0 @@ --- 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/kotlin/src/main/resources/jets/schema.sql b/examples/kotlin/src/main/resources/jets/schema.sql deleted file mode 100644 index 2cc4aca574..0000000000 --- a/examples/kotlin/src/main/resources/jets/schema.sql +++ /dev/null @@ -1,35 +0,0 @@ -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/kotlin/src/main/resources/ondeck b/examples/kotlin/src/main/resources/ondeck new file mode 120000 index 0000000000..37517cb367 --- /dev/null +++ b/examples/kotlin/src/main/resources/ondeck @@ -0,0 +1 @@ +../../../../ondeck \ No newline at end of file diff --git a/examples/kotlin/src/main/resources/ondeck/query/city.sql b/examples/kotlin/src/main/resources/ondeck/query/city.sql deleted file mode 100644 index f34dc9961e..0000000000 --- a/examples/kotlin/src/main/resources/ondeck/query/city.sql +++ /dev/null @@ -1,26 +0,0 @@ --- 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/kotlin/src/main/resources/ondeck/query/venue.sql b/examples/kotlin/src/main/resources/ondeck/query/venue.sql deleted file mode 100644 index 8c6bd02664..0000000000 --- a/examples/kotlin/src/main/resources/ondeck/query/venue.sql +++ /dev/null @@ -1,49 +0,0 @@ --- 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/kotlin/src/main/resources/ondeck/schema/0001_city.sql b/examples/kotlin/src/main/resources/ondeck/schema/0001_city.sql deleted file mode 100644 index af38f16bb5..0000000000 --- a/examples/kotlin/src/main/resources/ondeck/schema/0001_city.sql +++ /dev/null @@ -1,4 +0,0 @@ -CREATE TABLE city ( - slug text PRIMARY KEY, - name text NOT NULL -) diff --git a/examples/kotlin/src/main/resources/ondeck/schema/0002_venue.sql b/examples/kotlin/src/main/resources/ondeck/schema/0002_venue.sql deleted file mode 100644 index 940de7a5a8..0000000000 --- a/examples/kotlin/src/main/resources/ondeck/schema/0002_venue.sql +++ /dev/null @@ -1,18 +0,0 @@ -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/kotlin/src/main/resources/ondeck/schema/0003_add_column.sql b/examples/kotlin/src/main/resources/ondeck/schema/0003_add_column.sql deleted file mode 100644 index 9b334bccce..0000000000 --- a/examples/kotlin/src/main/resources/ondeck/schema/0003_add_column.sql +++ /dev/null @@ -1,3 +0,0 @@ -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/kotlin/src/test/kotlin/com/example/authors/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/authors/mysql/QueriesImplTest.kt similarity index 67% rename from examples/kotlin/src/test/kotlin/com/example/authors/QueriesImplTest.kt rename to examples/kotlin/src/test/kotlin/com/example/authors/mysql/QueriesImplTest.kt index 93608a1dfc..3cb7d7887d 100644 --- a/examples/kotlin/src/test/kotlin/com/example/authors/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/authors/mysql/QueriesImplTest.kt @@ -1,6 +1,6 @@ -package com.example.authors +package com.example.authors.mysql -import com.example.dbtest.DbTestExtension +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 @@ -10,7 +10,7 @@ class QueriesImplTest() { companion object { @JvmField @RegisterExtension - val dbtest = DbTestExtension("src/main/resources/authors/schema.sql") + val dbtest = MysqlDbTestExtension("src/main/resources/authors/mysql/schema.sql") } @Test @@ -22,19 +22,25 @@ class QueriesImplTest() { val name = "Brian Kernighan" val bio = "Co-author of The C Programming Language and The Go Programming Language" - val insertedAuthor = db.createAuthor( + val id = db.createAuthor( name = name, bio = bio ) - val expectedAuthor = Author(insertedAuthor.id, name, bio) - assertEquals(expectedAuthor, insertedAuthor) + assertEquals(id, 1) + val expectedAuthor = Author(id, name, bio) - val fetchedAuthor = db.getAuthor(insertedAuthor.id) + 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 @@ -46,11 +52,10 @@ class QueriesImplTest() { val name = "Brian Kernighan" val bio = null - val insertedAuthor = db.createAuthor(name, bio) - val expectedAuthor = Author(insertedAuthor.id, name, bio) - assertEquals(expectedAuthor, insertedAuthor) + val id = db.createAuthor(name, bio) + val expectedAuthor = Author(id, name, bio) - val fetchedAuthor = db.getAuthor(insertedAuthor.id) + val fetchedAuthor = db.getAuthor(id) assertEquals(expectedAuthor, fetchedAuthor) val listedAuthors = db.listAuthors() diff --git a/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt new file mode 100644 index 0000000000..38c7a46fbd --- /dev/null +++ b/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt @@ -0,0 +1,62 @@ +package com.example.authors.postgresql + +import com.example.authors.Author +import com.example.authors.QueriesImpl +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/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt index 89e480b884..1652ebe144 100644 --- a/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt @@ -1,6 +1,6 @@ package com.example.booktest.postgresql -import com.example.dbtest.DbTestExtension +import com.example.dbtest.PostgresDbTestExtension import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import java.time.OffsetDateTime @@ -8,7 +8,7 @@ import java.time.format.DateTimeFormatter class QueriesImplTest { companion object { - @JvmField @RegisterExtension val dbtest = DbTestExtension("src/main/resources/booktest/postgresql/schema.sql") + @JvmField @RegisterExtension val dbtest = PostgresDbTestExtension("src/main/resources/booktest/postgresql/schema.sql") } @Test @@ -23,7 +23,7 @@ class QueriesImplTest { authorId = author.authorId, isbn = "1", title = "my book title", - booktype = BookType.NONFICTION, + bookType = BookType.NONFICTION, year = 2016, available = OffsetDateTime.now(), tags = listOf() @@ -33,7 +33,7 @@ class QueriesImplTest { authorId = author.authorId, isbn = "2", title = "the second book", - booktype = BookType.NONFICTION, + bookType = BookType.NONFICTION, year = 2016, available = OffsetDateTime.now(), tags = listOf("cool", "unique") @@ -49,7 +49,7 @@ class QueriesImplTest { authorId = author.authorId, isbn = "3", title = "the third book", - booktype = BookType.NONFICTION, + bookType = BookType.NONFICTION, year = 2001, available = OffsetDateTime.now(), tags = listOf("cool") @@ -59,7 +59,7 @@ class QueriesImplTest { authorId = author.authorId, isbn = "4", title = "4th place finisher", - booktype = BookType.NONFICTION, + bookType = BookType.NONFICTION, year = 2011, available = OffsetDateTime.now(), tags = listOf("other") @@ -82,7 +82,7 @@ class QueriesImplTest { val formatter = DateTimeFormatter.ISO_DATE_TIME for (book in books0) { - println("Book ${book.bookId} (${book.booktype}): ${book.title} available: ${book.available.format(formatter)}") + 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}") } diff --git a/examples/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt b/examples/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt new file mode 100644 index 0000000000..1eacae8a3b --- /dev/null +++ b/examples/kotlin/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&sslmode=disable" + return DriverManager.getConnection(url) + } + + fun getConnection(): Connection { + return getConnection(testDb) + } +} \ No newline at end of file diff --git a/examples/kotlin/src/test/kotlin/com/example/dbtest/DbTestExtension.kt b/examples/kotlin/src/test/kotlin/com/example/dbtest/PostgresDbTestExtension.kt similarity index 90% rename from examples/kotlin/src/test/kotlin/com/example/dbtest/DbTestExtension.kt rename to examples/kotlin/src/test/kotlin/com/example/dbtest/PostgresDbTestExtension.kt index 66f831477e..55a7d6f91d 100644 --- a/examples/kotlin/src/test/kotlin/com/example/dbtest/DbTestExtension.kt +++ b/examples/kotlin/src/test/kotlin/com/example/dbtest/PostgresDbTestExtension.kt @@ -9,12 +9,14 @@ import java.sql.Connection import java.sql.DriverManager import kotlin.streams.toList -const val schema = "dinosql_test" - -class DbTestExtension(private val migrationsPath: String) : BeforeEachCallback, AfterEachCallback { +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" diff --git a/examples/kotlin/src/test/kotlin/com/example/ondeck/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt similarity index 86% rename from examples/kotlin/src/test/kotlin/com/example/ondeck/QueriesImplTest.kt rename to examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt index dab8687f99..621e979097 100644 --- a/examples/kotlin/src/test/kotlin/com/example/ondeck/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt @@ -1,13 +1,13 @@ -package com.example.ondeck +package com.example.ondeck.postgresql -import com.example.dbtest.DbTestExtension +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 = DbTestExtension("src/main/resources/ondeck/schema") + @JvmField @RegisterExtension val dbtest = PostgresDbTestExtension("src/main/resources/ondeck/postgresql/schema") } @Test diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index 6bb26fa3e2..3c14e3e43c 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -141,7 +141,9 @@ func Generate(e Env, dir string, stderr io.Writer) (map[string]string, error) { if sql.Gen.Go != nil { name = combo.Go.Package } else if sql.Gen.Kotlin != nil { - parseOpts.UsePositionalParameters = true + if sql.Engine == config.EnginePostgreSQL { + parseOpts.UsePositionalParameters = true + } name = combo.Kotlin.Package } diff --git a/internal/codegen/kotlin/gen.go b/internal/codegen/kotlin/gen.go index a010ddd387..020f354720 100644 --- a/internal/codegen/kotlin/gen.go +++ b/internal/codegen/kotlin/gen.go @@ -622,6 +622,9 @@ interface Queries { {{- if eq .Cmd ":execrows"}} fun {{.MethodName}}({{.Arg.Args}}): Int {{- end}} + {{- if eq .Cmd ":execresult"}} + fun {{.MethodName}}({{.Arg.Args}}): Long + {{- end}} {{end}} } ` @@ -755,6 +758,26 @@ class QueriesImpl(private val conn: Connection) : Queries { } } {{end}} + +{{if eq .Cmd ":execresult"}} +{{range .Comments}}//{{.}} +{{end}} + @Throws(SQLException::class) + {{ if $.EmitInterface }}override {{ end -}} + override fun {{.MethodName}}({{.Arg.Args}}): Long { + return conn.prepareStatement({{.ConstantName}}, Statement.RETURN_GENERATED_KEYS).use { stmt -> + {{ .Arg.Bindings }} + + stmt.execute() + + val results = stmt.generatedKeys + if (!results.next()) { + throw SQLException("no generated key returned") + } + results.getLong(1) + } + } +{{end}} {{end}} } ` diff --git a/internal/codegen/kotlin/imports.go b/internal/codegen/kotlin/imports.go index 6480a1f5bd..62c7a5ec0d 100644 --- a/internal/codegen/kotlin/imports.go +++ b/internal/codegen/kotlin/imports.go @@ -92,6 +92,7 @@ func (i *importer) modelImports() [][]string { func stdImports(uses func(name string) bool) map[string]struct{} { std := map[string]struct{}{ "java.sql.SQLException": {}, + "java.sql.Statement": {}, } if uses("LocalDate") { std["java.time.LocalDate"] = struct{}{} diff --git a/internal/endtoend/endtoend_test.go b/internal/endtoend/endtoend_test.go index cd82dfb69b..b112d62a65 100644 --- a/internal/endtoend/endtoend_test.go +++ b/internal/endtoend/endtoend_test.go @@ -115,6 +115,9 @@ func cmpDirectory(t *testing.T, dir string, actual map[string]string) { if !strings.HasSuffix(path, ".go") && !strings.HasSuffix(path, ".kt") { return nil } + if strings.Contains(path, "/kotlin/build") { + return nil + } if strings.HasSuffix(path, "_test.go") || strings.Contains(path, "src/test/") { return nil } From 80206627b180c766bdaf939fca4c7fa69e7dd416 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Mon, 9 Nov 2020 20:54:34 -0500 Subject: [PATCH 2/3] kotlin: support mysql types and adds MySQL tests for all existing examples --- examples/kotlin/sqlc.json | 26 +- .../authors/{ => postgresql}/Models.kt | 2 +- .../authors/{ => postgresql}/Queries.kt | 2 +- .../authors/{ => postgresql}/QueriesImpl.kt | 2 +- .../com/example/booktest/mysql/Models.kt | 32 ++ .../com/example/booktest/mysql/Queries.kt | 56 ++++ .../com/example/booktest/mysql/QueriesImpl.kt | 276 ++++++++++++++++++ .../kotlin/com/example/ondeck/mysql/Models.kt | 38 +++ .../com/example/ondeck/mysql/Queries.kt | 50 ++++ .../com/example/ondeck/mysql/QueriesImpl.kt | 276 ++++++++++++++++++ .../authors/postgresql/QueriesImplTest.kt | 2 - .../example/booktest/mysql/QueriesImplTest.kt | 98 +++++++ .../example/dbtest/MysqlDbTestExtension.kt | 2 +- .../example/ondeck/mysql/QueriesImplTest.kt | 50 ++++ examples/ondeck/mysql/city.sql.go | 8 +- examples/ondeck/mysql/db_test.go | 2 +- examples/ondeck/mysql/querier.go | 2 +- examples/ondeck/mysql/query/city.sql | 2 +- internal/codegen/kotlin/gen.go | 134 +++------ internal/codegen/kotlin/imports.go | 12 +- internal/codegen/kotlin/mysql_type.go | 74 +++++ internal/codegen/kotlin/postgresql_type.go | 108 +++++++ 22 files changed, 1137 insertions(+), 117 deletions(-) rename examples/kotlin/src/main/kotlin/com/example/authors/{ => postgresql}/Models.kt (75%) rename examples/kotlin/src/main/kotlin/com/example/authors/{ => postgresql}/Queries.kt (91%) rename examples/kotlin/src/main/kotlin/com/example/authors/{ => postgresql}/QueriesImpl.kt (98%) create mode 100644 examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Models.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt create mode 100644 examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt create mode 100644 examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt create mode 100644 examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt create mode 100644 internal/codegen/kotlin/mysql_type.go create mode 100644 internal/codegen/kotlin/postgresql_type.go diff --git a/examples/kotlin/sqlc.json b/examples/kotlin/sqlc.json index ddd52d572e..a03ef85b2c 100644 --- a/examples/kotlin/sqlc.json +++ b/examples/kotlin/sqlc.json @@ -7,8 +7,8 @@ "engine": "postgresql", "gen": { "kotlin": { - "out": "src/main/kotlin/com/example/authors", - "package": "com.example.authors" + "out": "src/main/kotlin/com/example/authors/postgresql", + "package": "com.example.authors.postgresql" } } }, @@ -55,6 +55,28 @@ "package": "com.example.authors.mysql" } } + }, + { + "schema": "src/main/resources/booktest/mysql/schema.sql", + "queries": "src/main/resources/booktest/mysql/query.sql", + "engine": "mysql:beta", + "gen": { + "kotlin": { + "out": "src/main/kotlin/com/example/booktest/mysql", + "package": "com.example.booktest.mysql" + } + } + }, + { + "schema": "src/main/resources/ondeck/mysql/schema", + "queries": "src/main/resources/ondeck/mysql/query", + "engine": "mysql:beta", + "gen": { + "kotlin": { + "out": "src/main/kotlin/com/example/ondeck/mysql", + "package": "com.example.ondeck.mysql" + } + } } ] } diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/Models.kt b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Models.kt similarity index 75% rename from examples/kotlin/src/main/kotlin/com/example/authors/Models.kt rename to examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Models.kt index 916a88d7c8..c919f666e5 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/Models.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Models.kt @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. -package com.example.authors +package com.example.authors.postgresql data class Author ( val id: Long, diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt similarity index 91% rename from examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt rename to examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt index fc9343998a..0578a492d3 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. -package com.example.authors +package com.example.authors.postgresql import java.sql.Connection import java.sql.SQLException diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt similarity index 98% rename from examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt rename to examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt index 194b0391fa..d26a38cf8d 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. -package com.example.authors +package com.example.authors.postgresql import java.sql.Connection import java.sql.SQLException diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Models.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Models.kt new file mode 100644 index 0000000000..ba78981fbc --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Models.kt @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. + +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/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt new file mode 100644 index 0000000000..a902badd71 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt @@ -0,0 +1,56 @@ +// Code generated by sqlc. DO NOT EDIT. + +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/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt new file mode 100644 index 0000000000..4ed92de8a4 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt @@ -0,0 +1,276 @@ +// Code generated by sqlc. DO NOT EDIT. + +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()) { + 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) + override fun getBook(bookId: Int): Book { + return conn.prepareStatement(getBook).use { stmt -> + stmt.setInt(1, bookId) + + val results = stmt.executeQuery() + if (!results.next()) { + throw SQLException("no rows in result set") + } + 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/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt new file mode 100644 index 0000000000..5bbfd046ce --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Models.kt @@ -0,0 +1,38 @@ +// Code generated by sqlc. DO NOT EDIT. + +package com.example.ondeck.mysql + +import java.sql.Timestamp +import java.time.Instant + +enum class VenuesStatus(val value: String) { + OPEN("open"), + CLOSED("closed"); + + companion object { + private val map = VenuesStatus.values().associateBy(VenuesStatus::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: VenuesStatus, + 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/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt new file mode 100644 index 0000000000..6048a1d271 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt @@ -0,0 +1,50 @@ +// Code generated by sqlc. DO NOT EDIT. + +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: VenuesStatus, + 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/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt new file mode 100644 index 0000000000..d953d197e2 --- /dev/null +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt @@ -0,0 +1,276 @@ +// Code generated by sqlc. DO NOT EDIT. + +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: VenuesStatus, + 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()) { + 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(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()) { + throw SQLException("no rows in result set") + } + val ret = Venue( + results.getLong(1), + VenuesStatus.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), + VenuesStatus.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/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt index 38c7a46fbd..9cb8d9687b 100644 --- a/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt @@ -1,7 +1,5 @@ package com.example.authors.postgresql -import com.example.authors.Author -import com.example.authors.QueriesImpl import com.example.dbtest.PostgresDbTestExtension import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test diff --git a/examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt new file mode 100644 index 0000000000..d02b7f0509 --- /dev/null +++ b/examples/kotlin/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/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt b/examples/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt index 1eacae8a3b..243a441476 100644 --- a/examples/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt +++ b/examples/kotlin/src/test/kotlin/com/example/dbtest/MysqlDbTestExtension.kt @@ -36,7 +36,7 @@ class MysqlDbTestExtension(private val migrationsPath: String) : BeforeEachCallb } private fun getConnection(db: String): Connection { - val url = "jdbc:mysql://$host:$port/$db?user=$user&password=$pass&sslmode=disable" + val url = "jdbc:mysql://$host:$port/$db?user=$user&password=$pass&allowMultiQueries=true" return DriverManager.getConnection(url) } diff --git a/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt new file mode 100644 index 0000000000..6f1b61597d --- /dev/null +++ b/examples/kotlin/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 = VenuesStatus.OPEN, + statuses = listOf(VenuesStatus.OPEN, VenuesStatus.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/ondeck/mysql/city.sql.go b/examples/ondeck/mysql/city.sql.go index f6ae1ea873..fa7872b273 100644 --- a/examples/ondeck/mysql/city.sql.go +++ b/examples/ondeck/mysql/city.sql.go @@ -5,10 +5,9 @@ package ondeck import ( "context" - "database/sql" ) -const createCity = `-- name: CreateCity :execresult +const createCity = `-- name: CreateCity :exec INSERT INTO city ( name, slug @@ -23,8 +22,9 @@ type CreateCityParams struct { Slug string `json:"slug"` } -func (q *Queries) CreateCity(ctx context.Context, arg CreateCityParams) (sql.Result, error) { - return q.exec(ctx, q.createCityStmt, createCity, arg.Name, arg.Slug) +func (q *Queries) CreateCity(ctx context.Context, arg CreateCityParams) error { + _, err := q.exec(ctx, q.createCityStmt, createCity, arg.Name, arg.Slug) + return err } const getCity = `-- name: GetCity :one diff --git a/examples/ondeck/mysql/db_test.go b/examples/ondeck/mysql/db_test.go index 76912f1ce3..cd4d215c65 100644 --- a/examples/ondeck/mysql/db_test.go +++ b/examples/ondeck/mysql/db_test.go @@ -26,7 +26,7 @@ func join(vals ...string) sql.NullString { func runOnDeckQueries(t *testing.T, q *Queries) { ctx := context.Background() - _, err := q.CreateCity(ctx, CreateCityParams{ + err := q.CreateCity(ctx, CreateCityParams{ Slug: "san-francisco", Name: "San Francisco", }) diff --git a/examples/ondeck/mysql/querier.go b/examples/ondeck/mysql/querier.go index 60e8f0daa0..a307753262 100644 --- a/examples/ondeck/mysql/querier.go +++ b/examples/ondeck/mysql/querier.go @@ -8,7 +8,7 @@ import ( ) type Querier interface { - CreateCity(ctx context.Context, arg CreateCityParams) (sql.Result, error) + CreateCity(ctx context.Context, arg CreateCityParams) error CreateVenue(ctx context.Context, arg CreateVenueParams) (sql.Result, error) DeleteVenue(ctx context.Context, arg DeleteVenueParams) error GetCity(ctx context.Context, slug string) (City, error) diff --git a/examples/ondeck/mysql/query/city.sql b/examples/ondeck/mysql/query/city.sql index 275f4f2e68..c387e9d000 100644 --- a/examples/ondeck/mysql/query/city.sql +++ b/examples/ondeck/mysql/query/city.sql @@ -8,7 +8,7 @@ SELECT * FROM city WHERE slug = ?; -/* name: CreateCity :execresult */ +/* name: CreateCity :exec */ INSERT INTO city ( name, slug diff --git a/internal/codegen/kotlin/gen.go b/internal/codegen/kotlin/gen.go index 020f354720..665f09ff4d 100644 --- a/internal/codegen/kotlin/gen.go +++ b/internal/codegen/kotlin/gen.go @@ -4,7 +4,6 @@ import ( "bufio" "bytes" "fmt" - "log" "regexp" "sort" "strings" @@ -93,7 +92,11 @@ func jdbcSet(t ktType, idx int, name string) string { return fmt.Sprintf(`stmt.setArray(%d, conn.createArrayOf("%s", %s.map { v -> v.value }.toTypedArray()))`, idx, t.DataType, name) } if t.IsEnum { - return fmt.Sprintf("stmt.setObject(%d, %s.value, %s)", idx, name, "Types.OTHER") + if t.Engine == config.EnginePostgreSQL { + return fmt.Sprintf("stmt.setObject(%d, %s.value, %s)", idx, name, "Types.OTHER") + } else { + return fmt.Sprintf("stmt.setString(%d, %s.value)", idx, name) + } } if t.IsArray { return fmt.Sprintf(`stmt.setArray(%d, conn.createArrayOf("%s", %s.toTypedArray()))`, idx, t.DataType, name) @@ -101,6 +104,9 @@ func jdbcSet(t ktType, idx int, name string) string { if t.IsTime() { return fmt.Sprintf("stmt.setObject(%d, %s)", idx, name) } + if t.IsInstant() { + return fmt.Sprintf("stmt.setTimestamp(%d, Timestamp.from(%s))", idx, name) + } return fmt.Sprintf("stmt.set%s(%d, %s)", t.Name, idx, name) } @@ -150,6 +156,9 @@ func jdbcGet(t ktType, idx int) string { if t.IsTime() { return fmt.Sprintf(`results.getObject(%d, %s::class.java)`, idx, t.Name) } + if t.IsInstant() { + return fmt.Sprintf(`results.getTimestamp(%d).toInstant()`, idx) + } return fmt.Sprintf(`results.get%s(%d)`, t.Name, idx) } @@ -303,6 +312,7 @@ type ktType struct { IsArray bool IsNull bool DataType string + Engine config.Engine } func (t ktType) String() string { @@ -326,6 +336,9 @@ func (t ktType) jdbcType() string { if t.IsEnum || t.IsTime() { return "Object" } + if t.IsInstant() { + return "Timestamp" + } return t.Name } @@ -333,6 +346,10 @@ func (t ktType) IsTime() bool { return t.Name == "LocalDate" || t.Name == "LocalDateTime" || t.Name == "LocalTime" || t.Name == "OffsetDateTime" } +func (t ktType) IsInstant() bool { + return t.Name == "Instant" +} + func makeType(r *compiler.Result, col *compiler.Column, settings config.CombinedSettings) ktType { typ, isEnum := ktInnerType(r, col, settings) return ktType{ @@ -341,105 +358,19 @@ func makeType(r *compiler.Result, col *compiler.Column, settings config.Combined IsArray: col.IsArray, IsNull: !col.NotNull, DataType: col.DataType, + Engine: settings.Package.Engine, } } func ktInnerType(r *compiler.Result, col *compiler.Column, settings config.CombinedSettings) (string, bool) { - columnType := col.DataType - - switch columnType { - case "serial", "pg_catalog.serial4": - return "Int", false - - case "bigserial", "pg_catalog.serial8": - return "Long", false - - case "smallserial", "pg_catalog.serial2": - return "Short", false - - case "integer", "int", "int4", "pg_catalog.int4": - return "Int", false - - case "bigint", "pg_catalog.int8": - return "Long", false - - case "smallint", "pg_catalog.int2": - return "Short", false - - case "float", "double precision", "pg_catalog.float8": - return "Double", false - - case "real", "pg_catalog.float4": - return "Float", false - - case "pg_catalog.numeric": - return "java.math.BigDecimal", false - - case "bool", "pg_catalog.bool": - return "Boolean", false - - case "jsonb": - // TODO: support json and byte types - return "String", false - - case "bytea", "blob", "pg_catalog.bytea": - return "String", false - - case "date": - // Date and time mappings from https://jdbc.postgresql.org/documentation/head/java8-date-time.html - return "LocalDate", false - - case "pg_catalog.time", "pg_catalog.timetz": - return "LocalTime", false - - case "pg_catalog.timestamp": - return "LocalDateTime", false - - case "pg_catalog.timestamptz", "timestamptz": - // TODO - return "OffsetDateTime", false - - case "text", "pg_catalog.varchar", "pg_catalog.bpchar", "string": - return "String", false - - case "uuid": - // TODO - return "uuid.UUID", false - - case "inet": - // TODO - return "net.IP", false - - case "void": - // TODO - // A void value always returns NULL. Since there is no built-in NULL - // value into the SQL package, we'll use sql.NullBool - return "sql.NullBool", false - - case "any": - // TODO - return "Any", false - + // TODO: Extend the engine interface to handle types + switch settings.Package.Engine { + case config.EngineMySQL, config.EngineMySQLBeta: + return mysqlType(r, col, settings) + case config.EnginePostgreSQL: + return postgresType(r, col, settings) default: - for _, schema := range r.Catalog.Schemas { - if schema.Name == "pg_catalog" { - continue - } - for _, typ := range schema.Types { - enum, ok := typ.(*catalog.Enum) - if !ok { - continue - } - if columnType == enum.Name { - if schema.Name == r.Catalog.DefaultSchema { - return DataClassName(enum.Name, settings), true - } - return DataClassName(schema.Name+"_"+enum.Name, settings), true - } - } - } - log.Printf("unknown PostgreSQL type: %s\n", columnType) - return "interface{}", false + return "Any", false } } @@ -501,13 +432,16 @@ func ktColumnName(c *compiler.Column, pos int) string { return fmt.Sprintf("column_%d", pos+1) } -var jdbcSQLRe = regexp.MustCompile(`\B\$\d+\b`) +var postgresPlaceholderRegexp = regexp.MustCompile(`\B\$\d+\b`) // HACK: jdbc doesn't support numbered parameters, so we need to transform them to question marks... // But there's no access to the SQL parser here, so we just do a dumb regexp replace instead. This won't work if // the literal strings contain matching values, but good enough for a prototype. -func jdbcSQL(s string) string { - return jdbcSQLRe.ReplaceAllString(s, "?") +func jdbcSQL(s string, engine config.Engine) string { + if engine == config.EnginePostgreSQL { + return postgresPlaceholderRegexp.ReplaceAllString(s, "?") + } + return s } func buildQueries(r *compiler.Result, settings config.CombinedSettings, structs []Struct) []Query { @@ -527,7 +461,7 @@ func buildQueries(r *compiler.Result, settings config.CombinedSettings, structs FieldName: codegen.LowerTitle(query.Name) + "Stmt", MethodName: codegen.LowerTitle(query.Name), SourceName: query.Filename, - SQL: jdbcSQL(query.SQL), + SQL: jdbcSQL(query.SQL, settings.Package.Engine), Comments: query.Comments, } diff --git a/internal/codegen/kotlin/imports.go b/internal/codegen/kotlin/imports.go index 62c7a5ec0d..6942d84a99 100644 --- a/internal/codegen/kotlin/imports.go +++ b/internal/codegen/kotlin/imports.go @@ -67,6 +67,10 @@ func (i *importer) interfaceImports() [][]string { func (i *importer) modelImports() [][]string { std := make(map[string]struct{}) + if i.usesType("Instant") { + std["java.time.Instant"] = struct{}{} + std["java.sql.Timestamp"] = struct{}{} + } if i.usesType("LocalDate") { std["java.time.LocalDate"] = struct{}{} } @@ -92,7 +96,11 @@ func (i *importer) modelImports() [][]string { func stdImports(uses func(name string) bool) map[string]struct{} { std := map[string]struct{}{ "java.sql.SQLException": {}, - "java.sql.Statement": {}, + "java.sql.Statement": {}, + } + if uses("Instant") { + std["java.time.Instant"] = struct{}{} + std["java.sql.Timestamp"] = struct{}{} } if uses("LocalDate") { std["java.time.LocalDate"] = struct{}{} @@ -150,7 +158,7 @@ func (i *importer) queryImports(filename string) [][]string { std := stdImports(uses) std["java.sql.Connection"] = struct{}{} - if hasEnum() { + if hasEnum() && i.Settings.Package.Engine == config.EnginePostgreSQL { std["java.sql.Types"] = struct{}{} } diff --git a/internal/codegen/kotlin/mysql_type.go b/internal/codegen/kotlin/mysql_type.go new file mode 100644 index 0000000000..1aafee7ce6 --- /dev/null +++ b/internal/codegen/kotlin/mysql_type.go @@ -0,0 +1,74 @@ +package kotlin + +import ( + "log" + + "github.com/kyleconroy/sqlc/internal/compiler" + "github.com/kyleconroy/sqlc/internal/config" + "github.com/kyleconroy/sqlc/internal/debug" + "github.com/kyleconroy/sqlc/internal/sql/catalog" +) + +func mysqlType(r *compiler.Result, col *compiler.Column, settings config.CombinedSettings) (string, bool) { + columnType := col.DataType + + switch columnType { + + case "varchar", "text", "char", "tinytext", "mediumtext", "longtext": + return "String", false + + case "int", "integer", "smallint", "mediumint", "year": + return "Int", false + + case "bigint": + return "Long", false + + case "blob", "binary", "varbinary", "tinyblob", "mediumblob", "longblob": + return "String", false + + case "double", "double precision", "real": + return "Double", false + + case "decimal", "dec", "fixed": + return "String", false + + case "enum": + // TODO: Proper Enum support + return "String", false + + case "date", "datetime", "time": + return "LocalDateTime", false + + case "timestamp": + return "Instant", false + + case "boolean", "bool", "tinyint": + return "Boolean", false + + case "json": + return "String", false + + case "any": + return "Any", false + + default: + for _, schema := range r.Catalog.Schemas { + for _, typ := range schema.Types { + switch t := typ.(type) { + case *catalog.Enum: + if t.Name == columnType { + if schema.Name == r.Catalog.DefaultSchema { + return DataClassName(t.Name, settings), true + } + return DataClassName(schema.Name+"_"+t.Name, settings), true + } + } + } + } + if debug.Active { + log.Printf("Unknown MySQL type: %s\n", columnType) + } + return "Any", false + + } +} diff --git a/internal/codegen/kotlin/postgresql_type.go b/internal/codegen/kotlin/postgresql_type.go new file mode 100644 index 0000000000..4c2dee1d68 --- /dev/null +++ b/internal/codegen/kotlin/postgresql_type.go @@ -0,0 +1,108 @@ +package kotlin + +import ( + "log" + + "github.com/kyleconroy/sqlc/internal/compiler" + "github.com/kyleconroy/sqlc/internal/config" + "github.com/kyleconroy/sqlc/internal/sql/catalog" +) + +func postgresType(r *compiler.Result, col *compiler.Column, settings config.CombinedSettings) (string, bool) { + columnType := col.DataType + + switch columnType { + case "serial", "pg_catalog.serial4": + return "Int", false + + case "bigserial", "pg_catalog.serial8": + return "Long", false + + case "smallserial", "pg_catalog.serial2": + return "Short", false + + case "integer", "int", "int4", "pg_catalog.int4": + return "Int", false + + case "bigint", "pg_catalog.int8": + return "Long", false + + case "smallint", "pg_catalog.int2": + return "Short", false + + case "float", "double precision", "pg_catalog.float8": + return "Double", false + + case "real", "pg_catalog.float4": + return "Float", false + + case "pg_catalog.numeric": + return "java.math.BigDecimal", false + + case "bool", "pg_catalog.bool": + return "Boolean", false + + case "jsonb": + // TODO: support json and byte types + return "String", false + + case "bytea", "blob", "pg_catalog.bytea": + return "String", false + + case "date": + // Date and time mappings from https://jdbc.postgresql.org/documentation/head/java8-date-time.html + return "LocalDate", false + + case "pg_catalog.time", "pg_catalog.timetz": + return "LocalTime", false + + case "pg_catalog.timestamp": + return "LocalDateTime", false + + case "pg_catalog.timestamptz", "timestamptz": + // TODO + return "OffsetDateTime", false + + case "text", "pg_catalog.varchar", "pg_catalog.bpchar", "string": + return "String", false + + case "uuid": + // TODO + return "uuid.UUID", false + + case "inet": + // TODO + return "net.IP", false + + case "void": + // TODO + // A void value always returns NULL. Since there is no built-in NULL + // value into the SQL package, we'll use sql.NullBool + return "sql.NullBool", false + + case "any": + // TODO + return "Any", false + + default: + for _, schema := range r.Catalog.Schemas { + if schema.Name == "pg_catalog" { + continue + } + for _, typ := range schema.Types { + enum, ok := typ.(*catalog.Enum) + if !ok { + continue + } + if columnType == enum.Name { + if schema.Name == r.Catalog.DefaultSchema { + return DataClassName(enum.Name, settings), true + } + return DataClassName(schema.Name+"_"+enum.Name, settings), true + } + } + } + log.Printf("unknown PostgreSQL type: %s\n", columnType) + return "Any", false + } +} From 0b85966bb14c3c648fe626b5fd12d32977efef04 Mon Sep 17 00:00:00 2001 From: Yunchi Luo Date: Fri, 13 Nov 2020 12:13:41 -0500 Subject: [PATCH 3/3] kotlin: change :one to rely on null assertions --- .../com/example/authors/mysql/Queries.kt | 2 +- .../com/example/authors/mysql/QueriesImpl.kt | 4 ++-- .../com/example/authors/postgresql/Queries.kt | 4 ++-- .../example/authors/postgresql/QueriesImpl.kt | 8 ++++---- .../com/example/booktest/mysql/Queries.kt | 4 ++-- .../com/example/booktest/mysql/QueriesImpl.kt | 8 ++++---- .../example/booktest/postgresql/Queries.kt | 8 ++++---- .../booktest/postgresql/QueriesImpl.kt | 16 +++++++-------- .../main/kotlin/com/example/jets/Queries.kt | 2 +- .../kotlin/com/example/jets/QueriesImpl.kt | 4 ++-- .../com/example/ondeck/mysql/Queries.kt | 4 ++-- .../com/example/ondeck/mysql/QueriesImpl.kt | 8 ++++---- .../com/example/ondeck/postgresql/Queries.kt | 10 +++++----- .../example/ondeck/postgresql/QueriesImpl.kt | 20 +++++++++---------- .../authors/postgresql/QueriesImplTest.kt | 4 ++-- .../example/booktest/mysql/QueriesImplTest.kt | 4 ++-- .../booktest/postgresql/QueriesImplTest.kt | 8 ++++---- .../example/ondeck/mysql/QueriesImplTest.kt | 4 ++-- .../ondeck/postgresql/QueriesImplTest.kt | 4 ++-- internal/codegen/kotlin/gen.go | 6 +++--- 20 files changed, 66 insertions(+), 66 deletions(-) diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt index bd9bddb084..0eaada1617 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/Queries.kt @@ -14,7 +14,7 @@ interface Queries { fun deleteAuthor(id: Long) @Throws(SQLException::class) - fun getAuthor(id: Long): Author + fun getAuthor(id: Long): Author? @Throws(SQLException::class) fun listAuthors(): List diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt index 0112297d4d..e8efb7c0bb 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/mysql/QueriesImpl.kt @@ -57,13 +57,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getAuthor(id: Long): Author { + override fun getAuthor(id: Long): Author? { return conn.prepareStatement(getAuthor).use { stmt -> stmt.setLong(1, id) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Author( results.getLong(1), diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt index 0578a492d3..6598aabeda 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/Queries.kt @@ -8,13 +8,13 @@ import java.sql.Statement interface Queries { @Throws(SQLException::class) - fun createAuthor(name: String, bio: String?): Author + fun createAuthor(name: String, bio: String?): Author? @Throws(SQLException::class) fun deleteAuthor(id: Long) @Throws(SQLException::class) - fun getAuthor(id: Long): Author + fun getAuthor(id: Long): Author? @Throws(SQLException::class) fun listAuthors(): List diff --git a/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt index d26a38cf8d..a9a813d082 100644 --- a/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/authors/postgresql/QueriesImpl.kt @@ -33,14 +33,14 @@ ORDER BY name class QueriesImpl(private val conn: Connection) : Queries { @Throws(SQLException::class) - override fun createAuthor(name: String, bio: String?): Author { + 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()) { - throw SQLException("no rows in result set") + return null } val ret = Author( results.getLong(1), @@ -64,13 +64,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getAuthor(id: Long): Author { + override fun getAuthor(id: Long): Author? { return conn.prepareStatement(getAuthor).use { stmt -> stmt.setLong(1, id) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Author( results.getLong(1), diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt index a902badd71..b6e712fea0 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/Queries.kt @@ -34,10 +34,10 @@ interface Queries { fun deleteBook(bookId: Int) @Throws(SQLException::class) - fun getAuthor(authorId: Int): Author + fun getAuthor(authorId: Int): Author? @Throws(SQLException::class) - fun getBook(bookId: Int): Book + fun getBook(bookId: Int): Book? @Throws(SQLException::class) fun updateBook( diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt index 4ed92de8a4..e1012d8c42 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/mysql/QueriesImpl.kt @@ -197,13 +197,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getAuthor(authorId: Int): Author { + override fun getAuthor(authorId: Int): Author? { return conn.prepareStatement(getAuthor).use { stmt -> stmt.setInt(1, authorId) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Author( results.getInt(1), @@ -217,13 +217,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getBook(bookId: Int): Book { + override fun getBook(bookId: Int): Book? { return conn.prepareStatement(getBook).use { stmt -> stmt.setInt(1, bookId) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Book( results.getInt(1), diff --git a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt index 41d67eaf08..4416dbd24c 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/Queries.kt @@ -16,7 +16,7 @@ interface Queries { fun booksByTitleYear(title: String, year: Int): List @Throws(SQLException::class) - fun createAuthor(name: String): Author + fun createAuthor(name: String): Author? @Throws(SQLException::class) fun createBook( @@ -26,16 +26,16 @@ interface Queries { title: String, year: Int, available: OffsetDateTime, - tags: List): Book + tags: List): Book? @Throws(SQLException::class) fun deleteBook(bookId: Int) @Throws(SQLException::class) - fun getAuthor(authorId: Int): Author + fun getAuthor(authorId: Int): Author? @Throws(SQLException::class) - fun getBook(bookId: Int): Book + fun getBook(bookId: Int): Book? @Throws(SQLException::class) fun updateBook( 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 index 19c5bf8033..f65e6def25 100644 --- a/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/booktest/postgresql/QueriesImpl.kt @@ -133,13 +133,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun createAuthor(name: String): Author { + override fun createAuthor(name: String): Author? { return conn.prepareStatement(createAuthor).use { stmt -> stmt.setString(1, name) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Author( results.getInt(1), @@ -160,7 +160,7 @@ class QueriesImpl(private val conn: Connection) : Queries { title: String, year: Int, available: OffsetDateTime, - tags: List): Book { + tags: List): Book? { return conn.prepareStatement(createBook).use { stmt -> stmt.setInt(1, authorId) stmt.setString(2, isbn) @@ -172,7 +172,7 @@ class QueriesImpl(private val conn: Connection) : Queries { val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Book( results.getInt(1), @@ -201,13 +201,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getAuthor(authorId: Int): Author { + override fun getAuthor(authorId: Int): Author? { return conn.prepareStatement(getAuthor).use { stmt -> stmt.setInt(1, authorId) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Author( results.getInt(1), @@ -221,13 +221,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getBook(bookId: Int): Book { + override fun getBook(bookId: Int): Book? { return conn.prepareStatement(getBook).use { stmt -> stmt.setInt(1, bookId) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = Book( results.getInt(1), diff --git a/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt index 6371a1fbc6..7f53ec868d 100644 --- a/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/jets/Queries.kt @@ -8,7 +8,7 @@ import java.sql.Statement interface Queries { @Throws(SQLException::class) - fun countPilots(): Long + fun countPilots(): Long? @Throws(SQLException::class) fun deletePilot(id: 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 index 68b0278ab2..717beb6082 100644 --- a/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/jets/QueriesImpl.kt @@ -21,12 +21,12 @@ SELECT id, name FROM pilots LIMIT 5 class QueriesImpl(private val conn: Connection) : Queries { @Throws(SQLException::class) - override fun countPilots(): Long { + override fun countPilots(): Long? { return conn.prepareStatement(countPilots).use { stmt -> val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = results.getLong(1) if (results.next()) { diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt index 6048a1d271..005299ea8a 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/Queries.kt @@ -26,10 +26,10 @@ interface Queries { fun deleteVenue(slug: String, slug_2: String) @Throws(SQLException::class) - fun getCity(slug: String): City + fun getCity(slug: String): City? @Throws(SQLException::class) - fun getVenue(slug: String, city: String): Venue + fun getVenue(slug: String, city: String): Venue? @Throws(SQLException::class) fun listCities(): List diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt index d953d197e2..a30b7564c5 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/mysql/QueriesImpl.kt @@ -147,13 +147,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getCity(slug: String): City { + override fun getCity(slug: String): City? { return conn.prepareStatement(getCity).use { stmt -> stmt.setString(1, slug) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = City( results.getString(1), @@ -167,14 +167,14 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getVenue(slug: String, city: String): Venue { + 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()) { - throw SQLException("no rows in result set") + return null } val ret = Venue( results.getLong(1), diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt index ddbe5d79b8..31bde13c9c 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/Queries.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime interface Queries { @Throws(SQLException::class) - fun createCity(name: String, slug: String): City + fun createCity(name: String, slug: String): City? @Throws(SQLException::class) fun createVenue( @@ -20,16 +20,16 @@ interface Queries { spotifyPlaylist: String, status: Status, statuses: List, - tags: List): Int + tags: List): Int? @Throws(SQLException::class) fun deleteVenue(slug: String) @Throws(SQLException::class) - fun getCity(slug: String): City + fun getCity(slug: String): City? @Throws(SQLException::class) - fun getVenue(slug: String, city: String): Venue + fun getVenue(slug: String, city: String): Venue? @Throws(SQLException::class) fun listCities(): List @@ -41,7 +41,7 @@ interface Queries { fun updateCityName(name: String, slug: String) @Throws(SQLException::class) - fun updateVenueName(name: String, slug: String): Int + fun updateVenueName(name: String, slug: String): Int? @Throws(SQLException::class) fun venueCountByCity(): List diff --git a/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt index 7b79d5dacd..4c4e07589d 100644 --- a/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt +++ b/examples/kotlin/src/main/kotlin/com/example/ondeck/postgresql/QueriesImpl.kt @@ -104,14 +104,14 @@ class QueriesImpl(private val conn: Connection) : Queries { // This is the third line @Throws(SQLException::class) - override fun createCity(name: String, slug: String): City { + 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()) { - throw SQLException("no rows in result set") + return null } val ret = City( results.getString(1), @@ -132,7 +132,7 @@ class QueriesImpl(private val conn: Connection) : Queries { spotifyPlaylist: String, status: Status, statuses: List, - tags: List): Int { + tags: List): Int? { return conn.prepareStatement(createVenue).use { stmt -> stmt.setString(1, slug) stmt.setString(2, name) @@ -144,7 +144,7 @@ class QueriesImpl(private val conn: Connection) : Queries { val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = results.getInt(1) if (results.next()) { @@ -165,13 +165,13 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getCity(slug: String): City { + override fun getCity(slug: String): City? { return conn.prepareStatement(getCity).use { stmt -> stmt.setString(1, slug) val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = City( results.getString(1), @@ -185,14 +185,14 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun getVenue(slug: String, city: String): Venue { + 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()) { - throw SQLException("no rows in result set") + return null } val ret = Venue( results.getInt(1), @@ -265,14 +265,14 @@ class QueriesImpl(private val conn: Connection) : Queries { } @Throws(SQLException::class) - override fun updateVenueName(name: String, slug: String): Int { + 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()) { - throw SQLException("no rows in result set") + return null } val ret = results.getInt(1) if (results.next()) { diff --git a/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt index 9cb8d9687b..86c24fb68c 100644 --- a/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/authors/postgresql/QueriesImplTest.kt @@ -25,7 +25,7 @@ class QueriesImplTest() { val insertedAuthor = db.createAuthor( name = name, bio = bio - ) + )!! val expectedAuthor = Author(insertedAuthor.id, name, bio) Assertions.assertEquals(expectedAuthor, insertedAuthor) @@ -46,7 +46,7 @@ class QueriesImplTest() { val name = "Brian Kernighan" val bio = null - val insertedAuthor = db.createAuthor(name, bio) + val insertedAuthor = db.createAuthor(name, bio)!! val expectedAuthor = Author(insertedAuthor.id, name, bio) Assertions.assertEquals(expectedAuthor, insertedAuthor) diff --git a/examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt index d02b7f0509..f69cd85e12 100644 --- a/examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/booktest/mysql/QueriesImplTest.kt @@ -18,7 +18,7 @@ class QueriesImplTest { val conn = dbtest.getConnection() val db = QueriesImpl(conn) val authorId = db.createAuthor("Unknown Master") - val author = db.getAuthor(authorId.toInt()) + val author = db.getAuthor(authorId.toInt())!! // Start a transaction conn.autoCommit = false @@ -84,7 +84,7 @@ class QueriesImplTest { 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) + val author2 = db.getAuthor(book.authorId)!! println("Book ${book.bookId} author: ${author2.name}") } diff --git a/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt index 1652ebe144..1a4316123d 100644 --- a/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/booktest/postgresql/QueriesImplTest.kt @@ -15,7 +15,7 @@ class QueriesImplTest { fun testQueries() { val conn = dbtest.getConnection() val db = QueriesImpl(conn) - val author = db.createAuthor("Unknown Master") + val author = db.createAuthor("Unknown Master")!! // Start a transaction conn.autoCommit = false @@ -37,7 +37,7 @@ class QueriesImplTest { year = 2016, available = OffsetDateTime.now(), tags = listOf("cool", "unique") - ) + )!! db.updateBook( bookId = b1.bookId, @@ -53,7 +53,7 @@ class QueriesImplTest { year = 2001, available = OffsetDateTime.now(), tags = listOf("cool") - ) + )!! db.createBook( authorId = author.authorId, @@ -83,7 +83,7 @@ class QueriesImplTest { 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) + val author2 = db.getAuthor(book.authorId)!! println("Book ${book.bookId} author: ${author2.name}") } diff --git a/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt index 6f1b61597d..e946157bab 100644 --- a/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/ondeck/mysql/QueriesImplTest.kt @@ -32,7 +32,7 @@ class QueriesImplTest { val venue = q.getVenue( slug = "the-fillmore", city = city.slug - ) + )!! assertEquals(venueId, venue.id) assertEquals(city, q.getCity(city.slug)) @@ -42,7 +42,7 @@ class QueriesImplTest { q.updateCityName(slug = city.slug, name = "SF") q.updateVenueName(slug = venue.slug, name = "Fillmore") - val fresh = q.getVenue(venue.slug, city.slug) + val fresh = q.getVenue(venue.slug, city.slug)!! assertEquals("Fillmore", fresh.name) q.deleteVenue(venue.slug, venue.slug) diff --git a/examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt b/examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt index 621e979097..990f4398d1 100644 --- a/examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt +++ b/examples/kotlin/src/test/kotlin/com/example/ondeck/postgresql/QueriesImplTest.kt @@ -16,7 +16,7 @@ class QueriesImplTest { val city = q.createCity( slug = "san-francisco", name = "San Francisco" - ) + )!! val venueId = q.createVenue( slug = "the-fillmore", name = "The Fillmore", @@ -29,7 +29,7 @@ class QueriesImplTest { val venue = q.getVenue( slug = "the-fillmore", city = city.slug - ) + )!! assertEquals(venueId, venue.id) assertEquals(city, q.getCity(city.slug)) diff --git a/internal/codegen/kotlin/gen.go b/internal/codegen/kotlin/gen.go index 665f09ff4d..245fd0aba5 100644 --- a/internal/codegen/kotlin/gen.go +++ b/internal/codegen/kotlin/gen.go @@ -545,7 +545,7 @@ interface Queries { {{- range .Queries}} @Throws(SQLException::class) {{- if eq .Cmd ":one"}} - fun {{.MethodName}}({{.Arg.Args}}): {{.Ret.Type}} + fun {{.MethodName}}({{.Arg.Args}}): {{.Ret.Type}}? {{- end}} {{- if eq .Cmd ":many"}} fun {{.MethodName}}({{.Arg.Args}}): List<{{.Ret.Type}}> @@ -629,13 +629,13 @@ class QueriesImpl(private val conn: Connection) : Queries { {{range .Comments}}//{{.}} {{end}} @Throws(SQLException::class) - override fun {{.MethodName}}({{.Arg.Args}}): {{.Ret.Type}} { + override fun {{.MethodName}}({{.Arg.Args}}): {{.Ret.Type}}? { return conn.prepareStatement({{.ConstantName}}).use { stmt -> {{.Arg.Bindings}} val results = stmt.executeQuery() if (!results.next()) { - throw SQLException("no rows in result set") + return null } val ret = {{.Ret.ResultSet}} if (results.next()) {