DBD-XBase

 view release on metacpan or  search on metacpan

lib/DBD/XBase.pm  view on Meta::CPAN

my %TYPE_INFO_TYPES = map { ( $TYPE_INFO_ALL[$_][0] => $_ ) } ( 1 .. $#TYPE_INFO_ALL );
my %REVTYPES = qw( C char N numeric F float L boolean D date M blob T time );
my %REVSQLTYPES = map { ( $_ => $TYPE_INFO_ALL[  $TYPE_INFO_TYPES{ uc $REVTYPES{$_} } ][1] ) } keys %REVTYPES;

### use Data::Dumper; print STDERR Dumper \@TYPE_INFO_ALL, \%TYPE_INFO_TYPES, \%REVTYPES, \%REVSQLTYPES;

sub type_info_all {
	my $dbh = shift;
	my $result = [ @TYPE_INFO_ALL ];
	my $i = 0;
	my $hash = { map { ( $_ => $i++) } @{$result->[0]} };
	$result->[0] = $hash;
	$result;
}
sub type_info {
	my ($dbh, $type) = @_;
	my @result = ();
	for my $row ( 1 .. $#TYPE_INFO_ALL ) {
		if ($type == DBI::SQL_ALL_TYPES or $type == $TYPE_INFO_ALL[$row][1])
			{ push @result, { map { ( $TYPE_INFO_ALL[0][$_] => $TYPE_INFO_ALL[$row][$_] ) } ( 0 .. $#{$TYPE_INFO_ALL[0]} ) } }
	}
	@result;
}

sub DESTROY {
	# To avoid autoloaded DESTROY
}


# #####################
# The statement package

package DBD::XBase::st;
use strict;
$DBD::XBase::st::imp_data_size = 0;

# Binding of parameters: numbers are converted to :pnumber form,
# values are stored in the sth->{'xbase_bind_values'}->name of the
# parameter hash
sub bind_param {
	my ($sth, $parameter) = (shift, shift);
	if ($parameter =~ /^\d+$/) { $parameter = ':p'.$parameter; }
	$sth->{'xbase_bind_values'}{$parameter} = shift;
	1;
}

# Returns number of rows fetched until now
sub rows {
	defined $_[0]->{'xbase_rows'} ? $_[0]->{'xbase_rows'} : -1;
}

sub _set_rows {
	my $sth = shift;
	if (not @_ or not defined $_[0]) {
		$sth->{'xbase_rows'} = undef; return -1;
	}
	$sth->{'xbase_rows'} = ( $_[0] ? $_[0] : '0E0' );
}
# Execute the current statement, possibly binding parameters. For
# nonselect commands the actions needs to be done here, for select we
# just create the cursor and wait for fetchrows
sub execute {
	my $sth = shift;

	# the binds_order arrayref holds the conversion from the first
	# occurence of the named parameter to its name;
	# we bind the parameters here
	my $parsed_sql = $sth->{'xbase_parsed_sql'};
	for (my $i = 0; $i < @_; $i++) {
		$sth->bind_param($parsed_sql->{'binds_order'}[$i], $_[$i]);
	}

	# binded parameters
	my $bind_values = $sth->{'xbase_bind_values'};

	# cancel the count of rows done in the previous run, this is a
	# new execute
	$sth->{'xbase_rows'} = undef;
	delete $sth->{'xbase_lines'};
	
	# we'll nee dbh, table name and to command to do with them	
	my $dbh = $sth->{'Database'};
	my $table = $parsed_sql->{'table'}[0];
	my $command = $parsed_sql->{'command'};
		
	# create table first; we just create it and are done
	if ($command eq 'create') {
		my $filename = $dbh->{'Name'} . '/' . $table;
		my %opts;
		# get the name and the fields info
		@opts{ qw( name field_names field_types field_lengths
				field_decimals ) } =
			( $filename, @{$parsed_sql}{ qw( createfields
				createtypes createlengths createdecimals ) } );
		# try to create the table (and memo automatically)
		my $xbase = XBase->create(%opts) or do {
			$sth->DBI::set_err(10, XBase->errstr());
			return;
		};
		# keep the table open
		$dbh->{'xbase_tables'}->{$table} = $xbase;	
		return $sth->DBD::XBase::st::_set_rows(0);	# return true
	}

	# let's see if we've already opened the table
	my $xbase = $dbh->{'xbase_tables'}->{$table};
	if (not defined $xbase) {
		# if not, open the table now
		my $filename = $dbh->{'Name'} . '/' . $table;
		my %opts = ('name' => $filename);
		$opts{'ignorememo'} = 1 if $dbh->{'xbase_ignorememo'};
		# try to open the table using XBase.pm
		$xbase = new XBase(%opts) or do {
			$sth->DBI::set_err(3, "Table $table not found: "
							. XBase->errstr());
			return;
		};
		$dbh->{'xbase_tables'}->{$table} = $xbase;	
	}

	# the following is not multiple-statements safe -- I'd overwrite

lib/DBD/XBase.pm  view on Meta::CPAN


	# the array usedfields holds a list of field names that were
	# explicitely mentioned somewhere in the SQL query -- select
	# fields list, where clause, set clauses in update ...
	# we'll try to make a list of those that do not exist in the table
	my %nonexist;
	for my $field (@{$parsed_sql->{'usedfields'}}) {
		$nonexist{$field} = 1 unless defined $xbase->field_type($field);
	}
	if (keys %nonexist) {
		$sth->DBI::set_err(4,
			sprintf 'Field %s not found in table %s',
				join(', ', sort keys %nonexist), $table);
		return;
	}

	# inserting values means appending a new row with reasonable
	# values; the insertfn function expects the TABLE object and
	# the BIND hash (it doesn' make use of them at the moment,
	# AFAIK, because only constants are supported), it returns list
	# of values
	if ($command eq 'insert') {
		my $last = $xbase->last_record;
		my @values = &{$parsed_sql->{'insertfn'}}($xbase, $bind_values);
		
		### here, we'd really need a check for too many or too
		### few values
		if (defined $parsed_sql->{'insertfields'}) {
			my %newval;
			@newval{ @{$parsed_sql->{'insertfields'} } } = @values;
			@values = @newval{ $xbase->field_names };
		}
		$xbase->set_record($last + 1, @values) or do {
			$sth->DBI::set_err(49,'Insert failed: '.$xbase->errstr);
			return;
		};
		return $sth->DBD::XBase::st::_set_rows(1);	# we've added one row
	}


	# rows? what do we need rows here for? never mind.
	my $rows;

	# wherefn is defined if the statement had where clause; it
	# should be called with $TABLE, $VALUES and $BIND parameters
	my $wherefn = $parsed_sql->{'wherefn'};

	# we expand selectall to list of fields
	if (defined $parsed_sql->{'selectall'} and not defined $parsed_sql->{'selectfieldscount'}) {
		$parsed_sql->{'selectnames'} = [ $xbase->field_names ];
		push @{$parsed_sql->{'usedfields'}}, $xbase->field_names;
		$parsed_sql->{'selectfieldscount'} = scalar $xbase->field_names;
	}

	# we only set NUM_OF_FIELDS for select command -- which is
	# exactly what selectfieldscount means
	if (not $sth->FETCH('NUM_OF_FIELDS')) {
		$sth->STORE('NUM_OF_FIELDS', $parsed_sql->{'selectfieldscount'});
	}
		
	# this cursor will be needed, because both select and update and
	# delete with where clause need to fetch the data first
	my $cursor = $xbase->prepare_select(@{$parsed_sql->{'usedfields'}});

	
	# select with order by clause will be done using "substatement"
	if ($command eq 'select' and defined $parsed_sql->{'orderfields'}) {
		my @orderfields = @{$parsed_sql->{'orderfields'}};

		# make a copy of the $parsed_sql hash, but delete the
		# orderfields value
		my $subparsed_sql = { %$parsed_sql };
		delete $subparsed_sql->{'orderfields'};
		delete $subparsed_sql->{'selectall'};

		my $selectfn = $parsed_sql->{'selectfn'};
		$subparsed_sql->{'selectfn'} = sub {
			my ($TABLE, $VALUES, $BINDS) = @_;
			return map({ XBase::SQL::Expr->field($_, $TABLE, $VALUES)->value } @orderfields), &{$selectfn}($TABLE, $VALUES, $BINDS);
		};
### use Data::Dumper; print STDERR Dumper $subparsed_sql;
		$subparsed_sql->{'selectfieldscount'} += scalar(@orderfields);

		# make new $sth
		my $substh = DBI::_new_sth($dbh, {
			'Statement' => $sth->{'Statement'},
			'xbase_parsed_sql' => $subparsed_sql,
			}); 
		
		# bind all parameters in the substh
		for my $key (keys %$bind_values) {
			$substh->bind_param($key, $bind_values->{$key});
		}
		
		# execute and fetch all rows
		$substh->execute;
### use Data::Dumper; print STDERR Dumper $substh->{'xbase_parsed_sql'};
		my $data = $substh->fetchall_arrayref;

		my $sortfn = '';
		for (my $i = 0; $i < @orderfields; $i++) {
			$sortfn .= ' or ' if $i > 0;
			if ($xbase->field_type($orderfields[$i]) =~ /^[CML]$/) {
				if (lc($parsed_sql->{'orderdescs'}[$i]) eq 'desc') {
					$sortfn .= "\$_[1]->[$i] cmp \$_[0]->[$i]";
				} else {
					$sortfn .= "\$_[0]->[$i] cmp \$_[1]->[$i]";
				}
			} else {
				if (lc($parsed_sql->{'orderdescs'}[$i]) eq 'desc') {
					$sortfn .= "\$_[1]->[$i] <=> \$_[0]->[$i]";
				} else {
					$sortfn .= "\$_[0]->[$i] <=> \$_[1]->[$i]";
				}
			}
		}
		my $fn = eval "sub { $sortfn }";
		# sort them and store in xbase_lines
		$sth->{'xbase_lines'} =
			[ map { [ @{$_}[scalar(@orderfields) .. scalar(@$_) - 1 ] ] }
				sort { &{$fn}($a, $b) } @$data ];
	} elsif ($command eq 'select') {
		$sth->{'xbase_cursor'} = $cursor;
	} elsif ($command eq 'delete') {
		if (not defined $wherefn) {
			my $last = $xbase->last_record;
			for (my $i = 0; $i <= $last; $i++) {
				if (not (($xbase->get_record_nf($i, 0))[0])) {
					$xbase->delete_record($i);
					$rows = 0 unless defined $rows;
					$rows++;
				}
			}
		} else {
			my $values;
			while (defined($values = $cursor->fetch_hashref)) {
				next unless &{$wherefn}($xbase, $values,
				$bind_values, 0);
				$xbase->delete_record($cursor->last_fetched);
				$rows = 0 unless defined $rows;
				$rows++;
			}
		}
	} elsif ($command eq 'update') {
		my $values;
		while (defined($values = $cursor->fetch_hashref)) {
			next if defined $wherefn and not
			&{$wherefn}($xbase, $values, $bind_values);
			my %newval;
			@newval{ @{$parsed_sql->{'updatefields'}} } =
			&{$parsed_sql->{'updatefn'}}($xbase, $values,
			$bind_values);
			$xbase->update_record_hash($cursor->last_fetched, %newval);
			$rows = 0 unless defined $rows;
			$rows++;
		}
	} elsif ($command eq 'drop') {
		# dropping the table is really easy
		$xbase->drop or do {
			$sth->DBI::set_err(60, "Dropping table $table failed: "
							. $xbase->errstr);
			return;
		};
		delete $dbh->{'xbase_tables'}{$table};
		$rows = -1;
	}
	
	# finaly, set the number of rows (what if somebody will ask) and
	# return it to curious crowds
	return $sth->DBD::XBase::st::_set_rows($rows);
}



sub fetch {
        my $sth = shift;
	my $retarray;
	if (defined $sth->{'xbase_lines'}) {
		$retarray = shift @{$sth->{'xbase_lines'}};
	} elsif (defined $sth->{'xbase_cursor'}) {
		my $cursor = $sth->{'xbase_cursor'};
		my $wherefn = $sth->{'xbase_parsed_sql'}{'wherefn'};

		my $xbase = $cursor->table;
		my $values;
		while (defined($values = $cursor->fetch_hashref)) {
			### use Data::Dumper; print Dumper $sth->{'xbase_bind_values'};
			next if defined $wherefn and not
			&{$wherefn}($xbase, $values,
					$sth->{'xbase_bind_values'});
			last;
		}
		$retarray = [ &{$sth->{'xbase_parsed_sql'}{'selectfn'}}($xbase, $values, $sth->{'xbase_bind_values'}) ]
			if defined $values;
	}

### use Data::Dumper; print Dumper $retarray;

	return unless defined $retarray;

### print STDERR "sth->{'NUM_OF_FIELDS'}: $sth->{'NUM_OF_FIELDS'} sth->{'NUM_OF_PARAMS'}: $sth->{'NUM_OF_PARAMS'}\n";


	$sth->_set_fbav($retarray); return $retarray;

	my $i = 0;
	for my $ref ( @{$sth->{'xbase_bind_col'}} ) {
		next unless defined $ref;
		$$ref = $retarray->[$i];
	} continue {
		$i++;
	}
	
	return $retarray;
}
*fetchrow_arrayref = \&fetch;

sub FETCH {
	my ($sth, $attrib) = @_;
	my $parsed_sql = $sth->{'xbase_parsed_sql'};
	if ($attrib =~ /^xbase_/) {
		return $sth->{$attrib};
	}
	if ($attrib eq 'NAME') {
		if (defined $sth->{'xbase_nondata_name'}) {
			return $sth->{'xbase_nondata_name'};
		}
		return [ @{$parsed_sql->{'selectnames'}} ];
	} elsif ($attrib eq 'NULLABLE') {
		return [ (1) x scalar(@{$parsed_sql->{'selectnames'}}) ];
	} elsif ($attrib eq 'TYPE') {
		return [ map { ( $REVSQLTYPES{$_} or undef ) }
			map { ( $sth->{'Database'}->{'xbase_tables'}->{$parsed_sql->{'table'}[0]}->field_type($_)  or undef ) }
				@{$parsed_sql->{'selectnames'}} ];
	} elsif ($attrib eq 'PRECISION') {
		return [ map { $sth->{'Database'}->{'xbase_tables'}->{$parsed_sql->{'table'}[0]}->field_length($_) }
			@{$parsed_sql->{'selectnames'}} ];
	} elsif ($attrib eq 'SCALE') {
		return [ map { $sth->{'Database'}->{'xbase_tables'}->{$parsed_sql->{'table'}[0]}->field_decimal($_) }
			@{$parsed_sql->{'selectnames'}} ];
	} elsif ($attrib eq 'ChopBlanks') {
		return $parsed_sql->{'ChopBlanks'};
	} else {
		return $sth->DBD::_::st::FETCH($attrib);
	}
}



( run in 1.329 second using v1.01-cache-2.11-cpan-39bf76dae61 )