DBD-XBase

 view release on metacpan or  search on metacpan

lib/XBase.pm  view on Meta::CPAN

	}
	my ($rs, $fs, $undef, $fields, $table)
				= @options{ qw( rs fs undef fields table ) };
	if (defined $table) {
		eval 'use Data::ShowTable';
		if ($@) {
			warn "You requested table output format but the module Data::ShowTable doesn't\nseem to be installed correctly. Falling back to standard\n";
			$table = undef;
		} else {
			delete $options{'rs'};
			delete $options{'fs'};
		}
	}

	my @fields = ();
	my @unknown_fields;
	if (defined $fields) {
		if (ref $fields eq 'ARRAY') {
			@fields = @$fields;
		} else {
			@fields = split /\s*,\s*/, $fields;
			my $i = 0;
			while ($i < @fields) {
				if (defined $self->field_name_to_num($fields[$i])) {
					$i++;
				} elsif ($fields[$i] =~ /^(.*)-(.*)/) {
					local $^W = 0;
					my @allfields = $self->field_names;
					my ($start, $end) = ($1, $2);
					if ($start eq '') {
						$start = $allfields[0];
					}
					if ($end eq '') {
						$end = $allfields[$#allfields];
					}
					my $start_num = $self->field_name_to_num($start);
					my $end_num = $self->field_name_to_num($end);
					if ($start ne '' and not defined $start_num) {
						push @unknown_fields, $start;
					}
					if ($end ne '' and not defined $end_num) {
						push @unknown_fields, $end;
					}
					unless (defined $start and defined $end) {
						$start = 0; $end = -1;
					}
					
					splice @fields, $i, 1, @allfields[$start_num .. $end_num];
				} else {
					push @unknown_fields, $fields[$i];
					$i++;
				}
			}
		}
	}

	if (@unknown_fields) {
		$self->Error("There have been unknown fields `@unknown_fields' specified.\n");
		return 0;
	}
	my $cursor = $self->prepare_select(@fields);
	my @record;
	if (defined $table) {
		local $^W = 0;
		&ShowBoxTable( $cursor->names(), [], [],
			sub {
				if ($_[0]) { $cursor->rewind(); }
				else { $cursor->fetch() }
				});
	} else {
		while (@record = $cursor->fetch) {
			print join($fs, map { defined $_ ? $_ : $undef } @record), $rs;
		}
	}
	1;
}


# ###################
# Reading the records

# Returns fields of the specified record; parameters and number of the
# record (starting from 0) and optionally names of the required
# fields. If no names are specified, all fields are returned. The
# first value in the returned list if always 1/0 deleted flag. Returns
# empty list on error.

sub get_record {
	my ($self, $num) = (shift, shift);
	$self->NullError();
	$self->get_record_nf( $num, map { $self->field_name_to_num($_); } @_);
}
*get_record_as_hash = \&get_record_hash;
sub get_record_hash {
	my ($self, $num) = @_;
	my @list = $self->get_record($num) or return;
	my $hash = {};
	@{$hash}{ '_DELETED', $self->field_names() } = @list;
	return %$hash if wantarray;
	$hash;
}
sub get_record_nf {
	my ($self, $num, @fieldnums) = @_;
	my $data = $self->read_record($num) or return;
	if (not @fieldnums) {
		@fieldnums = ( 0 .. $self->last_field );
	}
	my $unpack = join ' ', '@0a1',
		map { my $e;
			defined $_ and $e = $self->{'field_unpacks'}[$_];
			defined $e ? $e : '@0a0'; } @fieldnums;
	
	my $rproc = $self->{'field_rproc'};
	my @fns = (\&_read_deleted, map { (defined $_ and defined $rproc->[$_]) ? $rproc->[$_] : sub { undef; }; } @fieldnums);

	my @out = unpack $unpack, $data;
### 	if ($self->{'encrypted'} ne "\000") {
### 		for my $data (@out) {
### 			for (my $i = 0; $i < length($data); $i++) {
### 				## my $num = unpack 'C', substr($data, $i, 1);
### 				## substr($data, $i, 1) = 	pack 'C', (($num >> 3) | ($num << 5) ^ 020);
### 				my $num = unpack 'C', substr($data, $i, 1);
### 				substr($data, $i, 1) = 	pack 'C', (($num >> 1) | ($num << 7) ^ 052);
### 				}
### 			}
### 		}

	for (@out) { $_ = &{ shift @fns }($_); }

	@out;
}

