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