X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FRecord.pm;h=3e3af524cf1c7174ec540023e3d4841c45907f39;hb=15e57a4859d967a13113602b112c4aa197ca6002;hp=cbd6ad860654bc8fab59689348931e4ad637d1fa;hpb=1411da26cb9204d8f239ace310d2c4da503046b2;p=freeside.git diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index cbd6ad860..3e3af524c 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -4,7 +4,8 @@ use strict; use vars qw( $AUTOLOAD @ISA @EXPORT_OK $DEBUG $conf $conf_encryption $me %virtual_fields_cache - $nowarn_identical $no_update_diff $no_check_foreign + $nowarn_identical $nowarn_classload + $no_update_diff $no_check_foreign ); use Exporter; use Carp qw(carp cluck croak confess); @@ -36,6 +37,7 @@ $DEBUG = 0; $me = '[FS::Record]'; $nowarn_identical = 0; +$nowarn_classload = 0; $no_update_diff = 0; $no_check_foreign = 0; @@ -152,7 +154,8 @@ sub new { unless ( defined ( $self->table ) ) { $self->{'Table'} = shift; - carp "warning: FS::Record::new called with table name ". $self->{'Table'}; + carp "warning: FS::Record::new called with table name ". $self->{'Table'} + unless $nowarn_classload; } $self->{'Hash'} = shift; @@ -212,23 +215,24 @@ objects. The preferred usage is to pass a hash reference of named parameters: - my @records = qsearch( { - 'table' => 'table_name', - 'hashref' => { 'field' => 'value' - 'field' => { 'op' => '<', - 'value' => '420', - }, - }, - - #these are optional... - 'select' => '*', - 'extra_sql' => 'AND field ', - 'order_by' => 'ORDER BY something', - #'cache_obj' => '', #optional - 'addl_from' => 'LEFT JOIN othtable USING ( field )', - 'debug' => 1, - } - ); + @records = qsearch( { + 'table' => 'table_name', + 'hashref' => { 'field' => 'value' + 'field' => { 'op' => '<', + 'value' => '420', + }, + }, + + #these are optional... + 'select' => '*', + 'extra_sql' => 'AND field = ? AND intfield = ?', + 'extra_param' => [ 'value', [ 5, 'int' ] ], + 'order_by' => 'ORDER BY something', + #'cache_obj' => '', #optional + 'addl_from' => 'LEFT JOIN othtable USING ( field )', + 'debug' => 1, + } + ); Much code still uses old-style positional parameters, this is also probably fine in the common case where there are only two parameters: @@ -248,19 +252,31 @@ fine in the common case where there are only two parameters: my %TYPE = (); #for debugging +sub _is_fs_float { + my ($type, $value) = @_; + if ( ( $type =~ /(numeric)/i && $value =~ /^[+-]?\d+(\.\d+)?$/ ) || + ( $type =~ /(real|float4)/i && $value =~ /[-+]?\d*\.?\d+([eE][-+]?\d+)?/) + ) { + return 1; + } + ''; +} + sub qsearch { - my($stable, $record, $select, $extra_sql, $order_by, $cache, $addl_from ); + my($stable, $record, $cache ); + my( $select, $extra_sql, $extra_param, $order_by, $addl_from ); my $debug = ''; if ( ref($_[0]) ) { #hashref for now, eventually maybe accept a list too my $opt = shift; - $stable = $opt->{'table'} or die "table name is required"; - $record = $opt->{'hashref'} || {}; - $select = $opt->{'select'} || '*'; - $extra_sql = $opt->{'extra_sql'} || ''; - $order_by = $opt->{'order_by'} || ''; - $cache = $opt->{'cache_obj'} || ''; - $addl_from = $opt->{'addl_from'} || ''; - $debug = $opt->{'debug'} || ''; + $stable = $opt->{'table'} or die "table name is required"; + $record = $opt->{'hashref'} || {}; + $select = $opt->{'select'} || '*'; + $extra_sql = $opt->{'extra_sql'} || ''; + $extra_param = $opt->{'extra_param'} || []; + $order_by = $opt->{'order_by'} || ''; + $cache = $opt->{'cache_obj'} || ''; + $addl_from = $opt->{'addl_from'} || ''; + $debug = $opt->{'debug'} || ''; } else { ($stable, $record, $select, $extra_sql, $cache, $addl_from ) = @_; $select ||= '*'; @@ -283,7 +299,8 @@ sub qsearch { if ( eval 'scalar(@FS::'. $table. '::ISA);' ) { @virtual_fields = grep exists($record->{$_}), "FS::$table"->virtual_fields; } else { - cluck "warning: FS::$table not loaded; virtual fields not searchable"; + cluck "warning: FS::$table not loaded; virtual fields not searchable" + unless $nowarn_classload; @virtual_fields = (); } @@ -310,19 +327,18 @@ sub qsearch { ) { my $value = $record->{$field}; + my $op = (ref($value) && $value->{op}) ? $value->{op} : '='; $value = $value->{'value'} if ref($value); my $type = dbdef->table($table)->column($field)->type; my $TYPE = SQL_VARCHAR; - if ( $type =~ /(int|(big)?serial)/i && $value =~ /^\d+(\.\d+)?$/ ) { + if ( $type =~ /(big)?(int|serial)/i && $value =~ /^\d+(\.\d+)?$/ ) { $TYPE = SQL_INTEGER; #DBD::Pg 1.49: Cannot bind ... unknown sql_type 6 with SQL_FLOAT - } elsif ( ( $type =~ /(numeric)/i && $value =~ /^[+-]?\d+(\.\d+)?$/) - || ( $type =~ /(real|float4)/i - && $value =~ /[-+]?\d*\.?\d+([eE][-+]?\d+)?/ - ) - ) { + #fixed by DBD::Pg 2.11.8 + #can change back to SQL_FLOAT in early-mid 2010, once everyone's upgraded + } elsif ( _is_fs_float( $type, $value ) ) { $TYPE = SQL_DECIMAL; } @@ -333,8 +349,30 @@ sub qsearch { warn " bind_param $bind (for field $field), $value, TYPE $TYPE{$TYPE}\n"; } - $sth->bind_param($bind++, $value, { TYPE => $TYPE } ); + #if this needs to be re-enabled, it needs to use a custom op like + #"APPROX=" or something (better name?, not '=', to avoid affecting other + # searches + #if ($TYPE eq SQL_DECIMAL && $op eq 'APPROX=' ) { + # # these values are arbitrary; better (faster?) ones welcome + # $sth->bind_param($bind++, $value*1.00001, { TYPE => $TYPE } ); + # $sth->bind_param($bind++, $value*.99999, { TYPE => $TYPE } ); + #} else { + $sth->bind_param($bind++, $value, { TYPE => $TYPE } ); + #} + + } + foreach my $param ( @$extra_param ) { + my $TYPE = SQL_VARCHAR; + my $value = $param; + if ( ref($param) ) { + $value = $param->[0]; + my $type = $param->[1]; + if ( $type =~ /(big)?(int|serial)/i && $value =~ /^\d+(\.\d+)?$/ ) { + $TYPE = SQL_INTEGER; + } # & DECIMAL? well, who cares for now + } + $sth->bind_param($bind++, $value, { TYPE => $TYPE } ); } # $sth->execute( map $record->{$_}, @@ -346,7 +384,8 @@ sub qsearch { if ( eval 'scalar(@FS::'. $table. '::ISA);' ) { @virtual_fields = "FS::$table"->virtual_fields; } else { - cluck "warning: FS::$table not loaded; virtual fields not returned either"; + cluck "warning: FS::$table not loaded; virtual fields not returned either" + unless $nowarn_classload; @virtual_fields = (); } @@ -416,7 +455,8 @@ sub qsearch { } } } else { - cluck "warning: FS::$table not loaded; returning FS::Record objects"; + cluck "warning: FS::$table not loaded; returning FS::Record objects" + unless $nowarn_classload; @return = map { FS::Record->new( $table, { %{$_} } ); } values(%result); @@ -481,6 +521,9 @@ sub get_real_fields { my $op = '='; my $column = $_; + my $type = dbdef->table($table)->column($column)->type; + my $value = $record->{$column}; + $value = $value->{'value'} if ref($value); if ( ref($record->{$_}) ) { $op = $record->{$_}{'op'} if $record->{$_}{'op'}; #$op = 'LIKE' if $op =~ /^ILIKE$/i && driver_name ne 'Pg'; @@ -495,8 +538,7 @@ sub get_real_fields { if ( ! defined( $record->{$_} ) || $record->{$_} eq '' ) { if ( $op eq '=' ) { if ( driver_name eq 'Pg' ) { - my $type = dbdef->table($table)->column($column)->type; - if ( $type =~ /(int|(big)?serial)/i ) { + if ( $type =~ /(int|numeric|real|float4|(big)?serial)/i ) { qq-( $column IS NULL )-; } else { qq-( $column IS NULL OR $column = '' )-; @@ -506,8 +548,7 @@ sub get_real_fields { } } elsif ( $op eq '!=' ) { if ( driver_name eq 'Pg' ) { - my $type = dbdef->table($table)->column($column)->type; - if ( $type =~ /(int|(big)?serial)/i ) { + if ( $type =~ /(int|numeric|real|float4|(big)?serial)/i ) { qq-( $column IS NOT NULL )-; } else { qq-( $column IS NOT NULL AND $column != '' )-; @@ -522,6 +563,11 @@ sub get_real_fields { qq-( $column $op "" )-; } } + #if this needs to be re-enabled, it needs to use a custom op like + #"APPROX=" or something (better name?, not '=', to avoid affecting other + # searches + #} elsif ( $op eq 'APPROX=' && _is_fs_float( $type, $value ) ) { + # ( "$column <= ?", "$column >= ?" ); } else { "$column $op ?"; } @@ -1662,11 +1708,14 @@ sub batch_import { my $record = $class->new( \%hash ); + my $param = {}; while ( scalar(@later) ) { my $sub = shift @later; my $data = shift @later; - &{$sub}($record, $data, $conf); # $record->&{$sub}($data, $conf); + &{$sub}($record, $data, $conf, $param); # $record->&{$sub}($data, $conf); + last if exists( $param->{skiprow} ); } + next if exists( $param->{skiprow} ); my $error = $record->insert; @@ -1698,16 +1747,18 @@ sub _h_statement { $time ||= time; + my %nohistory = map { $_=>1 } $self->nohistory_fields; + my @fields = - grep { defined($self->getfield($_)) && $self->getfield($_) ne "" } + grep { defined($self->get($_)) && $self->get($_) ne "" && ! $nohistory{$_} } real_fields($self->table); ; - # If we're encrypting then don't ever store the payinfo or CVV2 in the history.... - # You can see if it changed by the paymask... - if ($conf && $conf->exists('encryption') ) { - @fields = grep $_ ne 'payinfo' && $_ ne 'cvv2', @fields; + # If we're encrypting then don't store the payinfo in the history + if ( $conf && $conf->exists('encryption') ) { + @fields = grep { $_ ne 'payinfo' } @fields; } + my @values = map { _quote( $self->getfield($_), $self->table, $_) } @fields; "INSERT INTO h_". $self->table. " ( ". @@ -1914,7 +1965,7 @@ sub ut_money { =item ut_text COLUMN Check/untaint text. Alphanumerics, spaces, and the following punctuation -symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] +symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < > May not be null. If there is an error, returns the error, otherwise returns false. @@ -1926,7 +1977,7 @@ sub ut_text { #warn "notexist ". \¬exist. "\n"; #warn "AUTOLOAD ". \&AUTOLOAD. "\n"; $self->getfield($field) - =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]+)$/ + =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>]+)$/ or return gettext('illegal_or_empty_text'). " $field: ". $self->getfield($field); $self->setfield($field,$1);