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