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