# Processing on read
sub _read_deleted {
	my $value = shift;
	if ($value eq '*') { return 1; } elsif ($value eq ' ') { return 0; }
	undef;
}

sub get_all_records {
	my $self = shift;
	my $cursor = $self->prepare_select(@_);

	my $result = [];
	my @record;
	while (@record = $cursor->fetch())
		{ push @$result, [ @record ]; }
	$result;
}

# #############
# Write records

# Write record, values of the fields are in the argument list.
# Record is always undeleted
sub set_record {
	my ($self, $num, @data) = @_;
	$self->NullError();
	my $wproc = $self->{'field_wproc'};

	if (defined $self->{'attached_index_columns'}) {
		my @nfs = keys %{$self->{'attached_index_columns'}};
		my ($del, @old_data) = $self->get_record_nf($num, @nfs);

		local $^W = 0;
		for my $nf (@nfs) {
			if ($old_data[$nf] ne $data[$nf]) {
				for my $idx (@{$self->{'attached_index_columns'}{$nf}}) {
					$idx->delete($old_data[$nf], $num + 1);
					$idx->insert($data[$nf], $num + 1);
				}
			}
		}
	}

	for (my $i = 0; $i <= $#$wproc; $i++) {
		$data[$i] = &{ $wproc->[$i] }($data[$i]);
	}
	unshift @data, ' ';

### 	if ($self->{'encrypted'} ne "\000") {
### 		for my $data (@data) {
### 			for (my $i = 0; $i < length($data); $i++) {
### 				my $num = unpack 'C', substr($data, $i, 1);
### 				substr($data, $i, 1) = 	pack 'C', (($num << 3) | ($num >> 5) ^ 020);
### 				}
### 			}
### 		}

	$self->write_record($num, @data);
}

# Write record, fields are specified as hash, unspecified are set to
# undef/empty
sub set_record_hash {
	my ($self, $num, %data) = @_;
	$self->NullError();
	$self->set_record($num, map { $data{$_} } $self->field_names );
}

