Proper placeholder support in the AST
[dbsrgits/SQL-Abstract.git] / lib / SQL / Abstract / Test.pm
CommitLineData
fffe6900 1package SQL::Abstract::Test; # see doc at end of file
2
3use strict;
4use warnings;
5aad8cf3 5use base qw/Test::Builder::Module Exporter/;
fffe6900 6use Data::Dumper;
4abea32b 7use Test::Builder;
01dd4e4f 8use SQL::Abstract::Tree;
fffe6900 9
e7827ba2 10our @EXPORT_OK = qw/&is_same_sql_bind &is_same_sql &is_same_bind
01dd4e4f 11 &eq_sql_bind &eq_sql &eq_bind
fffe6900 12 $case_sensitive $sql_differ/;
13
a24cc3a0 14my $sqlat = SQL::Abstract::Tree->new;
15
fffe6900 16our $case_sensitive = 0;
e40f5df9 17our $parenthesis_significant = 0;
fffe6900 18our $sql_differ; # keeps track of differing portion between SQLs
5aad8cf3 19our $tb = __PACKAGE__->builder;
fffe6900 20
35149895 21# All of these keywords allow their parameters to be specified with or without parenthesis without changing the semantics
01b64cb7 22my @unrollable_ops = (
1b17d1b0 23 'ON',
24 'WHERE',
25 'GROUP \s+ BY',
26 'HAVING',
27 'ORDER \s+ BY',
28);
6f01d627 29my $unrollable_ops_re = join ' | ', @unrollable_ops;
b7b0f832 30$unrollable_ops_re = qr/$unrollable_ops_re/xi;
1b17d1b0 31
fffe6900 32sub is_same_sql_bind {
33 my ($sql1, $bind_ref1, $sql2, $bind_ref2, $msg) = @_;
34
35 # compare
25823711 36 my $same_sql = eq_sql($sql1, $sql2);
fffe6900 37 my $same_bind = eq_bind($bind_ref1, $bind_ref2);
38
a6daa642 39 # call Test::Builder::ok
1a828f61 40 my $ret = $tb->ok($same_sql && $same_bind, $msg);
fffe6900 41
42 # add debugging info
43 if (!$same_sql) {
e7827ba2 44 _sql_differ_diag($sql1, $sql2);
fffe6900 45 }
46 if (!$same_bind) {
e7827ba2 47 _bind_differ_diag($bind_ref1, $bind_ref2);
fffe6900 48 }
1a828f61 49
50 # pass ok() result further
51 return $ret;
fffe6900 52}
53
e7827ba2 54sub is_same_sql {
55 my ($sql1, $sql2, $msg) = @_;
56
57 # compare
58 my $same_sql = eq_sql($sql1, $sql2);
59
60 # call Test::Builder::ok
1a828f61 61 my $ret = $tb->ok($same_sql, $msg);
e7827ba2 62
63 # add debugging info
64 if (!$same_sql) {
65 _sql_differ_diag($sql1, $sql2);
66 }
1a828f61 67
68 # pass ok() result further
69 return $ret;
e7827ba2 70}
71
72sub is_same_bind {
73 my ($bind_ref1, $bind_ref2, $msg) = @_;
74
75 # compare
76 my $same_bind = eq_bind($bind_ref1, $bind_ref2);
77
78 # call Test::Builder::ok
1a828f61 79 my $ret = $tb->ok($same_bind, $msg);
e7827ba2 80
81 # add debugging info
82 if (!$same_bind) {
83 _bind_differ_diag($bind_ref1, $bind_ref2);
84 }
1a828f61 85
86 # pass ok() result further
87 return $ret;
e7827ba2 88}
89
90sub _sql_differ_diag {
91 my ($sql1, $sql2) = @_;
92
93 $tb->diag("SQL expressions differ\n"
94 ." got: $sql1\n"
95 ."expected: $sql2\n"
96 ."differing in :\n$sql_differ\n"
97 );
98}
99
100sub _bind_differ_diag {
101 my ($bind_ref1, $bind_ref2) = @_;
102
103 $tb->diag("BIND values differ\n"
104 ." got: " . Dumper($bind_ref1)
105 ."expected: " . Dumper($bind_ref2)
106 );
107}
108
109sub eq_sql_bind {
110 my ($sql1, $bind_ref1, $sql2, $bind_ref2) = @_;
111
112 return eq_sql($sql1, $sql2) && eq_bind($bind_ref1, $bind_ref2);
113}
114
115
fffe6900 116sub eq_bind {
117 my ($bind_ref1, $bind_ref2) = @_;
fffe6900 118
fdfbbc65 119 local $Data::Dumper::Useqq = 1;
120 local $Data::Dumper::Sortkeys = 1;
121
122 return Dumper($bind_ref1) eq Dumper($bind_ref2);
fffe6900 123}
124
125sub eq_sql {
25823711 126 my ($sql1, $sql2) = @_;
127
128 # parse
a24cc3a0 129 my $tree1 = $sqlat->parse($sql1);
130 my $tree2 = $sqlat->parse($sql2);
25823711 131
1b17d1b0 132 return 1 if _eq_sql($tree1, $tree2);
25823711 133}
134
135sub _eq_sql {
fffe6900 136 my ($left, $right) = @_;
137
939db550 138 # one is defined the other not
139 if ( (defined $left) xor (defined $right) ) {
140 return 0;
141 }
142 # one is undefined, then so is the other
143 elsif (not defined $left) {
d15c14cc 144 return 1;
145 }
0769ac0e 146 # different amount of elements
147 elsif (@$left != @$right) {
148 $sql_differ = sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse ($_) } ($left, $right) );
149 return 0;
150 }
151 # one is empty - so is the other
152 elsif (@$left == 0) {
153 return 1;
154 }
1b17d1b0 155 # one is a list, the other is an op with a list
156 elsif (ref $left->[0] xor ref $right->[0]) {
a24cc3a0 157 $sql_differ = sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse ($_) } ($left, $right) );
fffe6900 158 return 0;
159 }
1b17d1b0 160 # one is a list, so is the other
161 elsif (ref $left->[0]) {
162 for (my $i = 0; $i <= $#$left or $i <= $#$right; $i++ ) {
163 return 0 if (not _eq_sql ($left->[$i], $right->[$i]) );
164 }
165 return 1;
166 }
167 # both are an op-list combo
168 else {
169
e40f5df9 170 # unroll parenthesis if possible/allowed
171 _parenthesis_unroll ($_) for ($left, $right);
1b17d1b0 172
173 # if operators are different
b9a4fdae 174 if ( $left->[0] ne $right->[0] ) {
1b17d1b0 175 $sql_differ = sprintf "OP [$left->[0]] != [$right->[0]] in\nleft: %s\nright: %s\n",
a24cc3a0 176 $sqlat->unparse($left),
177 $sqlat->unparse($right);
1b17d1b0 178 return 0;
179 }
180 # elsif operators are identical, compare operands
01dd4e4f 181 else {
b9a4fdae 182 if ($left->[0] eq 'LITERAL' ) { # unary
01b64cb7 183 (my $l = " $left->[1][0] " ) =~ s/\s+/ /g;
184 (my $r = " $right->[1][0] ") =~ s/\s+/ /g;
1b17d1b0 185 my $eq = $case_sensitive ? $l eq $r : uc($l) eq uc($r);
01b64cb7 186 $sql_differ = "[$l] != [$r]\n" if not $eq;
1b17d1b0 187 return $eq;
188 }
189 else {
190 my $eq = _eq_sql($left->[1], $right->[1]);
a24cc3a0 191 $sql_differ ||= sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse ($_) } ($left, $right) ) if not $eq;
1b17d1b0 192 return $eq;
193 }
fffe6900 194 }
195 }
196}
197
e40f5df9 198sub _parenthesis_unroll {
199 my $ast = shift;
200
201 return if $parenthesis_significant;
202 return unless (ref $ast and ref $ast->[1]);
203
204 my $changes;
205 do {
206 my @children;
207 $changes = 0;
208
209 for my $child (@{$ast->[1]}) {
0769ac0e 210 # the current node in this loop is *always* a PAREN
e40f5df9 211 if (not ref $child or not $child->[0] eq 'PAREN') {
212 push @children, $child;
213 next;
214 }
215
216 # unroll nested parenthesis
0769ac0e 217 while ( @{$child->[1]} && $child->[1][0][0] eq 'PAREN') {
e40f5df9 218 $child = $child->[1][0];
219 $changes++;
220 }
221
222 # if the parenthesis are wrapped around an AND/OR matching the parent AND/OR - open the parenthesis up and merge the list
223 if (
224 ( $ast->[0] eq 'AND' or $ast->[0] eq 'OR')
225 and
226 $child->[1][0][0] eq $ast->[0]
227 ) {
228 push @children, @{$child->[1][0][1]};
229 $changes++;
230 }
fffe6900 231
e40f5df9 232 # if the parent operator explcitly allows it nuke the parenthesis
6f01d627 233 elsif ( $ast->[0] =~ $unrollable_ops_re ) {
e40f5df9 234 push @children, $child->[1][0];
235 $changes++;
236 }
237
4e914a7c 238 # only *ONE* LITERAL or placeholder element
9e8dab3f 239 elsif (
4e914a7c 240 @{$child->[1]} == 1 && (
241 $child->[1][0][0] eq 'LITERAL'
242 or
243 $child->[1][0][0] eq 'PLACEHOLDER'
244 )
9e8dab3f 245 ) {
246 push @children, $child->[1][0];
247 $changes++;
248 }
249
0769ac0e 250 # only one element in the parenthesis which is a binary op
251 # and has exactly two grandchildren
252 # the only time when we can *not* unroll this is when both
253 # the parent and the child are mathops (in which case we'll
254 # break precedence) or when the child is BETWEEN (special
255 # case)
e40f5df9 256 elsif (
257 @{$child->[1]} == 1
258 and
0769ac0e 259 $child->[1][0][0] =~ SQL::Abstract::Tree::_binary_op_re()
e40f5df9 260 and
0769ac0e 261 $child->[1][0][0] ne 'BETWEEN'
e40f5df9 262 and
0769ac0e 263 @{$child->[1][0][1]} == 2
264 and
265 ! (
266 $child->[1][0][0] =~ SQL::Abstract::Tree::_math_op_re()
267 and
268 $ast->[0] =~ SQL::Abstract::Tree::_math_op_re()
269 )
e40f5df9 270 ) {
271 push @children, $child->[1][0];
272 $changes++;
273 }
274
0769ac0e 275 # a function binds tighter than a mathop - see if our ancestor is a
b3b79607 276 # mathop, and our content is:
277 # a single non-mathop child with a single PAREN grandchild which
278 # would indicate mathop ( nonmathop ( ... ) )
4e914a7c 279 # or a single non-mathop with a single LITERAL ( nonmathop foo )
280 # or a single non-mathop with a single PLACEHOLDER ( nonmathop ? )
0769ac0e 281 elsif (
282 @{$child->[1]} == 1
283 and
284 @{$child->[1][0][1]} == 1
285 and
0769ac0e 286 $ast->[0] =~ SQL::Abstract::Tree::_math_op_re()
287 and
288 $child->[1][0][0] !~ SQL::Abstract::Tree::_math_op_re
b3b79607 289 and
290 (
291 $child->[1][0][1][0][0] eq 'PAREN'
4e914a7c 292 or
b3b79607 293 $child->[1][0][1][0][0] eq 'LITERAL'
4e914a7c 294 or
295 $child->[1][0][1][0][0] eq 'PLACEHOLDER'
b3b79607 296 )
0769ac0e 297 ) {
298 push @children, $child->[1][0];
299 $changes++;
300 }
301
302
e40f5df9 303 # otherwise no more mucking for this pass
304 else {
305 push @children, $child;
306 }
307 }
308
309 $ast->[1] = \@children;
310
311 } while ($changes);
312
313}
fffe6900 314
7853a177 315sub parse { $sqlat->parse(@_) }
fffe6900 3161;
317
318
319__END__
320
321=head1 NAME
322
323SQL::Abstract::Test - Helper function for testing SQL::Abstract
324
325=head1 SYNOPSIS
326
327 use SQL::Abstract;
328 use Test::More;
e7827ba2 329 use SQL::Abstract::Test import => [qw/
330 is_same_sql_bind is_same_sql is_same_bind
331 eq_sql_bind eq_sql eq_bind
332 /];
ec9af79e 333
fffe6900 334 my ($sql, @bind) = SQL::Abstract->new->select(%args);
e7827ba2 335
01dd4e4f 336 is_same_sql_bind($given_sql, \@given_bind,
fffe6900 337 $expected_sql, \@expected_bind, $test_msg);
338
e7827ba2 339 is_same_sql($given_sql, $expected_sql, $test_msg);
340 is_same_bind(\@given_bind, \@expected_bind, $test_msg);
341
01dd4e4f 342 my $is_same = eq_sql_bind($given_sql, \@given_bind,
e7827ba2 343 $expected_sql, \@expected_bind);
344
345 my $sql_same = eq_sql($given_sql, $expected_sql);
346 my $bind_same = eq_bind(\@given_bind, \@expected_bind);
347
fffe6900 348=head1 DESCRIPTION
349
350This module is only intended for authors of tests on
351L<SQL::Abstract|SQL::Abstract> and related modules;
352it exports functions for comparing two SQL statements
353and their bound values.
354
355The SQL comparison is performed on I<abstract syntax>,
356ignoring differences in spaces or in levels of parentheses.
357Therefore the tests will pass as long as the semantics
358is preserved, even if the surface syntax has changed.
359
ec9af79e 360B<Disclaimer> : the semantic equivalence handling is pretty limited.
361A lot of effort goes into distinguishing significant from
362non-significant parenthesis, including AND/OR operator associativity.
363Currently this module does not support commutativity and more
364intelligent transformations like Morgan laws, etc.
365
01dd4e4f 366For a good overview of what this test framework is capable of refer
ec9af79e 367to C<t/10test.t>
fffe6900 368
369=head1 FUNCTIONS
370
371=head2 is_same_sql_bind
372
01dd4e4f 373 is_same_sql_bind($given_sql, \@given_bind,
fffe6900 374 $expected_sql, \@expected_bind, $test_msg);
375
376Compares given and expected pairs of C<($sql, \@bind)>, and calls
e7827ba2 377L<Test::Builder/ok> on the result, with C<$test_msg> as message. If the test
378fails, a detailed diagnostic is printed. For clients which use L<Test::More>,
379this is the one of the three functions (L</is_same_sql_bind>, L</is_same_sql>,
380L</is_same_bind>) that needs to be imported.
381
382=head2 is_same_sql
383
384 is_same_sql($given_sql, $expected_sql, $test_msg);
385
386Compares given and expected SQL statements, and calls L<Test::Builder/ok> on
387the result, with C<$test_msg> as message. If the test fails, a detailed
388diagnostic is printed. For clients which use L<Test::More>, this is the one of
389the three functions (L</is_same_sql_bind>, L</is_same_sql>, L</is_same_bind>)
390that needs to be imported.
391
392=head2 is_same_bind
393
394 is_same_bind(\@given_bind, \@expected_bind, $test_msg);
395
396Compares given and expected bind values, and calls L<Test::Builder/ok> on the
397result, with C<$test_msg> as message. If the test fails, a detailed diagnostic
398is printed. For clients which use L<Test::More>, this is the one of the three
399functions (L</is_same_sql_bind>, L</is_same_sql>, L</is_same_bind>) that needs
400to be imported.
401
402=head2 eq_sql_bind
403
01dd4e4f 404 my $is_same = eq_sql_bind($given_sql, \@given_bind,
e7827ba2 405 $expected_sql, \@expected_bind);
406
407Compares given and expected pairs of C<($sql, \@bind)>. Similar to
408L</is_same_sql_bind>, but it just returns a boolean value and does not print
409diagnostics or talk to L<Test::Builder>.
fffe6900 410
411=head2 eq_sql
412
413 my $is_same = eq_sql($given_sql, $expected_sql);
414
e7827ba2 415Compares the abstract syntax of two SQL statements. Similar to L</is_same_sql>,
416but it just returns a boolean value and does not print diagnostics or talk to
417L<Test::Builder>. If the result is false, the global variable L</$sql_differ>
418will contain the SQL portion where a difference was encountered; this is useful
419for printing diagnostics.
fffe6900 420
421=head2 eq_bind
422
423 my $is_same = eq_sql(\@given_bind, \@expected_bind);
424
e7827ba2 425Compares two lists of bind values, taking into account the fact that some of
426the values may be arrayrefs (see L<SQL::Abstract/bindtype>). Similar to
427L</is_same_bind>, but it just returns a boolean value and does not print
428diagnostics or talk to L<Test::Builder>.
fffe6900 429
430=head1 GLOBAL VARIABLES
431
e7827ba2 432=head2 $case_sensitive
fffe6900 433
434If true, SQL comparisons will be case-sensitive. Default is false;
435
e40f5df9 436=head2 $parenthesis_significant
437
438If true, SQL comparison will preserve and report difference in nested
439parenthesis. Useful for testing the C<-nest> modifier. Defaults to false;
440
e7827ba2 441=head2 $sql_differ
fffe6900 442
443When L</eq_sql> returns false, the global variable
444C<$sql_differ> contains the SQL portion
445where a difference was encountered.
446
447
448=head1 SEE ALSO
449
a6daa642 450L<SQL::Abstract>, L<Test::More>, L<Test::Builder>.
fffe6900 451
25823711 452=head1 AUTHORS
fffe6900 453
454Laurent Dami, E<lt>laurent.dami AT etat geneve chE<gt>
455
25823711 456Norbert Buchmuller <norbi@nix.hu>
457
e96c510a 458Peter Rabbitson <ribasushi@cpan.org>
459
fffe6900 460=head1 COPYRIGHT AND LICENSE
461
462Copyright 2008 by Laurent Dami.
463
464This library is free software; you can redistribute it and/or modify
01dd4e4f 465it under the same terms as Perl itself.