This document defines the query contract of sqlcj: the named-query file format, the accepted SQL shapes, how parameters are ordered and bound, and the Java that each query generates.
Related documents:
- Configuration owns
sqlcj.yaml, path resolution, and the rules that turn a SQL name into a Java name. - PostgreSQL Support owns the schema snapshot, the column types, the null contract, and the runtime connection contract.
- Quickstart runs the whole path end to end.
A sql[].queries file is a plain SQL file in which every statement is preceded
by a sqlcj header:
-- name: GetAuthor :one
SELECT id, name, bio
FROM authors
WHERE id = $1;- A header line starts with
-- name:and contains exactly the query name and the query annotation, separated by whitespace. - A query owns every following line until the next header or the end of the
file. The statement's trailing
;is part of the query. - Query order inside a file is preserved, and it is the order of the methods of the generated repository.
- A query name must be unique inside its own query source. Two configuration entries may use the same query name, because each entry generates its own repository.
- An unparsable header, an unknown annotation, a duplicate name, and a header without SQL are all rejected.
sqlcj accepts four annotations. Any other annotation is rejected.
| Annotation | Accepted statements | Generated return type | Result when no row matches | Result when several rows match |
|---|---|---|---|---|
:one |
SELECT, or a write with RETURNING |
the generated result record | QueryCardinalityException |
QueryCardinalityException |
:optional |
SELECT, or a write with RETURNING |
Optional<result record> |
Optional.empty() |
QueryCardinalityException |
:many |
SELECT, or a write with RETURNING |
List<result record> |
an empty list | every row |
:exec |
INSERT, UPDATE, DELETE without RETURNING |
int affected-row count |
0 |
the affected-row count |
The annotation and the statement must agree:
- a
SELECTmust be:one,:optional, or:many, - a write without
RETURNINGmust be:exec, - a write with
RETURNINGmust be:one,:optional, or:many.
The three result annotations differ only in how many rows they accept:
:onereturns exactly one row. It raisesdev.sqlcj.runtime.QueryCardinalityExceptionwhen the query returned no row and when it returned more than one.:optionalreturns at most one row, asOptional.empty()orOptional.of(row). It raises the same exception when the query returned more than one row.:manyreturns every row in the order the database produced it, and an empty list when there was none.
A cardinality check runs after the statement has executed, so a returning write that fails its check has already changed the database. The application controls whether that change is kept, by running the write on a caller-owned connection and rolling back. The exception messages are listed in PostgreSQL Support.
A SELECT query is analyzed against the schema snapshot of its own
configuration entry.
- The
FROMitem must be a table of that schema, optionally with an alias. - A table may be joined with
JOIN,INNER JOIN,LEFT JOIN, orLEFT OUTER JOIN, and inner and left joins may be chained in any order. A comma-separated source list and every other join modifier —RIGHT,FULL, a bareOUTER,CROSS,NATURAL,SEMI,APPLY,STRAIGHT,GLOBAL, a join hint, andUSING (...)— are rejected. - Each join requires exactly one
ONequality between a qualified column of the joined source and a qualified column of a source introduced earlier. A left join uses the same rule as an inner join. - Every column read from a left-joined source is nullable, even when the schema
declares it
NOT NULL, because an unmatched row reads it asNULL. The base source and every inner-joined source keep their schema nullability. - An alias replaces the table name as the exposed source name, so a qualified reference to an aliased table must use the alias and not the table name.
- Two sources may not expose the same name.
- A direct column of a source is selected by its own name or qualified with the exposed source name.
*expands across the sources in their declared order, each source in its schema column order.qualifier.*expands one source in its schema column order.- Selected-column order is the order of the generated result record and of the positional row mapper.
- An unqualified column must be found in exactly one source; an ambiguous or unknown column is rejected.
- A result expression that is not a direct column, a wildcard, the scalar count below, or the cast projection below — such as another function call, an arithmetic expression, or a literal — is rejected.
- An explicit alias names the result column, so
SELECT id AS author_idgenerates the record componentauthorIdwhile the component's type still comes from the column and its nullability from the column and its source. sqlcj itself resolves an alias only in the projection; every other clause reaches the database as written.
A read may count its matching rows with an aliased COUNT(*):
-- name: CountAuthors :one
SELECT COUNT(*) AS total
FROM authors;
-- name: CountAuthorsByCountry :many
SELECT country, COUNT(*) AS total
FROM authors
GROUP BY country
ORDER BY country;- The alias is required and may be written with or without
AS, so bothCOUNT(*) AS totalandCOUNT(*) totalgenerate the result recordCountAuthorsResultwith the single componenttotal. - The component is a non-null
Long: the count is typedBIGINTbecausecount(*)returnsbigint, and it is never null because a count is0when no row matches. - The count is one result column in its own position, so it may stand beside
direct columns, a wildcard, a cast projection, or another count, in either
order.
GROUP BYitself is not analyzed and reaches the database as written, so PostgreSQL rather than sqlcj checks that the grouping is valid. - The count is accepted over any supported source, predicate, ordering, and pagination shape, and under any annotation.
COUNT(*)without an alias is rejected withCOUNT(*) requires a result alias, such as COUNT(*) AS total.- Every other count is still an unsupported result expression, including
COUNT(column),COUNT(DISTINCT column),COUNT(t.*), a qualifiedpg_catalog.count(*), and theFILTERandOVERforms.
A read may also project a computed value by stating its type in a cast:
-- name: SumRoyaltiesByAuthor :one
SELECT SUM(amount)::numeric AS total
FROM royalties
WHERE author_id = $1;- Both cast spellings are the same projection, so
SUM(amount)::numeric AS totalandCAST(SUM(amount) AS numeric) AS totalare equivalent. - The alias is required and may be written with or without
AS, soSUM(amount)::numeric totalis equivalent as well. A cast projection without an alias is rejected withA cast projection requires a result alias, such as SUM(amount)::numeric AS total. - The result column takes the type the cast states, mapped exactly as a schema
column's declared type is, so a cast may name a declared enum type or a
one-dimensional array such as
::text[]. A cast type sqlcj does not map is rejected withResult column 'total' has unsupported cast type INTERVAL, naming the result column and the declared type. - The component is always nullable, because the cast operand is not analyzed
and so whether it can read as
NULLis unknown at compile time. - The operand itself is not analyzed and reaches the database as written, so
PostgreSQL rather than sqlcj checks its column references and its functions.
A placeholder in a projection is still rejected, including a cast placeholder
such as
$1::int AS x.
A WHERE clause may combine:
- the comparisons
=,<>,>,>=,<,<=between a direct column and a$Nplaceholder, in either order, AND,OR, and parentheses,INwith a fixed list of$Nplaceholders, such asid IN ($1, $2),LIKEandILIKEbetween a direct column and a$Npattern placeholder, such asname LIKE $1,IS NULLandIS NOT NULLon a direct column, such asbio IS NULL,BETWEENandNOT BETWEENon a direct column, such ascode BETWEEN $1 AND $2,= ANYbetween a direct column and one$Nlist placeholder, such asid = ANY($1).
A LIKE or ILIKE pattern placeholder requires a VARCHAR or TEXT column
and takes that column's type, so it is a String method parameter. sqlcj passes
the pattern through unchanged, so the caller supplies the % and _ wildcards
in the argument, as in "Al%". A negated NOT LIKE or NOT ILIKE, another
keyword such as SIMILAR TO, an ESCAPE clause, a BINARY modifier, a
non-text tested column, and a placeholder as the tested value are rejected. A
placeholder inside a computed pattern, such as '%' || $1 || '%', is rejected
as an unanalyzed placeholder location.
IS NULL and IS NOT NULL bind no placeholder, but their column is resolved
against the query sources, so an unknown, ambiguous, or badly qualified column
is rejected.
A BETWEEN or NOT BETWEEN bound that is a $N placeholder takes the tested
column's type, so code BETWEEN $1 AND $2 binds two Integer parameters named
after code and disambiguated as code1 and code2. The bounds are bound in
textual order, the start bound before the end bound, whatever their placeholder
indexes are. A placeholder bound beside a literal bound, as in
code BETWEEN $1 AND 10, is typed the same way and binds one parameter. A named
bound such as code BETWEEN :lo AND :hi is typed the same way and named after
its placeholder, while a placeholder as the tested value, as in
$1 BETWEEN code AND code, and a placeholder inside a computed bound, as in
code BETWEEN $1 + 1 AND $2, are rejected as unanalyzed placeholder locations.
<column> = ANY(<placeholder>) is the id-list read. The placeholder is one
whole list of the compared column's type, named after that column, so
WHERE id = ANY($1) takes a List<Long> id and the runtime binds the list as
one PostgreSQL array at one ? position. A null list and an empty list contain
no value, so each matches no row. A named list keeps its own name, and one name
used by two list predicates, as in
id = ANY(:ids) OR parent_id = ANY(:ids), is one parameter bound at both
positions.
The compared column must be a non-array column of a declared enum type or of a
mapped type other than BYTEA, JSON, and JSONB, which are the element
types sqlcj binds no array of; any other column is rejected. Only this exact
shape is analyzed: the operator is =, the column is its left operand, and
ANY is written unquoted and unqualified with exactly one placeholder
argument. id <> ANY($1), id = SOME($1), ANY($1) = id, and
id = ANY(ARRAY[$1, $2]) are therefore rejected as unanalyzed placeholder
locations, and sqlcj expands no IN list of its own.
A comparison that binds no placeholder, such as active = TRUE, contributes no
generated parameter and reaches the database as written.
A placeholder written as the direct operand of ::type or CAST(... AS type)
is typed by that cast instead of by the clause it appears in, anywhere inside
the WHERE clause: beside a compared column, as the tested value of IS NULL,
inside a concatenation, as a function argument, as an IN element, as a range
bound, as a pattern, or as the operand of another operator such as the array
overlap &&. This makes the optional filter and the computed pattern idioms
compile:
-- name: ListUsersByName :many
SELECT id, name
FROM users
WHERE (:name::text IS NULL OR name = :name)
ORDER BY id;
-- name: SearchUsers :many
SELECT id, name
FROM users
WHERE name LIKE '%' || :term::text || '%';ListUsersByName generates listUsersByName(String name), which returns every
row for a null argument, and SearchUsers generates
searchUsers(String term). The cast itself reaches the database as written. A
cast pattern states its own type, so the pattern restrictions above apply only
to an uncast pattern: name NOT LIKE $1::text, name SIMILAR TO $1::text, and
name LIKE $1::text ESCAPE '!' are accepted and typed text, whatever the
tested column's type is. Such a pattern is named after its placeholder rather
than after the tested column, so an indexed one is param<N>, as in param1
for $1. A cast that is the direct argument of an analyzed = ANY list, as in
id = ANY($1::bigint[]), id = ANY(CAST($1 AS bigint[])), or
id = ANY(:ids::bigint[]), states its own type and keeps the compared column's
name, while a cast under any other operator, such as id <> ANY($1::bigint[])
or tags && $1::varchar[], is named param<N>.
Parameters states the accepted cast types and the name a cast
placeholder takes.
ORDER BY over direct columns is supported for a stable list order, as in
ORDER BY id. sqlcj rewrites only $N parameter tokens; the rest of the
statement, including the ordering clause, reaches JDBC exactly as written. A
placeholder in ORDER BY is rejected, because it is not an analyzed parameter
location.
A read may page its rows with LIMIT and OFFSET. Each clause takes either a
literal value or a $N placeholder:
-- name: ListAuthorPage :many
SELECT id, name
FROM authors
ORDER BY id
LIMIT $1 OFFSET $2;- A
LIMITrow count placeholder is anINTEGERparameter namedlimit, and anOFFSETvalue placeholder is anINTEGERparameter namedoffset, soListAuthorPagegenerateslistAuthorPage(Integer limit, Integer offset). - The pagination parameters are bound after every
WHEREparameter, in the textual order of the two clauses, so bothLIMIT $1 OFFSET $2andOFFSET $2 LIMIT $1generate the same(limit, offset)method parameters while the second binds the offset first. - A value that binds no placeholder contributes no parameter and reaches the
database as written, so
LIMIT 10 OFFSET 5binds none, whileLIMIT 10 OFFSET $1andLIMIT ALL OFFSET $1each bind oneoffsetparameter. - Both values must be non-negative. sqlcj generates no validation, so PostgreSQL rejects a negative value when the query executes.
- A named value is named after its placeholder rather than after its clause, so
LIMIT :pageSize OFFSET :skipgenerateslistAuthorPage(Integer pageSize, Integer skip). - A placeholder in a computed value, as in
LIMIT $1 + 1orOFFSET $1 + 1, in theLIMIT a, bform, as inLIMIT 5, $1, and in aFETCH FIRST $1 ROWS ONLYclause are rejected as unanalyzed placeholder locations.
A write targets exactly one table of its entry's schema.
| Statement | Accepted shape |
|---|---|
INSERT |
an explicit column list and a single VALUES row, each value a placeholder or an expression that binds none, with an optional ON CONFLICT clause |
UPDATE |
SET assignments that each assign one direct column a placeholder or an expression that binds none, with an optional WHERE using the read predicate forms |
DELETE |
one target table with an optional WHERE using the read predicate forms |
-- name: CreateAuthor :one
INSERT INTO authors (name, bio)
VALUES ($1, $2)
RETURNING *;
-- name: UpdateAuthorBio :exec
UPDATE authors
SET bio = $2
WHERE id = $1;
-- name: DeleteAuthor :exec
DELETE
FROM authors
WHERE id = $1;An INSERT value or an UPDATE assignment that contains no placeholder — such
as DEFAULT, a literal, NULL, now(), or version + 1 — contributes no
generated parameter and reaches the database exactly as written. Its target
column must exist in the written table, but nothing binds it, so its type need
not be one sqlcj maps. The placeholders beside it keep their own $N or :name
numbering and textual binding order:
-- name: TouchAuthor :exec
UPDATE authors
SET bio = $2,
updated_at = now(),
version = version + 1
WHERE id = $1;TouchAuthor generates touchAuthor(Long id, String bio) and binds (bio, id),
exactly as it would without the two non-binding assignments. A write whose every
value binds no placeholder, such as
UPDATE authors SET version = version + 1, generates a method without
parameters.
An INSERT value or an UPDATE assignment may also compute a value from a cast
placeholder, which is typed by its cast wherever it appears inside the value:
-- name: UpdateAuthorBioOrKeep :exec
UPDATE authors
SET bio = COALESCE(:bio::text, bio)
WHERE id = :id;UpdateAuthorBioOrKeep generates updateAuthorBioOrKeep(String bio, Long id)
and keeps the assignment bound before the predicate. A cast placeholder that is
the whole value, as in SET bio = :bio::text, is typed by its cast as well and
named after its column. A value that contains an uncast placeholder, such as
COALESCE($2, bio), stays rejected.
An INSERT may end with one conflict clause whose target is a parenthesized
list of plain column names of the inserted table, followed by one action:
| Action | Accepted shape |
|---|---|
DO NOTHING |
no assignment, so the statement binds only its VALUES row |
DO UPDATE SET |
assignments analyzed exactly as UPDATE assignments are, where a value may also be EXCLUDED.column |
-- name: UpsertAuthor :one
INSERT INTO authors (id, name, bio)
VALUES (:id, :name, :bio)
ON CONFLICT (id) DO UPDATE
SET name = EXCLUDED.name,
bio = :bio,
version = authors.version + 1,
updated_at = now()
RETURNING *;UpsertAuthor generates upsertAuthor(Long id, String name, String bio) and
returns the AuthorsRow it inserted or updated.
A DO UPDATE placeholder is written after the VALUES placeholders, so it
binds after them: on a two-value insert,
ON CONFLICT (id) DO UPDATE SET active = $3 binds $1, $2, and $3 in that
order, and the repeated :bio above is bound at both of its positions. A
DO UPDATE placeholder is typed by its assigned column, or by its own cast
wherever a cast appears inside the assigned value.
EXCLUDED.column reads the proposed row, and that column must exist in the
inserted table. An EXCLUDED reference inside a larger expression is not
resolved, as no other computed write value is, so PostgreSQL checks it. sqlcj
does not match the conflict target against a unique index; PostgreSQL does.
A DO NOTHING action writes no row when a conflict occurs, so
DO NOTHING ... RETURNING returns no row on conflict and suits :optional or
:many rather than :one. A DO UPDATE ... RETURNING always returns the row
it inserted or updated.
Every other conflict form is rejected on every insert, whether :exec or
returning: a missing target, ON CONFLICT ON CONSTRAINT, a target element that
is an expression or carries a collation or an operator class, a conflict-target
predicate, and DO UPDATE ... WHERE.
A supported INSERT, UPDATE, or DELETE declared :one, :optional, or
:many may end with a RETURNING clause that lists either:
- unaliased direct columns of the target table, in the order they are declared, or
- a bare
*, which expands in the target table's schema column order.
A returning write produces the same generated record, positional row mapper, and
cardinality behavior as a read, so a database-generated SERIAL or BIGSERIAL
value is read back with its declared type. A clause that is exactly * returns
the target table's shared <TableName>Row record, while a RETURNING column
list keeps the query's own <QueryName>Result record.
An aliased or computed RETURNING item, a qualified table.*, an unknown
column, and RETURNING on :exec are rejected. Multi-row VALUES,
INSERT ... SELECT, UPDATE ... FROM, DELETE ... USING, and common table
expressions are rejected in a returning write, while an accepted ON CONFLICT
clause is analyzed the same way with and without RETURNING.
A query writes its parameters either as PostgreSQL $N placeholders or as
:name placeholders, and one query uses one of the two forms. The compiler
replaces each real placeholder token with a JDBC ? and leaves every other
character of the statement byte-for-byte unchanged, so $1 or :id inside a
string literal, a quoted identifier, or a comment is not a parameter.
- Placeholder indexes must be positive and contiguous from
$1. - A named placeholder is a colon followed directly by an unquoted name of ASCII
letters, digits, and underscores that does not start with a digit, such as
:userId. Names are compared exactly as written, so:termand:Termare two parameters, and a name spelled like a SQL keyword, such as:limit,:user, or:year, is an ordinary name. - Each distinct name is one parameter, numbered by its first textual
occurrence, and every occurrence of that name is bound at its own
?position. - A named placeholder is accepted wherever a
$Nplaceholder is, and names its generated method parameter after itself, soLIMIT :pageSizegeneratespageSizerather thanlimit. - The Java type of a placeholder is the type of the column it is compared with, assigned to, or inserted into.
- A placeholder written as the direct operand of
::typeorCAST(... AS type)is typed by that cast instead, wherever it appears inside aWHEREclause, anINSERTvalue, or anUPDATEassignment. The cast type may be any type a column may declare and sqlcj maps, including a declared enum name, written unquoted and matched case-insensitively, and a one-dimensional array, which binds aList. A cast type sqlcj does not map, such asINTERVALor a multi-dimensionalINT[][], is rejected naming the placeholder and the type. - A named cast placeholder keeps its own name. An indexed cast placeholder
keeps the name of the column whose value it is — a compared column, the
tested column of
INorBETWEEN, the tested column of a plainLIKEorILIKEpattern, which is the shape that accepts an uncast pattern, the compared column of an analyzed= ANYlist, or an inserted or assigned column — soname = $1::textnamesnameasname = $1does. Every other indexed cast placeholder is namedparam<N>after its own index, as inparam1for$1, and colliding names take the usual numeric suffix. - Occurrences of one placeholder may mix a cast and an uncast location, and the
parameter keeps the name and type of its first occurrence, so
(:name::text IS NULL OR name = :name)is oneStringparameter namedname. - Mixing
$Nand:nameplaceholders in one query is rejected, as are anonymous?placeholders, a qualified name such as:a.b, a quoted name such as:"x", and an&nameplaceholder. - A placeholder in a location sqlcj does not analyze — for example
ORDER BY $1orlower(name) = :name— is rejected rather than left unbound.
The two orders are distinct and both are observable:
- Logical order is placeholder index order. It is the order of the generated
method parameters:
$1is the first method parameter,$2the second. In a named query it is first-occurrence order: the name written first is the first method parameter. - Textual order is the order in which placeholder tokens appear in the SQL.
It is the JDBC binding order of the generated
?positions.
UpdateAuthorBio above reads $1 in its WHERE clause but writes $2 first
in its SET clause, so the generated method takes (id, bio) and binds
(bio, id):
public int updateAuthorBio(Long id, String bio) {
return executor.execute(
"AuthorRepository",
"UpdateAuthorBio",
"""
UPDATE authors
SET bio = ?
WHERE id = ?;
""",
java.util.Arrays.asList(bio, id)
);
}An index and a name may repeat. A repeated index produces one method parameter, named and typed from its first occurrence, and its value is bound at every textual position where the index occurs; a repeated name behaves the same way and keeps its own name. Occurrences of one index or one name whose inferred Java types differ are rejected.
UpdateAuthorBio written with named placeholders states the same two orders:
-- name: UpdateAuthorBio :exec
UPDATE authors
SET bio = :bio
WHERE id = :id;The generated method takes (bio, id), because :bio occurs first, and binds
(bio, id).
Each configuration entry generates one final repository class in the configured
java.package, written to the package directory under java.out and named
<sql[].name>Repository. Every named query of that entry becomes one method of
that repository; sqlcj never generates a class per query. Beside the
repositories, the package holds one <TableName>Row record file for each table
whose complete row an entry returns, and one Java enum file for each enum type
a query of an entry reads or binds.
- Every generated file begins with the fixed notice
// Code generated by sqlcj. DO NOT EDIT., followed by one blank line and thepackagedeclaration. The notice holds no version, timestamp, or filesystem path. - The repository has one
dev.sqlcj.runtime.QueryExecutorfield and one constructor taking that executor. - Methods appear in query-source order. A method name is the lower camel form
of the query name, as in
get_authorandGetAuthortogetAuthor. - A
:one,:optional, or:manyquery that returns one complete table row — a single-sourceSELECT *orSELECT <source>.*, or a write whoseRETURNINGclause is exactly*— returns the<TableName>Rowrecord. That record is a top-levelpublic recordinjava.package, generated once per table into its own file and shared by every repository of the package that returns that row. Each repository keeps its own privateRowMapperfield for the row, generated after the constructor in the order its queries first use it. - Every other
:one,:optional, or:manyquery generates a nestedpublic recordnamed<QueryName>Resultin upper camel case, whose components follow the selected-column order and are named after each column's projection alias or column name, plus a privateRowMapperfield that reads each column by its one-based position. That includes an explicit column list, even one naming every column, a wildcard combined with another projection item, a wildcard in a join, and aRETURNINGcolumn list. - A
:execquery generates no result record and returnsint. - Every generated method passes the repository class name and the query name to
the executor, as the first two arguments of its call, so a runtime failure
names the query the application called. A
JSONorJSONBargument is passed asnew dev.sqlcj.runtime.UntypedText(<parameter>), written out in full, so that the runtime binds its text without a declared SQL type; see Supported Column Types. - A column or parameter of a one-dimensional array type is a
java.util.List<T>of the element's Java type. Its argument is passed asnew dev.sqlcj.runtime.SqlArray("<element type>", <parameter>), or asdev.sqlcj.runtime.SqlArray.of("<enum type>", <parameter>, <EnumType>::label)for an array of an enum, and the column is read asdev.sqlcj.runtime.SqlArray.getList(resultSet, position, <Type>.class), so anulllist, an empty list, and anullelement are preserved in both directions; see Array Types. - A column or parameter of an enum type the schema declares uses the Java enum
the package generates for that type, by its simple name. Its argument is
passed as
new dev.sqlcj.runtime.UntypedText(<parameter> == null ? null : <parameter>.label()), and the column is read as<EnumType>.fromLabel(resultSet.getString(position)), so anullstaysnullin both directions; see Enum Types. - Generated repository source imports only
dev.sqlcj.runtime.QueryExecutor,dev.sqlcj.runtime.RowMapper,java.util.List,java.util.Optionalwhen the entry declares an:optionalquery, and the JDK types of the mapped columns, so the runtime artifact is the only sqlcj dependency a consumer needs. A row record imports the JDK types of its components alone, which includejava.util.Listfor an array component, and depends on no sqlcj type, and a generated enum imports nothing at all.
The Author entry of the Quickstart, which declares
CreateAuthor, GetAuthor, FindAuthor, ListAuthors, UpdateAuthorBio, and
DeleteAuthor, generates the row record of the authors table:
// Code generated by sqlcj. DO NOT EDIT.
package com.example.app.db;
import java.time.LocalDateTime;
/**
* Generated by sqlcj.
*
* Table: authors
*/
public record AuthorsRow(
Long id,
String name,
String bio,
LocalDateTime createdAt
) {
}and one AuthorRepository that uses it:
// Code generated by sqlcj. DO NOT EDIT.
package com.example.app.db;
import dev.sqlcj.runtime.QueryExecutor;
import dev.sqlcj.runtime.RowMapper;
import java.util.List;
import java.util.Optional;
import java.time.LocalDateTime;
/**
* Generated by sqlcj.
*
* Repository: Author
*/
public final class AuthorRepository {
private final QueryExecutor executor;
public AuthorRepository(QueryExecutor executor) {
this.executor = executor;
}
private static final RowMapper<AuthorsRow> authorsRowMapper =
resultSet -> new AuthorsRow(
resultSet.getObject(1, Long.class),
resultSet.getObject(2, String.class),
resultSet.getObject(3, String.class),
resultSet.getObject(4, LocalDateTime.class)
);
/**
* Query: CreateAuthor
* Table: authors
* Type: ONE
*/
public AuthorsRow createAuthor(String name, String bio) {
return executor.queryOne(
"AuthorRepository",
"CreateAuthor",
"""
INSERT INTO authors (name, bio)
VALUES (?, ?)
RETURNING *;""",
java.util.Arrays.asList(name, bio),
authorsRowMapper
);
}
// getAuthor follows here, in query-source order.
/**
* Query: FindAuthor
* Table: authors
* Type: OPTIONAL
*/
public Optional<AuthorsRow> findAuthor(Long id) {
return executor.queryOptional(
"AuthorRepository",
"FindAuthor",
"""
SELECT *
FROM authors
WHERE id = ?;""",
java.util.Arrays.asList(id),
authorsRowMapper
);
}
// listAuthors, updateAuthorBio, and deleteAuthor follow here.
}CreateAuthor, GetAuthor, FindAuthor, and ListAuthors each return one
complete authors row, so all four use the one AuthorsRow record and the one
authorsRowMapper field. getAuthor returns AuthorsRow, findAuthor returns
Optional<AuthorsRow>, and listAuthors returns List<AuthorsRow>. A second
entry of the same package that returns the full authors row returns that same
AuthorsRow class.
The application constructs the repository once per execution context:
AuthorRepository authors = new AuthorRepository(new JdbcQueryExecutor(dataSource));
AuthorsRow author = authors.getAuthor(1L);
Optional<AuthorsRow> found = authors.findAuthor(2L);A query name, projection alias, column name, and parameter name becomes a conventional Java name by one deterministic camel-case rule set, and names that would collide inside one generated repository are disambiguated in their SQL order. Those rules, the rejection of two queries of one entry that generate the same method, the rejection of two entries that define one row record differently, and the rejection of two entries whose repository files would collide, are documented in Generated Java Names.
The generated repositories are executed through
dev.sqlcj.runtime.JdbcQueryExecutor on a DataSource or on a caller-owned
Connection. Connection ownership, transaction control, and exception
translation are documented in
Connection Ownership and Transactions.
The supported subset is exactly the shapes described above. sqlcj does not validate the whole SQL language: a construct outside the subset is either rejected with a diagnostic or, when it introduces no parameter and no result column that sqlcj must type, carried into the executable SQL unanalyzed. Only the documented shapes are contract, tested, and safe to rely on.
Rejection stops the run with a diagnostic naming the query source, the query name, and its header line.
- A result expression that is not a direct column, a wildcard, an aliased
COUNT(*), or an aliased cast, including another function call, another aggregate, and a literal. ACOUNT(*)without an alias, a cast projection without an alias, and a cast projection whose type sqlcj does not map each fail with their own diagnostic, whileCOUNT(column),COUNT(DISTINCT column),COUNT(t.*),pg_catalog.count(*),lower(name) AS n, and theFILTERandOVERforms keep the unsupported-expression rejection. - A
FROMitem that is not a table, a comma-separated source list, a join that is not a plain inner or left join, a join predicate that is not one qualified equality, and a set operation such asUNION. - A placeholder in a location sqlcj does not analyze, including
ORDER BY $1, a named placeholder under a function such aslower(name) = :name, a computedLIKEpattern such as'%' || $1 || '%', a placeholder as the tested value of a range such as$1 BETWEEN id AND id, a computed range bound such asid BETWEEN $1 + 1 AND $2, a computed pagination value such asLIMIT $1 + 1orOFFSET $1 + 1, aLIMIT a, brow count such asLIMIT 5, $1, a placeholder inside a write value such asCOALESCE($2, bio), and aFETCH FIRST $1 ROWS ONLYclause, so dynamicINexpansion is unavailable. A cast does not widen these locations: a cast placeholder in a projection,ORDER BY, a join condition, or a pagination value, such asORDER BY $1::int, stays rejected, as does a placeholder that is not the direct operand of its cast, such as(:x)::int. - A cast type sqlcj does not map, such as
$1::intervalor a multi-dimensional$1::int[][], which is rejected naming the placeholder and the declared type, or the result column and the declared type when the cast is a projection such asid::interval AS i. - A
LIKE-family pattern placeholder that is negated, uses another keyword such asSIMILAR TO, carries anESCAPEclause or aBINARYmodifier, tests a non-text column, or stands as the tested value. These restrict the uncast pattern placeholder; a cast pattern states its own type. - A
= ANYlist placeholder whose compared column is an array column or aBYTEA,JSON, orJSONBcolumn, because sqlcj binds no array of those element types. Every form outside the analyzed shape — another operator such asid <> ANY($1), another quantifier such asid = SOME($1), a reversedANY($1) = id, a quoted or qualified"ANY"($1)orpg_catalog.any($1), a modifier such asANY(DISTINCT $1), more than one argument, and an argument that is not one placeholder, such asid = ANY(ARRAY[$1, $2])— keeps the unanalyzed-placeholder rejection. A cast does not widen them:id <> ANY($1::bigint[])is accepted only as any other cast placeholder is, named after its own index. - Anonymous
?placeholders,$Nand:nameplaceholders mixed in one query, a qualified name such as:a.b, a quoted name such as:"x", an&nameplaceholder, each of them also as the operand of a cast, such as:"x"::text,&x::text, or:a.b::text, one name whose occurrences have conflicting types, and non-contiguous or non-positive placeholder indexes. - An
INSERTwithout an explicit column list, with more than oneVALUESrow, or built from aSELECT; anUPDATEassignment that sets a column list, such asSET (name, active) = ('a', TRUE); and a written column that the table does not declare. - A conflict clause outside the accepted upsert shape, on an
:execand on a returning insert alike:ON CONFLICTwithout a target,ON CONFLICT ON CONSTRAINT, a target element that is an expression or carries a collation or an operator class, a conflict-target predicate such asON CONFLICT (id) WHERE active, andDO UPDATE ... WHERE. ADO UPDATEassignment that sets a column list, and an unknown target, assigned, orEXCLUDEDcolumn, are rejected as the write forms above are. - An aliased, computed, qualified-wildcard, or unknown
RETURNINGitem;RETURNINGon:exec; andUPDATE ... FROM,DELETE ... USING, or a common table expression in a returning write. - Any annotation other than
:one,:optional,:many, and:exec.
These are outside the subset and are not part of the contract. sqlcj neither models nor rejects them, so a statement that uses one may still compile while the generated Java describes only the part sqlcj did analyze. Do not rely on them:
DISTINCT,GROUP BY, andHAVING,- predicate forms other than the listed comparisons,
AND/OR, fixedINlists, pattern placeholders, null tests, ranges, and list predicates, such asLIKEwith a literal pattern, a range whose bounds are both literal such asid BETWEEN 1 AND 10,INwith a subquery, or= ANYover something other than a placeholder, such asid = ANY('{1,2}'), - common table expressions, and subqueries outside the
FROMitem, UPDATE ... FROMandDELETE ... USINGon a non-returning:execwrite,- the unique index an accepted
ON CONFLICTtarget matches, and the column references of anEXCLUDEDvalue that is not exactlyEXCLUDED.column.
sqlcj itself provides no macros, dynamic IN expansion, or query-building API.
The = ANY list predicate is the only analyzed array operator. An array
parameter is one whole list bound at one placeholder, not a placeholder list.
Unsupported schema input and unsupported column types are listed in PostgreSQL Support.
Query sources and annotations:
DefaultQueryParserTest.parsesSingleQuery,DefaultQueryParserTest.parsesMultipleQueries,DefaultQueryParserTest.preservesQueryOrder,DefaultQueryParserTest.parsesHeaderLineOfEachQuery,DefaultQueryParserTest.parsesOptionalQueryType,DefaultQueryParserTest.parsesExecQueryType,DefaultQueryParserTest.rejectsInvalidHeader,DefaultQueryParserTest.rejectsInvalidQueryType,DefaultQueryParserTest.rejectsDuplicateQueryNames, andDefaultQueryParserTest.rejectsQueryWithoutSqlcover the file format.QueryAnalyzerTest.shouldRejectSelectWithoutResultQueryType,QueryAnalyzerTest.shouldAnalyzeSelectDeclaredAsOptional,QueryAnalyzerTest.shouldRejectWriteWithoutExecQueryType,QueryAnalyzerTest.shouldRejectReturningWriteDeclaredAsExec, andQueryAnalyzerTest.shouldAnalyzeReturningWriteForEveryResultQueryTypecover annotation and statement agreement.JdbcQueryExecutorTest.shouldReturnTheRowWhenQueryOneFindsExactlyOneRow,JdbcQueryExecutorTest.shouldFailWhenQueryOneFindsNoRow,JdbcQueryExecutorTest.shouldFailWhenQueryOneFindsMoreThanOneRow,JdbcQueryExecutorTest.shouldReturnTheRowWhenQueryOptionalFindsOneRow,JdbcQueryExecutorTest.shouldReturnEmptyOptionalWhenQueryOptionalFindsNoRow,JdbcQueryExecutorTest.shouldFailWhenQueryOptionalFindsMoreThanOneRow,JdbcQueryExecutorTest.shouldReturnEmptyListWhenQueryManyFindsNoRows,JdbcQueryExecutorTest.shouldReturnAllRowsForQueryMany, andJdbcQueryExecutorTest.shouldReturnAffectedRowCountForExecutecover the cardinality results and their exact messages.PostgresIntegrationTest.shouldEnforceResultCardinalitiesAgainstPostgresexecutes:one,:optional, and:manyover zero, one, and two matching rows of a non-unique column against PostgreSQL 16, through both executor construction paths.JavaCodeGeneratorTest.shouldGenerateOptionalExecutionForOptionalQuery,JavaCodeGeneratorTest.shouldShareOneRowRecordBetweenOptionalAndOtherFullRowQueries,JavaCodeGeneratorTest.shouldNotImportOptionalWithoutAnOptionalQuery,JavaCodeGeneratorTest.shouldPassRepositoryAndQueryIdentityToEveryExecutorCall, andJavaCodeGeneratorNamingTest.shouldPassExactRepositoryAndQueryNameToTheExecutorcover the generated calls, return types, imports, and identity literals.
Reads:
QueryAnalyzerTest.shouldAnalyzeSelectWithExplicitColumns,QueryAnalyzerTest.shouldResolveAllColumns,QueryAnalyzerTest.shouldAnalyzeAliasedSingleTableSelect,QueryAnalyzerTest.shouldNameSelectedColumnAfterItsAlias,QueryAnalyzerTest.shouldNameJoinedSelectedColumnsAfterTheirAliases,QueryAnalyzerTest.shouldAnalyzeSingleInnerJoin,QueryAnalyzerTest.shouldExpandAllColumnsAcrossJoinedSourcesInOrder,QueryAnalyzerTest.shouldExpandQualifiedAllColumnsForOneSource,QueryAnalyzerTest.shouldRejectExcludedJoinReportedAsInnerJoin,QueryAnalyzerTest.shouldRejectAmbiguousUnqualifiedColumn,QueryAnalyzerTest.shouldResolveQueryParameterForComparisonOperator,QueryAnalyzerTest.shouldResolveQueryParametersInsideNestedAndOrExpressions, andQueryAnalyzerTest.shouldResolveQueryParametersInsideInExpressioncover the accepted read shapes.SqlcjCompilerIntegrationTest.shouldExecuteGeneratedAliasedQualifiedQuery,SqlcjCompilerIntegrationTest.shouldExecuteGeneratedJoinQueryWithDuplicateColumnNames,SqlcjCompilerIntegrationTest.shouldExecuteGeneratedJoinQueryWithProjectionAliases, andSqlcjCompilerIntegrationTest.shouldExecuteGeneratedMultipleJoinQuerycompile and execute them.PostgresIntegrationTest.shouldExecuteGeneratedOneQueryAgainstPostgresandPostgresIntegrationTest.shouldExecuteGeneratedManyQueryAgainstPostgresexecute an ordered list read against PostgreSQL 16.QueryAnalyzerTest.shouldResolveLikePatternParameterInTextualBindingOrder,QueryAnalyzerTest.shouldResolveLikePatternParameterFromTextColumn,QueryAnalyzerTest.shouldNotCreateParameterForLiteralLikePattern,QueryAnalyzerTest.shouldResolveNullPredicateColumnsWithoutParameters,QueryAnalyzerTest.shouldRejectUnknownColumnInNullPredicate,QueryAnalyzerTest.shouldRejectUnsupportedLikePlaceholderForm, andQueryAnalyzerTest.shouldRejectPlaceholderThatIsNotAnAnalyzedPredicateOperandcover the pattern and null predicates, andPostgresIntegrationTest.shouldExecuteGeneratedTextAndNullPredicatesAgainstPostgresexecutesLIKE, a case-insensitiveILIKE,IS NULL, andIS NOT NULLreads against PostgreSQL 16.QueryAnalyzerTest.shouldResolveRangeBoundParametersFromTestedColumn,QueryAnalyzerTest.shouldResolveNegatedRangeBoundParameters,QueryAnalyzerTest.shouldResolveRangeBoundParametersInTextualBindingOrder,QueryAnalyzerTest.shouldResolveRangeBoundParameterBesideLiteralBound,QueryAnalyzerTest.shouldNotCreateParameterForLiteralRange,QueryAnalyzerTest.shouldRejectUnknownColumnInRangePredicate, andQueryAnalyzerTest.shouldResolveNamedRangeBoundParameterscover the range predicates, andPostgresIntegrationTest.shouldExecuteGeneratedRangePredicatesAgainstPostgresexecutes aBETWEENread and aNOT BETWEENread whose bounds use out-of-order placeholder indexes against PostgreSQL 16.QueryAnalyzerTest.shouldResolveListParameterOfAnyFromItsComparedColumn,QueryAnalyzerTest.shouldResolveNamedListParameterOfAnyAtEveryOccurrence,QueryAnalyzerTest.shouldCarryTheEnumTypeAndBlankPaddingOfAListParameterOfAny,QueryAnalyzerTest.shouldResolveListParameterOfAnyInWrites,QueryAnalyzerTest.shouldNameTheCastArgumentOfAnyAfterItsComparedColumn,QueryAnalyzerTest.shouldNameACastArgumentOutsideTheListPredicateAfterItsPlaceholder,QueryAnalyzerTest.shouldRejectAListParameterOfAColumnWithoutAnArrayBinding,QueryAnalyzerTest.shouldRejectAPlaceholderOutsideTheListPredicateShape, andQueryAnalyzerTest.shouldRejectANameUsedAsBothAListAndItsElementcover the list predicate, its naming, its cast argument, and the forms that stay rejected,SqlcjCompilerIntegrationTest.shouldGenerateCompilableRepositoryForAListPredicatecompiles itsListmethod parameter and its array binding, andPostgresIntegrationTest.shouldExecuteGeneratedListPredicateAgainstPostgresandPostgresIntegrationTest.shouldExecuteGeneratedEnumListPredicateAgainstPostgresexecute an id list of no, one, and several ids and an enum-column list against PostgreSQL 16.QueryAnalyzerTest.shouldResolvePaginationParametersAfterPredicateParameters,QueryAnalyzerTest.shouldResolvePaginationParametersInTextualBindingOrder,QueryAnalyzerTest.shouldResolveOffsetParameterBesideUnanalyzedRowCount,QueryAnalyzerTest.shouldNotCreateParametersForLiteralPagination,QueryAnalyzerTest.shouldNameNamedPaginationParameterAfterItsPlaceholder, andQueryAnalyzerTest.shouldRejectPlaceholderInUnsupportedPaginationValuecover pagination, andPostgresIntegrationTest.shouldExecuteGeneratedPaginationAgainstPostgresexecutes aLIMIT ... OFFSET ...page and the same page written asOFFSET ... LIMIT ...against PostgreSQL 16.QueryAnalyzerTest.shouldResolveScalarCountColumnFromItsAlias,QueryAnalyzerTest.shouldResolveScalarCountBesidePredicateParameter,QueryAnalyzerTest.shouldResolveScalarCountBesideDirectColumn,QueryAnalyzerTest.shouldResolveScalarCountBeforeDirectColumn,QueryAnalyzerTest.shouldResolveGroupedCountBesidePredicateParameter, andQueryAnalyzerTest.shouldRejectUnsupportedCountProjectionFormcover the aliasedCOUNT(*)alone and beside a grouped column in either position, its focused diagnostic, and the count and function forms that stay rejected, andPostgresIntegrationTest.shouldExecuteGeneratedScalarCountAgainstPostgrescompiles a:onecount into a result record with oneLongcomponent and executes it against PostgreSQL 16 for a matching and a non-matching pattern, whilePostgresIntegrationTest.shouldExecuteGeneratedGroupedCountAgainstPostgresexecutes a:manygrouped count whose result record carries the groupedBooleancolumn and theLongcount in projection order.QueryAnalyzerTest.shouldResolveCastProjectionColumnFromItsAlias,QueryAnalyzerTest.shouldResolveEnumCastProjectionColumn,QueryAnalyzerTest.shouldResolveArrayCastProjectionColumn,QueryAnalyzerTest.shouldRejectUnsupportedCastProjectionForm, andQueryAnalyzerTest.shouldRejectCastPlaceholderProjectioncover both cast spellings and both alias spellings, the nullable column of the cast type including a declared enum and an array, the missing alias and the unmapped cast type, and the projected placeholder that stays rejected, andPostgresIntegrationTest.shouldExecuteGeneratedCastProjectionAgainstPostgresexecutes a:onecastSUMagainst PostgreSQL 16 whoseBigDecimalcomponent is the sum andnullwhen no row matches.QueryAnalyzerTest.shouldAnalyzeLeftJoinWithNullableJoinedColumns,QueryAnalyzerTest.shouldExpandAllColumnsOfLeftJoinedSourceAsNullable,QueryAnalyzerTest.shouldExpandQualifiedAllColumnsOfLeftJoinedSourceAsNullable,QueryAnalyzerTest.shouldAnalyzeLeftJoinAfterInnerJoin,QueryAnalyzerTest.shouldAnalyzeInnerJoinAfterLeftJoin,QueryAnalyzerTest.shouldResolveLeftJoinedParametersInTextualBindingOrder,QueryAnalyzerTest.shouldRejectUnsupportedJoinModifier, andQueryAnalyzerTest.shouldRejectLeftJoinWithoutSingleQualifiedEqualitycover both left-join spellings, the nullable columns of a left-joined source, the chained join orders, the unchanged parameters, and the join kinds andONshapes that stay rejected, andPostgresIntegrationTest.shouldExecuteGeneratedLeftJoinAgainstPostgresexecutes a left join against PostgreSQL 16 whose unmatched row reads the joined columns asnull.QueryAnalyzerTest.shouldResolveRowTableForFullRowSelect,QueryAnalyzerTest.shouldNotResolveRowTableForQuerySpecificResult,QueryAnalyzerTest.shouldNotResolveRowTableForJoinedWildcard, andQueryAnalyzerTest.shouldNotResolveRowTableForExecWritecover which reads return one complete table row.
Writes and RETURNING:
QueryAnalyzerTest.shouldAnalyzeInsert,QueryAnalyzerTest.shouldAnalyzeUpdate,QueryAnalyzerTest.shouldAnalyzeDelete,QueryAnalyzerTest.shouldAnalyzeInsertReturningColumnsInDeclaredOrder,QueryAnalyzerTest.shouldExpandInsertReturningAllColumnsInSchemaOrder,QueryAnalyzerTest.shouldAnalyzeDeleteReturningAllColumns,QueryAnalyzerTest.shouldRejectUnsupportedReturningItem,QueryAnalyzerTest.shouldRejectUnknownReturningColumn, andQueryAnalyzerTest.shouldRejectExcludedReturningWriteFormcover the write shapes.QueryAnalyzerTest.shouldAnalyzeInsertWithNonBindingValues,QueryAnalyzerTest.shouldAnalyzeNamedInsertWithNonBindingValues,QueryAnalyzerTest.shouldAnalyzeReturningInsertWithNonBindingValues,QueryAnalyzerTest.shouldAnalyzeUpdateWithNonBindingAssignments,QueryAnalyzerTest.shouldAnalyzeWriteWithoutAnyPlaceholder,QueryAnalyzerTest.shouldAnalyzeNonBindingValueOnAnUnsupportedTypeColumn,QueryAnalyzerTest.shouldRejectAnUnknownNonBindingTargetColumn,QueryAnalyzerTest.shouldRejectAPlaceholderInsideAnInsertValue,QueryAnalyzerTest.shouldRejectAPlaceholderInsideAnUpdateAssignment, andQueryAnalyzerTest.shouldRejectExcludedWriteValueFormcover the values that bind no placeholder, their numbering and binding order, and the nearby rejections.QueryAnalyzerTest.shouldAnalyzeUpsertThatUpdatesOnConflict,QueryAnalyzerTest.shouldAnalyzeReturningUpsertThatUpdatesOnConflict,QueryAnalyzerTest.shouldAnalyzeNamedUpsertThatUpdatesOnConflict,QueryAnalyzerTest.shouldAnalyzeUpsertThatDoesNothingOnConflict,QueryAnalyzerTest.shouldResolveRowTableForUpsertThatDoesNothingOnConflict,QueryAnalyzerTest.shouldRejectAConflictTargetThatIsNotColumnNames,QueryAnalyzerTest.shouldRejectExcludedConflictForm,QueryAnalyzerTest.shouldRejectAnUncastPlaceholderInsideAConflictUpdateValue, andQueryAnalyzerTest.shouldRejectAnUnknownConflictColumncover the accepted conflict target, both actions, theEXCLUDED, placeholder, cast, and computed assignments, the binding order after theVALUESplaceholders, the returning row type, and the conflict forms, unaccounted placeholder, and unknown target, assigned, andEXCLUDEDcolumns that are rejected on an:execand on a returning insert alike, andPostgresIntegrationTest.shouldExecuteGeneratedUpsertsAgainstPostgresexecutes aDO NOTHINGand aDO UPDATEaction, each with and withoutRETURNING, over a new row and then a conflicting one against PostgreSQL 16.QueryAnalyzerTest.shouldResolveRowTableForReturningAllColumnscovers the returning writes that produce a complete table row.PostgresIntegrationTest.shouldExecuteGeneratedWriteAgainstPostgres,PostgresIntegrationTest.shouldExecuteGeneratedInsertReturningAgainstPostgres,PostgresIntegrationTest.shouldExecuteGeneratedUpdateReturningAgainstPostgres, whose no-row returning update fails its:onecardinality check, andPostgresIntegrationTest.shouldExecuteGeneratedDeleteReturningAgainstPostgresexecute them against PostgreSQL 16.PostgresIntegrationTest.shouldExecuteGeneratedNonBindingWriteValuesAgainstPostgresexecutesDEFAULT, a literal,NULL,now(), andcode + 1in an:execinsert, a returning named insert, and an:execupdate against PostgreSQL 16.
Parameters:
QueryAnalyzerTest.shouldResolveBindingIndexesInTextualOrder,QueryAnalyzerTest.shouldRetainOneParameterForRepeatedIndex,QueryAnalyzerTest.shouldRejectRepeatedIndexWithConflictingType,QueryAnalyzerTest.shouldRejectGappedParameterIndexes,QueryAnalyzerTest.shouldRejectZeroParameterIndex,QueryAnalyzerTest.shouldRejectPlaceholderInUnsupportedLocation,QueryAnalyzerTest.shouldRejectAnonymousParameter, andQueryAnalyzerTest.shouldKeepPlaceholderTextThatIsNotAParametercover ordering, repetition, and rejection.SqlParserTest.shouldCompileNamedParametersByFirstOccurrence,SqlParserTest.shouldCompileNamesThatDifferInCaseAsDistinctParameters,SqlParserTest.shouldReportBothPlaceholderFormsOfOneSource,SqlParserTest.shouldNotCompileUnsupportedNamedPlaceholderForms,SqlParserTest.shouldPreserveNamedPlaceholderTextThatIsNotAParameter,SqlParameterCompilerTest.shouldReplaceReportedNamedParameterSpans,SqlParameterCompilerTest.shouldIgnoreNameSeparatedFromItsColon, andSqlParameterCompilerTest.shouldRejectNamedSpanThatDoesNotHoldTheParameterImagecover the compiled named form, its numbering, and its span guards.QueryAnalyzerTest.shouldResolveNamedParameter,QueryAnalyzerTest.shouldNumberNamedParametersByFirstOccurrence,QueryAnalyzerTest.shouldResolveNamedParametersOfUpdate,QueryAnalyzerTest.shouldResolveNamedParametersOfInsert,QueryAnalyzerTest.shouldResolveNamedParametersInInList,QueryAnalyzerTest.shouldResolveNamedLikePatternParameter,QueryAnalyzerTest.shouldResolveNamedParametersSpelledLikeKeywords,QueryAnalyzerTest.shouldRejectMixedPlaceholderForms,QueryAnalyzerTest.shouldRejectUnsupportedNamedPlaceholderForm,QueryAnalyzerTest.shouldRejectNamedPlaceholderInUnanalyzedLocation, andQueryAnalyzerTest.shouldRejectNamedPlaceholderWithConflictingTypescover the analyzed named locations, the generated parameter names, and the named rejections.SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithNamedPlaceholdersandPostgresIntegrationTest.shouldExecuteGeneratedNamedPlaceholdersAgainstPostgrescompile and execute a named query that repeats a name and orders its parameters by first occurrence.SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithOutOfOrderPlaceholders,SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithRepeatedPlaceholder,SqlcjCompilerIntegrationTest.shouldExecuteGeneratedUpdateWithOutOfOrderPlaceholders, andSqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithProtectedPlaceholderTextexecute the compiled binding order.QueryAnalyzerTest.shouldResolveNamedCastParameterOfOptionalFilter,QueryAnalyzerTest.shouldResolveNamedCastParameterInsideComputedLikePattern,QueryAnalyzerTest.shouldResolveNamedCastParameterInsideUpdateAssignment,QueryAnalyzerTest.shouldResolveCastKeywordParameterNamedAfterItsComparedColumn,QueryAnalyzerTest.shouldNameCastParameterAfterItsColumn,QueryAnalyzerTest.shouldNameCastPatternAfterItsPlaceholderInAnUntypedPatternShape,QueryAnalyzerTest.shouldNameNamedCastPatternAfterItsPlaceholder,QueryAnalyzerTest.shouldResolveEnumCastParameter,QueryAnalyzerTest.shouldResolveArrayCastParameterNamedAfterItsPlaceholder,QueryAnalyzerTest.shouldResolveBlankPaddedCastParameter,QueryAnalyzerTest.shouldNameIndexedCastParameterAfterItsIndex, andQueryAnalyzerTest.shouldResolveCastParametersOfInsertValuescover the cast types, the analyzed cast locations, and the name a cast placeholder takes, whileQueryAnalyzerTest.shouldRejectUnsupportedCastType,QueryAnalyzerTest.shouldRejectCastOccurrenceWithConflictingType,QueryAnalyzerTest.shouldRejectCastPlaceholderInUnanalyzedLocation, and the cast rows ofQueryAnalyzerTest.shouldRejectUnsupportedNamedPlaceholderFormcover the cast rejections.SqlParameterCompilerTest.shouldReportNamedParametersThatAreCastOperandsandSqlParameterCompilerTest.shouldNotReportCompiledNamedParameterThatIsACastOperandcover the cast operand the parser reports without a parse-tree node of its own.SqlcjCompilerIntegrationTest.shouldGenerateCompilableJavaForCastPlaceholderscompiles a repository whose cast and uncast placeholders of one column generate two disambiguated parameters, andPostgresIntegrationTest.shouldExecuteGeneratedCastTypedOptionalFilterAgainstPostgresexecutes a named cast-typed optional filter against PostgreSQL 16 with a null and a non-null argument.
Generated Java:
JavaNamesTestcovers the camel-case naming rules, the acronym, quoted-name, keyword, and digit-initial cases, the disambiguation suffixes, and the rejection of a SQL name without a letter or digit.JavaCodeGeneratorTestcovers the generated repository, its single executor field and constructor, the nested result records and row mappers, method signatures, and compilation of the generated source.SqlcjCompilerIntegrationTest.shouldGenerateCompilableJavaFilesandSqlcjCompilerIntegrationTest.shouldGenerateCompilableJavaForReturningWritescompile the generated output of the supported query shapes.JavaCodeGeneratorTest.shouldShareOneRowRecordAcrossFullRowQueriesOfOneTable,JavaCodeGeneratorTest.shouldGenerateOneRowRecordPerRowTable,JavaCodeGeneratorTest.shouldDisambiguateRowMapperFromQueryRowMapper, andJavaCodeGeneratorTest.shouldRejectRowTypesThatAreEqualIgnoringCasecover the shared row records, their mapper fields, and their collision.SqlcjCompilerIntegrationTest.shouldGenerateOneSharedRowRecordForTwoEntries,SqlcjCompilerIntegrationTest.shouldDeleteTheStaleRowRecordOfARemovedFullRowQuery,SqlcjCompilerIntegrationTest.shouldReportTwoEntriesThatDefineOneRowDifferently, andSqlcjCompilerIntegrationTest.shouldReportRowRecordsOfTwoEntriesThatAreEqualIgnoringCasecover the one top-level row record two entries share, its manifest entry and stale deletion, and the two cross-entry diagnostics.PostgresIntegrationTest.shouldExecuteGeneratedFullRowQueriesIntoOneSharedRowTypeAgainstPostgresexecutes a returning write, a full-row read, and a qualified full-row list of one table into one row type against PostgreSQL 16.JavaCodeGeneratorTest.shouldBindAndReadArrayColumnsPerElementType,JavaCodeGeneratorTest.shouldBindEnumArrayLabelsAndReadEnumArrayColumnsByLabel, andJavaCodeGeneratorTest.shouldImportListForAnArrayRowComponentcover the generatedListtype, theSqlArrayargument and read, and the row record'sjava.util.Listimport, andPostgresIntegrationTest.shouldRoundTripArrayValuesexecutes them against PostgreSQL 16.