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