good formatting for both subqueries and otherwise
[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
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 }
1b17d1b0 144 # one is a list, the other is an op with a list
145 elsif (ref $left->[0] xor ref $right->[0]) {
a24cc3a0 146 $sql_differ = sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse ($_) } ($left, $right) );
fffe6900 147 return 0;
148 }
1b17d1b0 149 # one is a list, so is the other
150 elsif (ref $left->[0]) {
151 for (my $i = 0; $i <= $#$left or $i <= $#$right; $i++ ) {
152 return 0 if (not _eq_sql ($left->[$i], $right->[$i]) );
153 }
154 return 1;
155 }
156 # both are an op-list combo
157 else {
158
e40f5df9 159 # unroll parenthesis if possible/allowed
160 _parenthesis_unroll ($_) for ($left, $right);
1b17d1b0 161
162 # if operators are different
b9a4fdae 163 if ( $left->[0] ne $right->[0] ) {
1b17d1b0 164 $sql_differ = sprintf "OP [$left->[0]] != [$right->[0]] in\nleft: %s\nright: %s\n",
a24cc3a0 165 $sqlat->unparse($left),
166 $sqlat->unparse($right);
1b17d1b0 167 return 0;
168 }
169 # elsif operators are identical, compare operands
01dd4e4f 170 else {
b9a4fdae 171 if ($left->[0] eq 'LITERAL' ) { # unary
01b64cb7 172 (my $l = " $left->[1][0] " ) =~ s/\s+/ /g;
173 (my $r = " $right->[1][0] ") =~ s/\s+/ /g;
1b17d1b0 174 my $eq = $case_sensitive ? $l eq $r : uc($l) eq uc($r);
01b64cb7 175 $sql_differ = "[$l] != [$r]\n" if not $eq;
1b17d1b0 176 return $eq;
177 }
178 else {
179 my $eq = _eq_sql($left->[1], $right->[1]);
a24cc3a0 180 $sql_differ ||= sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse ($_) } ($left, $right) ) if not $eq;
1b17d1b0 181 return $eq;
182 }
fffe6900 183 }
184 }
185}
186
e40f5df9 187sub _parenthesis_unroll {
188 my $ast = shift;
189
190 return if $parenthesis_significant;
191 return unless (ref $ast and ref $ast->[1]);
192
193 my $changes;
194 do {
195 my @children;
196 $changes = 0;
197
198 for my $child (@{$ast->[1]}) {
199 if (not ref $child or not $child->[0] eq 'PAREN') {
200 push @children, $child;
201 next;
202 }
203
204 # unroll nested parenthesis
205 while ($child->[1][0][0] eq 'PAREN') {
206 $child = $child->[1][0];
207 $changes++;
208 }
209
210 # if the parenthesis are wrapped around an AND/OR matching the parent AND/OR - open the parenthesis up and merge the list
211 if (
212 ( $ast->[0] eq 'AND' or $ast->[0] eq 'OR')
213 and
214 $child->[1][0][0] eq $ast->[0]
215 ) {
216 push @children, @{$child->[1][0][1]};
217 $changes++;
218 }
fffe6900 219
e40f5df9 220 # if the parent operator explcitly allows it nuke the parenthesis
221 elsif ( grep { $ast->[0] =~ /^ $_ $/xi } @unrollable_ops ) {
222 push @children, $child->[1][0];
223 $changes++;
224 }
225
b9a4fdae 226 # only one LITERAL element in the parenthesis
9e8dab3f 227 elsif (
b9a4fdae 228 @{$child->[1]} == 1 && $child->[1][0][0] eq 'LITERAL'
9e8dab3f 229 ) {
230 push @children, $child->[1][0];
231 $changes++;
232 }
233
b9a4fdae 234 # only one element in the parenthesis which is a binary op with two LITERAL sub-children
e40f5df9 235 elsif (
236 @{$child->[1]} == 1
237 and
01dd4e4f 238 grep { $child->[1][0][0] =~ /^ $_ $/xi } (SQL::Abstract::Tree::_binary_op_keywords())
e40f5df9 239 and
b9a4fdae 240 $child->[1][0][1][0][0] eq 'LITERAL'
e40f5df9 241 and
b9a4fdae 242 $child->[1][0][1][1][0] eq 'LITERAL'
e40f5df9 243 ) {
244 push @children, $child->[1][0];
245 $changes++;
246 }
247
248 # otherwise no more mucking for this pass
249 else {
250 push @children, $child;
251 }
252 }
253
254 $ast->[1] = \@children;
255
256 } while ($changes);
257
258}
fffe6900 259
fffe6900 2601;
261
262
263__END__
264
265=head1 NAME
266
267SQL::Abstract::Test - Helper function for testing SQL::Abstract
268
269=head1 SYNOPSIS
270
271 use SQL::Abstract;
272 use Test::More;
e7827ba2 273 use SQL::Abstract::Test import => [qw/
274 is_same_sql_bind is_same_sql is_same_bind
275 eq_sql_bind eq_sql eq_bind
276 /];
ec9af79e 277
fffe6900 278 my ($sql, @bind) = SQL::Abstract->new->select(%args);
e7827ba2 279
01dd4e4f 280 is_same_sql_bind($given_sql, \@given_bind,
fffe6900 281 $expected_sql, \@expected_bind, $test_msg);
282
e7827ba2 283 is_same_sql($given_sql, $expected_sql, $test_msg);
284 is_same_bind(\@given_bind, \@expected_bind, $test_msg);
285
01dd4e4f 286 my $is_same = eq_sql_bind($given_sql, \@given_bind,
e7827ba2 287 $expected_sql, \@expected_bind);
288
289 my $sql_same = eq_sql($given_sql, $expected_sql);
290 my $bind_same = eq_bind(\@given_bind, \@expected_bind);
291
fffe6900 292=head1 DESCRIPTION
293
294This module is only intended for authors of tests on
295L<SQL::Abstract|SQL::Abstract> and related modules;
296it exports functions for comparing two SQL statements
297and their bound values.
298
299The SQL comparison is performed on I<abstract syntax>,
300ignoring differences in spaces or in levels of parentheses.
301Therefore the tests will pass as long as the semantics
302is preserved, even if the surface syntax has changed.
303
ec9af79e 304B<Disclaimer> : the semantic equivalence handling is pretty limited.
305A lot of effort goes into distinguishing significant from
306non-significant parenthesis, including AND/OR operator associativity.
307Currently this module does not support commutativity and more
308intelligent transformations like Morgan laws, etc.
309
01dd4e4f 310For a good overview of what this test framework is capable of refer
ec9af79e 311to C<t/10test.t>
fffe6900 312
313=head1 FUNCTIONS
314
315=head2 is_same_sql_bind
316
01dd4e4f 317 is_same_sql_bind($given_sql, \@given_bind,
fffe6900 318 $expected_sql, \@expected_bind, $test_msg);
319
320Compares given and expected pairs of C<($sql, \@bind)>, and calls
e7827ba2 321L<Test::Builder/ok> on the result, with C<$test_msg> as message. If the test
322fails, a detailed diagnostic is printed. For clients which use L<Test::More>,
323this is the one of the three functions (L</is_same_sql_bind>, L</is_same_sql>,
324L</is_same_bind>) that needs to be imported.
325
326=head2 is_same_sql
327
328 is_same_sql($given_sql, $expected_sql, $test_msg);
329
330Compares given and expected SQL statements, and calls L<Test::Builder/ok> on
331the result, with C<$test_msg> as message. If the test fails, a detailed
332diagnostic is printed. For clients which use L<Test::More>, this is the one of
333the three functions (L</is_same_sql_bind>, L</is_same_sql>, L</is_same_bind>)
334that needs to be imported.
335
336=head2 is_same_bind
337
338 is_same_bind(\@given_bind, \@expected_bind, $test_msg);
339
340Compares given and expected bind values, and calls L<Test::Builder/ok> on the
341result, with C<$test_msg> as message. If the test fails, a detailed diagnostic
342is printed. For clients which use L<Test::More>, this is the one of the three
343functions (L</is_same_sql_bind>, L</is_same_sql>, L</is_same_bind>) that needs
344to be imported.
345
346=head2 eq_sql_bind
347
01dd4e4f 348 my $is_same = eq_sql_bind($given_sql, \@given_bind,
e7827ba2 349 $expected_sql, \@expected_bind);
350
351Compares given and expected pairs of C<($sql, \@bind)>. Similar to
352L</is_same_sql_bind>, but it just returns a boolean value and does not print
353diagnostics or talk to L<Test::Builder>.
fffe6900 354
355=head2 eq_sql
356
357 my $is_same = eq_sql($given_sql, $expected_sql);
358
e7827ba2 359Compares the abstract syntax of two SQL statements. Similar to L</is_same_sql>,
360but it just returns a boolean value and does not print diagnostics or talk to
361L<Test::Builder>. If the result is false, the global variable L</$sql_differ>
362will contain the SQL portion where a difference was encountered; this is useful
363for printing diagnostics.
fffe6900 364
365=head2 eq_bind
366
367 my $is_same = eq_sql(\@given_bind, \@expected_bind);
368
e7827ba2 369Compares two lists of bind values, taking into account the fact that some of
370the values may be arrayrefs (see L<SQL::Abstract/bindtype>). Similar to
371L</is_same_bind>, but it just returns a boolean value and does not print
372diagnostics or talk to L<Test::Builder>.
fffe6900 373
374=head1 GLOBAL VARIABLES
375
e7827ba2 376=head2 $case_sensitive
fffe6900 377
378If true, SQL comparisons will be case-sensitive. Default is false;
379
e40f5df9 380=head2 $parenthesis_significant
381
382If true, SQL comparison will preserve and report difference in nested
383parenthesis. Useful for testing the C<-nest> modifier. Defaults to false;
384
e7827ba2 385=head2 $sql_differ
fffe6900 386
387When L</eq_sql> returns false, the global variable
388C<$sql_differ> contains the SQL portion
389where a difference was encountered.
390
391
392=head1 SEE ALSO
393
a6daa642 394L<SQL::Abstract>, L<Test::More>, L<Test::Builder>.
fffe6900 395
25823711 396=head1 AUTHORS
fffe6900 397
398Laurent Dami, E<lt>laurent.dami AT etat geneve chE<gt>
399
25823711 400Norbert Buchmuller <norbi@nix.hu>
401
e96c510a 402Peter Rabbitson <ribasushi@cpan.org>
403
fffe6900 404=head1 COPYRIGHT AND LICENSE
405
406Copyright 2008 by Laurent Dami.
407
408This library is free software; you can redistribute it and/or modify
01dd4e4f 409it under the same terms as Perl itself.