Skip to content

Commit 8c8637e

Browse files
authored
Merge pull request #61 from sqlcj-dev/chore/sample-everyday-sql-expressions
test: exercise everyday SQL expressions in the sample and on PostgreSQL
2 parents 7087c48 + 447450d commit 8c8637e

5 files changed

Lines changed: 281 additions & 17 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE authors ADD COLUMN updated_at TIMESTAMP;

‎examples/maven-postgresql/sql/queries.sql‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,33 @@ SELECT *
6464
FROM books
6565
WHERE format = $1
6666
ORDER BY id;
67+
68+
-- name: FilterAuthors :many
69+
SELECT *
70+
FROM authors
71+
WHERE (:name::text IS NULL OR name = :name)
72+
ORDER BY id;
73+
74+
-- name: RenameAuthor :exec
75+
UPDATE authors
76+
SET name = :name, updated_at = now()
77+
WHERE id = :id;
78+
79+
-- name: CountBooksByAuthor :many
80+
SELECT author_id, COUNT(*) AS books
81+
FROM books
82+
GROUP BY author_id
83+
ORDER BY author_id;
84+
85+
-- name: UpsertAuthor :one
86+
INSERT INTO authors (id, name, bio)
87+
VALUES (:id, :name, :bio)
88+
ON CONFLICT (id) DO UPDATE
89+
SET name = EXCLUDED.name, bio = EXCLUDED.bio, updated_at = now()
90+
RETURNING *;
91+
92+
-- name: ListAuthorsByIds :many
93+
SELECT *
94+
FROM authors
95+
WHERE id = ANY(:ids)
96+
ORDER BY id;

‎examples/maven-postgresql/src/main/java/com/example/app/App.java‎

Lines changed: 90 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,26 @@
2121
* code: create, read, optional read, list, update, a missing-row read, a
2222
* committed transaction, a rolled back transaction, a case-insensitive search,
2323
* a count, a page, a book insert, a left-joined projection, a cataloged book
24-
* insert, a read by enum value, and delete.
24+
* insert, a read by enum value, an optional filter by name, a read by id list,
25+
* a grouped book count, a rename, an upsert, and delete.
2526
*
26-
* <p>All thirteen named queries are methods of one generated
27+
* <p>All eighteen named queries are methods of one generated
2728
* {@link AuthorRepository}. The repository is constructed once per execution
2829
* context: once from the {@code DataSource}-backed executor, and once more per
2930
* transaction from a caller-owned connection.
3031
*
31-
* <p>Create, read, optional read, list, search, and page each return one
32-
* complete {@code authors} row, so all six share the single top-level
33-
* {@link AuthorsRow} record of the generated package, and the two queries that
34-
* return a complete {@code books} row share the top-level {@link BooksRow}
35-
* record. A query with its own result shape generates its own nested record:
36-
* {@link AuthorRepository.CountAuthorsResult} carries the non-null
37-
* {@code Long} count, and {@link AuthorRepository.ListAuthorBooksResult}
38-
* carries an author name beside the title of the left-joined {@code books} row,
39-
* which is {@code null} for an author without a book.
32+
* <p>Create, read, optional read, list, search, page, filter, upsert, and the
33+
* read by id list each return one complete {@code authors} row, so all nine
34+
* share the single top-level {@link AuthorsRow} record of the generated
35+
* package, and the two queries that return a complete {@code books} row share
36+
* the top-level {@link BooksRow} record. A query with its own result shape
37+
* generates its own nested record: {@link AuthorRepository.CountAuthorsResult}
38+
* carries the non-null {@code Long} count,
39+
* {@link AuthorRepository.ListAuthorBooksResult} carries an author name beside
40+
* the title of the left-joined {@code books} row, which is {@code null} for an
41+
* author without a book, and
42+
* {@link AuthorRepository.CountBooksByAuthorResult} carries an author id beside
43+
* the non-null {@code Long} number of that author's books.
4044
*
4145
* <p>Row absence is expressed by the {@code :optional} {@code FindAuthor}
4246
* query, which returns an empty {@link Optional}, while the {@code :one}
@@ -49,6 +53,16 @@
4953
* {@code JSONB} document as JSON text, which PostgreSQL returns normalized.
5054
* The enum is also bound as a query parameter to select books by format.
5155
*
56+
* <p>The fourth migration adds the nullable {@code updated_at} column to
57+
* {@code authors}, so {@link AuthorsRow} carries it as a
58+
* {@link java.time.LocalDateTime}. The rename and the upsert set it with
59+
* {@code now()} without binding a parameter for it, and the upsert targets the
60+
* existing author of the conflicting id, so the {@code BIGSERIAL} key is never
61+
* given an explicit new value. The filter binds its one {@code :name}
62+
* parameter to both of its occurrences, so a {@code null} argument returns
63+
* every author and a name returns only that author, and the read by id list
64+
* binds one {@code List} as one server array, so an empty list matches no row.
65+
*
5266
* <p>Every step is checked, so the process exits non-zero as soon as one
5367
* generated operation returns an unexpected result.
5468
*
@@ -218,6 +232,71 @@ public static void main(String[] args) throws SQLException {
218232
+ " " + cataloged.tags() + " " + cataloged.details()
219233
);
220234

