@@ -171,8 +171,8 @@ after `code` and disambiguated as `code1` and `code2`. The bounds are bound in
171171textual order, the start bound before the end bound, whatever their placeholder
172172indexes are. A placeholder bound beside a literal bound, as in
173173` code BETWEEN $1 AND 10 ` , is typed the same way and binds one parameter. A named
174- bound such as ` code BETWEEN :lo AND :hi ` is rejected like any other named
175- placeholder, while a placeholder as the tested value, as in
174+ bound such as ` code BETWEEN :lo AND :hi ` is typed the same way and named after
175+ its placeholder, while a placeholder as the tested value, as in
176176` $1 BETWEEN code AND code ` , and a placeholder inside a computed bound, as in
177177` code BETWEEN $1 + 1 AND $2 ` , are rejected as unanalyzed placeholder locations.
178178
@@ -213,8 +213,10 @@ LIMIT $1 OFFSET $2;
213213 parameter.
214214- Both values must be non-negative. sqlcj generates no validation, so PostgreSQL
215215 rejects a negative value when the query executes.
216- - A named value such as ` LIMIT :n ` or ` OFFSET :n ` is rejected like any other
217- named placeholder. A placeholder in a computed value, as in ` LIMIT $1 + 1 ` or
216+ - A named value is named after its placeholder rather than after its clause, so
217+ ` LIMIT :pageSize OFFSET :skip ` generates
218+ ` listAuthorPage(Integer pageSize, Integer skip) ` .
219+ - A placeholder in a computed value, as in ` LIMIT $1 + 1 ` or
218220 ` OFFSET $1 + 1 ` , in the ` LIMIT a, b ` form, as in ` LIMIT 5, $1 ` , and in a
219221 ` FETCH FIRST $1 ROWS ONLY ` clause are rejected as unanalyzed placeholder
220222 locations.
@@ -225,8 +227,8 @@ A write targets exactly one table of its entry's schema.
225227
226228| Statement | Accepted shape |
227229| --- | --- |
228- | ` INSERT ` | an explicit column list and a single ` VALUES ` row whose values are all ` $N ` placeholders |
229- | ` UPDATE ` | ` SET ` assignments that each assign one direct column a single ` $N ` placeholder, with an optional ` WHERE ` using the read predicate forms |
230+ | ` INSERT ` | an explicit column list and a single ` VALUES ` row whose values are all placeholders |
231+ | ` UPDATE ` | ` SET ` assignments that each assign one direct column a single placeholder, with an optional ` WHERE ` using the read predicate forms |
230232| ` DELETE ` | one target table with an optional ` WHERE ` using the read predicate forms |
231233
232234``` sql
@@ -268,24 +270,40 @@ table expressions are rejected in a returning write.
268270
269271## Parameters
270272
271- sqlcj uses PostgreSQL ` $N ` placeholders. The compiler replaces each real
272- placeholder token with a JDBC ` ? ` and leaves every other character of the
273- statement byte-for-byte unchanged, so ` $1 ` inside a string literal, a quoted
274- identifier, or a comment is not a parameter.
273+ A query writes its parameters either as PostgreSQL ` $N ` placeholders or as
274+ ` :name ` placeholders, and one query uses one of the two forms. The compiler
275+ replaces each real placeholder token with a JDBC ` ? ` and leaves every other
276+ character of the statement byte-for-byte unchanged, so ` $1 ` or ` :id ` inside a
277+ string literal, a quoted identifier, or a comment is not a parameter.
275278
276279- Placeholder indexes must be positive and contiguous from ` $1 ` .
280+ - A named placeholder is a colon followed directly by an unquoted name of ASCII
281+ letters, digits, and underscores that does not start with a digit, such as
282+ ` :userId ` . Names are compared exactly as written, so ` :term ` and ` :Term ` are
283+ two parameters, and a name spelled like a SQL keyword, such as ` :limit ` ,
284+ ` :user ` , or ` :year ` , is an ordinary name.
285+ - Each distinct name is one parameter, numbered by its first textual
286+ occurrence, and every occurrence of that name is bound at its own ` ? `
287+ position.
288+ - A named placeholder is accepted wherever a ` $N ` placeholder is, and names its
289+ generated method parameter after itself, so ` LIMIT :pageSize ` generates
290+ ` pageSize ` rather than ` limit ` .
277291- The Java type of a placeholder is the type of the column it is compared with,
278292 assigned to, or inserted into.
279- - Anonymous ` ? ` placeholders and named ` :name ` placeholders are rejected.
293+ - Mixing ` $N ` and ` :name ` placeholders in one query is rejected, as are
294+ anonymous ` ? ` placeholders, a qualified name such as ` :a.b ` , a quoted name
295+ such as ` :"x" ` , and an ` &name ` placeholder.
280296- A placeholder in a location sqlcj does not analyze — for example ` ORDER BY $1 `
281- — is rejected rather than left unbound.
297+ or ` lower(name) = :name ` — is rejected rather than left unbound.
282298
283299### Logical order versus textual order
284300
285301The two orders are distinct and both are observable:
286302
287303- ** Logical order** is placeholder index order. It is the order of the generated
288- method parameters: ` $1 ` is the first method parameter, ` $2 ` the second.
304+ method parameters: ` $1 ` is the first method parameter, ` $2 ` the second. In a
305+ named query it is first-occurrence order: the name written first is the first
306+ method parameter.
289307- ** Textual order** is the order in which placeholder tokens appear in the SQL.
290308 It is the JDBC binding order of the generated ` ? ` positions.
291309
@@ -308,11 +326,24 @@ public int updateAuthorBio(Long id, String bio) {
308326}
309327```
310328
311- An index may repeat. A repeated index produces one method parameter, named and
312- typed from its first occurrence, and its value is bound at every textual
313- position where the index occurs. Occurrences of one index whose inferred Java
329+ An index and a name may repeat. A repeated index produces one method parameter,
330+ named and typed from its first occurrence, and its value is bound at every
331+ textual position where the index occurs; a repeated name behaves the same way
332+ and keeps its own name. Occurrences of one index or one name whose inferred Java
314333types differ are rejected.
315334
335+ ` UpdateAuthorBio ` written with named placeholders states the same two orders:
336+
337+ ``` sql
338+ -- name: UpdateAuthorBio :exec
339+ UPDATE authors
340+ SET bio = :bio
341+ WHERE id = :id;
342+ ```
343+
344+ The generated method takes ` (bio, id) ` , because ` :bio ` occurs first, and binds
345+ ` (bio, id) ` .
346+
316347## Generated Java
317348
318349Each configuration entry generates one final repository class in the configured
@@ -531,7 +562,9 @@ name, and its header line.
531562 is not a plain inner or left join, a join predicate that is not one qualified
532563 equality, and a set operation such as ` UNION ` .
533564- A placeholder in a location sqlcj does not analyze, including ` ORDER BY $1 ` , a
534- computed ` LIKE ` pattern such as ` '%' || $1 || '%' ` , a placeholder as the
565+ named placeholder under a function such as ` lower(name) = :name ` or under a
566+ cast such as ` :name::text ` , a computed ` LIKE ` pattern such as
567+ ` '%' || $1 || '%' ` , a placeholder as the
535568 tested value of a range such as ` $1 BETWEEN id AND id ` , a computed range
536569 bound such as ` id BETWEEN $1 + 1 AND $2 ` , a computed pagination value such as
537570 ` LIMIT $1 + 1 ` or ` OFFSET $1 + 1 ` , a ` LIMIT a, b ` row count such as
@@ -540,11 +573,10 @@ name, and its header line.
540573- A ` LIKE ` -family pattern placeholder that is negated, uses another keyword such
541574 as ` SIMILAR TO ` , carries an ` ESCAPE ` clause or a ` BINARY ` modifier, tests a
542575 non-text column, or stands as the tested value.
543- - A named range bound such as ` id BETWEEN :lo AND :hi ` and a named pagination
544- value such as ` LIMIT :n ` or ` OFFSET :n ` , which fail with the named-placeholder
545- diagnostic.
546- - Anonymous ` ? ` and named ` :name ` placeholders, and non-contiguous or
547- non-positive placeholder indexes.
576+ - Anonymous ` ? ` placeholders, ` $N ` and ` :name ` placeholders mixed in one query,
577+ a qualified name such as ` :a.b ` , a quoted name such as ` :"x" ` , an ` &name `
578+ placeholder, one name whose occurrences have conflicting types, and
579+ non-contiguous or non-positive placeholder indexes.
548580- An ` INSERT ` without an explicit column list, with more than one ` VALUES ` row,
549581 with a value that is not a placeholder, or built from a ` SELECT ` ; and an
550582 ` UPDATE ` assignment that is not a single placeholder.
@@ -569,9 +601,9 @@ them:
569601- ` ON CONFLICT ` , ` UPDATE ... FROM ` , and ` DELETE ... USING ` on a non-returning
570602 ` :exec ` write.
571603
572- sqlcj itself provides no named parameters, macros, array operators such as
573- ` = ANY ` , dynamic ` IN ` expansion, or query-building API. An array parameter is
574- one whole list bound at one placeholder, not a placeholder list.
604+ sqlcj itself provides no macros, array operators such as ` = ANY ` , dynamic ` IN `
605+ expansion, or query-building API. An array parameter is one whole list bound at
606+ one placeholder, not a placeholder list.
575607
576608Unsupported schema input and unsupported column types are listed in
577609[ PostgreSQL Support] ( postgresql.md#unsupported-types-and-ddl ) .
@@ -659,16 +691,16 @@ Reads:
659691 ` QueryAnalyzerTest.shouldResolveRangeBoundParameterBesideLiteralBound ` ,
660692 ` QueryAnalyzerTest.shouldNotCreateParameterForLiteralRange ` ,
661693 ` QueryAnalyzerTest.shouldRejectUnknownColumnInRangePredicate ` , and
662- ` QueryAnalyzerTest.shouldRejectNamedRangeBound ` cover the range predicates,
663- and
694+ ` QueryAnalyzerTest.shouldResolveNamedRangeBoundParameters ` cover the range
695+ predicates, and
664696 ` PostgresIntegrationTest.shouldExecuteGeneratedRangePredicatesAgainstPostgres `
665697 executes a ` BETWEEN ` read and a ` NOT BETWEEN ` read whose bounds use
666698 out-of-order placeholder indexes against PostgreSQL 16.
667699- ` QueryAnalyzerTest.shouldResolvePaginationParametersAfterPredicateParameters ` ,
668700 ` QueryAnalyzerTest.shouldResolvePaginationParametersInTextualBindingOrder ` ,
669701 ` QueryAnalyzerTest.shouldResolveOffsetParameterBesideUnanalyzedRowCount ` ,
670702 ` QueryAnalyzerTest.shouldNotCreateParametersForLiteralPagination ` ,
671- ` QueryAnalyzerTest.shouldRejectNamedPaginationValue ` , and
703+ ` QueryAnalyzerTest.shouldNameNamedPaginationParameterAfterItsPlaceholder ` , and
672704 ` QueryAnalyzerTest.shouldRejectPlaceholderInUnsupportedPaginationValue ` cover
673705 pagination, and
674706 ` PostgresIntegrationTest.shouldExecuteGeneratedPaginationAgainstPostgres `
@@ -731,10 +763,36 @@ Parameters:
731763 ` QueryAnalyzerTest.shouldRejectGappedParameterIndexes ` ,
732764 ` QueryAnalyzerTest.shouldRejectZeroParameterIndex ` ,
733765 ` QueryAnalyzerTest.shouldRejectPlaceholderInUnsupportedLocation ` ,
734- ` QueryAnalyzerTest.shouldRejectAnonymousParameter ` ,
735- ` QueryAnalyzerTest.shouldRejectNamedParameter ` , and
766+ ` QueryAnalyzerTest.shouldRejectAnonymousParameter ` , and
736767 ` QueryAnalyzerTest.shouldKeepPlaceholderTextThatIsNotAParameter ` cover
737768 ordering, repetition, and rejection.
769+ - ` SqlParserTest.shouldCompileNamedParametersByFirstOccurrence ` ,
770+ ` SqlParserTest.shouldCompileNamesThatDifferInCaseAsDistinctParameters ` ,
771+ ` SqlParserTest.shouldReportBothPlaceholderFormsOfOneSource ` ,
772+ ` SqlParserTest.shouldNotCompileUnsupportedNamedPlaceholderForms ` ,
773+ ` SqlParserTest.shouldPreserveNamedPlaceholderTextThatIsNotAParameter ` ,
774+ ` SqlParameterCompilerTest.shouldReplaceReportedNamedParameterSpans ` ,
775+ ` SqlParameterCompilerTest.shouldIgnoreNameSeparatedFromItsColon ` , and
776+ ` SqlParameterCompilerTest.shouldRejectNamedSpanThatDoesNotHoldTheParameterImage `
777+ cover the compiled named form, its numbering, and its span guards.
778+ - ` QueryAnalyzerTest.shouldResolveNamedParameter ` ,
779+ ` QueryAnalyzerTest.shouldNumberNamedParametersByFirstOccurrence ` ,
780+ ` QueryAnalyzerTest.shouldResolveNamedParametersOfUpdate ` ,
781+ ` QueryAnalyzerTest.shouldResolveNamedParametersOfInsert ` ,
782+ ` QueryAnalyzerTest.shouldResolveNamedParametersInInList ` ,
783+ ` QueryAnalyzerTest.shouldResolveNamedLikePatternParameter ` ,
784+ ` QueryAnalyzerTest.shouldResolveNamedParametersSpelledLikeKeywords ` ,
785+ ` QueryAnalyzerTest.shouldRejectMixedPlaceholderForms ` ,
786+ ` QueryAnalyzerTest.shouldRejectUnsupportedNamedPlaceholderForm ` ,
787+ ` QueryAnalyzerTest.shouldRejectNamedPlaceholderInUnanalyzedLocation ` , and
788+ ` QueryAnalyzerTest.shouldRejectNamedPlaceholderWithConflictingTypes ` cover the
789+ analyzed named locations, the generated parameter names, and the named
790+ rejections.
791+ - ` SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithNamedPlaceholders `
792+ and
793+ ` PostgresIntegrationTest.shouldExecuteGeneratedNamedPlaceholdersAgainstPostgres `
794+ compile and execute a named query that repeats a name and orders its
795+ parameters by first occurrence.
738796- ` SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithOutOfOrderPlaceholders ` ,
739797 ` SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithRepeatedPlaceholder ` ,
740798 ` SqlcjCompilerIntegrationTest.shouldExecuteGeneratedUpdateWithOutOfOrderPlaceholders ` ,
0 commit comments