@@ -179,6 +179,38 @@ its placeholder, while a placeholder as the tested value, as in
179179A comparison that binds no placeholder, such as ` active = TRUE ` , contributes no
180180generated parameter and reaches the database as written.
181181
182+ A placeholder written as the direct operand of ` ::type ` or ` CAST(... AS type) `
183+ is typed by that cast instead of by the clause it appears in, anywhere inside
184+ the ` WHERE ` clause: beside a compared column, as the tested value of ` IS NULL ` ,
185+ inside a concatenation, as a function argument, as an ` IN ` element, as a range
186+ bound, as a pattern, or as the operand of another operator such as the array
187+ overlap ` && ` . This makes the optional filter and the computed pattern idioms
188+ compile:
189+
190+ ``` sql
191+ -- name: ListUsersByName :many
192+ SELECT id, name
193+ FROM users
194+ WHERE (:name::text IS NULL OR name = :name)
195+ ORDER BY id;
196+
197+ -- name: SearchUsers :many
198+ SELECT id, name
199+ FROM users
200+ WHERE name LIKE ' %' || :term::text || ' %' ;
201+ ```
202+
203+ ` ListUsersByName ` generates ` listUsersByName(String name) ` , which returns every
204+ row for a null argument, and ` SearchUsers ` generates
205+ ` searchUsers(String term) ` . The cast itself reaches the database as written. A
206+ cast pattern states its own type, so the pattern restrictions above apply only
207+ to an uncast pattern: ` name NOT LIKE $1::text ` , ` name SIMILAR TO $1::text ` , and
208+ ` name LIKE $1::text ESCAPE '!' ` are accepted and typed ` text ` , whatever the
209+ tested column's type is. Such a pattern is named after its placeholder rather
210+ than after the tested column, so an indexed one is ` param<N> ` , as in ` param1 `
211+ for ` $1 ` . [ Parameters] ( #parameters ) states the accepted cast types and the name
212+ a cast placeholder takes.
213+
182214### Ordering
183215
184216` ORDER BY ` over direct columns is supported for a stable list order, as in
@@ -272,6 +304,22 @@ value binds no placeholder, such as
272304` UPDATE authors SET version = version + 1 ` , generates a method without
273305parameters.
274306
307+ An ` INSERT ` value or an ` UPDATE ` assignment may also compute a value from a cast
308+ placeholder, which is typed by its cast wherever it appears inside the value:
309+
310+ ``` sql
311+ -- name: UpdateAuthorBioOrKeep :exec
312+ UPDATE authors
313+ SET bio = COALESCE(:bio::text , bio)
314+ WHERE id = :id;
315+ ```
316+
317+ ` UpdateAuthorBioOrKeep ` generates ` updateAuthorBioOrKeep(String bio, Long id) `
318+ and keeps the assignment bound before the predicate. A cast placeholder that is
319+ the whole value, as in ` SET bio = :bio::text ` , is typed by its cast as well and
320+ named after its column. A value that contains an uncast placeholder, such as
321+ ` COALESCE($2, bio) ` , stays rejected.
322+
275323### ` RETURNING `
276324
277325A supported ` INSERT ` , ` UPDATE ` , or ` DELETE ` declared ` :one ` , ` :optional ` , or
@@ -314,6 +362,26 @@ string literal, a quoted identifier, or a comment is not a parameter.
314362 ` pageSize ` rather than ` limit ` .
315363- The Java type of a placeholder is the type of the column it is compared with,
316364 assigned to, or inserted into.
365+ - A placeholder written as the direct operand of ` ::type ` or
366+ ` CAST(... AS type) ` is typed by that cast instead, wherever it appears inside
367+ a ` WHERE ` clause, an ` INSERT ` value, or an ` UPDATE ` assignment. The cast type
368+ may be any type a column may declare and sqlcj maps, including a declared
369+ enum name, written unquoted and matched case-insensitively, and a
370+ one-dimensional array, which binds a ` List ` . A cast type sqlcj does not map,
371+ such as ` INTERVAL ` or a multi-dimensional ` INT[][] ` , is rejected naming the
372+ placeholder and the type.
373+ - A named cast placeholder keeps its own name. An indexed cast placeholder
374+ keeps the name of the column whose value it is — a compared column, the
375+ tested column of ` IN ` or ` BETWEEN ` , the tested column of a plain ` LIKE ` or
376+ ` ILIKE ` pattern, which is the shape that accepts an uncast pattern, or an
377+ inserted or assigned column — so ` name = $1::text ` names ` name ` as
378+ ` name = $1 ` does. Every other indexed cast placeholder is named ` param<N> `
379+ after its own index, as in ` param1 ` for ` $1 ` , and colliding names take the
380+ usual numeric suffix.
381+ - Occurrences of one placeholder may mix a cast and an uncast location, and the
382+ parameter keeps the name and type of its first occurrence, so
383+ ` (:name::text IS NULL OR name = :name) ` is one ` String ` parameter named
384+ ` name ` .
317385- Mixing ` $N ` and ` :name ` placeholders in one query is rejected, as are
318386 anonymous ` ? ` placeholders, a qualified name such as ` :a.b ` , a quoted name
319387 such as ` :"x" ` , and an ` &name ` placeholder.
@@ -586,22 +654,30 @@ name, and its header line.
586654 is not a plain inner or left join, a join predicate that is not one qualified
587655 equality, and a set operation such as ` UNION ` .
588656- A placeholder in a location sqlcj does not analyze, including ` ORDER BY $1 ` , a
589- named placeholder under a function such as ` lower(name) = :name ` or under a
590- cast such as ` :name::text ` , a computed ` LIKE ` pattern such as
657+ named placeholder under a function such as ` lower(name) = :name ` , a computed
658+ ` LIKE ` pattern such as
591659 ` '%' || $1 || '%' ` , a placeholder as the
592660 tested value of a range such as ` $1 BETWEEN id AND id ` , a computed range
593661 bound such as ` id BETWEEN $1 + 1 AND $2 ` , a computed pagination value such as
594662 ` LIMIT $1 + 1 ` or ` OFFSET $1 + 1 ` , a ` LIMIT a, b ` row count such as
595663 ` LIMIT 5, $1 ` , a placeholder inside a write value such as
596664 ` COALESCE($2, bio) ` , and a ` FETCH FIRST $1 ROWS ONLY ` clause, so dynamic ` IN `
597- expansion is unavailable.
665+ expansion is unavailable. A cast does not widen these locations: a cast
666+ placeholder in a projection, ` ORDER BY ` , a join condition, or a pagination
667+ value, such as ` ORDER BY $1::int ` , stays rejected, as does a placeholder that
668+ is not the direct operand of its cast, such as ` (:x)::int ` .
669+ - A cast type sqlcj does not map, such as ` $1::interval ` or a
670+ multi-dimensional ` $1::int[][] ` , which is rejected naming the placeholder and
671+ the declared type.
598672- A ` LIKE ` -family pattern placeholder that is negated, uses another keyword such
599673 as ` SIMILAR TO ` , carries an ` ESCAPE ` clause or a ` BINARY ` modifier, tests a
600- non-text column, or stands as the tested value.
674+ non-text column, or stands as the tested value. These restrict the uncast
675+ pattern placeholder; a cast pattern states its own type.
601676- Anonymous ` ? ` placeholders, ` $N ` and ` :name ` placeholders mixed in one query,
602677 a qualified name such as ` :a.b ` , a quoted name such as ` :"x" ` , an ` &name `
603- placeholder, one name whose occurrences have conflicting types, and
604- non-contiguous or non-positive placeholder indexes.
678+ placeholder, each of them also as the operand of a cast, such as
679+ ` :"x"::text ` , ` &x::text ` , or ` :a.b::text ` , one name whose occurrences have
680+ conflicting types, and non-contiguous or non-positive placeholder indexes.
605681- An ` INSERT ` without an explicit column list, with more than one ` VALUES ` row,
606682 or built from a ` SELECT ` ; an ` UPDATE ` assignment that sets a column list, such
607683 as ` SET (name, active) = ('a', TRUE) ` ; and a written column that the table does
@@ -841,6 +917,35 @@ Parameters:
841917 and
842918 ` SqlcjCompilerIntegrationTest.shouldExecuteGeneratedQueryWithProtectedPlaceholderText `
843919 execute the compiled binding order.
920+ - ` QueryAnalyzerTest.shouldResolveNamedCastParameterOfOptionalFilter ` ,
921+ ` QueryAnalyzerTest.shouldResolveNamedCastParameterInsideComputedLikePattern ` ,
922+ ` QueryAnalyzerTest.shouldResolveNamedCastParameterInsideUpdateAssignment ` ,
923+ ` QueryAnalyzerTest.shouldResolveCastKeywordParameterNamedAfterItsComparedColumn ` ,
924+ ` QueryAnalyzerTest.shouldNameCastParameterAfterItsColumn ` ,
925+ ` QueryAnalyzerTest.shouldNameCastPatternAfterItsPlaceholderInAnUntypedPatternShape ` ,
926+ ` QueryAnalyzerTest.shouldNameNamedCastPatternAfterItsPlaceholder ` ,
927+ ` QueryAnalyzerTest.shouldResolveEnumCastParameter ` ,
928+ ` QueryAnalyzerTest.shouldResolveArrayCastParameterNamedAfterItsPlaceholder ` ,
929+ ` QueryAnalyzerTest.shouldResolveBlankPaddedCastParameter ` ,
930+ ` QueryAnalyzerTest.shouldNameIndexedCastParameterAfterItsIndex ` , and
931+ ` QueryAnalyzerTest.shouldResolveCastParametersOfInsertValues ` cover the cast
932+ types, the analyzed cast locations, and the name a cast placeholder takes,
933+ while ` QueryAnalyzerTest.shouldRejectUnsupportedCastType ` ,
934+ ` QueryAnalyzerTest.shouldRejectCastOccurrenceWithConflictingType ` ,
935+ ` QueryAnalyzerTest.shouldRejectCastPlaceholderInUnanalyzedLocation ` , and the
936+ cast rows of
937+ ` QueryAnalyzerTest.shouldRejectUnsupportedNamedPlaceholderForm ` cover the
938+ cast rejections.
939+ - ` SqlParameterCompilerTest.shouldReportNamedParametersThatAreCastOperands ` and
940+ ` SqlParameterCompilerTest.shouldNotReportCompiledNamedParameterThatIsACastOperand `
941+ cover the cast operand the parser reports without a parse-tree node of its
942+ own.
943+ - ` SqlcjCompilerIntegrationTest.shouldGenerateCompilableJavaForCastPlaceholders `
944+ compiles a repository whose cast and uncast placeholders of one column
945+ generate two disambiguated parameters, and
946+ ` PostgresIntegrationTest.shouldExecuteGeneratedCastTypedOptionalFilterAgainstPostgres `
947+ executes a named cast-typed optional filter against PostgreSQL 16 with a null
948+ and a non-null argument.
844949
845950Generated Java:
846951
0 commit comments