package SQL::Common; use base qw(Exporter); use strict; use Utils; use Contenido::DateTime; use Data::Dumper; our @EXPORT = qw(&NIL); sub NIL { return '___NEXT___'; } my %_TEXT_CONDITIONS = ( '!=' => 1, '<>' => 1, 'NOT' => 1, 'LIKE' => 1, 'ILIKE' => 1, 'NOT LIKE' => 1, 'NOT ILIKE' => 1, '~' => 1, ); my %_CONDITIONS = ( 'NOT' => 1, '!=' => 1, '<>' => 1, '<' => 1, '>' => 1, '>=' => 1, '<=' => 1, ); my %_DATE_CONDITIONS = ( '!=' => 1, '<>' => 1, '<' => 1, '>' => 1, '>=' => 1, '<=' => 1, ); sub _generic_int_filter { my ($field,$value,$negation)=@_; my $mode = $negation ? 'NOT IN':'IN'; #undef <=> NULL if ( !defined($value) ) { $mode = $negation ? 'IS NOT' : 'IS'; return "$field $mode NULL", []; } elsif ( (ref($value) eq 'ARRAY') and @$value ) { my $values_string = '?, 'x(scalar (@$value)-1).'?'; return " ($field $mode ($values_string)) ", [@$value]; } elsif (ref($value) eq 'ARRAY') { # skip empty array return ($negation ? ' TRUE ' : ' FALSE '), []; } elsif ( $value eq 'positive' || $value eq 'negative' || $value eq 'natural' ) { $mode = $value eq 'positive' ? '>' : $value eq 'negative' ? '<' : '>='; return "$field $mode 0", []; } elsif ( $value =~ /^[0-9 ,]+$/ ) { my @values = split(/\s*,\s*/, $value); my $values_string = '?, 'x(scalar (@values)-1).'?'; return " ($field $mode ($values_string)) ", \@values; } else { my $mason_comp = ref($HTML::Mason::Commands::m) ? $HTML::Mason::Commands::m->current_comp() : undef; my $mason_file = ref($mason_comp) ? $mason_comp->path : undef; my ($package, $filename, $line) = caller; warn "WARNING: $$: Неверно задан формат integer фильтра для $field ('$value'). Это может быть либо число, либо ряд чисел через запятую либо ссылка на массив чисел. called from '$package/$filename/$line' ".($mason_file ? "called from $mason_file" : '')."\n"; return ' FALSE ', []; } } sub _generic_composite_filter { my ($field, $condition, $value, $mode ) = @_; unless ( $condition ) { warn "Contenido Warning (_generic_composite_filter): Неверно задан формат!\n"; return ' FALSE '; } $condition = uc($condition) if $condition =~ /^[\w\s]+$/; if ( $value eq NIL() ) { return ' TRUE '; } elsif ( defined($value) && !ref($value) ) { if ( ( $mode eq 'text' && !$_TEXT_CONDITIONS{$condition} ) || ( $mode ne 'text' && !$_CONDITIONS{$condition} ) ) { warn "Contenido Warning (_generic_composite_filter): Неверно задан формат неравенства, недопустимое условие \"$condition\"!\n"; return ' FALSE '; } return "$field $condition ?", $value; } elsif ( ref($value) eq 'ARRAY' && scalar(@{ $value }) ) { my $in = 'IN ('; my $i = 0; unless ( $condition eq 'NOT' ) { warn "Contenido Warning (_generic_composite_filter): Неверно задан формат, недопустимое условие \"$condition\"!\n"; return ' FALSE '; } foreach my $v ( @{ $value } ) { $in .= '?'; $in .= ',' if $#{ $value } != $i; $i++; } $in .= ')'; return "$field $condition $in", $value; } else { warn "Contenido Warning (_generic_composite_filter): Неверно задано значение, ожидается скаляр или ссылка на массив!\n "; return ' FALSE '; } } sub _composite_date_filter { my ($field, $condition, $value ) = @_; unless ( $condition ) { warn "Contenido Warning (_composite_date_filter): Неверно задан формат!\n"; return ' FALSE '; } if ( $value eq NIL() ) { return ' TRUE '; } elsif ( defined($value) && !ref($value) ) { unless ( $_DATE_CONDITIONS{$condition} ) { warn "Contenido Warning (_composite_date_filter): Неверно задан формат неравенства, недопустимое условие \"$condition\"!\n"; return ' FALSE '; } if ( $value =~ /^\d+$/ ) { my $date = Contenido::DateTime->new(epoch => $value)->strftime('%Y-%m-%d %H:%M:%S'); return " ($field $condition ?::TIMESTAMP) ", $date; } elsif ( $value =~ /^\d{4}-\d{2}-\d{2}$/ || $value =~ /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?:\:\d{2})?$/ ) { return " ($field $condition ?::TIMESTAMP) ", $value; } else { warn "Contenido Warning (_composite_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n"; return ' FALSE '; } } else { warn "Contenido Warning (_composite_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n"; return ' FALSE '; } } sub _generic_null_filter { my ($field, $param )= @_; $param = uc($param); if ( $param ne 'NULL' && $param ne 'NOT NULL' ) { warn "Contenido Warning (_generic_null_filter): Неверно задан значение, ожидается 'NULL' или 'NOT NULL'!\n"; return ' FALSE '; } return "$field IS $param"; } sub _generic_date_filter { my ($field, $param )= @_; if ( defined($param) && !ref($param) ) { if ( $param =~ /^\d+$/ ) { my $date = Contenido::DateTime->new(epoch => $param)->strftime('%Y-%d-%m %H:%M'); return " ($field >= ?::TIMESTAMP AND $field < ?::TIMESTAMP+'1 min'::INTERVAL) ", [$date, $date]; } elsif ( $param =~ /^\d{4}-\d{2}-\d{2}$/ ) { return " ($field >= ?::TIMESTAMP AND $field < ?::TIMESTAMP+'1 day'::INTERVAL) ", [$param, $param]; } elsif ( $param =~ /^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}(?:\:\d{2})?$/ ) { return " ($field >= ?::TIMESTAMP AND $field < ?::TIMESTAMP+'1 min'::INTERVAL) ", [$param, $param]; } else { warn "Contenido Warning (_generic_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n"; return ' FALSE '; } } elsif ( !defined($param) ) { return "$field IS NULL"; } else { warn "Contenido Warning (_generic_date_filter): Неверно задано значение! Правильный формат строка \"YYYY-MM-DD[ HH24:MI[:SS]]\" или дата в формате unixtime\n"; return ' FALSE '; } } sub _generic_float_filter { my ($field,$value,$negation)=@_; my $mode = $negation ? 'NOT IN':'IN'; #undef <=> NULL if ( !defined($value) ) { $mode = $negation ? 'IS NOT' : 'IS'; return "$field $mode NULL", []; } elsif (ref($value) eq 'ARRAY' and @$value) { my $values_string = '?, 'x(scalar (@$value)-1).'?'; return " ($field $mode ($values_string)) ", $value; } elsif (ref($value) eq 'ARRAY') { # skip empty array return ($negation ? ' TRUE ' : ' FALSE '), []; } elsif ( $value eq 'positive' || $value eq 'negative' ) { $mode = $value eq 'positive' ? '>' : '<'; return "$field $mode 0", []; } else { return " ($field $mode (?)) ", [$value]; } } # raw flag for disable placeholders use... (need manual escaping before)... sometime need for partial indexes use with prepared statements sub _generic_text_filter { my ($field,$value,$negation,$raw)=@_; my $mode = $negation ? 'NOT IN':'IN'; #undef <=> NULL if ( !defined($value) ) { $mode = $negation ? 'IS NOT' : 'IS'; return "$field $mode NULL", []; } elsif (ref($value) eq 'ARRAY') { if ($raw) { my $values_string = "'".join ("','",@$value)."'"; return " ($field $mode ($values_string)) ", []; } else { my $values_string = '?, 'x(scalar (@$value)-1).'?'; return " ($field $mode ($values_string)) ", $value; } } else { if ($raw) { return " ($field $mode ('$value')) ", []; } else { return " ($field $mode (?)) ", [$value]; } } } sub _generic_name_filter { my ($field,$value,$negation,$opts)=@_; $opts ||= {}; #like and ilike modes incompatible with [] in $opts{name} if ($opts->{like}) { my $mode = $negation ? "NOT" : ""; return " $field $mode LIKE ? ", [$value]; } elsif ($opts->{ilike}) { my $mode = $negation ? "NOT" : ""; return " $field $mode ILIKE ? ", [$value]; } else { return &SQL::Common::_generic_text_filter($field,$value,$negation,0); } } sub _generic_intarray_filter { my ($field, $value, $opts)=@_; #undef <=> FALSE here!!!! if (defined($value)) { if (ref($value) ne 'ARRAY') { if ($value =~ /^[\d ,]+$/) { $value = [split(/\s*,\s*/, $value)]; } else { warn "Contenido Warning: В списке для _generic_int_array_filter есть нечисловые элементы ('$value'). Параметр игнорируется."; return [' FALSE '], []; } } if (@$value) { my $op = (ref($opts) eq 'HASH' and ($opts->{intersect} or $opts->{contains})) ? '@>' : '&&'; if ($DBD::Pg::VERSION=~/^1\./) { my $ph_string = '?, 'x$#{$value}.'?'; return [" ($field $op ARRAY[$ph_string]::integer[]) "], $value; } else { return [" ($field $op ?::integer[]) "], [$value]; } } else { return [' FALSE '], []; } } else { return [' FALSE '], []; } } sub _get_limit { my %opts=@_; if (exists($opts{limit})) { return ' LIMIT 1000' unless defined $opts{limit}; if ($opts{limit} !~ /\D/) { return ' LIMIT '.$opts{limit}; } else { warn "Contenido Warning: Неверно заданы пределы выборки ($opts{limit})"; return ' LIMIT 1000'; } } elsif ($opts{no_limit}) { return undef; } else { return ' LIMIT 1000'; } } sub _get_offset { my %opts=@_; if ( exists($opts{offset})) { return undef unless defined $opts{offset}; if ($opts{offset} !~ /\D/) { return ' OFFSET '.$opts{offset}; } else { my $mason_comp = ref($HTML::Mason::Commands::m) ? $HTML::Mason::Commands::m->current_comp() : undef; my $mason_file = ref($mason_comp) ? $mason_comp->path : undef; warn "ERROR: $$ ".__PACKAGE__." ".scalar(localtime())." ".($mason_file ? "called from $mason_file" : '')." Для _get_offset неверно задан параметр offset: '$opts{offset}'\n"; return undef; } } } 1;