DBIx-Class-Async
view release on metacpan or search on metacpan
lib/DBIx/Class/Async/SelectNormaliser.pm view on Meta::CPAN
The C<select> attribute is processed by a different code path inside
L<SQL::Abstract> -- specifically C<_select_field> -- which does not recognise
C<-ident> as a special sigil and instead treats it as a function name. The
hash key C<-ident> becomes the function, its value becomes the argument, and
the result is the literal string C<-IDENT(me.status)> in the SQL output,
which is a syntax error on every database.
=head2 Why not fix upstream?
Extending C<select> to support C<-ident> in L<DBIx::Class> or
L<SQL::Abstract::More> is non-trivial:
=over 4
=item *
C<select> hashrefs are already used for function calls:
C<< { count => 'me.id', -as => 'cnt' } >>. Adding C<-ident> as a special
sigil inside the same hashref form requires distinguishing
C<< { -ident => 'col', -as => 'alias' } >> (identifier alias) from
C<< { func => 'col', -as => 'alias' } >> (function call) without introducing
ambiguity. A column named C<ident> would be indistinguishable from the
operator.
=item *
The C<select>/C<as> separation is deliberate DBIC design: C<select> is a list
of SQL expressions and C<as> is a parallel list of Perl-side aliases. Adding
inline C<-as> to C<select> as well (which DBIC already supports for functions)
and now C<-ident> would create three overlapping ways to alias a column, all
with subtly different semantics.
=item *
Changing this in C<SQL::Abstract> would affect all DBIC users and all other
consumers of SQL::Abstract, requiring deprecation cycles and backwards
compatibility guarantees that are beyond the scope of a single distribution.
=back
=head2 The solution: pre-processing in DBIx::Class::Async
Rather than patching upstream, this module pre-processes the C<select> and
C<as> attributes before they reach L<SQL::Abstract>. Any
C<< { -ident => $col, -as => $alias } >> hashref is rewritten to its canonical
DBIC form: a bare column name string in C<select> and a corresponding entry in
C<as>. All other forms -- bare strings, function hashrefs, literal SQL
references -- are left completely unchanged.
This approach:
=over 4
=item *
B<Requires no upstream changes.> The transformation happens entirely in
DBIx::Class::Async before the attrs touch SQL::Abstract.
=item *
B<Is transparent to callers.> Application code that already uses the
canonical C<select>/C<as> form is unaffected. Callers who prefer the
C<-ident> form get intuitive, correct behaviour.
=item *
B<Is safe to compose.> Function hashrefs (C<< { count => 'me.id' } >>) are
detected by the absence of C<-ident> and passed through untouched, so all
existing query patterns continue to work.
=item *
B<Is explicit about intent.> C<-ident> says clearly "this is a column
name, not a function and not a literal string", which is useful documentation
in itself.
=back
=head2 Integration Points
This module is called from two places in DBIx::Class::Async:
=over 4
=item C<DBIx::Class::Async::ResultSet::search()>
Before building the payload for the worker, C<search()> calls
L</normalise_attrs> on the incoming attrs hashref. This means all ResultSet
operations that flow through C<search> (C<all>, C<count>, C<update>, etc.)
benefit automatically.
=item C<DBIx::Class::Async::ResultSet::search_rs()>
The same normalisation is applied when building a new ResultSet object, so
chained searches also produce correct SQL.
=back
=head2 Interaction with C<-as> in function hashrefs
DBIC supports an inline C<-as> inside function hashrefs:
{ count => 'me.id', -as => 'total' }
This module does B<not> touch that form. The C<-as> key is only consumed when
it appears alongside C<-ident>. In a function hashref, C<-as> is already
handled correctly by DBIC and SQL::Abstract and must not be extracted into the
C<as> array, because DBIC expects the alias to come from the function hashref
itself in that case.
=head2 Partial C<as> arrays
The incoming C<as> array may be shorter than C<select>, absent entirely, or
partially specified. This module fills in missing entries from C<-as> values
found in the C<select> items. Entries already present in C<as> take priority
over any C<-as> in the corresponding C<select> item, preserving the behaviour
of callers who specify both.
=cut
=head1 METHODS
( run in 0.739 second using v1.01-cache-2.11-cpan-39bf76dae61 )