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