Line # Revision Author
1 8 ahitrov@rambler.ru package SQL::Common;
2 use base qw(Exporter);
3
4 use strict;
5 use Utils;
6 use Contenido::DateTime;
7 use Data::Dumper;
8
9 our @EXPORT = qw(&NIL);
10
11 sub NIL {
12 return '___NEXT___';
13 }
14
15 my %_TEXT_CONDITIONS = (
16 '!=' => 1,
17 '<>' => 1,
18 'NOT' => 1,
19 'LIKE' => 1,
20 'ILIKE' => 1,
21 'NOT LIKE' => 1,
22 'NOT ILIKE' => 1,
23 '~' => 1,
24 );
25 my %_CONDITIONS = (
26 'NOT' => 1,
27 '!=' => 1,
28 '<>' => 1,
29 '<' => 1,
30 '>' => 1,
31 '>=' => 1,
32 '<=' => 1,
33 );
34 my %_DATE_CONDITIONS = (
35 '!=' => 1,
36 '<>' => 1,
37 '<' => 1,
38 '>' => 1,
39 '>=' => 1,
40 '<=' => 1,
41 );
42
43 sub _generic_int_filter {
44 my ($field,$value,$negation)=@_;
45
46 my $mode = $negation ? 'NOT IN':'IN';
47
48 #undef <=> NULL
49 if ( !defined($value) ) {
50 $mode = $negation ? 'IS NOT' : 'IS';
51 return "$field $mode NULL", [];
52 } elsif ( (ref($value) eq 'ARRAY') and @$value ) {
53 my $values_string = '?, 'x(scalar (@$value)-1).'?';
54 return " ($field $mode ($values_string)) ", [@$value];
55 } elsif (ref($value) eq 'ARRAY') {
56 # skip empty array
57 return ($negation ? ' TRUE ' : ' FALSE '), [];
58 } elsif ( $value eq 'positive' || $value eq 'negative' || $value eq 'natural' ) {
59 $mode = $value eq 'positive' ? '>' : $value eq 'negative' ? '<' : '>=';
60 return "$field $mode 0", [];
61 } elsif ( $value =~ /^[0-9 ,]+$/ ) {
62 my @values = split(/\s*,\s*/, $value);
63 my $values_string = '?, 'x(scalar (@values)-1).'?';
64 return " ($field $mode ($values_string)) ", \@values;
65 } else {
66 my $mason_comp = ref($HTML::Mason::Commands::m) ? $HTML::Mason::Commands::m->current_comp() : undef;
67 my $mason_file = ref($mason_comp) ? $mason_comp->path : undef;
68 my ($package, $filename, $line) = caller;
69
70 warn "WARNING: $$: Неверно задан формат integer фильтра для $field ('$value'). Это может быть либо число, либо ряд чисел через запятую либо ссылка на массив чисел. called from '$package/$filename/$line' ".($mason_file ? "called from $mason_file" : '')."\n";
71 return ' FALSE ', [];
72 }
73 }
74
75 sub _generic_composite_filter {
76 my ($field, $condition, $value, $mode ) = @_;
77 unless ( $condition ) {
78 warn "Contenido Warning (_generic_composite_filter): Неверно задан формат!\n";
79 return ' FALSE ';
80 }
81 $condition = uc($condition) if $condition =~ /^[\w\s]+$/;
82 if ( $value eq NIL() ) {
83 return ' TRUE ';
84 } elsif ( defined($value) && !ref($value) ) {
85 if ( ( $mode eq 'text' && !$_TEXT_CONDITIONS{$condition} ) || ( $mode ne 'text' && !$_CONDITIONS{$condition} ) ) {
86 warn "Contenido Warning (_generic_composite_filter): Неверно задан формат неравенства, недопустимое условие \"$condition\"!\n";
87 return ' FALSE ';
88 }
89 return "$field $condition ?", $value;
90 } elsif ( ref($value) eq 'ARRAY' && scalar(@{ $value }) ) {
91 my $in = 'IN (';
92 my $i = 0;
93 unless ( $condition eq 'NOT' ) {
94 warn "Contenido Warning (_generic_composite_filter): Неверно задан формат, недопустимое условие \"$condition\"!\n";
95 return ' FALSE ';
96 }
97 foreach my $v ( @{ $value } ) {
98 $in .= '?';
99 $in .= ',' if $#{ $value } != $i;
100 $i++;
101 }
102 $in .= ')';
103 return "$field $condition $in", $value;
104 } else {
105 warn "Contenido Warning (_generic_composite_filter): Неверно задано значение, ожидается скаляр или ссылка на массив!\n ";
106 return ' FALSE ';
107 }
108 }
109
110 sub _composite_date_filter {
111 my ($field, $condition, $value ) = @_;
112 unless ( $condition ) {
113 warn "Contenido Warning (_composite_date_filter): Неверно задан формат!\n";
114 return ' FALSE ';
115 }
116 if ( $value eq NIL() ) {
117 return ' TRUE ';
118 } elsif ( defined($value) && !ref($value) ) {
119 unless ( $_DATE_CONDITIONS{$condition} ) {
120 warn "Contenido Warning (_composite_date_filter): Неверно задан формат неравенства, недопустимое условие \"$condition\"!\n";
121 return ' FALSE ';
122 }
123 if ( $value =~ /^\d+$/ ) {
124 my $date = Contenido::DateTime->new(epoch => $value)->strftime('%Y-%m-%d %H:%M:%S');
125 return " ($field $condition ?::TIMESTAMP) ", $date;
126 } elsif ( $value =~ /^\d{4}-\d{2}-\d{2}$/ || $value =~ /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?:\:\d{2})?$/ ) {
127 return " ($field $condition ?::TIMESTAMP) ", $value;
128 } else {
129 warn "Contenido Warning (_composite_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n";
130 return ' FALSE ';
131 }
132 } else {
133 warn "Contenido Warning (_composite_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n";
134 return ' FALSE ';
135 }
136 }
137
138 sub _generic_null_filter {
139 my ($field, $param )= @_;
140 $param = uc($param);
141 if ( $param ne 'NULL' && $param ne 'NOT NULL' ) {
142 warn "Contenido Warning (_generic_null_filter): Неверно задан значение, ожидается 'NULL' или 'NOT NULL'!\n";
143 return ' FALSE ';
144 }
145 return "$field IS $param";
146 }
147
148 sub _generic_date_filter {
149 my ($field, $param )= @_;
150 if ( defined($param) && !ref($param) ) {
151 if ( $param =~ /^\d+$/ ) {
152 my $date = Contenido::DateTime->new(epoch => $param)->strftime('%Y-%d-%m %H:%M');
153 return " ($field >= ?::TIMESTAMP AND $field < ?::TIMESTAMP+'1 min'::INTERVAL) ", [$date, $date];
154 } elsif ( $param =~ /^\d{4}-\d{2}-\d{2}$/ ) {
155 return " ($field >= ?::TIMESTAMP AND $field < ?::TIMESTAMP+'1 day'::INTERVAL) ", [$param, $param];
156 } elsif ( $param =~ /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?:\:\d{2})?$/ ) {
157 return " ($field >= ?::TIMESTAMP AND $field < ?::TIMESTAMP+'1 min'::INTERVAL) ", [$param, $param];
158 } else {
159 warn "Contenido Warning (_generic_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n";
160 return ' FALSE ';
161 }
162 } elsif ( !defined($param) ) {
163 return "$field IS NULL";
164 } else {
165 warn "Contenido Warning (_generic_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n";
166 return ' FALSE ';
167 }
168 }
169
170 sub _generic_float_filter {
171 my ($field,$value,$negation)=@_;
172
173 my $mode = $negation ? 'NOT IN':'IN';
174
175 #undef <=> NULL
176 if ( !defined($value) ) {
177 $mode = $negation ? 'IS NOT' : 'IS';
178 return "$field $mode NULL", [];
179 } elsif (ref($value) eq 'ARRAY' and @$value) {
180 780 ahitrov my $values_string = '?, 'x(scalar (@$value)-1).'?';
181 return " ($field $mode ($values_string)) ", $value;
182 } elsif (ref($value) eq 'ARRAY') {
183 # skip empty array
184 return ($negation ? ' TRUE ' : ' FALSE '), [];
185 } elsif ( $value eq 'positive' || $value eq 'negative' ) {
186 $mode = $value eq 'positive' ? '>' : '<';
187 return "$field $mode 0", [];
188 8 ahitrov@rambler.ru } else {
189 return " ($field $mode (?)) ", [$value];
190 }
191 }
192
193 # raw flag for disable placeholders use... (need manual escaping before)... sometime need for partial indexes use with prepared statements
194 sub _generic_text_filter {
195 my ($field,$value,$negation,$raw)=@_;
196
197 my $mode = $negation ? 'NOT IN':'IN';
198
199 #undef <=> NULL
200 if ( !defined($value) ) {
201 $mode = $negation ? 'IS NOT' : 'IS';
202 return "$field $mode NULL", [];
203 } elsif (ref($value) eq 'ARRAY') {
204 if ($raw) {
205 my $values_string = "'".join ("','",@$value)."'";
206 return " ($field $mode ($values_string)) ", [];
207 } else {
208 my $values_string = '?, 'x(scalar (@$value)-1).'?';
209 return " ($field $mode ($values_string)) ", $value;
210 }
211 } else {
212 if ($raw) {
213 return " ($field $mode ('$value')) ", [];
214 } else {
215 return " ($field $mode (?)) ", [$value];
216 }
217 }
218 }
219
220 sub _generic_name_filter {
221 my ($field,$value,$negation,$opts)=@_;
222 $opts ||= {};
223
224 #like and ilike modes incompatible with [] in $opts{name}
225 if ($opts->{like}) {
226 my $mode = $negation ? "NOT" : "";
227 return " $field $mode LIKE ? ", [$value];
228 } elsif ($opts->{ilike}) {
229 my $mode = $negation ? "NOT" : "";
230 return " $field $mode ILIKE ? ", [$value];
231 } else {
232 return &SQL::Common::_generic_text_filter($field,$value,$negation,0);
233 }
234 }
235
236 sub _generic_intarray_filter {
237 my ($field, $value, $opts)=@_;
238
239 #undef <=> FALSE here!!!!
240 if (defined($value)) {
241 if (ref($value) ne 'ARRAY') {
242 if ($value =~ /^[\d ,]+$/) {
243 $value = [split(/\s*,\s*/, $value)];
244 } else {
245 warn "Contenido Warning: В списке для _generic_int_array_filter есть нечисловые элементы ('$value'). Параметр игнорируется.";
246 return [' FALSE '], [];
247 }
248 }
249
250 if (@$value) {
251 my $op = (ref($opts) eq 'HASH' and ($opts->{intersect} or $opts->{contains})) ? '@>' : '&&';
252 567 ahitrov if ($DBD::Pg::VERSION=~/^1\./) {
253 my $ph_string = '?, 'x$#{$value}.'?';
254 8 ahitrov@rambler.ru return [" ($field $op ARRAY[$ph_string]::integer[]) "], $value;
255 } else {
256 567 ahitrov return [" ($field $op ?::integer[]) "], [$value];
257 }
258 8 ahitrov@rambler.ru } else {
259 return [' FALSE '], [];
260 }
261 } else {
262 return [' FALSE '], [];
263 }
264 }
265
266 sub _get_limit {
267 my %opts=@_;
268 if (exists($opts{limit})) {
269 return ' LIMIT 1000' unless defined $opts{limit};
270 if ($opts{limit} !~ /\D/) {
271 return ' LIMIT '.$opts{limit};
272 } else {
273 warn "Contenido Warning: Неверно заданы пределы выборки ($opts{limit})";
274 return ' LIMIT 1000';
275 }
276 } elsif ($opts{no_limit}) {
277 return undef;
278 } else {
279 return ' LIMIT 1000';
280 }
281 }
282
283 sub _get_offset {
284 my %opts=@_;
285 if ( exists($opts{offset})) {
286 return undef unless defined $opts{offset};
287 if ($opts{offset} !~ /\D/) {
288 return ' OFFSET '.$opts{offset};
289 } else {
290 my $mason_comp = ref($HTML::Mason::Commands::m) ? $HTML::Mason::Commands::m->current_comp() : undef;
291 my $mason_file = ref($mason_comp) ? $mason_comp->path : undef;
292
293 warn "ERROR: $$ ".__PACKAGE__." ".scalar(localtime())." ".($mason_file ? "called from $mason_file" : '')." Для _get_offset неверно задан параметр offset: '$opts{offset}'\n";
294 return undef;
295 }
296 }
297 }
298
299
300 1;