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