Yet another loose end on the cond collapser
[dbsrgits/DBIx-Class.git] / t / sqlmaker / dbihacks_internals.t
1 use strict;
2 use warnings;
3 use Test::More;
4 use Test::Warn;
5
6 use lib qw(t/lib);
7 use DBICTest ':DiffSQL';
8 use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
9
10 use Data::Dumper;
11 BEGIN {
12   if ( eval { require Test::Differences } ) {
13     no warnings 'redefine';
14     *is_deeply = \&Test::Differences::eq_or_diff;
15   }
16 }
17
18 my $schema = DBICTest->init_schema( no_deploy => 1);
19 my $sm = $schema->storage->sql_maker;
20
21 {
22   package # hideee
23     DBICTest::SillyInt;
24
25   use overload
26     fallback => 1,
27     '0+' => sub { ${$_[0]} },
28   ;
29 }
30 my $num = bless( \do { my $foo = 69 }, 'DBICTest::SillyInt' );
31
32 is($num, 69, 'test overloaded object is "sane"');
33 is("$num", 69, 'test overloaded object is "sane"');
34
35 for my $t (
36   {
37     where => { artistid => 1, charfield => undef },
38     cc_result => { artistid => 1, charfield => undef },
39     sql => 'WHERE artistid = ? AND charfield IS NULL',
40     efcc_result => { artistid => 1 },
41     efcc_n_result => { artistid => 1, charfield => undef },
42   },
43   {
44     where => { -and => [ artistid => 1, charfield => undef, { rank => 13 } ] },
45     cc_result => { artistid => 1, charfield => undef, rank => 13 },
46     sql => 'WHERE artistid = ?  AND charfield IS NULL AND rank = ?',
47     efcc_result => { artistid => 1, rank => 13 },
48     efcc_n_result => { artistid => 1, charfield => undef, rank => 13 },
49   },
50   {
51     where => { -and => [ { artistid => 1, charfield => undef}, { rank => 13 } ] },
52     cc_result => { artistid => 1, charfield => undef, rank => 13 },
53     sql => 'WHERE artistid = ?  AND charfield IS NULL AND rank = ?',
54     efcc_result => { artistid => 1, rank => 13 },
55     efcc_n_result => { artistid => 1, charfield => undef, rank => 13 },
56   },
57   {
58     where => { -and => [ -or => { name => 'Caterwauler McCrae' }, 'rank' ] },
59     cc_result => { name => 'Caterwauler McCrae', rank => undef },
60     sql => 'WHERE name = ? AND rank IS NULL',
61     efcc_result => { name => 'Caterwauler McCrae' },
62     efcc_n_result => { name => 'Caterwauler McCrae', rank => undef },
63   },
64   {
65     where => { -and => [ [ [ artist => {'=' => \'foo' } ] ], { name => \[ '= ?', 'bar' ] } ] },
66     cc_result => { artist => {'=' => \'foo' }, name => \[ '= ?', 'bar' ] },
67     sql => 'WHERE artist = foo AND name = ?',
68     efcc_result => { artist => \'foo' },
69   },
70   {
71     where => { -and => [ -or => { name => 'Caterwauler McCrae', artistid => 2 } ] },
72     cc_result => { -or => [ artistid => 2, name => 'Caterwauler McCrae' ] },
73     sql => 'WHERE artistid = ? OR name = ?',
74     efcc_result => {},
75   },
76   {
77     where => { -or => { name => 'Caterwauler McCrae', artistid => 2 } },
78     cc_result => { -or => [ artistid => 2, name => 'Caterwauler McCrae' ] },
79     sql => 'WHERE artistid = ? OR name = ?',
80     efcc_result => {},
81   },
82   {
83     where => { -and => [ \'foo=bar',  [ { artistid => { '=', $num } } ], { name => 'Caterwauler McCrae'} ] },
84     cc_result => { -and => [ \'foo=bar' ], name => 'Caterwauler McCrae', artistid => $num },
85     sql => 'WHERE foo=bar AND artistid = ? AND name = ?',
86     efcc_result => { name => 'Caterwauler McCrae', artistid => $num },
87   },
88   {
89     where => { -and => [ \'foo=bar',  [ { artistid => { '=', $num } } ], { name => 'Caterwauler McCrae'}, \'buzz=bozz' ] },
90     cc_result => { -and => [ \'foo=bar', \'buzz=bozz' ], name => 'Caterwauler McCrae', artistid => $num },
91     sql => 'WHERE foo=bar AND artistid = ? AND name = ? AND buzz=bozz',
92     collapsed_sql => 'WHERE foo=bar AND buzz=bozz AND artistid = ? AND name = ?',
93     efcc_result => { name => 'Caterwauler McCrae', artistid => $num },
94   },
95   {
96     where => { artistid => [ $num ], rank => [ 13, 2, 3 ], charfield => [ undef ] },
97     cc_result => { artistid => $num, charfield => undef, rank => [13, 2, 3] },
98     sql => 'WHERE artistid = ? AND charfield IS NULL AND ( rank = ? OR rank = ? OR rank = ? )',
99     efcc_result => { artistid => $num },
100     efcc_n_result => { artistid => $num, charfield => undef },
101   },
102   {
103     where => { artistid => { '=' => 1 }, rank => { '>' => 12 }, charfield => { '=' => undef } },
104     cc_result => { artistid => 1, charfield => undef, rank => { '>' => 12 } },
105     sql => 'WHERE artistid = ? AND charfield IS NULL AND rank > ?',
106     efcc_result => { artistid => 1 },
107     efcc_n_result => { artistid => 1, charfield => undef },
108   },
109   {
110     where => { artistid => { '=' => [ 1 ], }, charfield => { '=' => [ -AND => \'1', \['?',2] ] }, rank => { '=' => [ -OR => $num, $num ] } },
111     cc_result => { artistid => 1, charfield => [-and => { '=' => \['?',2] }, { '=' => \'1' } ], rank => { '=' => [$num, $num] } },
112     sql => 'WHERE artistid = ? AND charfield = 1 AND charfield = ? AND ( rank = ? OR rank = ? )',
113     collapsed_sql => 'WHERE artistid = ? AND charfield = ? AND charfield = 1 AND ( rank = ? OR rank = ? )',
114     efcc_result => { artistid => 1, charfield => UNRESOLVABLE_CONDITION },
115   },
116   {
117     where => { -and => [ artistid => 1, artistid => 2 ], name => [ -and => { '!=', 1 }, 2 ], charfield => [ -or => { '=', 2 } ], rank => [-and => undef, { '=', undef }, { '!=', 2 } ] },
118     cc_result => { artistid => [ -and => 1, 2 ], name => [ -and => { '!=', 1 }, 2 ], charfield => 2, rank => [ -and => { '!=', 2 }, undef ] },
119     sql => 'WHERE artistid = ? AND artistid = ? AND charfield = ? AND name != ? AND name = ? AND rank IS NULL AND rank IS NULL AND rank != ?',
120     collapsed_sql => 'WHERE artistid = ? AND artistid = ? AND charfield = ? AND name != ? AND name = ? AND rank != ? AND rank IS NULL',
121     efcc_result => {
122       artistid => UNRESOLVABLE_CONDITION,
123       name => 2,
124       charfield => 2,
125     },
126     efcc_n_result => {
127       artistid => UNRESOLVABLE_CONDITION,
128       name => 2,
129       charfield => 2,
130       rank => undef,
131     },
132   },
133   (map { {
134     where => $_,
135     sql => 'WHERE (rank = 13 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank != 42)',
136     collapsed_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank = 13) AND (artistid = ? OR charfield IS NULL OR rank != 42)',
137     cc_result => { -and => [
138       { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
139       { -or => [ artistid => 1, charfield => undef, rank => { '!=' => \42 } ] },
140     ] },
141     efcc_result => {},
142     efcc_n_result => {},
143   } } (
144
145     { -and => [
146       -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
147       -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
148     ] },
149
150     {
151       -OR => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
152       -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
153     },
154
155   ) ),
156   {
157     where => { -or => [
158       -and => [ foo => { '!=', { -value => undef } }, bar => { -in => [ 69, 42 ] } ],
159       foo => { '=', { -value => undef } },
160       baz => { '!=' => { -ident => 'bozz' } },
161       baz => { -ident => 'buzz' },
162     ] },
163     sql => 'WHERE ( foo IS NOT NULL AND bar IN ( ?, ? ) ) OR foo IS NULL OR baz != bozz OR baz = buzz',
164     collapsed_sql => 'WHERE baz != bozz OR baz = buzz OR foo IS NULL OR ( bar IN ( ?, ? ) AND foo IS NOT NULL )',
165     cc_result => { -or => [
166       baz => { '!=' => { -ident => 'bozz' } },
167       baz => { '=' => { -ident => 'buzz' } },
168       foo => undef,
169       { bar => { -in => [ 69, 42 ] }, foo => { '!=', undef } }
170     ] },
171     efcc_result => {},
172   },
173   {
174     where => { -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => { '=' => 1 }, genreid => { '=' => \['?', 2] } ] },
175     sql => 'WHERE rank = 13 OR charfield IS NULL OR artistid = ? OR genreid = ?',
176     collapsed_sql => 'WHERE artistid = ? OR charfield IS NULL OR genreid = ? OR rank = 13',
177     cc_result => { -or => [ artistid => 1, charfield => undef, genreid => { '=' => \['?', 2] }, rank => { '=' => \13 } ] },
178     efcc_result => {},
179     efcc_n_result => {},
180   },
181   {
182     where => { -and => [
183       -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
184       -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '=' => \13 } },
185     ] },
186     cc_result => { -and => [
187       { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
188       { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
189     ] },
190     sql => 'WHERE (rank = 13 OR charfield IS NULL OR artistid = ?) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
191     collapsed_sql => 'WHERE (artistid = ? OR charfield IS NULL OR rank = 13) AND (artistid = ? OR charfield IS NULL OR rank = 13)',
192     efcc_result => {},
193     efcc_n_result => {},
194   },
195   {
196     where => { -and => [
197       -or => [ rank => { '=' => \13 }, charfield => { '=' => undef }, artistid => 1 ],
198       -or => { artistid => { '=' => 1 }, charfield => undef, rank => { '!=' => \42 } },
199       -and => [ foo => { '=' => \1 }, bar => 2 ],
200       -and => [ foo => 3, bar => { '=' => \4 } ],
201       -exists => \'(SELECT 1)',
202       -exists => \'(SELECT 2)',
203       -not => { foo => 69 },
204       -not => { foo => 42 },
205     ]},
206     sql => 'WHERE
207           ( rank = 13 OR charfield IS NULL OR artistid = ? )
208       AND ( artistid = ? OR charfield IS NULL OR rank != 42 )
209       AND foo = 1
210       AND bar = ?
211       AND foo = ?
212       AND bar = 4
213       AND (EXISTS (SELECT 1))
214       AND (EXISTS (SELECT 2))
215       AND NOT foo = ?
216       AND NOT foo = ?
217     ',
218     collapsed_sql => 'WHERE
219           ( artistid = ? OR charfield IS NULL OR rank = 13 )
220       AND ( artistid = ? OR charfield IS NULL OR rank != 42 )
221       AND (EXISTS (SELECT 1))
222       AND (EXISTS (SELECT 2))
223       AND NOT foo = ?
224       AND NOT foo = ?
225       AND bar = 4
226       AND bar = ?
227       AND foo = 1
228       AND foo = ?
229     ',
230     cc_result => {
231       -and => [
232         { -or => [ artistid => 1, charfield => undef, rank => { '=' => \13 } ] },
233         { -or => [ artistid => 1, charfield => undef, rank => { '!=' => \42 } ] },
234         { -exists => \'(SELECT 1)' },
235         { -exists => \'(SELECT 2)' },
236         { -not => { foo => 69 } },
237         { -not => { foo => 42 } },
238       ],
239       foo => [ -and => { '=' => \1 }, 3 ],
240       bar => [ -and => { '=' => \4 }, 2 ],
241     },
242     efcc_result => {
243       foo => UNRESOLVABLE_CONDITION,
244       bar => UNRESOLVABLE_CONDITION,
245     },
246     efcc_n_result => {
247       foo => UNRESOLVABLE_CONDITION,
248       bar => UNRESOLVABLE_CONDITION,
249     },
250   },
251   {
252     where => { -and => [
253       [ '_macro.to' => { -like => '%correct%' }, '_wc_macros.to' => { -like => '%correct%' } ],
254       { -and => [ { 'group.is_active' => 1 }, { 'me.is_active' => 1 } ] }
255     ] },
256     cc_result => {
257       'group.is_active' => 1,
258       'me.is_active' => 1,
259       -or => [
260         '_macro.to' => { -like => '%correct%' },
261         '_wc_macros.to' => { -like => '%correct%' },
262       ],
263     },
264     sql => 'WHERE ( _macro.to LIKE ? OR _wc_macros.to LIKE ? ) AND group.is_active = ? AND me.is_active = ?',
265     efcc_result => { 'group.is_active' => 1, 'me.is_active' => 1 },
266   },
267
268   {
269     where => { -and => [
270       artistid => { -value => [1] },
271       charfield => { -ident => 'foo' },
272       name => { '=' => { -value => undef } },
273       rank => { '=' => { -ident => 'bar' } },
274     ] },
275     sql => 'WHERE artistid = ? AND charfield = foo AND name IS NULL AND rank = bar',
276     cc_result => {
277       artistid => { -value => [1] },
278       name => undef,
279       charfield => { '=', { -ident => 'foo' } },
280       rank => { '=' => { -ident => 'bar' } },
281     },
282     efcc_result => {
283       artistid => [1],
284       charfield => { -ident => 'foo' },
285       rank => { -ident => 'bar' },
286     },
287     efcc_n_result => {
288       artistid => [1],
289       name => undef,
290       charfield => { -ident => 'foo' },
291       rank => { -ident => 'bar' },
292     },
293   },
294
295   {
296     where => { artistid => [] },
297     cc_result => { artistid => [] },
298     efcc_result => {},
299   },
300   (map {
301     {
302       where => { -and => $_ },
303       cc_result => undef,
304       efcc_result => {},
305       sql => '',
306     },
307     {
308       where => { -or => $_ },
309       cc_result => undef,
310       efcc_result => {},
311       sql => '',
312     },
313     {
314       where => { -or => [ foo => 1, $_ ] },
315       cc_result => { foo => 1 },
316       efcc_result => { foo => 1 },
317       sql => 'WHERE foo = ?',
318     },
319     {
320       where => { -or => [ $_, foo => 1 ] },
321       cc_result => { foo => 1 },
322       efcc_result => { foo => 1 },
323       sql => 'WHERE foo = ?',
324     },
325     {
326       where => { -and => [ fuu => 2, $_, foo => 1 ] },
327       sql => 'WHERE fuu = ? AND foo = ?',
328       collapsed_sql => 'WHERE foo = ? AND fuu = ?',
329       cc_result => { foo => 1, fuu => 2 },
330       efcc_result => { foo => 1, fuu => 2 },
331     },
332   } (
333     # bare
334     [], {},
335     # singles
336     [ {} ], [ [] ],
337     # doubles
338     [ [], [] ], [ {}, {} ], [ [], {} ], [ {}, [] ],
339     # tripples
340     [ {}, [], {} ], [ [], {}, [] ]
341   )),
342
343   # FIXME legacy compat crap, possibly worth undef/dieing in SQLMaker
344   { where => { artistid => {} }, sql => '', cc_result => undef, efcc_result => {}, efcc_n_result => {} },
345
346   # batshit insanity, just to be thorough
347   {
348     where => { -and => [ [ 'artistid' ], [ -and => [ artistid => { '!=', 69 }, artistid => undef, artistid => { '=' => 200 } ]], artistid => [], { -or => [] }, { -and => [] }, [ 'charfield' ], { name => [] }, 'rank' ] },
349     cc_result => { artistid => [ -and => [], { '!=', 69 }, undef, 200  ], charfield => undef, name => [], rank => undef },
350     sql => 'WHERE artistid IS NULL AND artistid != ? AND artistid IS NULL AND artistid = ? AND 0=1 AND charfield IS NULL AND 0=1 AND rank IS NULL',
351     collapsed_sql => 'WHERE 0=1 AND artistid != ? AND artistid IS NULL AND artistid = ? AND charfield IS NULL AND 0=1 AND rank IS NULL',
352     efcc_result => { artistid => UNRESOLVABLE_CONDITION },
353     efcc_n_result => { artistid => UNRESOLVABLE_CONDITION, charfield => undef, rank => undef },
354   },
355
356   # original test from RT#93244
357   {
358     where => {
359       -and => [
360         \[
361           "LOWER(me.title) LIKE ?",
362           '%spoon%',
363         ],
364         [ { 'me.title' => 'Spoonful of bees' } ],
365     ]},
366     cc_result => {
367       -and => [ \[
368         "LOWER(me.title) LIKE ?",
369         '%spoon%',
370       ]],
371       'me.title' => 'Spoonful of bees',
372     },
373     sql => 'WHERE LOWER(me.title) LIKE ? AND me.title = ?',
374     efcc_result => { 'me.title' => 'Spoonful of bees' },
375   },
376
377   # crazy literals
378   {
379     where => {
380       -or => [
381         \'foo = bar',
382       ],
383     },
384     sql => 'WHERE foo = bar',
385     cc_result => {
386       -and => [
387         \'foo = bar',
388       ],
389     },
390     efcc_result => {},
391   },
392   {
393     where => {
394       -or => [
395         \'foo = bar',
396         \'baz = ber',
397       ],
398     },
399     sql => 'WHERE foo = bar OR baz = ber',
400     collapsed_sql => 'WHERE baz = ber OR foo = bar',
401     cc_result => {
402       -or => [
403         \'baz = ber',
404         \'foo = bar',
405       ],
406     },
407     efcc_result => {},
408   },
409   {
410     where => {
411       -and => [
412         \'foo = bar',
413         \'baz = ber',
414       ],
415     },
416     sql => 'WHERE foo = bar AND baz = ber',
417     cc_result => {
418       -and => [
419         \'foo = bar',
420         \'baz = ber',
421       ],
422     },
423     efcc_result => {},
424   },
425   {
426     where => {
427       -and => [
428         \'foo = bar',
429         \'baz = ber',
430         x => { -ident => 'y' },
431       ],
432     },
433     sql => 'WHERE foo = bar AND baz = ber AND x = y',
434     cc_result => {
435       -and => [
436         \'foo = bar',
437         \'baz = ber',
438       ],
439       x => { '=' => { -ident => 'y' } }
440     },
441     efcc_result => { x => { -ident => 'y' } },
442   },
443 ) {
444
445   for my $w (
446     $t->{where},
447     $t->{where},  # do it twice, make sure we didn't destory the condition
448     [ -and => $t->{where} ],
449     [ -AND => $t->{where} ],
450     { -OR => [ -AND => $t->{where} ] },
451     ( keys %{$t->{where}} <= 1 ? [ %{$t->{where}} ] : () ),
452     ( (keys %{$t->{where}} == 1 and $t->{where}{-or})
453       ? ( ref $t->{where}{-or} eq 'HASH'
454         ? [ map { $_ => $t->{where}{-or}{$_} } sort keys %{$t->{where}{-or}} ]
455         : $t->{where}{-or}
456       )
457       : ()
458     ),
459   ) {
460     my $name = do { local ($Data::Dumper::Indent, $Data::Dumper::Terse, $Data::Dumper::Sortkeys) = (0, 1, 1); Dumper $w };
461
462     my ($generated_sql) = $sm->where($w);
463
464     is_same_sql ( $generated_sql, $t->{sql}, "Expected SQL from $name" )
465       if exists $t->{sql};
466
467     is_same_sql(
468       ($sm->where($t->{cc_result}))[0],
469       ( $t->{collapsed_sql} || $t->{sql} || $generated_sql ),
470       "Collapse did not alter *the semantics* of the final SQL based on $name",
471     );
472
473     my $collapsed_cond = $schema->storage->_collapse_cond($w);
474
475     is_deeply(
476       $collapsed_cond,
477       $t->{cc_result},
478       "Expected collapsed condition produced on $name",
479     );
480
481     is_deeply(
482       $schema->storage->_extract_fixed_condition_columns($w),
483       $t->{efcc_result},
484       "Expected fixed_condition produced on $name",
485     );
486
487     is_deeply(
488       $schema->storage->_extract_fixed_condition_columns($w, 'consider_nulls'),
489       $t->{efcc_n_result},
490       "Expected fixed_condition including NULLs produced on $name",
491     ) if $t->{efcc_n_result};
492
493     die unless Test::Builder->new->is_passing;
494   }
495 }
496
497 done_testing;