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