Work on compat layer
[dbsrgits/SQL-Abstract-2.0-ish.git] / lib / SQL / Abstract / Compat.pm
CommitLineData
0bcf772f 1use MooseX::Declare;
2
3class SQL::Abstract::Compat {
7c300b3a 4
5 use Moose::Util::TypeConstraints;
bad761ba 6 use MooseX::Types::Moose qw/Str ScalarRef ArrayRef HashRef/;
0a18aa4f 7 use SQL::Abstract::Types::Compat ':all';
1c51edc4 8 use SQL::Abstract::Types qw/AST/;
0a18aa4f 9 use SQL::Abstract::AST::v1;
10 use Data::Dump qw/pp/;
1c51edc4 11 use Devel::PartialDump qw/dump/;
12 use Carp qw/croak/;
bad761ba 13
0a18aa4f 14 class_type 'SQL::Abstract';
7c300b3a 15 clean;
16
17 has logic => (
18 is => 'rw',
19 isa => LogicEnum,
aa0f2366 20 default => 'AND',
f94aef7f 21 coerce => 1,
22 required => 1,
7c300b3a 23 );
24
0a18aa4f 25 has visitor => (
26 is => 'rw',
27 isa => 'SQL::Abstract',
28 clearer => 'clear_visitor',
29 lazy => 1,
30 builder => '_build_visitor',
31 );
bad761ba 32
f94aef7f 33 has cmp => (
34 is => 'rw',
35 isa => 'Str',
36 default => '=',
37 required => 1,
38 );
39
40 our %CMP_MAP = (
41 '=' => '==',
42 );
bad761ba 43
c7e5fddf 44 has convert => (
45 is => 'rw',
46 isa => 'Str',
47 predicate => 'has_field_convertor'
48 );
49
7c300b3a 50 method select(Str|ArrayRef|ScalarRef $from, ArrayRef|Str $fields,
0a18aa4f 51 WhereType $where?,
52 WhereType $order?)
53 {
68960b60 54 my $ast = $self->select_ast($from,$fields,$where,$order);
55
56 return ($self->visitor->dispatch($ast), $self->visitor->binds);
57 }
58 method select_ast(Str|ArrayRef|ScalarRef $from, ArrayRef|Str $fields,
59 WhereType $where?,
60 WhereType $order?)
61 {
1c51edc4 62 my $ast = {
63 -type => 'select',
64 columns => [
65 map {
c7e5fddf 66 $self->mk_name(0, $_)
1c51edc4 67 } ( is_Str($fields) ? $fields : @$fields )
68 ],
69 tablespec => $self->tablespec($from)
70 };
0a18aa4f 71
1c51edc4 72
73 $ast->{where} = $self->recurse_where($where)
74 if defined $where;
68960b60 75 return $ast;
0a18aa4f 76 }
bad761ba 77
0a18aa4f 78 method where(WhereType $where,
79 WhereType $order?)
80 {
81 my $ret = "";
82
83 if ($where) {
1c51edc4 84 my $ast = $self->recurse_where($where);
0a18aa4f 85 $ret .= "WHERE " . $self->visitor->_expr($ast);
86 }
87
88 return $ret;
7c300b3a 89 }
bad761ba 90
0a18aa4f 91 method _build_visitor() {
92 return SQL::Abstract->create(1);
93 }
94
1c51edc4 95 sub mk_name {
c7e5fddf 96 my ($self, $use_convert) = (shift,shift);
97 my $ast = { -type => 'name', args => [ @_ ] };
98
99 return $ast
100 unless $use_convert && $self->has_field_convertor;
101
102 return $self->apply_convert($ast);
1c51edc4 103 }
104
105 method tablespec(Str|ArrayRef|ScalarRef $from) {
c7e5fddf 106 return $self->mk_name(0, $from)
1c51edc4 107 if is_Str($from);
108 }
109
110 method recurse_where(WhereType $ast, LogicEnum $logic?) returns (AST) {
111 return $self->recurse_where_hash($logic || 'AND', $ast) if is_HashRef($ast);
112 return $self->recurse_where_array($logic || 'OR', $ast) if is_ArrayRef($ast);
113 croak "Unknown where clause type " . dump($ast);
114 }
115
116 method recurse_where_hash(LogicEnum $logic, HashRef $ast) returns (AST) {
117 my @args;
118 my $ret = {
119 -type => 'expr',
120 op => lc $logic,
121 args => \@args
122 };
123
124 while (my ($key,$value) = each %$ast) {
125 if ($key =~ /^-(or|and)$/) {
126 my $val = $self->recurse_where($value, uc $1);
127 if ($val->{op} eq $ret->{op}) {
128 push @args, @{$val->{args}};
129 }
130 else {
131 push @args, $val;
132 }
133 next;
134 }
135
136 push @args, $self->field($key, $value);
137 }
138
139 return $args[0] if @args == 1;
140
141 return $ret;
142 }
143
144 method recurse_where_array(LogicEnum $logic, ArrayRef $ast) returns (AST) {
145 my @args;
146 my $ret = {
147 -type => 'expr',
148 op => lc $logic,
149 args => \@args
150 };
151 my @nodes = @$ast;
152
153 while (my $key = shift @nodes) {
154 if ($key =~ /^-(or|and)$/) {
155 my $value = shift @nodes
156 or confess "missing value after $key at " . dump($ast);
157
158 my $val = $self->recurse_where($value, uc $1);
159 if ($val->{op} eq $ret->{op}) {
160 push @args, @{$val->{args}};
161 }
162 else {
163 push @args, $val;
164 }
165 next;
166 }
167
168 push @args, $self->recurse_where($key);
169 }
170
171 return $args[0] if @args == 1;
172
173 return $ret;
174 }
175
176 method field(Str $key, $value) returns (AST) {
f94aef7f 177 my $op = $CMP_MAP{$self->cmp} || $self->cmp;
1c51edc4 178 my $ret = {
179 -type => 'expr',
f94aef7f 180 op => $op,
1c51edc4 181 args => [
c7e5fddf 182 $self->mk_name(1, $key)
1c51edc4 183 ],
184 };
185
186 if (is_HashRef($value)) {
187 my ($op, @rest) = keys %$value;
188 confess "Don't know how to handle " . dump($value) . " (too many keys)"
189 if @rest;
68960b60 190 $value = $value->{$op};
1c51edc4 191
192 # TODO: Validate the op?
68960b60 193 if ($op =~ /^-?(?:(not)[_ ])?([a-z_]+)$/i) {
194 $ret->{op} = lc $2;
195 $ret->{op} = "not_" . $ret->{op} if $1;
1c51edc4 196
68960b60 197 if (is_ArrayRef($value)) {
1c51edc4 198 push @{$ret->{args}}, $self->value($_)
68960b60 199 for @{$value};
1c51edc4 200 return $ret;
201 }
202 }
203 else {
204 $ret->{op} = $op;
205 }
68960b60 206
207 if (is_ArrayRef($value)) {
208 local $self->{cmp} = $op;
209
210 my $ast = {
211 -type => 'expr',
212 # Handle e => { '!=', [qw(f g)] }.
213 # SQLA treats this as a 'DWIM'
214 op => $op eq '!=' ? 'or' : 'and',
215 args => [ map {
216 $self->field($key, $_)
217 } @{$value} ]
218 };
219 return $ast;
220 }
221 push @{$ret->{args}}, $self->value($value);
1c51edc4 222
223 }
224 elsif (is_ArrayRef($value)) {
225 # Return an or clause, sort of.
226 return {
227 -type => 'expr',
228 op => 'or',
229 args => [ map {
68960b60 230 $self->field($key, $_)
1c51edc4 231 } @$value ]
232 };
233 }
234 else {
235 push @{$ret->{args}}, $self->value($value);
236 }
237
238 return $ret;
239 }
240
241 method value($value) returns (AST) {
c7e5fddf 242 return $self->apply_convert( { -type => 'value', value => $value })
1c51edc4 243 if is_Str($value);
244
245 confess "Don't know how to handle terminal value " . dump($value);
246 }
247
c7e5fddf 248 method apply_convert(AST $ast) {
249 return $ast unless $self->has_field_convertor;
250
251 return {
252 -type => 'expr',
253 op => $self->convert,
254 args => [ $ast ]
255 };
256 }
257
1c51edc4 258
0bcf772f 259}
260
261=head1 NAME
262
263SQL::Abstract::Compant - compatibility layer for SQL::Abstrct v 1.xx
264
265=head1 DESCRIPTION
266
267This class attempts to maintain the original behaviour of version 1 of
268SQL::Abstract. It does this by internally converting to an AST and then using
269the standard AST visitor.
270
271If so desired, you can get hold of this transformed AST somehow. This is aimed
272at libraries such as L<DBIx::Class> that use SQL::Abstract-style arrays or
273hashes as part of their public interface.
274
275=head1 AUTHOR
276
277Ash Berlin C<< <ash@cpan.org> >>
278
279=cut
280
2811;