#export dbdef for now... everything else expects to find it here
@EXPORT_OK = qw(dbh fields hfields qsearch qsearchs dbdef jsearch
- str2time_sql str2time_sql_closing );
+ str2time_sql str2time_sql_closing regexp_sql not_regexp_sql );
$DEBUG = 0;
$me = '[FS::Record]';
$self->set(@_);
}
+=item exists COLUMN
+
+Returns true if the column/field/key COLUMN exists.
+
+=cut
+
+sub exists {
+ my($self,$field) = @_;
+ exists($self->{Hash}->{$field});
+}
+
=item AUTLOADED METHODS
$record->column is a synonym for $record->get('column');
format_headers => $opt->{format_headers},
format_sep_chars => $opt->{format_sep_chars},
format_fixedlength_formats => $opt->{format_fixedlength_formats},
+ format_row_callbacks => $opt->{format_row_callbacks},
#per-import
job => $job,
file => $file,
=item table
+=item format - usual way to specify import, with this format string selecting data from the formats and format_* info hashes
+
=item formats
=item format_types
=item format_fixedlength_formats
+=item format_row_callbacks
+
+=item fields - Alternate way to specify import, specifying import fields directly as a listref
+
+=item postinsert_callback
+
=item params
=item job
csv, xls or fixedlength
-=item format
-
=item empty_ok
=back
if $DEBUG;
my $table = $param->{table};
- my $formats = $param->{formats};
my $job = $param->{job};
my $file = $param->{file};
- my $format = $param->{'format'};
my $params = $param->{params} || {};
- die "unknown format $format" unless exists $formats->{ $format };
+ my( $type, $header, $sep_char, $fixedlength_format, $row_callback, @fields );
+ my $postinsert_callback = '';
+ if ( $param->{'format'} ) {
+
+ my $format = $param->{'format'};
+ my $formats = $param->{formats};
+ die "unknown format $format" unless exists $formats->{ $format };
+
+ $type = $param->{'format_types'}
+ ? $param->{'format_types'}{ $format }
+ : $param->{type} || 'csv';
+
+
+ $header = $param->{'format_headers'}
+ ? $param->{'format_headers'}{ $param->{'format'} }
+ : 0;
+
+ $sep_char = $param->{'format_sep_chars'}
+ ? $param->{'format_sep_chars'}{ $param->{'format'} }
+ : ',';
+
+ $fixedlength_format =
+ $param->{'format_fixedlength_formats'}
+ ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
+ : '';
+
+ $row_callback =
+ $param->{'format_row_callbacks'}
+ ? $param->{'format_row_callbacks'}{ $param->{'format'} }
+ : '';
+
+ @fields = @{ $formats->{ $format } };
+
+ } elsif ( $param->{'fields'} ) {
- my $type = $param->{'format_types'}
- ? $param->{'format_types'}{ $format }
- : $param->{type} || 'csv';
+ $type = ''; #infer from filename
+ $header = 0;
+ $sep_char = ',';
+ $fixedlength_format = '';
+ $row_callback = '';
+ @fields = @{ $param->{'fields'} };
+
+ $postinsert_callback = $param->{'postinsert_callback'}
+ if $param->{'postinsert_callback'}
+
+ } else {
+ die "neither format nor fields specified";
+ }
+
+ #my $file = $param->{file};
unless ( $type ) {
if ( $file =~ /\.(\w+)$/i ) {
if $param->{'default_csv'} && $type ne 'xls';
}
- my $header = $param->{'format_headers'}
- ? $param->{'format_headers'}{ $param->{'format'} }
- : 0;
-
- my $sep_char = $param->{'format_sep_chars'}
- ? $param->{'format_sep_chars'}{ $param->{'format'} }
- : ',';
-
- my $fixedlength_format =
- $param->{'format_fixedlength_formats'}
- ? $param->{'format_fixedlength_formats'}{ $param->{'format'} }
- : '';
-
- my @fields = @{ $formats->{ $format } };
my $row = 0;
my $count;
local $FS::UID::AutoCommit = 0;
my $dbh = dbh;
+ #my $params = $param->{params} || {};
if ( $param->{'batch_namecol'} && $param->{'batch_namevalue'} ) {
my $batch_col = $param->{'batch_keycol'};
$params->{ $batch_col } = $batch_value;
}
-
+
+ #my $job = $param->{job};
my $line;
my $imported = 0;
my( $last, $min_sec ) = ( time, 5 ); #progressbar foo
next if $line =~ /^\s*$/; #skip empty lines
+ $line = &{$row_callback}($line) if $row_callback;
+
$parser->parse($line) or do {
$dbh->rollback if $oldAutoCommit;
return "can't parse: ". $parser->error_input();
}
+ #my $table = $param->{table};
my $class = "FS::$table";
my $record = $class->new( \%hash );
$row++;
$imported++;
+ if ( $postinsert_callback ) {
+ my $error = &{$postinsert_callback}($record, $param);
+ if ( $error ) {
+ $dbh->rollback if $oldAutoCommit;
+ return "postinsert_callback error". ( $line ? " for $line" : '' ).
+ ": $error";
+ }
+ }
+
if ( $job && time - $min_sec > $last ) { #progress bar
$job->update_statustext( int(100 * $imported / $count) );
$last = time;
sub ut_textn {
my($self,$field)=@_;
- $self->getfield($field)
- =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]]*)$/
- or return gettext('illegal_text'). " $field: ". $self->getfield($field);
- $self->setfield($field,$1);
- '';
+ return $self->setfield($field, '') if $self->getfield($field) =~ /^$/;
+ $self->ut_text($field);
}
=item ut_alpha COLUMN
'';
}
-=item ut_alpha COLUMN
+=item ut_alphan COLUMN
Check/untaint alphanumeric strings (no spaces). May be null. If there is an
error, returns the error, otherwise returns false.
'';
}
+=item ut_alphasn COLUMN
+
+Check/untaint alphanumeric strings, spaces allowed. May be null. If there is
+an error, returns the error, otherwise returns false.
+
+=cut
+
+sub ut_alphasn {
+ my($self,$field)=@_;
+ $self->getfield($field) =~ /^([\w ]*)$/
+ or return "Illegal (alphanumeric) $field: ". $self->getfield($field);
+ $self->setfield($field,$1);
+ '';
+}
+
+
=item ut_alpha_lower COLUMN
Check/untaint lowercase alphanumeric strings (no spaces). May not be null. If
$h ? $h->history_date : '';
}
-=item scalar_sql SQL
+=item scalar_sql SQL [ PLACEHOLDER, ... ]
+
+A class or object method. Executes the sql statement represented by SQL and
+returns a scalar representing the result: the first column of the first row.
-A class method with a propensity for becoming an instance method. This
-method executes the sql statement represented by SQL and returns a scalar
-representing the result. Don't ask for rows -- you get the first column
-of the first row. Don't give me bogus SQL or I'll die on you.
+Dies on bogus SQL. Returns an empty string if no row is returned.
-Returns an empty string in the event of no rows.
+Typically used for statments which return a single value such as "SELECT
+COUNT(*) FROM table WHERE something" OR "SELECT column FROM table WHERE key = ?"
=cut
sub scalar_sql {
- my($self, $sql ) = ( shift, shift );
+ my($self, $sql) = (shift, shift);
my $sth = dbh->prepare($sql) or die dbh->errstr;
- $sth->execute
+ $sth->execute(@_)
or die "Unexpected error executing statement $sql: ". $sth->errstr;
- $sth->fetchrow_arrayref->[0] || '';
+ my $scalar = $sth->fetchrow_arrayref->[0];
+ defined($scalar) ? $scalar : '';
}
=back
return ' ) ';
}
+=item regexp_sql [ DRIVER_NAME ]
+
+Returns the operator to do a regular expression comparison based on database
+type, such as '~' for Pg or 'REGEXP' for mysql.
+
+You can pass an optional driver name such as "Pg", "mysql" or
+$dbh->{Driver}->{Name} to return a function for that database instead of
+the current database.
+
+=cut
+
+sub regexp_sql {
+ my $driver = shift || driver_name;
+
+ return '~' if $driver =~ /^Pg/i;
+ return 'REGEXP' if $driver =~ /^mysql/i;
+
+ die "don't know how to use regular expressions in ". driver_name." databases";
+
+}
+
+=item not_regexp_sql [ DRIVER_NAME ]
+
+Returns the operator to do a regular expression negation based on database
+type, such as '!~' for Pg or 'NOT REGEXP' for mysql.
+
+You can pass an optional driver name such as "Pg", "mysql" or
+$dbh->{Driver}->{Name} to return a function for that database instead of
+the current database.
+
+=cut
+
+sub not_regexp_sql {
+ my $driver = shift || driver_name;
+
+ return '!~' if $driver =~ /^Pg/i;
+ return 'NOT REGEXP' if $driver =~ /^mysql/i;
+
+ die "don't know how to use regular expressions in ". driver_name." databases";
+
+}
+
=back
=head1 BUGS