235+
List<AuthorsRow> unfiltered = authors.filterAuthors(null);
236+
237+
checkEquals(2, unfiltered.size(), "FilterAuthors row count without a name");
238+
checkEquals(created.id(), unfiltered.get(0).id(), "FilterAuthors first unfiltered id");
239+
checkEquals(committedId, unfiltered.get(1).id(), "FilterAuthors second unfiltered id");
240+
241+
List<AuthorsRow> filtered = authors.filterAuthors("Ada Lovelace");
242+
243+
checkEquals(1, filtered.size(), "FilterAuthors row count for one name");
244+
checkEquals(created.id(), filtered.get(0).id(), "FilterAuthors filtered id");
245+
checkEquals("Ada Lovelace", filtered.get(0).name(), "FilterAuthors filtered name");
246+
247+
System.out.println(
248+
"filtered: " + unfiltered.size() + " without a name, "
249+
+ filtered.get(0).name() + " by name"
250+
);
251+
252+
List<AuthorsRow> byIds = authors.listAuthorsByIds(List.of(created.id(), committedId));
253+
254+
checkEquals(2, byIds.size(), "ListAuthorsByIds row count for two ids");
255+
checkEquals(created.id(), byIds.get(0).id(), "ListAuthorsByIds first id");
256+
checkEquals(committedId, byIds.get(1).id(), "ListAuthorsByIds second id");
257+
checkEquals(
258+
0,
259+
authors.listAuthorsByIds(List.of()).size(),
260+
"ListAuthorsByIds row count for an empty list"
261+
);
262+
263+
System.out.println("by ids: " + byIds.get(0).id() + " " + byIds.get(1).id());
264+
265+
List<AuthorRepository.CountBooksByAuthorResult> bookCounts = authors.countBooksByAuthor();
266+
267+
checkEquals(1, bookCounts.size(), "CountBooksByAuthor row count");
268+
checkEquals(committedId, bookCounts.get(0).authorId(), "CountBooksByAuthor author id");
269+
checkEquals(2L, bookCounts.get(0).books(), "CountBooksByAuthor count");
270+
271+
System.out.println(
272+
"book counts: " + bookCounts.get(0).authorId() + " / " + bookCounts.get(0).books()
273+
);
274+
275+
int renamedRows = authors.renameAuthor("Ada Byron", created.id());
276+
277+
checkEquals(1, renamedRows, "RenameAuthor affected rows");
278+
279+
AuthorsRow renamed = authors.getAuthor(created.id());
280+
281+
checkEquals("Ada Byron", renamed.name(), "name after rename");
282+
check(renamed.updatedAt() != null, "RenameAuthor left updatedAt null");
283+
284+
System.out.println("renamed: " + renamed.name() + " at " + renamed.updatedAt());
285+
286+
AuthorsRow upserted = authors.upsertAuthor(
287+
committedId,
288+
"Grace Murray Hopper",
289+
"Rear admiral"
290+
);
291+
292+
check(upserted != null, "UpsertAuthor returned no row");
293+
checkEquals(committedId, upserted.id(), "UpsertAuthor id");
294+
checkEquals("Grace Murray Hopper", upserted.name(), "UpsertAuthor name");
295+
checkEquals("Rear admiral", upserted.bio(), "UpsertAuthor bio");
296+
check(upserted.updatedAt() != null, "UpsertAuthor left updatedAt null");
297+
298+
System.out.println("upserted: " + upserted.name() + " / " + upserted.bio());
299+
221300
int deletedRows = authors.deleteAuthor(created.id());
222301

