summaryrefslogtreecommitdiff
path: root/Master/tlpkg/tlperl/lib/DBIx/Simple.pm
diff options
context:
space:
mode:
Diffstat (limited to 'Master/tlpkg/tlperl/lib/DBIx/Simple.pm')
-rwxr-xr-xMaster/tlpkg/tlperl/lib/DBIx/Simple.pm926
1 files changed, 926 insertions, 0 deletions
diff --git a/Master/tlpkg/tlperl/lib/DBIx/Simple.pm b/Master/tlpkg/tlperl/lib/DBIx/Simple.pm
new file mode 100755
index 00000000000..dc13b3c0d18
--- /dev/null
+++ b/Master/tlpkg/tlperl/lib/DBIx/Simple.pm
@@ -0,0 +1,926 @@
+use 5.006;
+use strict;
+use DBI;
+use Carp ();
+
+$DBIx::Simple::VERSION = '1.32';
+$Carp::Internal{$_} = 1
+ for qw(DBIx::Simple DBIx::Simple::Result DBIx::Simple::DeadObject);
+
+my $quoted = qr/(?:'[^']*'|"[^"]*")*/; # 'foo''bar' simply matches the (?:) twice
+my $quoted_mysql = qr/(?:(?:[^\\']*(?:\\.[^\\']*)*)'|"(?:[^\\"]*(?:\\.[^\\"]*)*)")*/;
+
+my %statements; # "$db" => { "$st" => $st, ... }
+my %old_statements; # "$db" => [ [ $query, $st ], ... ]
+my %keep_statements; # "$db" => $int
+
+my $err_message = '%s no longer usable (because of %%s)';
+my $err_cause = '%s at %s line %d';
+
+package DBIx::Simple;
+
+### private helper subs
+
+sub _dummy { bless \my $dummy, 'DBIx::Simple::Dummy' }
+sub _swap {
+ my ($hash1, $hash2) = @_;
+ my $tempref = ref $hash1;
+ my $temphash = { %$hash1 };
+ %$hash1 = %$hash2;
+ bless $hash1, ref $hash2;
+ %$hash2 = %$temphash;
+ bless $hash2, $tempref;
+}
+
+### constructor
+
+sub connect {
+ my ($class, @arguments) = @_;
+ my $self = { lc_columns => 1, result_class => 'DBIx::Simple::Result' };
+ if (defined $arguments[0] and UNIVERSAL::isa($arguments[0], 'DBI::db')) {
+ $self->{dont_disconnect} = 1;
+ $self->{dbh} = shift @arguments;
+ Carp::carp("Additional arguments for $class->connect are ignored")
+ if @arguments;
+ } else {
+ $arguments[3]->{PrintError} = 0
+ unless defined $arguments[3] and defined $arguments[3]{PrintError};
+ $self->{dbh} = DBI->connect(@arguments);
+ }
+
+ return undef unless $self->{dbh};
+
+ $self->{dbd} = $self->{dbh}->{Driver}->{Name};
+ bless $self, $class;
+
+ $statements{$self} = {};
+ $old_statements{$self} = [];
+ $keep_statements{$self} = 16;
+
+ return $self;
+}
+
+sub new {
+ my ($class) = shift;
+ $class->connect(@_);
+}
+
+### properties
+
+sub keep_statements : lvalue { $keep_statements{ $_[0] } }
+sub lc_columns : lvalue { $_[0]->{lc_columns} }
+sub result_class : lvalue { $_[0]->{result_class} }
+
+sub abstract : lvalue {
+ require SQL::Abstract;
+ $_[0]->{abstract} ||= SQL::Abstract->new;
+}
+
+### private methods
+
+# Replace (??) with (?, ?, ?, ...)
+sub _replace_omniholder {
+ my ($self, $query, $binds) = @_;
+ return if $$query !~ /\(\?\?\)/;
+ my $omniholders = 0;
+ my $q = $self->{dbd} =~ /mysql/ ? $quoted_mysql : $quoted;
+ $$query =~ s[($q|\(\?\?\))] {
+ $1 eq '(??)'
+ ? do {
+ Carp::croak('There can be only one omniholder')
+ if $omniholders++;
+ '(' . join(', ', ('?') x @$binds) . ')'
+ }
+ : $1
+ }eg;
+}
+
+# Invalidate and clean up
+sub _die {
+ my ($self, $cause) = @_;
+
+ defined and $_->_die($cause, 0)
+ for values %{ $statements{$self} },
+ map $$_[1], @{ $old_statements{$self} };
+ delete $statements{$self};
+ delete $old_statements{$self};
+ delete $keep_statements{$self};
+
+ unless ($self->{dont_disconnect}) {
+ # Conditional, because destruction order is not guaranteed
+ # during global destruction.
+ $self->{dbh}->disconnect() if defined $self->{dbh};
+ }
+
+ _swap(
+ $self,
+ bless {
+ what => 'Database object',
+ cause => $cause
+ }, 'DBIx::Simple::DeadObject'
+ ) unless $cause =~ /DESTROY/; # Let's not cause infinite loops :)
+}
+
+### public methods
+
+sub query {
+ my ($self, $query, @binds) = @_;
+ $self->{success} = 0;
+
+ $self->_replace_omniholder(\$query, \@binds);
+
+ my $st;
+ my $sth;
+
+ my $old = $old_statements{$self};
+
+ if (my $i = (grep $old->[$_][0] eq $query, 0..$#$old)[0]) {
+ $st = splice(@$old, $i, 1)->[1];
+ $sth = $st->{sth};
+ } else {
+ eval { $sth = $self->{dbh}->prepare($query) } or do {
+ if ($@) {
+ $@ =~ s/ at \S+ line \d+\.\n\z//;
+ Carp::croak($@);
+ }
+ $self->{reason} = "Prepare failed ($DBI::errstr)";
+ return _dummy;
+ };
+
+ # $self is quoted on purpose, to pass along the stringified version,
+ # and avoid increasing reference count.
+ $st = bless {
+ db => "$self",
+ sth => $sth,
+ query => $query
+ }, 'DBIx::Simple::Statement';
+ $statements{$self}{$st} = $st;
+ }
+
+ eval { $sth->execute(@binds) } or do {
+ if ($@) {
+ $@ =~ s/ at \S+ line \d+\.\n\z//;
+ Carp::croak($@);
+ }
+
+ $self->{reason} = "Execute failed ($DBI::errstr)";
+ return _dummy;
+ };
+
+ $self->{success} = 1;
+
+ return bless { st => $st, lc_columns => $self->{lc_columns} }, $self->{result_class};
+}
+
+sub error {
+ my ($self) = @_;
+ return 'DBI error: ' . (ref $self ? $self->{dbh}->errstr : $DBI::errstr);
+}
+
+sub dbh { $_[0]->{dbh} }
+sub begin_work { $_[0]->{dbh}->begin_work }
+sub begin { $_[0]->begin_work }
+sub commit { $_[0]->{dbh}->commit }
+sub rollback { $_[0]->{dbh}->rollback }
+sub func { shift->{dbh}->func(@_) }
+
+sub last_insert_id {
+ my ($self) = @_;
+
+ ($self->{dbi_version} ||= DBI->VERSION) >= 1.38 or Carp::croak(
+ "DBI v1.38 required for last_insert_id" .
+ "--this is only $self->{dbi_version}, stopped"
+ );
+
+ return shift->{dbh}->last_insert_id(@_);
+}
+
+sub disconnect {
+ my ($self) = @_;
+ $self->_die(sprintf($err_cause, "$self->disconnect", (caller)[1, 2]));
+}
+
+sub DESTROY {
+ my ($self) = @_;
+ $self->_die(sprintf($err_cause, "$self->DESTROY", (caller)[1, 2]));
+}
+
+### public methods wrapping SQL::Abstract
+
+for my $method (qw/select insert update delete/) {
+ no strict 'refs';
+ *$method = sub {
+ my $self = shift;
+ return $self->query($self->abstract->$method(@_));
+ }
+}
+
+### public method wrapping SQL::Interp
+
+sub iquery {
+ require SQL::Interp;
+ my $self = shift;
+ return $self->query( SQL::Interp::sql_interp(@_) );
+}
+
+package DBIx::Simple::Dummy;
+
+use overload
+ '""' => sub { shift },
+ bool => sub { 0 };
+
+sub new { bless \my $dummy, shift }
+sub AUTOLOAD { return }
+
+package DBIx::Simple::DeadObject;
+
+sub _die {
+ my ($self) = @_;
+ Carp::croak(
+ sprintf(
+ "(This should NEVER happen!) " .
+ sprintf($err_message, $self->{what}),
+ $self->{cause}
+ )
+ );
+}
+
+sub AUTOLOAD {
+ my ($self) = @_;
+ Carp::croak(
+ sprintf(
+ sprintf($err_message, $self->{what}),
+ $self->{cause}
+ )
+ );
+}
+sub DESTROY { }
+
+package DBIx::Simple::Statement;
+
+sub _die {
+ my ($self, $cause, $save) = @_;
+
+ $self->{sth}->finish() if defined $self->{sth};
+ $self->{dead} = 1;
+
+ my $stringy_db = "$self->{db}";
+ my $stringy_self = "$self";
+
+ my $foo = bless {
+ what => 'Statement object',
+ cause => $cause
+ }, 'DBIx::Simple::DeadObject';
+
+ DBIx::Simple::_swap($self, $foo);
+
+ my $old = $old_statements{ $foo->{db} };
+ my $keep = $keep_statements{ $foo->{db} };
+
+ if ($save and $keep) {
+ $foo->{dead} = 0;
+ shift @$old until @$old + 1 <= $keep;
+ push @$old, [ $foo->{query}, $foo ];
+ }
+
+ delete $statements{ $stringy_db }{ $stringy_self };
+}
+
+sub DESTROY {
+ # This better only happen during global destruction...
+ return if $_[0]->{dead};
+ $_[0]->_die('Ehm', 0);
+}
+
+package DBIx::Simple::Result;
+
+sub _die {
+ my ($self, $cause) = @_;
+ if ($cause) {
+ $self->{st}->_die($cause, 1);
+ DBIx::Simple::_swap(
+ $self,
+ bless {
+ what => 'Result object',
+ cause => $cause,
+ }, 'DBIx::Simple::DeadObject'
+ );
+ } else {
+ $cause = $self->{st}->{cause};
+ DBIx::Simple::_swap(
+ $self,
+ bless {
+ what => 'Result object',
+ cause => $cause
+ }, 'DBIx::Simple::DeadObject'
+ );
+ Carp::croak(
+ sprintf(
+ sprintf($err_message, $self->{what}),
+ $cause
+ )
+ );
+ }
+}
+
+sub func { shift->{st}->{sth}->func(@_) }
+sub attr { my $dummy = $_[0]->{st}->{sth}->{$_[1]} }
+
+sub columns {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my $c = $_[0]->{st}->{sth}->{ $_[0]->{lc_columns} ? 'NAME_lc' : 'NAME' };
+ return wantarray ? @$c : $c;
+}
+
+sub bind {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ $_[0]->{st}->{sth}->bind_columns(\@_[1..$#_]);
+}
+
+sub fetch {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ return $_[0]->{st}->{sth}->fetch;
+}
+
+sub into {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my $sth = $_[0]->{st}->{sth};
+ $sth->bind_columns(\@_[1..$#_]) if @_ > 1;
+ return $sth->fetch;
+}
+
+sub list {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ return $_[0]->{st}->{sth}->fetchrow_array if wantarray;
+ return($_[0]->{st}->{sth}->fetchrow_array)[-1];
+}
+
+sub array {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my $row = $_[0]->{st}->{sth}->fetchrow_arrayref or return;
+ return [ @$row ];
+}
+
+sub hash {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ return $_[0]->{st}->{sth}->fetchrow_hashref(
+ $_[0]->{lc_columns} ? 'NAME_lc' : 'NAME'
+ );
+}
+
+sub flat {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ return map @$_, $_[0]->arrays if wantarray;
+ return [ map @$_, $_[0]->arrays ];
+}
+
+sub arrays {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ return @{ $_[0]->{st}->{sth}->fetchall_arrayref } if wantarray;
+ return $_[0]->{st}->{sth}->fetchall_arrayref;
+}
+
+sub hashes {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my ($self) = @_;
+ my @return;
+ my $dummy;
+ push @return, $dummy while $dummy = $self->hash;
+ return wantarray ? @return : \@return;
+}
+
+sub map_hashes {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my ($self, $keyname) = @_;
+ Carp::croak('Key column name not optional') if not defined $keyname;
+ my @rows = $self->hashes;
+ my @keys;
+ push @keys, delete $_->{$keyname} for @rows;
+ my %return;
+ @return{@keys} = @rows;
+ return wantarray ? %return : \%return;
+}
+
+sub map_arrays {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my ($self, $keyindex) = @_;
+ $keyindex += 0;
+ my @rows = $self->arrays;
+ my @keys;
+ push @keys, splice @$_, $keyindex, 1 for @rows;
+ my %return;
+ @return{@keys} = @rows;
+ return wantarray ? %return : \%return;
+}
+
+sub map {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ return map @$_, @{ $_[0]->{st}->{sth}->fetchall_arrayref } if wantarray;
+ return { map @$_, @{ $_[0]->{st}->{sth}->fetchall_arrayref } };
+}
+
+sub rows {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ $_[0]->{st}->{sth}->rows;
+}
+
+sub xto {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ require DBIx::XHTML_Table;
+ my $self = shift;
+ my $attr = ref $_[0] ? $_[0] : { @_ };
+
+ # Old DBD::SQLite (.29) spits out garbage if done *after* fetching.
+ my $columns = $self->{st}->{sth}->{NAME};
+
+ return DBIx::XHTML_Table->new(
+ scalar $self->arrays,
+ $columns,
+ $attr
+ );
+}
+
+sub html {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my $self = shift;
+ my $attr = ref $_[0] ? $_[0] : { @_ };
+ return $self->xto($attr)->output($attr);
+}
+
+sub text {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my ($self, $type) = @_;
+ my $text_table = defined $type && length $type
+ ? 0
+ : eval { require Text::Table; $type = 'table'; 1 };
+ $type ||= 'neat';
+ if ($type eq 'box' or $type eq 'table') {
+ my $box = $type eq 'box';
+ $text_table or require Text::Table;
+ my @columns = map +{ title => $_, align_title => 'center' },
+ @{ $self->{st}->{sth}->{NAME} };
+ my $c = 0;
+ splice @columns, $_ + $c++, 0, \' | ' for 1 .. $#columns;
+ my $table = Text::Table->new(
+ ($box ? \'| ' : ()),
+ @columns,
+ ($box ? \' |' : ())
+ );
+ $table->load($self->arrays);
+ my $rule = $table->rule(qw/- +/);
+ return join '',
+ ($box ? $rule : ()),
+ $table->title, $rule, $table->body,
+ ($box ? $rule : ());
+ }
+ Carp::carp("Unknown type '$type'; using 'neat'") if $type ne 'neat';
+ return join '', map DBI::neat_list($_) . "\n", $self->arrays;
+}
+
+sub finish {
+ $_[0]->_die if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my ($self) = @_;
+ $self->_die(
+ sprintf($err_cause, "$self->finish", (caller)[1, 2])
+ );
+}
+
+sub DESTROY {
+ return if ref $_[0]->{st} eq 'DBIx::Simple::DeadObject';
+ my ($self) = @_;
+ $self->_die(
+ sprintf($err_cause, "$self->DESTROY", (caller)[1, 2])
+ );
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+DBIx::Simple - Easy-to-use OO interface to DBI
+
+=head1 SYNOPSIS
+
+=head2 DBIx::Simple
+
+ $db = DBIx::Simple->connect(...) # or ->new
+
+ $db->keep_statements = 16
+ $db->lc_columns = 1
+ $db->result_class = 'DBIx::Simple::Result';
+
+ $db->begin_work $db->commit
+ $db->rollback $db->disconnect
+ $db->func(...) $db->last_insert_id
+
+ $result = $db->query(...)
+
+=head2 DBIx::SImple + SQL::Interp
+
+ $result = $db->iquery(...)
+
+=head2 DBIx::Simple + SQL::Abstract
+
+ $db->abstract = SQL::Abstract->new(...)
+
+ $result = $db->select(...)
+ $result = $db->insert(...)
+ $result = $db->update(...)
+ $result = $db->delete(...)
+
+=head2 DBIx::Simple::Result
+
+ @columns = $result->columns
+
+ $result->into($foo, $bar, $baz)
+ $row = $result->fetch
+
+ @row = $result->list @rows = $result->flat
+ $row = $result->array @rows = $result->arrays
+ $row = $result->hash @rows = $result->hashes
+
+ %map = $result->map_arrays(...)
+ %map = $result->map_hashes(...)
+ %map = $result->map
+
+ $rows = $result->rows
+
+ $dump = $result->text
+
+ $result->finish
+
+=head2 DBIx::Simple::Result + DBIx::XHTML_Table
+
+ $html = $result->html(...)
+
+ $table_object = $result->xto(...)
+
+=head2 Examples
+
+Please read L<DBIx::Simple::Examples> for code examples.
+
+=head1 DESCRIPTION
+
+DBIx::Simple provides a simplified interface to DBI, Perl's powerful database
+module.
+
+This module is aimed at rapid development and easy maintenance. Query
+preparation and execution are combined in a single method, the result object
+(which is a wrapper around the statement handle) provides easy row-by-row and
+slurping methods.
+
+The C<query> method returns either a result object, or a dummy object. The
+dummy object returns undef (or an empty list) for all methods and when used in
+boolean context, is false. The dummy object lets you postpone (or skip) error
+checking, but it also makes immediate error checking simply C<<
+$db->query(...) or die $db->error >>.
+
+=head2 DBIx::Simple methods
+
+=over 4
+
+=item C<< DBIx::Simple->connect($dbh) >>
+
+=item C<< DBIx::Simple->connect($dsn, $user, $pass, \%options) >>
+
+=item C<< DBIx::Simple->new($dbh) >>
+
+=item C<< DBIx::Simple->new($dsn, $user, $pass, \%options) >>
+
+The C<connect> or C<new> class method takes either an existing DBI object
+($dbh), or a list of arguments to pass to C<< DBI->connect >>. See L<DBI> for a
+detailed description.
+
+You cannot use this method to clone a DBIx::Simple object: the $dbh passed
+should be a DBI::db object, not a DBIx::Simple object.
+
+This method is the constructor and returns a DBIx::Simple object on success. On
+failure, it returns undef.
+
+=item C<lc_columns = $bool>
+
+When true at time of query execution, makes C<columns>, C<hash>, C<hashes>, and
+C<map_hashes> use lower cased column names. C<lc_columns> is true by default.
+
+=item C<keep_statements = $integer>
+
+Sets the number of statement objects that DBIx::Simple can keep for reuse. This
+can dramatically speed up repeated queries (like when used in a loop).
+C<keep_statements> is 16 by default.
+
+A query is only reused if it equals a previously used one literally. This means
+that to benefit from this caching mechanism, you must use placeholders and
+never interpolate variables yourself.
+
+ # Wrong:
+ $db->query("INSERT INTO foo VALUES ('$foo', '$bar', '$baz')");
+ $db->query("SELECT FROM foo WHERE foo = '$foo' OR bar = '$bar'");
+
+ # Right:
+ $db->query('INSERT INTO foo VALUES (??)', $foo, $bar, $baz);
+ $db->query('SELECT FROM foo WHERE foo = ? OR bar = ?', $foo, $baz);
+
+Of course, automatic value escaping is a much better reason for using
+placeholders.
+
+=item C<result_class = $string>
+
+Class to use for result objects. Defaults to DBIx::Simple::Result. A
+constructor is not used.
+
+=item C<error>
+
+Returns the error string of the last DBI method. See the discussion of "C<err>"
+and "C<errstr>" in L<DBI>.
+
+=item C<query($query, @values)>
+
+The C<query> method prepares and executes the query and returns a result object.
+
+If the string C<(??)> is present in the query, it is replaced with a list of as
+many question marks as @values.
+
+The database drivers substitute placeholders (question marks that do not appear
+in quoted literals) in the query with the given @values, after them escaping
+them. You should always use placeholders, and never use raw user input in
+database queries.
+
+On success, returns a DBIx::Simple::Result object.
+
+On failure, returns a DBIx::Simple::Dummy object.
+
+=item C<iquery>
+
+Uses SQL::Interp to interpolate values into a query, and uses the resulting
+generated query and bind arguments with C<query>.
+
+See SQL::Interp's documentation for usage information.
+
+I<Requires that Mark Stosberg's SQL::Interp module be installed. It is
+available from CPAN. SQL::Interp is a fork from David Manura's
+SQL::Interpolate.>
+
+=item C<select>, C<insert>, C<update>, C<delete>
+
+Calls the respective method on C<abstract>, and uses the resulting generated
+query and bind arguments with C<query>.
+
+See SQL::Abstract's documentation for usage information. You can override the
+object by assigning to the C<abstract> property.
+
+Obviously, calling C<query> directly is faster for the computer and using these
+abstracting methods is faster for the programmer.
+
+=item C<< abstract = SQL::Abstract->new(...) >>
+
+Sets the object to use with the C<select>, C<insert>, C<update> and C<delete>
+methods. On first access, will create one with SQL::Abstract's default options.
+
+I<Requires that Nathan Wiger's SQL::Abstract module be installed. It is
+available from CPAN.>
+
+In theory, you can assign any object to this property, as long as that object
+has these four methods, and they return a list suitable for use with the
+C<query> method.
+
+=item C<begin_work>, C<begin>, C<commit>, C<rollback>
+
+These transaction related methods call the DBI respective methods and
+Do What You Mean. See L<DBI> for details.
+
+C<begin> is an alias for C<begin_work>.
+
+=item C<func(...)>
+
+This calls the C<func> method of DBI. See L<DBI> for details.
+
+=item C<last_insert_id(...)>
+
+This calls the C<last_insert_id> method of DBI. See L<DBI> for details. Note
+that this feature requires DBI 1.38 or newer.
+
+=item C<dbh>
+
+Exposes the internal database handle. Use this only if you know what you are
+doing. Keeping a reference or doing queries can interfere with DBIx::Simple's
+garbage collection and error reporting.
+
+=item C<disconnect>
+
+Destroys (finishes) active statements and disconnects. Whenever the database
+object is destroyed, this happens automatically if DBIx::Simple handled the
+connection (i.e. you didn't use an existing DBI handle). After disconnecting,
+you can no longer use the database object or any of its result objects.
+
+=back
+
+=head2 DBIx::Simple::Dummy
+
+The C<query> method of DBIx::Simple returns a dummy object on failure. Its
+methods all return an empty list or undef, depending on context. When used in
+boolean context, a dummy object evaluates to false.
+
+=head2 DBIx::Simple::Result methods
+
+=over 12
+
+=item C<columns>
+
+Returns a list of column names. In scalar context, returns an array reference.
+
+Column names are lower cased if C<lc_columns> was true when the query was
+executed.
+
+=item C<bind(LIST)>
+
+Binds the given LIST to the columns. The elements of LIST must be writable
+LVALUEs. In other words, use this method as:
+
+ $result->bind(my ($foo, $bar));
+ $result->fetch;
+
+Or, combined:
+
+ $result->into(my ($foo, $bar));
+
+Unlike with DBI's C<bind_columns>, the C<\> operator is not needed.
+
+Bound variables are very efficient. Binding a tied variable doesn't work.
+
+=item C<fetch>
+
+Fetches a single row and returns a reference to the array that holds the
+values. This is the same array every time.
+
+Subsequent fetches (using any method) may change the values in the variables
+passed and the returned reference's array.
+
+=item C<into(LIST)>
+
+Combines C<bind> with C<fetch>. Returns what C<fetch> returns.
+
+=item C<list>
+
+Fetches a single row and returns a list of values. In scalar context,
+returns only the last value.
+
+=item C<array>
+
+Fetches a single row and returns an array reference.
+
+=item C<hash>
+
+Fetches a single row and returns a hash reference.
+
+Keys are lower cased if C<lc_columns> was true when the query was executed.
+
+=item C<flat>
+
+Fetches all remaining rows and returns a flattened list.
+
+In scalar context, returns an array reference.
+
+=item C<arrays>
+
+Fetches all remaining rows and returns a list of array references.
+
+In scalar context, returns an array reference.
+
+=item C<hashes>
+
+Fetches all remaining rows and returns a list of hash references.
+
+In scalar context, returns an array reference.
+
+Keys are lower cased if C<lc_columns> was true when the query was executed.
+
+=item C<map_arrays($column_number)>
+
+Constructs a hash of array references keyed by the values in the chosen column.
+
+In scalar context, returns a hash reference.
+
+In list context, returns interleaved keys and values.
+
+=item C<map_hashes($column_name)>
+
+Constructs a hash of hash references keyed by the values in the chosen column.
+
+In scalar context, returns a hash reference.
+
+In list context, returns interleaved keys and values.
+
+=item C<map>
+
+Constructs a simple hash, using the two columns as key/value pairs. Should
+only be used with queries that return two columns.
+
+In scalar context, returns a hash reference.
+
+In list context, returns interleaved keys and values.
+
+=item C<rows>
+
+Returns the number of rows affected by the last row affecting command, or -1 if
+the number of rows is not known or not available.
+
+For SELECT statements, it is generally not possible to know how many rows are
+returned. MySQL does provide this information. See L<DBI> for a detailed
+explanation.
+
+=item C<xto(%attr)>
+
+Returns a DBIx::XHTML_Table object, passing the constructor a reference to
+C<%attr>.
+
+I<Requires that Jeffrey Hayes Anderson's DBIx::XHTML_Table module be installed.
+It is available from CPAN.>
+
+In general, using the C<html> method (described below) is much easier. C<xto>
+is available in case you need more flexibility.
+
+This method ignores the C<lc_columns> property.
+
+=item C<html(%attr)>
+
+Returns an (X)HTML formatted table, using the DBIx::XHTML_Table module. Passes
+a reference to C<%attr> to both the constructor and the C<output> method.
+
+I<Requires that Jeffrey Hayes Anderson's DBIx::XHTML_Table module be installed.
+It is available from CPAN.>
+
+This method is a shortcut method. That means that
+
+ $result->html
+
+ $result->html(
+ tr => { bgcolor => [ 'silver', 'white' ] },
+ no_ucfirst => 1
+ )
+
+do the same as:
+
+ $result->xto->output
+
+ $result->xto(
+ tr => { bgcolor => [ 'silver', 'white' ] }
+ )->output(
+ no_ucfirst => 1
+ );
+
+=item C<text($type)>
+
+Returns a string with a simple text representation of the data. C<$type>
+can be any of: C<neat>, C<table>, C<box>. It defaults to C<table> if
+Text::Table is installed, to C<neat> if it is.
+
+I<C<table> and C<box> require that Anno Siegel's Text::Table module be
+installed. It is available from CPAN.>
+
+
+=item C<attr(...)>
+
+Returns a copy of an sth attribute (property). See L<DBI/"Statement Handle
+Attributes"> for details.
+
+=item C<func(...)>
+
+This calls the C<func> method of DBI. See L<DBI> for details.
+
+=item C<finish>
+
+Finishes the statement. After finishing a statement, it can no longer be used.
+When the result object is destroyed, its statement handle is automatically
+finished and destroyed. There should be no reason to call this method
+explicitly; just let the result object go out of scope.
+
+=back
+
+=head1 MISCELLANEOUS
+
+The mapping methods do not check whether the keys are unique. Rows that are
+fetched later overwrite earlier ones.
+
+PrintError is disabled by default. If you enable it, beware that it will report
+line numbers in DBIx/Simple.pm.
+
+=head1 LICENSE
+
+There is no license. This software was released into the public domain. Do with
+it what you want, but on your own risk. The author disclaims any
+responsibility.
+
+=head1 AUTHOR
+
+Juerd Waalboer <juerd@cpan.org> <http://juerd.nl/>
+
+=head1 SEE ALSO
+
+L<perl>, L<perlref>
+
+L<DBI>, L<DBIx::Simple::Examples>, L<SQL::Abstract>, L<DBIx::XHTML_Table>
+
+=cut
+