@@ -99,13 +99,39 @@ configuration entry.
9999 positional row mapper.
100100- An unqualified column must be found in exactly one source; an ambiguous or
101101 unknown column is rejected.
102- - A result expression that is not a direct column or a wildcard, such as a
103- function call, an arithmetic expression, or a literal, is rejected.
102+ - A result expression that is not a direct column, a wildcard, or the scalar
103+ count below — such as another function call, an arithmetic expression, or a
104+ literal — is rejected.
104105- An explicit alias names the result column, so ` SELECT id AS author_id `
105106 generates the record component ` authorId ` while the component's type and
106107 nullability still come from the column. sqlcj itself resolves an alias only in
107108 the projection; every other clause reaches the database as written.
108109
110+ A read may count its matching rows with an aliased ` COUNT(*) ` as its only
111+ projection:
112+
113+ ``` sql
114+ -- name: CountAuthors :one
115+ SELECT COUNT (* ) AS total
116+ FROM authors;
117+ ```
118+
119+ - The alias is required and may be written with or without ` AS ` , so both
120+ ` COUNT(*) AS total ` and ` COUNT(*) total ` generate the result record
121+ ` CountAuthorsResult ` with the single component ` total ` .
122+ - The component is a non-null ` Long ` : the count is typed ` BIGINT ` because
123+ ` count(*) ` returns ` bigint ` , and it is never null because a count is ` 0 ` when
124+ no row matches.
125+ - The count is accepted over any supported source, predicate, ordering, and
126+ pagination shape, and under any annotation.
127+ - ` COUNT(*) ` without an alias is rejected with
128+ ` COUNT(*) requires a result alias, such as COUNT(*) AS total. ` , and a
129+ ` COUNT(*) ` beside another projection item is rejected with
130+ ` COUNT(*) must be the only SELECT item. `
131+ - Every other count is still an unsupported result expression, including
132+ ` COUNT(column) ` , ` COUNT(DISTINCT column) ` , ` COUNT(t.*) ` , a qualified
133+ ` pg_catalog.count(*) ` , and the ` FILTER ` and ` OVER ` forms.
134+
109135### Predicates
110136
111137A ` WHERE ` clause may combine:
@@ -440,8 +466,12 @@ the documented shapes are contract, tested, and safe to rely on.
440466Rejection stops the run with a diagnostic naming the query source, the query
441467name, and its header line.
442468
443- - A result expression that is not a direct column or a wildcard, including a
444- function call, an aggregate, and a literal.
469+ - A result expression that is not a direct column, a wildcard, or an aliased
470+ sole ` COUNT(*) ` , including another function call, another aggregate, and a
471+ literal. A ` COUNT(*) ` without an alias and a ` COUNT(*) ` beside another
472+ projection item each fail with their own diagnostic, while ` COUNT(column) ` ,
473+ ` COUNT(DISTINCT column) ` , ` COUNT(t.*) ` , ` pg_catalog.count(*) ` , and the
474+ ` FILTER ` and ` OVER ` forms keep the unsupported-expression rejection.
445475- A ` FROM ` item that is not a table, a comma-separated source list, a join that
446476 is not a plain inner join, a join predicate that is not one qualified
447477 equality, and a set operation such as ` UNION ` .
@@ -588,6 +618,14 @@ Reads:
588618 ` PostgresIntegrationTest.shouldExecuteGeneratedPaginationAgainstPostgres `
589619 executes a ` LIMIT ... OFFSET ... ` page and the same page written as
590620 ` OFFSET ... LIMIT ... ` against PostgreSQL 16.
621+ - ` QueryAnalyzerTest.shouldResolveScalarCountColumnFromItsAlias ` ,
622+ ` QueryAnalyzerTest.shouldResolveScalarCountBesidePredicateParameter ` , and
623+ ` QueryAnalyzerTest.shouldRejectUnsupportedCountProjectionForm ` cover the
624+ aliased sole ` COUNT(*) ` , its focused diagnostics, and the count and function
625+ forms that stay rejected, and
626+ ` PostgresIntegrationTest.shouldExecuteGeneratedScalarCountAgainstPostgres `
627+ compiles a ` :one ` count into a result record with one ` Long ` component and
628+ executes it against PostgreSQL 16 for a matching and a non-matching pattern.
591629- ` QueryAnalyzerTest.shouldResolveRowTableForFullRowSelect ` ,
592630 ` QueryAnalyzerTest.shouldNotResolveRowTableForQuerySpecificResult ` ,
593631 ` QueryAnalyzerTest.shouldNotResolveRowTableForJoinedWildcard ` , and
0 commit comments