# Write record, fields specified as hash, unspecified will be
# unchanged
sub update_record_hash {
	my ($self, $num) = ( shift, shift );
	$self->NullError();

lib/XBase.pm  view on Meta::CPAN

=item get_record_as_hash

Returns hash (in list context) or reference to hash (in scalar
context) containing field values indexed by field names. The name of
the deleted flag is C<_DELETED>. The only parameter in the call is
the record number. The field names are returned as uppercase.

=back

=head2 Writing the data

All three writing methods always undelete the record. On success they
return true -- the record number actually written.

=over 4

=item set_record

As parameters, takes the number of the record and the list of values
of the fields. It writes the record to the file. Unspecified fields
(if you pass less than you should) are set to undef/empty.

=item set_record_hash

Takes number of the record and hash as parameters, sets the fields,
unspecified are undefed/emptied.

=item update_record_hash

Like B<set_record_hash> but fields that do not have value specified
in the hash retain their value.

=back

To explicitely delete/undelete a record, use methods B<delete_record>
or B<undelete_record> with record number as a parameter.

Assorted examples of reading and writing:

    my @data = $table->get_record(3, "jezek", "krtek");
    my $hashref = $table->get_record_as_hash(38);
    $table->set_record_hash(8, "jezek" => "jezecek",
					"krtek" => 5);
    $table->undelete_record(4);

This is a code to update field MSG in record where ID is 123.

    use XBase;
    my $table = new XBase "test.dbf" or die XBase->errstr;
    for (0 .. $table->last_record) {
    	my ($deleted, $id) = $table->get_record($_, "ID")
    	die $table->errstr unless defined $deleted;
    	next if $deleted;
	$table->update_record_hash($_, "MSG" => "New message")
						if $id == 123;
    }

=head2 Sequentially reading the file

If you plan to sequentially walk through the file, you can create
a cursor first and then repeatedly call B<fetch> to get next record.

=over 4

=item prepare_select

As parameters, pass list of field names to return, if no parameters,
the following B<fetch> will return all fields.

=item prepare_select_with_index

The first parameter is the file name of the index file, the rest is
as above. For index types that can hold more index structures in on
file, use arrayref instead of the file name and in that array include
file name and the tag name, and optionaly the index type.
The B<fetch> will then return records in the ascending order,
according to the index.

=back

Prepare will return object cursor, the following method are methods of
the cursor, not of the table.

=over 4

=item fetch

Returns the fields of the next available undeleted record. The list
thus doesn't contain the C<_DELETED> flag since you are guaranteed
that the record is not deleted.

=item fetch_hashref

Returns a hash reference of fields for the next non deleted record.

=item last_fetched

Returns the number of the record last fetched.

=item find_eq

This only works with cursor created via B<prepare_select_with_index>.
Will roll to the first record what is equal to specified argument, or
to the first greater if there is none equal. The following B<fetch>es
then continue normally.

=back

Examples of using cursors:

    my $table = new XBase "names.dbf" or die XBase->errstr;
    my $cursor = $table->prepare_select("ID", "NAME", "STREET");
    while (my @data = $cursor->fetch) {
	### do something here, like print "@data\n";
    }

    my $table = new XBase "employ.dbf";
    my $cur = $table->prepare_select_with_index("empid.ndx");
    ## my $cur = $table->prepare_select_with_index(
		["empid.cdx", "ADDRES", "char"], "id", "address");
    $cur->find_eq(1097);
    while (my $hashref = $cur->fetch_hashref
			and $hashref->{"ID"} == 1097) {
	### do something here with $hashref
    }

The second example shows that after you have done B<find_eq>, the
B<fetch>es continue untill the end of the index, so you have to check
whether you are still on records with given value. And if there is no
record with value 1097 in the indexed field, you will just get the
next record in the order.

The updating example can be rewritten to:

    use XBase;
    my $table = new XBase "test.dbf" or die XBase->errstr;
    my $cursor = $table->prepare_select("ID")
    while (my ($id) = $cursor->fetch) {
	$table->update_record_hash($cursor->last_fetched,
			"MSG" => "New message") if $id == 123	
    }

=head2 Dumping the content of the file

A method B<get_all_records> returns reference to an array containing
array of values for each undeleted record at once. As parameters,
pass list of fields to return for each record.

To print the content of the file in a readable form, use method
B<dump_records>. It prints all not deleted records from the file. By
default, all fields are printed, separated by colons, one record on
a row. The method can have parameters in a form of a hash with the
following keys:

=over 4

=item rs

Record separator, string, newline by default.

=item fs

Field separator, string, one colon by default.

=item fields

Reference to a list of names of the fields to print. By default it's
undef, meaning all fields.

=item undef

What to print for undefined (NULL) values, empty string by default.

=back

Example of use is

    use XBase;
    my $table = new XBase "table" or die XBase->errstr;
    $table->dump_records("fs" => " | ", "rs" => " <-+\n",
			"fields" => [ "id", "msg" ]);'

Also note that there is a script dbfdump(1) that does the printing.

=head2 Errors and debugging

If the method fails (returns false or null list), the error message
can be retrieved via B<errstr> method. If the B<new> or B<create>
method fails, you have no object so you get the error message using
class syntax C<XBase-E<gt>errstr()>.

The method B<header_info> returns (not prints) string with
information about the file and about the fields.

Module XBase::Base(3) defines some basic functions that are inherited
by both XBase and XBase::Memo(3) module.

=head1 DATA TYPES



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