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