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