27ac22f51c035ed56a94abf1837304e22428566a
[dbsrgits/SQL-Abstract.git] / t / 05in_between.t
1 #!/usr/bin/perl
2
3 use strict;
4 use warnings;
5 use Test::More;
6 use Test::Exception;
7 use SQL::Abstract::Test import => ['is_same_sql_bind'];
8
9 use Data::Dumper;
10 use SQL::Abstract;
11
12 my @in_between_tests = (
13   {
14     where => { x => { -between => [1, 2] } },
15     stmt => 'WHERE (x BETWEEN ? AND ?)',
16     bind => [qw/1 2/],
17     test => '-between with two placeholders',
18   },
19   {
20     where => { x => { -between => [\"1", 2] } },
21     stmt => 'WHERE (x BETWEEN 1 AND ?)',
22     bind => [qw/2/],
23     test => '-between with one literal sql arg and one placeholder',
24   },
25   {
26     where => { x => { -between => [1, \"2"] } },
27     stmt => 'WHERE (x BETWEEN ? AND 2)',
28     bind => [qw/1/],
29     test => '-between with one placeholder and one literal sql arg',
30   },
31   {
32     where => { x => { -between => [\'current_date - 1', \'current_date - 0'] } },
33     stmt => 'WHERE (x BETWEEN current_date - 1 AND current_date - 0)',
34     bind => [],
35     test => '-between with two literal sql arguments',
36   },
37   {
38     where => { x => { -between => [ \['current_date - ?', 1], \['current_date - ?', 0] ] } },
39     stmt => 'WHERE (x BETWEEN current_date - ? AND current_date - ?)',
40     bind => [1, 0],
41     test => '-between with two literal sql arguments with bind',
42   },
43   {
44     where => { x => { -between => \['? AND ?', 1, 2] } },
45     stmt => 'WHERE (x BETWEEN ? AND ?)',
46     bind => [1,2],
47     test => '-between with literal sql with placeholders (\["? AND ?", scalar, scalar])',
48   },
49   {
50     where => { x => { -between => \["'something' AND ?", 2] } },
51     stmt => "WHERE (x BETWEEN 'something' AND ?)",
52     bind => [2],
53     test => '-between with literal sql with one literal arg and one placeholder (\["\'something\' AND ?", scalar])',
54   },
55   {
56     where => { x => { -between => \["? AND 'something'", 1] } },
57     stmt => "WHERE (x BETWEEN ? AND 'something')",
58     bind => [1],
59     test => '-between with literal sql with one placeholder and one literal arg (\["? AND \'something\'", scalar])',
60   },
61   {
62     where => { x => { -between => \"'this' AND 'that'" } },
63     stmt => "WHERE (x BETWEEN 'this' AND 'that')",
64     bind => [],
65     test => '-between with literal sql with a literal (\"\'this\' AND \'that\'")',
66   },
67
68   # generate a set of invalid -between tests
69   ( map { {
70     where => { x => { -between => $_ } },
71     test => 'invalid -between args',
72     exception => qr|Operator 'BETWEEN' requires either an arrayref with two defined values or expressions, or a single literal scalarref/arrayref-ref|,
73   } } (
74     [ 1, 2, 3 ],
75     [ 1, undef, 3 ],
76     [ undef, 2, 3 ],
77     [ 1, 2, undef ],
78     [ 1, undef ],
79     [ undef, 2 ],
80     [ undef, undef ],
81     [ 1 ],
82     [ undef ],
83     [],
84     1,
85     undef,
86   )),
87   {
88     where => {
89       start0 => { -between => [ 1, { -upper => 2 } ] },
90       start1 => { -between => \["? AND ?", 1, 2] },
91       start2 => { -between => \"lower(x) AND upper(y)" },
92       start3 => { -between => [
93         \"lower(x)",
94         \["upper(?)", 'stuff' ],
95       ] },
96     },
97     stmt => "WHERE (
98           ( start0 BETWEEN ? AND UPPER ?          )
99       AND ( start1 BETWEEN ? AND ?                )
100       AND ( start2 BETWEEN lower(x) AND upper(y)  )
101       AND ( start3 BETWEEN lower(x) AND upper(?)  )
102     )",
103     bind => [1, 2, 1, 2, 'stuff'],
104     test => '-between POD test',
105   },
106   {
107     args => { bindtype => 'columns' },
108     where => {
109       start0 => { -between => [ 1, { -upper => 2 } ] },
110       start1 => { -between => \["? AND ?", [ start1 => 1], [start1 => 2] ] },
111       start2 => { -between => \"lower(x) AND upper(y)" },
112       start3 => { -between => [
113         \"lower(x)",
114         \["upper(?)", [ start3 => 'stuff'] ],
115       ] },
116     },
117     stmt => "WHERE (
118           ( start0 BETWEEN ? AND UPPER ?          )
119       AND ( start1 BETWEEN ? AND ?                )
120       AND ( start2 BETWEEN lower(x) AND upper(y)  )
121       AND ( start3 BETWEEN lower(x) AND upper(?)  )
122     )",
123     bind => [
124       [ start0 => 1 ],
125       [ start0 => 2 ],
126       [ start1 => 1 ],
127       [ start1 => 2 ],
128       [ start3 => 'stuff' ],
129     ],
130     test => '-between POD test',
131   },
132
133   {
134     parenthesis_significant => 1,
135     where => { x => { -in => [ 1 .. 3] } },
136     stmt => "WHERE ( x IN (?, ?, ?) )",
137     bind => [ 1 .. 3],
138     test => '-in with an array of scalars',
139   },
140   {
141     parenthesis_significant => 1,
142     where => { x => { -in => [] } },
143     stmt => "WHERE ( 0=1 )",
144     bind => [],
145     test => '-in with an empty array',
146   },
147   {
148     parenthesis_significant => 1,
149     where => { x => { -in => \'( 1,2,lower(y) )' } },
150     stmt => "WHERE ( x IN ( 1,2,lower(y) ) )",
151     bind => [],
152     test => '-in with a literal scalarref',
153   },
154   {
155     parenthesis_significant => 1,
156     where => { x => { -in => \['( ( ?,?,lower(y) ) )', 1, 2] } },
157     stmt => "WHERE ( x IN ( ?,?,lower(y) ) )",  # note that outer parens are opened even though literal was requested (RIBASUSHI)
158     bind => [1, 2],
159     test => '-in with a literal arrayrefref',
160   },
161   {
162     parenthesis_significant => 1,
163     where => {
164       status => { -in => \"(SELECT status_codes\nFROM states)" },
165     },
166     # failed to open outer parens on a multi-line query in 1.61 (semifor)
167     stmt => " WHERE ( status IN ( SELECT status_codes FROM states )) ",
168     bind => [],
169     test => '-in multi-line subquery test',
170   },
171   {
172     parenthesis_significant => 1,
173     where => {
174       customer => { -in => \[
175         'SELECT cust_id FROM cust WHERE balance > ?',
176         2000,
177       ]},
178       status => { -in => \'SELECT status_codes FROM states' },
179     },
180     stmt => "
181       WHERE ((
182             customer IN ( SELECT cust_id FROM cust WHERE balance > ? )
183         AND status IN ( SELECT status_codes FROM states )
184       ))
185     ",
186     bind => [2000],
187     test => '-in POD test',
188   },
189   {
190     where => { x => { -in => [ \['LOWER(?)', 'A' ], \'LOWER(b)', { -lower => 'c' } ] } },
191     stmt => " WHERE ( x IN ( LOWER(?), LOWER(b), LOWER ? ) )",
192     bind => [qw/A c/],
193     test => '-in with an array of function array refs with args',
194   },
195   {
196     exception => qr/
197       \QSQL::Abstract before v1.75 used to generate incorrect SQL \E
198       \Qwhen the -IN operator was given an undef-containing list: \E
199       \Q!!!AUDIT YOUR CODE AND DATA!!! (the upcoming Data::Query-based \E
200       \Qversion of SQL::Abstract will emit the logically correct SQL \E
201       \Qinstead of raising this exception)\E
202     /x,
203     where => { x => { -in => [ 1, undef ] } },
204     stmt => " WHERE ( x IN ( ? ) OR x IS NULL )",
205     bind => [ 1 ],
206     test => '-in with undef as an element', 
207   },
208   {
209     exception => qr/
210       \QSQL::Abstract before v1.75 used to generate incorrect SQL \E
211       \Qwhen the -IN operator was given an undef-containing list: \E
212       \Q!!!AUDIT YOUR CODE AND DATA!!! (the upcoming Data::Query-based \E
213       \Qversion of SQL::Abstract will emit the logically correct SQL \E
214       \Qinstead of raising this exception)\E
215     /x,
216     where => { x => { -in => [ 1, undef, 2, 3, undef ] } },
217     stmt => " WHERE ( x IN ( ?, ?, ? ) OR x IS NULL )",
218     bind => [ 1, 2, 3 ],
219     test => '-in with multiple undef elements',
220   },
221 );
222
223 for my $case (@in_between_tests) {
224   TODO: {
225     local $TODO = $case->{todo} if $case->{todo};
226     local $SQL::Abstract::Test::parenthesis_significant = $case->{parenthesis_significant};
227
228     local $Data::Dumper::Terse = 1;
229
230     my @w;
231     local $SIG{__WARN__} = sub { push @w, @_ };
232     my $sql = SQL::Abstract->new ($case->{args} || {});
233
234     if ($case->{exception}) {
235       throws_ok { $sql->where($case->{where}) } $case->{exception};
236     }
237     else {
238       lives_ok {
239         my ($stmt, @bind) = $sql->where($case->{where});
240         is_same_sql_bind(
241           $stmt,
242           \@bind,
243           $case->{stmt},
244           $case->{bind},
245         ) || diag "Search term:\n" . Dumper $case->{where};
246       } "$case->{test} doesn't die";
247     }
248
249     is (@w, 0, $case->{test} || 'No warnings within in-between tests')
250       || diag join "\n", 'Emitted warnings:', @w;
251   }
252 }
253
254 done_testing;