don't re-expand maybe list expr when already list
[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;
ba3f5e40 5use base qw(Test::Builder::Module);
4abea32b 6use Test::Builder;
328c5eac 7use Test::Deep ();
01dd4e4f 8use SQL::Abstract::Tree;
fffe6900 9
c9383a7b 10if (my $class = $ENV{SQL_ABSTRACT_TEST_AGAINST}) {
11 my $mod = join('/', split '::', $class).".pm";
12 require $mod;
13 eval qq{sub SQL::Abstract () { "\Q${class}\E" }; 1}
14 or die "Failed to create const sub for ${class}: $@";
15}
16
2fadf08e 17our @EXPORT_OK = qw(
18 is_same_sql_bind is_same_sql is_same_bind
19 eq_sql_bind eq_sql eq_bind dumper diag_where
20 $case_sensitive $sql_differ
21);
fffe6900 22
a24cc3a0 23my $sqlat = SQL::Abstract::Tree->new;
24
fffe6900 25our $case_sensitive = 0;
e40f5df9 26our $parenthesis_significant = 0;
0c2de280 27our $order_by_asc_significant = 0;
28
fffe6900 29our $sql_differ; # keeps track of differing portion between SQLs
70af236b 30our $tb; # not documented, but someone might be overriding it anyway
fffe6900 31
70c6f0e9 32sub _unpack_arrayrefref {
33
34 my @args;
35 for (1,2) {
36 my $chunk = shift @_;
37
ca4f826a 38 if (ref $chunk eq 'REF' and ref $$chunk eq 'ARRAY') {
70c6f0e9 39 my ($sql, @bind) = @$$chunk;
40 push @args, ($sql, \@bind);
41 }
42 else {
43 push @args, $chunk, shift @_;
44 }
45
46 }
47
48 # maybe $msg and ... stuff
49 push @args, @_;
50
51 @args;
52}
53
fffe6900 54sub is_same_sql_bind {
70c6f0e9 55 my ($sql1, $bind_ref1, $sql2, $bind_ref2, $msg) = &_unpack_arrayrefref;
fffe6900 56
57 # compare
25823711 58 my $same_sql = eq_sql($sql1, $sql2);
fffe6900 59 my $same_bind = eq_bind($bind_ref1, $bind_ref2);
60
a6daa642 61 # call Test::Builder::ok
70af236b 62 my $tb = $tb || __PACKAGE__->builder;
1a828f61 63 my $ret = $tb->ok($same_sql && $same_bind, $msg);
fffe6900 64
65 # add debugging info
66 if (!$same_sql) {
e7827ba2 67 _sql_differ_diag($sql1, $sql2);
fffe6900 68 }
69 if (!$same_bind) {
e7827ba2 70 _bind_differ_diag($bind_ref1, $bind_ref2);
fffe6900 71 }
1a828f61 72
73 # pass ok() result further
74 return $ret;
fffe6900 75}
76
e7827ba2 77sub is_same_sql {
78 my ($sql1, $sql2, $msg) = @_;
79
80 # compare
70c6f0e9 81 my $same_sql = eq_sql($sql1, $sql2);
e7827ba2 82
83 # call Test::Builder::ok
70af236b 84 my $tb = $tb || __PACKAGE__->builder;
1a828f61 85 my $ret = $tb->ok($same_sql, $msg);
e7827ba2 86
87 # add debugging info
88 if (!$same_sql) {
89 _sql_differ_diag($sql1, $sql2);
90 }
1a828f61 91
92 # pass ok() result further
93 return $ret;
e7827ba2 94}
95
96sub is_same_bind {
97 my ($bind_ref1, $bind_ref2, $msg) = @_;
98
99 # compare
100 my $same_bind = eq_bind($bind_ref1, $bind_ref2);
101
102 # call Test::Builder::ok
70af236b 103 my $tb = $tb || __PACKAGE__->builder;
1a828f61 104 my $ret = $tb->ok($same_bind, $msg);
e7827ba2 105
106 # add debugging info
107 if (!$same_bind) {
108 _bind_differ_diag($bind_ref1, $bind_ref2);
109 }
1a828f61 110
111 # pass ok() result further
112 return $ret;
e7827ba2 113}
114
2fadf08e 115sub dumper {
70c6f0e9 116 # FIXME
117 # if we save the instance, we will end up with $VARx references
118 # no time to figure out how to avoid this (Deepcopy is *not* an option)
119 require Data::Dumper;
120 Data::Dumper->new([])->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Maxdepth(0)
121 ->Values([@_])->Dump;
2fadf08e 122}
123
124sub diag_where{
70af236b 125 my $tb = $tb || __PACKAGE__->builder;
ca4f826a 126 $tb->diag("Search term:\n" . &dumper);
2fadf08e 127}
128
e7827ba2 129sub _sql_differ_diag {
70c6f0e9 130 my $sql1 = shift || '';
131 my $sql2 = shift || '';
e7827ba2 132
70af236b 133 my $tb = $tb || __PACKAGE__->builder;
9a9b40f8 134
135 if (my $profile = $ENV{SQL_ABSTRACT_TEST_TREE_PROFILE}) {
136 my $sqlat = SQL::Abstract::Tree->new(profile => $profile);
137 $_ = $sqlat->format($_) for ($sql1, $sql2);
138 }
139
ca4f826a 140 $tb->${\($tb->in_todo ? 'note' : 'diag')} (
b3437526 141 "SQL expressions differ\n"
70c6f0e9 142 ." got: $sql1\n"
143 ."want: $sql2\n"
144 ."\nmismatch around\n$sql_differ\n"
b3437526 145 );
e7827ba2 146}
147
148sub _bind_differ_diag {
149 my ($bind_ref1, $bind_ref2) = @_;
150
e026e02a 151 my $tb = $tb || __PACKAGE__->builder;
ca4f826a 152 $tb->${\($tb->in_todo ? 'note' : 'diag')} (
70c6f0e9 153 "BIND values differ " . dumper({ got => $bind_ref1, want => $bind_ref2 })
154 );
e7827ba2 155}
156
157sub eq_sql_bind {
70c6f0e9 158 my ($sql1, $bind_ref1, $sql2, $bind_ref2) = &_unpack_arrayrefref;
e7827ba2 159
160 return eq_sql($sql1, $sql2) && eq_bind($bind_ref1, $bind_ref2);
161}
162
163
328c5eac 164sub eq_bind { goto &Test::Deep::eq_deeply };
fffe6900 165
166sub eq_sql {
25823711 167 my ($sql1, $sql2) = @_;
168
169 # parse
a24cc3a0 170 my $tree1 = $sqlat->parse($sql1);
171 my $tree2 = $sqlat->parse($sql2);
25823711 172
6f2a5b66 173 undef $sql_differ;
1b17d1b0 174 return 1 if _eq_sql($tree1, $tree2);
25823711 175}
176
177sub _eq_sql {
fffe6900 178 my ($left, $right) = @_;
179
939db550 180 # one is defined the other not
ca4f826a 181 if ((defined $left) xor (defined $right)) {
182 $sql_differ = sprintf ("[%s] != [%s]\n", map { defined $_ ? $sqlat->unparse($_) : 'N/A' } ($left, $right) );
939db550 183 return 0;
184 }
6f2a5b66 185
939db550 186 # one is undefined, then so is the other
187 elsif (not defined $left) {
d15c14cc 188 return 1;
189 }
6f2a5b66 190
191 # both are empty
192 elsif (@$left == 0 and @$right == 0) {
0769ac0e 193 return 1;
194 }
6f2a5b66 195
196 # one is empty
197 if (@$left == 0 or @$right == 0) {
ca4f826a 198 $sql_differ = sprintf ("left: %s\nright: %s\n", map { @$_ ? $sqlat->unparse($_) : 'N/A'} ($left, $right) );
6f2a5b66 199 return 0;
200 }
201
1b17d1b0 202 # one is a list, the other is an op with a list
203 elsif (ref $left->[0] xor ref $right->[0]) {
6f2a5b66 204 $sql_differ = sprintf ("[%s] != [%s]\nleft: %s\nright: %s\n", map
ca4f826a 205 { ref $_ ? $sqlat->unparse($_) : $_ }
6f2a5b66 206 ($left->[0], $right->[0], $left, $right)
207 );
fffe6900 208 return 0;
209 }
6f2a5b66 210
211 # both are lists
1b17d1b0 212 elsif (ref $left->[0]) {
213 for (my $i = 0; $i <= $#$left or $i <= $#$right; $i++ ) {
6f2a5b66 214 if (not _eq_sql ($left->[$i], $right->[$i]) ) {
215 if (! $sql_differ or $sql_differ !~ /left\:\s .+ right:\s/xs) {
216 $sql_differ ||= '';
217 $sql_differ .= "\n" unless $sql_differ =~ /\n\z/;
ca4f826a 218 $sql_differ .= sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse($_) } ($left, $right) );
6f2a5b66 219 }
220 return 0;
221 }
1b17d1b0 222 }
223 return 1;
224 }
6f2a5b66 225
226 # both are ops
1b17d1b0 227 else {
228
e40f5df9 229 # unroll parenthesis if possible/allowed
ca4f826a 230 unless ($parenthesis_significant) {
6f2a5b66 231 $sqlat->_parenthesis_unroll($_) for $left, $right;
232 }
1b17d1b0 233
0c2de280 234 # unroll ASC order by's
235 unless ($order_by_asc_significant) {
236 $sqlat->_strip_asc_from_order_by($_) for $left, $right;
237 }
238
ca4f826a 239 if ($left->[0] ne $right->[0]) {
1b17d1b0 240 $sql_differ = sprintf "OP [$left->[0]] != [$right->[0]] in\nleft: %s\nright: %s\n",
a24cc3a0 241 $sqlat->unparse($left),
6f2a5b66 242 $sqlat->unparse($right)
243 ;
1b17d1b0 244 return 0;
245 }
6f2a5b66 246
247 # literals have a different arg-sig
248 elsif ($left->[0] eq '-LITERAL') {
249 (my $l = " $left->[1][0] " ) =~ s/\s+/ /g;
250 (my $r = " $right->[1][0] ") =~ s/\s+/ /g;
251 my $eq = $case_sensitive ? $l eq $r : uc($l) eq uc($r);
252 $sql_differ = "[$l] != [$r]\n" if not $eq;
253 return $eq;
254 }
255
256 # if operators are identical, compare operands
01dd4e4f 257 else {
6f2a5b66 258 my $eq = _eq_sql($left->[1], $right->[1]);
ca4f826a 259 $sql_differ ||= sprintf ("left: %s\nright: %s\n", map { $sqlat->unparse($_) } ($left, $right) ) if not $eq;
6f2a5b66 260 return $eq;
fffe6900 261 }
262 }
263}
264
7853a177 265sub parse { $sqlat->parse(@_) }
fffe6900 2661;
267
268
269__END__
270
271=head1 NAME
272
273SQL::Abstract::Test - Helper function for testing SQL::Abstract
274
275=head1 SYNOPSIS
276
277 use SQL::Abstract;
278 use Test::More;
e7827ba2 279 use SQL::Abstract::Test import => [qw/
280 is_same_sql_bind is_same_sql is_same_bind
281 eq_sql_bind eq_sql eq_bind
282 /];
ec9af79e 283
fffe6900 284 my ($sql, @bind) = SQL::Abstract->new->select(%args);
e7827ba2 285
01dd4e4f 286 is_same_sql_bind($given_sql, \@given_bind,
fffe6900 287 $expected_sql, \@expected_bind, $test_msg);
288
e7827ba2 289 is_same_sql($given_sql, $expected_sql, $test_msg);
290 is_same_bind(\@given_bind, \@expected_bind, $test_msg);
291
01dd4e4f 292 my $is_same = eq_sql_bind($given_sql, \@given_bind,
e7827ba2 293 $expected_sql, \@expected_bind);
294
295 my $sql_same = eq_sql($given_sql, $expected_sql);
296 my $bind_same = eq_bind(\@given_bind, \@expected_bind);
297
fffe6900 298=head1 DESCRIPTION
299
300This module is only intended for authors of tests on
301L<SQL::Abstract|SQL::Abstract> and related modules;
302it exports functions for comparing two SQL statements
303and their bound values.
304
305The SQL comparison is performed on I<abstract syntax>,
306ignoring differences in spaces or in levels of parentheses.
307Therefore the tests will pass as long as the semantics
308is preserved, even if the surface syntax has changed.
309
ec9af79e 310B<Disclaimer> : the semantic equivalence handling is pretty limited.
311A lot of effort goes into distinguishing significant from
312non-significant parenthesis, including AND/OR operator associativity.
313Currently this module does not support commutativity and more
70c6f0e9 314intelligent transformations like L<De Morgan's laws
315|http://en.wikipedia.org/wiki/De_Morgan's_laws>, etc.
ec9af79e 316
70c6f0e9 317For a good overview of what this test framework is currently capable of refer
ec9af79e 318to C<t/10test.t>
fffe6900 319
320=head1 FUNCTIONS
321
322=head2 is_same_sql_bind
323
70c6f0e9 324 is_same_sql_bind(
325 $given_sql, \@given_bind,
326 $expected_sql, \@expected_bind,
327 $test_msg
328 );
fffe6900 329
70c6f0e9 330 is_same_sql_bind(
331 \[$given_sql, @given_bind],
332 \[$expected_sql, @expected_bind],
333 $test_msg
334 );
335
336 is_same_sql_bind(
337 $dbic_rs->as_query
338 $expected_sql, \@expected_bind,
339 $test_msg
340 );
341
342Compares given and expected pairs of C<($sql, \@bind)> by unpacking C<@_>
343as shown in the examples above and passing the arguments to L</eq_sql> and
344L</eq_bind>. Calls L<Test::Builder/ok> with the combined result, with
345C<$test_msg> as message.
346If the test fails, a detailed diagnostic is printed.
e7827ba2 347
348=head2 is_same_sql
349
70c6f0e9 350 is_same_sql(
351 $given_sql,
352 $expected_sql,
353 $test_msg
354 );
e7827ba2 355
70c6f0e9 356Compares given and expected SQL statements via L</eq_sql>, and calls
357L<Test::Builder/ok> on the result, with C<$test_msg> as message.
358If the test fails, a detailed diagnostic is printed.
e7827ba2 359
360=head2 is_same_bind
361
70c6f0e9 362 is_same_bind(
363 \@given_bind,
364 \@expected_bind,
365 $test_msg
366 );
e7827ba2 367
70c6f0e9 368Compares given and expected bind values via L</eq_bind>, and calls
369L<Test::Builder/ok> on the result, with C<$test_msg> as message.
370If the test fails, a detailed diagnostic is printed.
e7827ba2 371
372=head2 eq_sql_bind
373
70c6f0e9 374 my $is_same = eq_sql_bind(
375 $given_sql, \@given_bind,
376 $expected_sql, \@expected_bind,
377 );
e7827ba2 378
70c6f0e9 379 my $is_same = eq_sql_bind(
380 \[$given_sql, @given_bind],
381 \[$expected_sql, @expected_bind],
382 );
383
384 my $is_same = eq_sql_bind(
385 $dbic_rs->as_query
386 $expected_sql, \@expected_bind,
387 );
388
389Unpacks C<@_> depending on the given arguments and calls L</eq_sql> and
390L</eq_bind>, returning their combined result.
fffe6900 391
392=head2 eq_sql
393
394 my $is_same = eq_sql($given_sql, $expected_sql);
395
e7827ba2 396Compares the abstract syntax of two SQL statements. Similar to L</is_same_sql>,
397but it just returns a boolean value and does not print diagnostics or talk to
398L<Test::Builder>. If the result is false, the global variable L</$sql_differ>
399will contain the SQL portion where a difference was encountered; this is useful
400for printing diagnostics.
fffe6900 401
402=head2 eq_bind
403
404 my $is_same = eq_sql(\@given_bind, \@expected_bind);
405
e7827ba2 406Compares two lists of bind values, taking into account the fact that some of
407the values may be arrayrefs (see L<SQL::Abstract/bindtype>). Similar to
408L</is_same_bind>, but it just returns a boolean value and does not print
409diagnostics or talk to L<Test::Builder>.
fffe6900 410
411=head1 GLOBAL VARIABLES
412
e7827ba2 413=head2 $case_sensitive
fffe6900 414
415If true, SQL comparisons will be case-sensitive. Default is false;
416
e40f5df9 417=head2 $parenthesis_significant
418
419If true, SQL comparison will preserve and report difference in nested
48d9f5f8 420parenthesis. Useful while testing C<IN (( x ))> vs C<IN ( x )>.
421Defaults to false;
e40f5df9 422
0c2de280 423=head2 $order_by_asc_significant
424
425If true SQL comparison will consider C<ORDER BY foo ASC> and
426C<ORDER BY foo> to be different. Default is false;
427
e7827ba2 428=head2 $sql_differ
fffe6900 429
430When L</eq_sql> returns false, the global variable
431C<$sql_differ> contains the SQL portion
432where a difference was encountered.
433
fffe6900 434=head1 SEE ALSO
435
a6daa642 436L<SQL::Abstract>, L<Test::More>, L<Test::Builder>.
fffe6900 437
25823711 438=head1 AUTHORS
fffe6900 439
70c6f0e9 440Laurent Dami <laurent.dami AT etat geneve ch>
fffe6900 441
25823711 442Norbert Buchmuller <norbi@nix.hu>
443
e96c510a 444Peter Rabbitson <ribasushi@cpan.org>
445
fffe6900 446=head1 COPYRIGHT AND LICENSE
447
448Copyright 2008 by Laurent Dami.
449
450This library is free software; you can redistribute it and/or modify
01dd4e4f 451it under the same terms as Perl itself.