Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,23 @@ concurrency:

jobs:
test:
name: Maven test
name: Maven test and release verification
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 20
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: quickstart
POSTGRES_USER: quickstart
POSTGRES_PASSWORD: quickstart
ports:
- 5432/tcp
options: >-
--health-cmd "pg_isready --username quickstart --dbname quickstart"
--health-interval 10s
--health-timeout 5s
--health-retries 10
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
Expand All @@ -32,4 +46,11 @@ jobs:
cache-dependency-path: pom.xml

- name: Run tests
run: mvn --batch-mode --no-transfer-progress test
run: mvn --batch-mode --no-transfer-progress -Dsqlcj.test.requireDocker=true test

- name: Verify staged artifacts with the external Maven sample
env:
SQLCJ_SAMPLE_JDBC_URL: jdbc:postgresql://localhost:${{ job.services.postgres.ports['5432'] }}/quickstart
SQLCJ_SAMPLE_DB_USER: quickstart
SQLCJ_SAMPLE_DB_PASSWORD: quickstart
run: scripts/verify-release.sh
61 changes: 61 additions & 0 deletions examples/maven-postgresql/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>sqlcj-maven-postgresql-sample</artifactId>
<version>1.0.0-SNAPSHOT</version>

<!--
An external consumer of the sqlcj artifacts. This project is not part of
the sqlcj reactor and never puts the sqlcj source tree on its classpath:
it depends only on the published runtime artifact and the PostgreSQL
driver, and the generated sources come from the packaged CLI.
-->

<properties>
<maven.compiler.release>21</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sqlcj.version>0.1.0-SNAPSHOT</sqlcj.version>
<sqlcj.generated.sources>${project.build.directory}/generated-sources/sqlcj</sqlcj.generated.sources>
</properties>

<dependencies>
<dependency>
<groupId>dev.sqlcj</groupId>
<artifactId>sqlcj-runtime</artifactId>
<version>${sqlcj.version}</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.13</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.15.0</version>
<configuration>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java</compileSourceRoot>
<compileSourceRoot>${sqlcj.generated.sources}</compileSourceRoot>
</compileSourceRoots>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.6.3</version>
<configuration>
<mainClass>com.example.app.App</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
24 changes: 24 additions & 0 deletions examples/maven-postgresql/sql/queries.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
-- name: CreateAuthor :one
INSERT INTO authors (name, bio)
VALUES ($1, $2)
RETURNING id, name, bio;

-- name: GetAuthor :one
SELECT id, name, bio
FROM authors
WHERE id = $1;

-- name: ListAuthors :many
SELECT id, name
FROM authors
ORDER BY id;

-- name: UpdateAuthorBio :exec
UPDATE authors
SET bio = $2
WHERE id = $1;

-- name: DeleteAuthor :exec
DELETE
FROM authors
WHERE id = $1;
7 changes: 7 additions & 0 deletions examples/maven-postgresql/sql/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
CREATE TABLE authors
(
id BIGSERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
bio TEXT,
created_at TIMESTAMP
);
7 changes: 7 additions & 0 deletions examples/maven-postgresql/sqlcj.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: "1"
sql:
- schema: sql/schema.sql
queries: sql/queries.sql
java:
package: com.example.app.db
out: target/generated-sources/sqlcj
190 changes: 190 additions & 0 deletions examples/maven-postgresql/src/main/java/com/example/app/App.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package com.example.app;

import com.example.app.db.CreateAuthor;
import com.example.app.db.DeleteAuthor;
import com.example.app.db.GetAuthor;
import com.example.app.db.ListAuthors;
import com.example.app.db.UpdateAuthorBio;
import dev.sqlcj.runtime.JdbcQueryExecutor;
import dev.sqlcj.runtime.QueryExecutor;
import org.postgresql.ds.PGSimpleDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;