223302
checkEquals(1, deletedRows, "DeleteAuthor affected rows");

‎scripts/verify-release.sh‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
# generates. The final generation uses the sample's own 'sqlcj.yaml' from the
2121
# sample directory and must restore the first generated output byte for byte
2222
# before the sample is compiled and run. The sample's migrations declare an
23-
# enum type and add an enum, an array, and a 'JSONB' column to 'books', so the
24-
# generated package holds the shared 'AuthorsRow' and 'BooksRow' records, the
25-
# 'BookFormat' enum, and one repository.
23+
# enum type and add an enum, an array, and a 'JSONB' column to 'books' and an
24+
# 'updated_at' column to 'authors', so the generated package holds the shared
25+
# 'AuthorsRow' and 'BooksRow' records, the 'BookFormat' enum, and one
26+
# repository whose methods cover every named query, including the named
27+
# parameter filter, the 'now()' rename, the grouped book count, the upsert, and
28+
# the id-list read.
2629
#
2730
# Requirements: JDK 21, Maven, psql, and a reachable PostgreSQL server.
2831
#
@@ -246,14 +249,18 @@ generated_count=$(find "${generated_root}" -type f -name '*.java' | wc -l)
246249

247250
for method in createAuthor getAuthor findAuthor listAuthors updateAuthorBio deleteAuthor \
248251
searchAuthors countAuthors listAuthorPage createBook listAuthorBooks \
249-
createCatalogedBook listBooksByFormat; do
252+
createCatalogedBook listBooksByFormat filterAuthors renameAuthor \
253+
countBooksByAuthor upsertAuthor listAuthorsByIds; do
250254
grep -q " ${method}(" "${generated_package}/AuthorRepository.java" \
251255
|| fail "the generated repository is missing the ${method} method"
252256
done
253257

254258
grep -q "LocalDateTime createdAt" "${generated_package}/AuthorsRow.java" \
255259
|| fail "the generated row is missing the column the second migration adds"
256260

261+
grep -q "LocalDateTime updatedAt" "${generated_package}/AuthorsRow.java" \
262+
|| fail "the generated row is missing the column the fourth migration adds"
263+
257264
diff -r "${first_generation}" "${generated_root}" \
258265
|| fail "generating from the sample directory did not restore its first generated output"
259266

@@ -287,6 +294,7 @@ PGPASSWORD="${db_password}" psql \
287294
--file "${sample_dir}/sql/migrations/V1__create_authors_and_books.sql" \
288295
--file "${sample_dir}/sql/migrations/V2__add_author_created_at_and_book_index.sql" \
289296
--file "${sample_dir}/sql/migrations/V3__add_book_format_tags_and_details.sql" \
297+
--file "${sample_dir}/sql/migrations/V4__add_author_updated_at.sql" \
290298
"${jdbc_url#jdbc:}"
291299

292300
log "Compiling the sample against the staged runtime"

0 commit comments

Comments
 (0)