Fix syntax error in example
[dbsrgits/SQL-Abstract.git] / lib / DBIx / Class / SQLMaker / Role / SQLA2Passthrough.pm
CommitLineData
5410c047 1package DBIx::Class::SQLMaker::Role::SQLA2Passthrough;
2
78227631 3use strict;
4use warnings;
5use Exporter 'import';
6
48389aca 7our @EXPORT = qw(on);
78227631 8
9sub on (&) {
10 my ($on) = @_;
11 sub {
12 my ($args) = @_;
13 $args->{self_resultsource}
14 ->schema->storage->sql_maker
15 ->expand_join_condition(
16 $on->($args),
17 $args
18 );
19 }
20}
21
5410c047 22use Role::Tiny;
23
24around select => sub {
25 my ($orig, $self, $table, $fields, $where, $rs_attrs, $limit, $offset) = @_;
26
27 $fields = \[ $self->render_expr({ -list => [
28 grep defined,
29 map +(ref($_) eq 'HASH'
30 ? do {
31 my %f = %$_;
32 my $as = delete $f{-as};
33 my ($f, $rhs) = %f;
48389aca 34 my $func = +{ ($f =~ /^-/ ? $f : "-${f}") => $rhs };
5410c047 35 ($as
36 ? +{ -op => [ 'as', $func, { -ident => [ $as ] } ] }
37 : $func)
38 }
39 : $_), ref($fields) eq 'ARRAY' ? @$fields : $fields
40 ] }, -ident) ];
41
42 if (my $gb = $rs_attrs->{group_by}) {
43 $rs_attrs = {
44 %$rs_attrs,
45 group_by => \[ $self->render_expr({ -list => $gb }, -ident) ]
46 };
47 }
48 $self->$orig($table, $fields, $where, $rs_attrs, $limit, $offset);
48389aca 49};
5410c047 50
78227631 51sub expand_join_condition {
52 my ($self, $cond, $args) = @_;
09cfd2c8 53 my ($type, %known) = do {
54 if (my $obj = $args->{self_result_object}) {
55 (self => $obj->get_columns)
56 } elsif (my $val = $args->{foreign_values}) {
57 (foreign => %$val)
58 } else {
59 ('')
60 }
61 };
62 my $maybe = $type ? 1 : 0;
63 my $outside;
78227631 64 my $wrap = sub {
65 my ($orig) = @_;
09cfd2c8 66 $outside = $orig;
78227631 67 sub {
68 my $res = $orig->(@_);
09cfd2c8 69 my ($name, $col) = @{$res->{-ident}};
78227631 70 if ($name eq 'self' or $name eq 'foreign') {
09cfd2c8 71 if ($type eq $name) {
72 $maybe = 0 unless exists $known{$col};
73 }
74 return { -ident => [ $args->{"${name}_alias"}, $col ] };
78227631 75 }
76 return $res;
77 };
78 };
79 my $sqla = $self->clone->wrap_op_expander(ident => $wrap);
09cfd2c8 80 my $aqt = $sqla->expand_expr($cond, -ident);
81 return $aqt unless $maybe;
82 my $inner_wrap = sub {
83 my $res = $outside->(@_);
84 my ($name, $col) = @{$res->{-ident}};
85 if ($name eq 'self' or $name eq 'foreign') {
86 if ($type eq $name) {
87 return { -bind => [ $args->{"${name}_alias"}.'.'.$col, $known{$col} ] };
88 }
89 return { -ident => [ $args->{"${name}_alias"}, $col ] };
90 }
91 return $res;
92 };
93 $sqla->op_expander(ident => $inner_wrap);
94 my $inner_aqt = $self->_collapsify($sqla->expand_expr($cond, -ident));
95 return ($aqt, $inner_aqt);
96}
97
98sub _collapsify {
99 my ($self, $aqt) = @_;
100 return $aqt unless my @opargs = @{$aqt->{-op}};
101 my ($logop, @args) = @opargs;
102 return $aqt unless $logop eq 'and';
103 my %collapsed = map {
104 my $q = $_;
105 return $aqt unless my @opargs = @{$q->{-op}};
106 my ($op, $lhs, @rest) = @opargs;
107 return $aqt unless my @ident = @{$lhs->{-ident}};
108 (join('.', @ident), { $op => \@rest });
109 } @args;
110 return \%collapsed;
78227631 111}
112
5410c047 1131;
78227631 114
115__END__
116
82283e69 117=head1 NAME
118
119DBIx::Class::SQLMaker::Role::SQLA2Passthrough - A test of future possibilities
120
121=head1 SYNOPSIS
122
123=over 4
124
125=item * select and group_by options are processed using the richer SQLA2 code
126
127=item * expand_join_condition is provided to more easily express rich joins
128
129=back
130
131See C<examples/sqla2passthrough.pl> for a small amount of running code.
132
78227631 133=head1 SETUP
134
135 (on_connect_call => sub {
136 my ($storage) = @_;
137 $storage->sql_maker
138 ->with::roles('DBIx::Class::SQLMaker::Role::SQLA2Passthrough');
139 })
140
141=head2 expand_join_condition
142
143 __PACKAGE__->has_many(minions => 'Blah::Person' => sub {
144 my ($args) = @_;
145 $args->{self_resultsource}
146 ->schema->storage->sql_maker
147 ->expand_join_condition(
148 $args
149 );
150 });
151
152=head2 on
153
154 __PACKAGE__->has_many(minions => 'Blah::Person' => on {
155 { 'self.group_id' => 'foreign.group_id',
156 'self.rank' => { '>', 'foreign.rank' } }
157 });
158
159Or with ParameterizedJoinHack,
160
161 __PACKAGE__->parameterized_has_many(
162 priority_tasks => 'MySchema::Result::Task',
163 [['min_priority'] => sub {
164 my $args = shift;
165 return +{
166 "$args->{foreign_alias}.owner_id" => {
167 -ident => "$args->{self_alias}.id",
168 },
169 "$args->{foreign_alias}.priority" => {
170 '>=' => $_{min_priority},
171 },
172 };
173 }],
174 );
175
176becomes
177
178 __PACKAGE__->parameterized_has_many(
179 priority_tasks => 'MySchema::Result::Task',
180 [['min_priority'] => on {
181 { 'foreign.owner_id' => 'self.id',
182 'foreign.priority' => { '>=', { -value => $_{min_priority} } } }
183 }]
184 );
82283e69 185
663bb686 186Note that foreign/self can appear in such a condition on either side, BUT
187if you want L<DBIx::Class> to be able to use a join-less version you must
188ensure that the LHS is all foreign columns, i.e.
189
190 on {
191 +{
192 'foreign.x' => 'self.x',
193 'self.y' => { -between => [ 'foreign.y1', 'foreign.y2' ] }
194 }
195 }
196
197is completely valid but DBIC will insist on doing a JOIN even if you
198have a fully populated row object to call C<search_related> on - to avoid
199the spurious JOIN, you must specify it with explicit LHS foreign cols as:
200
201 on {
202 +{
203 'foreign.x' => 'self.x',
204 'foreign.y1' => { '<=', 'self.y' },
205 'foreign.y2' => { '>=', 'self.y' },
206 }
207 }
208
82283e69 209=cut