/**
* Runs the documented sqlcj workflow against PostgreSQL through generated
* code: create, read, list, update, a missing-row read, a committed
* transaction, a rolled back transaction, and delete.
*
* <p>Every step is checked, so the process exits non-zero as soon as one
* generated operation returns an unexpected result.
*
* <p>The verification harness applies {@code sql/schema.sql} to an empty
* {@code authors} table before this application runs.
*/
public final class App {

private App() {
}

public static void main(String[] args) throws SQLException {
DataSource dataSource = dataSource();

QueryExecutor executor = new JdbcQueryExecutor(dataSource);

GetAuthor getAuthor = new GetAuthor(executor);
ListAuthors listAuthors = new ListAuthors(executor);

CreateAuthor.CreateAuthorResult created = new CreateAuthor(executor)
.createAuthor("Ada Lovelace", "First programmer");

check(created != null, "CreateAuthor returned no row");
check(created.id() != null, "CreateAuthor returned no database-generated id");
checkEquals("Ada Lovelace", created.name(), "CreateAuthor name");
checkEquals("First programmer", created.bio(), "CreateAuthor bio");

System.out.println("created: " + created.id() + " " + created.name());

GetAuthor.GetAuthorResult read = getAuthor.getAuthor(created.id());

check(read != null, "GetAuthor returned no row for the created author");
checkEquals(created.id(), read.id(), "GetAuthor id");
checkEquals("Ada Lovelace", read.name(), "GetAuthor name");
checkEquals("First programmer", read.bio(), "GetAuthor bio");

System.out.println("read: " + read.name() + " / " + read.bio());

List<ListAuthors.ListAuthorsResult> listed = listAuthors.listAuthors();

checkEquals(1, listed.size(), "ListAuthors row count after create");
checkEquals(created.id(), listed.get(0).id(), "ListAuthors id");
checkEquals("Ada Lovelace", listed.get(0).name(), "ListAuthors name");

System.out.println("listed: " + listed.get(0).id() + " " + listed.get(0).name());

int updatedRows = new UpdateAuthorBio(executor).updateAuthorBio(created.id(), "Mathematician");

checkEquals(1, updatedRows, "UpdateAuthorBio affected rows");
checkEquals("Mathematician", getAuthor.getAuthor(created.id()).bio(), "bio after update");

System.out.println("updated rows: " + updatedRows);

check(getAuthor.getAuthor(-1L) == null, "GetAuthor must return null for a missing row");

System.out.println("missing row: null");

Long committedId = writeAndCommit(dataSource);

GetAuthor.GetAuthorResult committed = getAuthor.getAuthor(committedId);

check(committed != null, "the committed author is not readable after commit");
checkEquals("Grace Hopper", committed.name(), "committed name");
checkEquals("Compiler pioneer", committed.bio(), "committed bio");

System.out.println("committed: " + committed.bio());

Long discardedId = writeAndRollback(dataSource);

check(getAuthor.getAuthor(discardedId) == null, "the rolled back author must not be readable");

System.out.println("rolled back: null");

int deletedRows = new DeleteAuthor(executor).deleteAuthor(created.id());

checkEquals(1, deletedRows, "DeleteAuthor affected rows");
check(getAuthor.getAuthor(created.id()) == null, "the deleted author must not be readable");

System.out.println("deleted rows: " + deletedRows);

List<ListAuthors.ListAuthorsResult> remaining = listAuthors.listAuthors();

checkEquals(1, remaining.size(), "ListAuthors row count after delete");
checkEquals(committedId, remaining.get(0).id(), "remaining author id");

System.out.println("sqlcj sample verification passed");
}

/**
* Writes through a caller-owned connection with auto-commit disabled and
* commits, so the written row must be visible to a later read.
*/
private static Long writeAndCommit(DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);

QueryExecutor transactional = new JdbcQueryExecutor(connection);

CreateAuthor.CreateAuthorResult author = new CreateAuthor(transactional)
.createAuthor("Grace Hopper", null);

check(author != null, "CreateAuthor returned no row inside the committed transaction");
checkEquals(null, author.bio(), "committed bio before update");

int rows = new UpdateAuthorBio(transactional).updateAuthorBio(author.id(), "Compiler pioneer");

checkEquals(1, rows, "UpdateAuthorBio affected rows inside the committed transaction");

connection.commit();

return author.id();
}
}

/**
* Writes through a caller-owned connection with auto-commit disabled and
* rolls back, so the written row must not be visible to a later read.
*/
private static Long writeAndRollback(DataSource dataSource) throws SQLException {
try (Connection connection = dataSource.getConnection()) {
connection.setAutoCommit(false);

QueryExecutor transactional = new JdbcQueryExecutor(connection);

CreateAuthor.CreateAuthorResult author = new CreateAuthor(transactional)
.createAuthor("Temporary Author", null);

check(author != null, "CreateAuthor returned no row inside the rolled back transaction");

int rows = new UpdateAuthorBio(transactional).updateAuthorBio(author.id(), "never stored");

checkEquals(1, rows, "UpdateAuthorBio affected rows inside the rolled back transaction");

connection.rollback();

return author.id();
}
}

private static DataSource dataSource() {
PGSimpleDataSource dataSource = new PGSimpleDataSource();

dataSource.setUrl(environment("SQLCJ_SAMPLE_JDBC_URL", "jdbc:postgresql://localhost:5432/quickstart"));
dataSource.setUser(environment("SQLCJ_SAMPLE_DB_USER", "quickstart"));
dataSource.setPassword(environment("SQLCJ_SAMPLE_DB_PASSWORD", "quickstart"));

return dataSource;
}

private static String environment(String name, String fallback) {
String value = System.getenv(name);

return value == null || value.isBlank() ? fallback : value;
}

private static void checkEquals(Object expected, Object actual, String description) {
check(
Objects.equals(expected, actual),
description + ": expected " + expected + " but was " + actual
);
}

private static void check(boolean condition, String message) {
if (!condition) {
throw new IllegalStateException(message);
}
}
}
Loading
Loading