######################################################################### # DBD::AnyData - a DBI driver for files and data structures # # This module is copyright (c), 2001 by Jeff Zucker # All rights reserved. # # This is free software. You may distribute it under # the same terms as Perl itself as specified in the # Perl README file. # # WARNING: no warranty of any kind is implied. # # To learn more: enter "perldoc DBD::AnyData" at the command prompt, # or search in this file for =head1 and read the text below it # ######################################################################### package DBD::AnyData; use strict; use AnyData; #require DBD::File; use base qw( DBD::File ); require SQL::Statement; require SQL::Eval; use vars qw($VERSION $err $errstr $sqlstate $drh $methods_already_installed); $VERSION = '0.09'; $err = 0; # holds error code for DBI::err $errstr = ""; # holds error string for DBI::errstr $sqlstate = ""; # holds SQL state for DBI::state $drh = undef; # holds driver handle once initialized sub driver { my($class, $attr) = @_; return $drh if $drh; # already created - return same one my $this = $class->SUPER::driver({ 'Name' => 'AnyData', 'Version' => $VERSION, 'Err' => \$DBD::AnyData::err, 'Errstr' => \$DBD::AnyData::errstr, 'State' => \$DBD::AnyData::sqlstate, 'Attribution' => 'DBD::AnyData by Jeff Zucker', }); if ( $DBI::VERSION >= 1.37 and !$methods_already_installed++ ) { DBD::AnyData::db->install_method('ad_import'); DBD::AnyData::db->install_method('ad_catalog'); DBD::AnyData::db->install_method('ad_convert'); DBD::AnyData::db->install_method('ad_export'); DBD::AnyData::db->install_method('ad_clear'); DBD::AnyData::db->install_method('ad_dump'); } return $this; } sub CLONE { undef $drh; } package DBD::AnyData::dr; # ====== DRIVER ====== $DBD::AnyData::dr::imp_data_size = 0; @DBD::AnyData::dr::ISA = qw(DBD::File::dr); sub connect { my($drh, $dbname, $user, $auth, $attr)= @_; my $dbh = DBI::_new_dbh($drh, { Name => $dbname, USER => $user, CURRENT_USER => $user, }); # PARSE EXTRA STRINGS IN DSN HERE # Process attributes from the DSN; we assume ODBC syntax # here, that is, the DSN looks like var1=val1;...;varN=valN my $var; if ($dbh) { $dbh->STORE('f_dir','./'); foreach $var (split(/;/, $dbname)) { ####################################################### # Patch from Tom Lowery for doing import on connect ####################################################### if( $var =~ m/^\s*?import/i ){ # Connect string includes a reference. $var =~ s/^\s+?(import\s+?=>\s+?)//; my $ref; $ref = eval( $var ); use Data::Dumper; print Dumper $ref; use Data::Dumper; print Dumper $var; $dbh->func($ref, 'ad_import'); ####################################################### # Patch from Wes Hardaker ####################################################### } elsif( $var =~ m/^\s*?default=(\S+)/i ){ # Default catalog selector to use $dbh->func('__default',$1, 'ad_catalog'); ####################################################### } elsif ($var =~ /(.*?)=(.*)/) { my $key = $1; my $val = $2; $dbh->STORE($key, $val); } } $dbh->STORE('Active',1); ### $dbh->func('read_catalog_from_disk'); } $dbh; } sub data_sources { my($drh,$driver_name)=@_; my @dirs = DBD::File::dr::data_sources($drh,$driver_name); @dirs = map { s/DBI:AnyData:f_dir=//; $_} @dirs; } sub disconnect_all{ shift->{ad_tables}={}; } sub DESTROY { shift->{ad_tables}={}; } package DBD::AnyData::db; # ====== DATABASE ====== $DBD::AnyData::db::imp_data_size = 0; @DBD::AnyData::db::ISA = qw(DBD::File::db); require SQL::Statement; sub prepare ($$;@) { my($dbh, $statement, @attribs)= @_; # create a 'blank' dbh my $sth = DBI::_new_sth($dbh, {'Statement' => $statement}); if ($sth) { $@ = ''; my $class = $sth->FETCH('ImplementorClass'); $class =~ s/::st$/::Statement/; my($stmt); my $sversion = $SQL::Statement::VERSION; if ($SQL::Statement::VERSION > 1) { my $parser = $dbh->{ad_sql_parser_object}; eval { $parser ||= $dbh->func('ad_cache_sql_parser_object') }; if ($@) { undef $@; $stmt = eval { $class->new($statement) }; } else { $stmt = eval { $class->new($statement,$parser) }; } } else { $stmt = eval { $class->new($statement) }; } # my $parser = SQL::Parser->new('SQL::Eval'); # $parser->feature("select","join",1); # my($stmt) = eval { $class->new($statement,$parser) }; if ($@) { DBI::set_err($dbh, 1, $@); undef $sth; } else { $sth->STORE('f_stmt', $stmt); $sth->STORE('f_params', []); $sth->STORE('NUM_OF_PARAMS', scalar($stmt->params())); } } $sth; } sub disconnect{ my $dbh = shift; $dbh->{ad_tables}={}; $dbh->STORE('Active',0); return 1; } # # DRIVER PRIVATE METHODS # sub ad_cache_sql_parser_object { my $dbh = shift; my $parser = { dialect => 'AnyData', RaiseError => $dbh->FETCH('RaiseError'), PrintError => $dbh->FETCH('PrintError'), }; my $sql_flags = $dbh->FETCH('ad_sql') || {}; %$parser = (%$parser,%$sql_flags); $parser = SQL::Parser->new($parser->{dialect},$parser); $dbh->{ad_sql_parser_object} = $parser; return $parser; } sub ad_mod_catalog { my( $self, $tname, $key, $value) =@_; $self->{ad_tables}->{$tname}->{$key}=$value; } sub ad_clear { my $self = shift; my $tname = shift; if ($tname eq 'all' or $tname eq '') { $self->{ad_tables}={}; } else { delete $self->{ad_tables}->{$tname}; } } sub ad_get_catalog { my $self = shift; my $tname = shift; ################################################################# # Patch from Wes Hardaker ################################################################# if ($tname) { return $self->{ad_tables}->{$tname} if ($self->{ad_tables}->{$tname}); return $self->{ad_tables}->{__default}; } ################################################################# return $self->{ad_tables}; } sub ad_export { my $dbh = shift; my $table_name = shift; my $format = shift; my $file_name = shift; my $flags = shift; my $data; my $catalog= $dbh->func($table_name,'ad_get_catalog'); #use Data::Dumper; print Dumper $catalog; if ( $catalog->{format} && 'XML HTMLtable' =~ /$catalog->{format}/ ) { #use Data::Dumper; print "!",Dumper $catalog; exit; my $sth = $dbh->prepare("SELECT 1 FROM $table_name") or die DBI->errstr; $sth->execute;# or die DBI->errstr; ###z return $catalog->{ad}->export($format,$file_name,$flags) if 'XML HTMLtable' =~ /$format/; return $catalog->{ad}->export($file_name,$flags) if 'XML HTMLtable' =~ /$format/; $data = $dbh->selectall_arrayref("SELECT * FROM $table_name"); #my $sth = $dbh->prepare("SELECT * FROM $table_name"); #$sth->execute; #unshift @$data, $sth->{NAME}; } else { #z $data = $dbh->func($table_name,'ad_get_catalog')->{records}; my $sth = $dbh->prepare("SELECT * FROM $table_name WHERE 1=0"); $sth->execute; $data = $catalog->{ad}->{storage}->{records}; } $data = $dbh->selectall_arrayref("SELECT * FROM $table_name") if $format =~ /XML|HTMLtable/; #use Data::Dumper; #die Dumper $data; # print Dumper $dbh->func( $table_name,'ad_get_catalog'); my $newcols = $dbh->func( $table_name,'ad_get_catalog' )->{ad}->{storage}->{col_names}; unshift @$data, $newcols if $newcols; return AnyData::adConvert('Base',$data,$format,$file_name,undef,$flags); # return AnyData::adExport({},$format,$data,$file_name,undef,$flags); } sub ad_convert { my $dbh = shift; my $format = shift; if ($format eq 'DBI') { my $data = shift; my $newformat = shift; die "table_name required to convert DBI" unless $_[1] and $_[1]->{table_name}; my $table_name = $_[1]->{table_name}; $dbh->func( $table_name, 'DBI', $data, 'ad_import'); my $rv = $dbh->func( $table_name, $newformat, 'ad_export'); $dbh->func( $table_name,'ad_clear'); return $rv; } return AnyData::adConvert($format,@_); } sub ad_import { my $dbh = shift; my($table_name,$format,$file_name,$flags) = @_; $format = 'CSV' if $format eq 'ARRAY'; my $old_catalog = $dbh->func($table_name,'ad_get_catalog'); my $old_columns; my $old_records; if ($old_catalog) { my $sth = $dbh->prepare("SELECT * FROM $table_name"); $sth->execute; $old_records = $sth->fetchall_arrayref; $old_columns = $sth->{NAME}; } my $sql = $flags->{sql} || "SELECT * FROM $table_name"; # die $sql; my @params = $flags->{params} || (); if ( 'XML HTMLtable' =~ /$format/) { $dbh->func($table_name,$format,$file_name,$flags,'ad_catalog'); my $sth= $dbh->prepare("SELECT * FROM $table_name WHERE 1=0"); $sth->execute; $sth->finish; return unless $old_catalog; } elsif (ref($file_name) ) { $flags->{recs}=$file_name; $flags->{storage}='RAM'; #$flags->{col_names} =$old_columns if $old_columns; $dbh->func($table_name,$format,'',$flags,'ad_catalog'); } else { $dbh->func($table_name,$format,$file_name,$flags,'ad_catalog'); #$dbh->func(@_,'ad_catalog'); } my $dbh2 = $dbh; $dbh2 = $file_name if $format eq 'DBI'; my $sth = $dbh2->prepare($sql) or die DBI->errstr; # die "$sql"; $sth->execute(@params) or die DBI->errstr; my $cols = $sth->{NAME} or die DBI->errstr; # die @$cols; my $records; if ($old_records) { my $colstr = join ',', @$old_columns; my $cr = join " TEXT,", @$old_columns; $cr = "CREATE TABLE temp__ ($cr TEXT)"; $dbh->do($cr) or die DBI->errstr; while (my $row = $sth->fetchrow_hashref) { my $old_row; if ($flags->{lookup_key}) { my $lookup = $flags->{lookup_key} || $sth->{NAME}->[0]; my $val = $row->{$lookup} || next; my $oldsth = $dbh->prepare(qq{ SELECT * FROM temp__ WHERE $lookup = '$val' }); $oldsth->execute; $old_row = $oldsth->fetchrow_hashref; my @tmp = $dbh->selectrow_array("SELECT * FROM temp__ WHERE $lookup = $val"); my $dup; for my $x(@tmp) { if (!defined $x) { $dup++; last; } } if ($dup) { #print "@tmp"; $dbh->do("DELETE FROM temp__ WHERE $lookup = $val") or die DBI->errstr; } } my @params; for (@$old_columns) { my $newval = $row->{$_}; $newval ||= $old_row->{$_}; push @params, $newval; } my $paramStr = (join ",", ("?") x @$old_columns); my $ins_sql ="INSERT INTO temp__ ($colstr) VALUES ($paramStr)"; $dbh->do($ins_sql,undef,@params) or die DBI->errstr; } $records ||= $dbh->selectall_arrayref($sql); } else { $records = $sth->fetchall_arrayref; } $cols = $old_columns if $old_columns; unshift @$records, $cols unless $flags->{col_names}; $dbh2->disconnect if $format eq 'DBI' and !$flags->{keep_connection}; $file_name = '' if ref($file_name) eq 'ARRAY'; delete $flags->{recs}; delete $flags->{storage}; delete $flags->{format}; #$flags = {} if 'XML HTMLtable' =~ /$format/; if ('XML HTMLtable' =~ /$format/) { delete $flags->{ad}; $flags->{file_name}=''; } # use Data::Dumper; print Dumper $flags; $flags->{records} ||= $records; $dbh->func($table_name,'ad_clear'); $dbh->func($table_name,'Base',$file_name,$flags,'ad_catalog'); my $firstrow = {}; return unless $records->[1]; @{$firstrow}{@$cols} = @{$records->[1]}; return $firstrow; } sub ad_catalog { my $dbh = shift; my @specs = @_; my $table_info = (ref $specs[0]) eq 'ARRAY' ? shift @specs : [\@specs]; for my $one_table(@{$table_info}) { my($table_name,$format,$file_name,$flags); if (ref $one_table eq 'ARRAY') { ($table_name,$format,$file_name,$flags) = @{$one_table}; $flags = {} unless $flags; $flags->{table_name}=$table_name; if (ref $format eq 'HASH') { $flags->{data} = $format->{data}; $format = 'Base'; } $flags->{format}=$format; $flags->{file_name}=$file_name; } if (ref $one_table eq 'HASH') { $flags = $one_table; } die "ERROR: ad_catalog requires a table name!" unless $flags->{table_name}; $table_name = $flags->{table_name}; $flags->{format} ||= 'Base'; $flags->{file_name} ||= ''; $flags->{eol} ||= "\n"; $flags->{f_dir} ||= $dbh->{f_dir}; $dbh->{ad_tables}->{$table_name} = $flags; } } sub ad_dump { my $dbh = shift; my $sql = shift; if (!$sql) { require Data::Dumper; $Data::Dumper::Indent=1; return Data::Dumper::Dumper $dbh->func('ad_get_catalog'); } my $txt; my $sth = $dbh->prepare($sql) or die $dbh->errstr; $sth->execute or die $sth->errstr; my @col_names = @{$sth->{NAME}}; $txt .= "<$_> " for @col_names; $txt .= "\n"; while (my @row = $sth->fetchrow_array) { for (@row) { $_ ||= ''; s/^\s*//; s/\s*$//; $txt .= "[$_] "; } $txt .= "\n"; } return $txt; } # END OF DRIVER PRIVATE METHODS sub table_info ($) { my($dbh) = @_; my @tables; if ($dbh->func('','ad_get_catalog')) { for ( keys %{ $dbh->func('','ad_get_catalog') } ) { push(@tables, [undef, undef, $_, "TABLE", undef]); } } my $names = ['TABLE_QUALIFIER', 'TABLE_OWNER', 'TABLE_NAME', 'TABLE_TYPE', 'REMARKS']; my $dbh2 = $dbh->{'csv_sponge_driver'}; if (!$dbh2) { $dbh2 = $dbh->{'csv_sponge_driver'} = DBI->connect("DBI:Sponge:"); if (!$dbh2) { DBI::set_err($dbh, 1, $DBI::errstr); return undef; } } # Temporary kludge: DBD::Sponge dies if @tables is empty. :-( return undef if !@tables; my $sth = $dbh2->prepare("TABLE_INFO", { 'rows' => \@tables, 'NAMES' => $names }); if (!$sth) { DBI::set_err($dbh, 1, $dbh2->errstr()); } $sth; } sub DESTROY { my $dbh = shift; $dbh->{ad_tables}={}; $dbh->STORE('Active',0); } package DBD::AnyData::st; # ====== STATEMENT ====== $DBD::AnyData::st::imp_data_size = 0; @DBD::AnyData::st::ISA = qw(DBD::File::st); # sub DESTROY ($) { undef; } # sub finish ($) {} package DBD::AnyData::Statement; #@DBD::AnyData::Statement::ISA = qw(SQL::Statement DBD::File::Statement); @DBD::AnyData::Statement::ISA = qw(SQL::Statement); sub open_table ($$$$$) { my($self, $data, $tname, $createMode, $lockMode) = @_; my $dbh = $data->{Database}; my $catalog = $dbh->func($tname,'ad_get_catalog'); if ( !$catalog ) { $dbh->func([[$tname,'Base','']],'ad_catalog'); $catalog = $dbh->func($tname,'ad_get_catalog'); $createMode = 'o'; $lockMode = undef; } my $format = $catalog->{format}; my $file = $catalog->{file_name}; my $ad = $catalog->{ad} ################################################################# # Patch from Wes Hardaker ################################################################# # || AnyData::adTable( $format, $file, $createMode, $lockMode, # $catalog ); || AnyData::adTable( $format, $file, $createMode, $lockMode, $catalog, $tname ); #print join("\n", $format,@$file,$createMode), "\n"; #use Data::Dumper; print Dumper $catalog; ################################################################# my $table = $ad->prep_dbd_table($tname,$createMode); my $cols = $table->{col_names}; if ( $cols and ref $cols ne 'ARRAY' ) { #$dbh->DBI::set_err(99, "\n $cols\n "); print "\n $cols\n "; exit; } if ( 'Base XML HTMLtable' =~ /$catalog->{format}/ or $file =~ /http:|ftp:/ or ref($file) eq 'ARRAY' ) { $ad->seek_first_record(); $dbh->func($tname, 'ad',$ad,'ad_mod_catalog'); } return bless $table, 'DBD::AnyData::Table'; } package DBD::AnyData::Table; @DBD::AnyData::Table::ISA = qw(SQL::Eval::Table); sub trim { my $x=shift; $x =~ s/^\s+//; $x =~ s/\s+$//; $x; } ################################## # fetch_row() ################################## sub fetch_row ($$$) { my($self, $data, $row) = @_; my $requested_cols = $data->{f_stmt}->{NAME}; my $dbh = $data->{Database}; my $fields = $self->{ad}->fetch_row($requested_cols); if ( $dbh->{ChopBlanks} ) { @$fields = map($_=&trim($_),@$fields); } $self->{row} = $fields; return $self->{row}; } ############################## # push_names() ############################## sub push_names ($$$) { my($self, $data, $names) = @_; #print @$names; $self->{ad}->push_names($names); } ################################ # push_row() ################################ sub push_row ($$$) { my($self, $data, $fields) = @_; my $requested_cols=[]; my @rc = $data->{f_stmt}->columns(); push @$requested_cols, $_->{column} for @rc; unshift @$fields, $requested_cols; $self->{ad}->push_row(@$fields); 1; } ################################ # seek() ################################ sub seek ($$$$) { my($self, $data, $pos, $whence) = @_; $self->{ad}->seek($pos,$whence); } ################################ # drop() ################################ sub drop ($$) { my($self, $data) = @_; return $self->{ad}->drop(); } ################################## # truncate() # CALLED WITH "DELETE" & "UPDATE" ################################## sub truncate ($$) { my($self, $data) = @_; $self->{ad}->truncate($data); } sub DESTROY { # wierd: this is needed to close file handle ??? my $self = shift; #print "CLOSING" if $self->{ad}->{storage}->{fh}; my $fh = $self->{ad}->{storage}->{fh} or return; $self->{ad}->DESTROY; undef $self->{ad}->{storage}->{fh}; } ############################################################################ 1; __END__ =head1 NAME DBD::AnyData -- DBI access to XML, CSV and other formats =head1 SYNOPSIS use DBI; my $dbh = DBI->connect('dbi:AnyData(RaiseError=>1):'); $dbh->func( 'trains', 'CSV', '/users/joe/cars.csv', 'ad_catalog'); $dbh->func( 'bikes', 'XML', [$xml_str], 'ad_import'); $dbh->func( 'cars', 'DBI', $mysql_dbh, 'ad_import'); # # ... DBI/SQL methods to access/modify the tables 'cars','bikes','trains' # print $dbh->func( 'cars', 'HTMLtable', 'ad_export'); or use DBI; my $dbh = DBI->connect('dbi:AnyData(RaiseError=>1):'); $dbh->func( 'Pipe', 'data.pipe', 'XML', 'data.xml', 'ad_convert'); or (many combinations of a dozen other data formats, see below) =head1 DESCRIPTION The DBD::AnyData module provides a DBI/SQL interface to data in many formats and from many sources. Currently supported formats include general format flatfiles (CSV, Fixed Length, Tab or Pipe "delimited", etc.), specific formats (passwd files, web logs, etc.), a variety of other kinds of formats (XML, Mp3, HTML tables), and, for some operations, any DBI accessible database. The number of supported formats will continue to grow rapidly since there is an open API making it easy for any author to create additional format parsers which can be plugged in to AnyData. Data in these various formats can come from local files, from remote files, or from perl data structures such as strings and arrays. Regardless of the format or source of the data, it may be accessed and/or modified using all standard DBI methods and a subset of SQL syntax. In addition to standard database access to files, the module also supports in-memory tables which allow you to create temporary views; to combine data from a number of sources; to quickly prototype database systems; and to display or save the data in any of the supported formats (e.g. to display data in a CSV file as an HTML table). These in-memory tables can be created from any combination of DBI databases or files of any format. They may also be created from perl data structures which means it's possible to quickly prototype a database system without any file access or rdbms backend. The module also supports converting files between any of the supported formats (e.g. save selected data from MySQL or Oracle to an XML file). Here a just a few examples of the capabilities: # SELECT DATA FROM A PASSWD FILE # $dbh->func( 'users', 'Passwd', '/etc/passwd', 'ad_catalog'); my $sth = $dbh->prepare("SELECT username,homedir,GID FROM users'); # INSERT A NEW ROW INTO A CSV FILE # $dbh->func( 'cars', 'CSV', 'cars.csv', 'ad_catalog'); $dbh->do("INSERT INTO cars VALUES ('Honda','Odyssey')"); # READ A REMOTE XML FILE AND PRINT IT AS AN HTML TABLE # print $dbh->func( 'XML', $url, 'HTMLtable', 'ad_convert'); # CONVERT A MYSQL DATABASE INTO XML AND SAVE IT IN A NEW FILE # $dbh->func( 'DBI', $mysql_dbh, 'XML', 'data.xml', 'ad_convert'); # CREATE AND ACCESS A VIEW CONTAINING DATA FROM AN ORACLE DATABASE # AND A TAB DELIMITED FILE # $dbh->func( 'combo', 'DBI', $oracle_dbh, 'ad_import'); $dbh->func( 'combo', 'Tab', 'data.tab', 'ad_import'); my $sth = $dbh->prepare("SELECT * FROM combo"); =head1 INSTALLATION To use DBD::AnyData you will need to install these modules, all available from CPAN and most available from activeState. * DBI * SQL::Statement * DBD::File * AnyData * DBD::AnyData Note: DBD::File is part of the DBD::CSV distribution Some advanced features require additional modules: * remote file access requires LWP (the libwww bundle) * XML access requires XML::Parser and XML::Twig * HTML table access requires HTML::Parser and HTML::TableExtract * HTML table writing requires CGI AnyData and DBD::AnyData themselves can either be installed with the standard makefile,make,make test, make install or by simply copying the files into their appropriate directories. The other modules require the full make process or a precompiled binary. =head1 QUICK START =head2 The Basics =over 1 =item There are four main steps in using DBD::AnyData in a script: 1. Specify that you want to use the DBI module 2. Create a database handle 3. Specify the tables, files, and formats you want 4. Use DBI/SQL commands to access and/or modify the data Steps #1, #2, and #3 can be as little as a single line of code each. Steps #3 and #4 can be omitted in some situations, see the sections below on "Working with In-Memory Data" and "Converting Data" =item Step #1 : Specify that you want to use the DBI module This step is always the same: just put this at the top of your script: use DBI; =item Step #2 Create a Database Handle This step can vary slightly depending on your needs but is usually this: my $dbh = DBI->connect('dbi:AnyData(RaiseError=>1):'); See the section below on "Connection Options" for other forms of connecting. See the section below on "Using Multiple Databases" for cases in which you may be creating more than one database handle. =item Step #3 : Specify the tables, files, and formats This step makes use of one of several methods unique to DBD::AnyData. These methods use the database handle created in step #2 to make a func() call and specify the name of the method as the last parameter. For example the 'ad_catalog' method would be called like this: $dbh->func( ..., 'ad_catalog') The ad_catalog() method takes three required parameters and one optional parameter: $dbh->func( $table, $format, $file, $flags, 'ad_catalog') $table = the name you will use to refer to the table in SQL commands $format = the format of the data ('XML', 'CSV', 'Fixed', etc.) $file = the name of a local or remote file holding the data $flags = an optional hash of flags required by some data formats For example: $dbh->func( 'cars', 'XML', 'cars.xml', 'ad_catalog' ) This specifies that the table name 'cars' will be used to access XML data stored in the file 'cars.xml'. Once you have issued a catalog command, you can use the name $table in SQL commands to access or modify the data in $file. The catalog only needs to be specified once for a table/file/format combination and can then be used for an unlimited number of processing commands. =item Step #4 : Use DBI/SQL commands to access and/or modify data DBD::AnyData supports all standard DBI methods and a subset of SQL syntax. See the section below "SQL Syntax" for a description of the supported SQL commands. See the DBI documentation for detailed description of DBI commands. The do() method can be used to create or drop a table and insert, delete, or update rows: $dbh->do("CREATE TABLE ... ) $dbh->do("DROP TABLE ... ) $dbh->do("INSERT INTO ... ) $dbh->do("UPDATE ... ) $dbh->do("DELETE ... ) A combination of the prepare(), execute(), and fetch() methods can be used to access data: my $sth = $dbh->prepare("SELECT * FROM cars WHERE make = 'Honda'"); $sth->execute(); while (my $row = $sth->fetchrow_hashref){ print $row->{model}; } =item Putting it all together This is the complete script needed to access data stored in CSV format in a file called "cars.csv". It prints all data from the "make" and "model" columns of the database. 1. use DBI; 2. my $dbh = DBI->connect('dbi:AnyData(RaiseError=>1):'); 3. $dbh->func( 'cars', 'CSV', 'cars.csv' 'ad_catalog'); 4. my $sth = $dbh->prepare("SELECT make, model FROM cars"); 5. $sth->execute(); 6. while (my $row = $sth->fetch) { 7. print "@$row\n"; 8. } Line 1 specifies that you will use the DBI module. Line 2 creates a database handle Line 3 specifies the table, format, and file holding the data Lines 4 through 8 use DBI and SQL to access data in the file =back =head2 Customizing table structure DBD::AnyData uses a number of defaults when it decides how to read data from a database and in many cases these defaults are all you will need. However, depending on the format and database you are using, you may need to specify other features such as column names, record separators, etc. You can specify this additional information in the $flags parameter of the ad_catalog and other DBD::AnyData methods. $flags is always a reference to a hash, i.e. one or more key value pairs joined with a =>, separated by commas, and delimited by curly braces: $flags = { key1 => value1, key2 => value2 ... } or in the method call: $dbh->func( $table, $format, $file, { key1=>,val1 ... }, 'ad_catalog'); =over 2 =item Column Names Some formats have pre-defined column names: Passwd username passwd UID GID fullname homedir shell Weblog remotehost usernname authuser date request status bytes referer client Mp3 song artist album year genre filename filesize Column names for the other formats can either be specified in the database itself or supplied by you in the $flags parameter. If the column names are specified in the database, they are taken from the first record in the database. For example in a CSV (Comma Separated Values) file or a Fixed Length file, the default is to treat the first line of the table as the list of column names. In an HTMLtable file, the default is to look for the first