diff --git a/Changes b/Changes index 9f98f61..4da3075 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,12 @@ Changes for Perl extension DBD-SQLite +1.30_01 to be released + - implemented NUM_OF_PARAMS statement handle attribute (ISHIGAKI) + - added experimental "sqlite_allow_multiple_statements" + database handle attribute, and "sqlite_unprepared_statements" + statement handle attribute, to allow processing a SQL dump + (ISHIGAKI) + 1.29 Fri 8 Jan 2010 - Updated to SQLite 3.6.22 (DUNCAND) diff --git a/dbdimp.c b/dbdimp.c index b825beb..7d6ab55 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -162,6 +162,7 @@ sqlite_db_login6(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *user, char *pa imp_dbh->collation_needed_callback = newSVsv( &PL_sv_undef ); imp_dbh->timeout = SQL_TIMEOUT; imp_dbh->handle_binary_nulls = FALSE; + imp_dbh->allow_multiple_statements = FALSE; sqlite3_busy_timeout(imp_dbh->db, SQL_TIMEOUT); @@ -340,6 +341,10 @@ sqlite_db_STORE_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) DBIc_set(imp_dbh, DBIcf_AutoCommit, SvTRUE(valuesv)); return TRUE; } + if (strEQ(key, "sqlite_allow_multiple_statements")) { + imp_dbh->allow_multiple_statements = !(! SvTRUE(valuesv)); + return TRUE; + } if (strEQ(key, "sqlite_unicode")) { #if PERL_UNICODE_DOES_NOT_WORK_WELL sqlite_trace(dbh, imp_dbh, 3, form("Unicode support is disabled for this version of perl.")); @@ -369,23 +374,26 @@ sqlite_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) char *key = SvPV_nolen(keysv); if (strEQ(key, "sqlite_version")) { - return newSVpv(sqlite3_version, 0); + return sv_2mortal(newSVpv(sqlite3_version, 0)); + } + if (strEQ(key, "sqlite_allow_multiple_statements")) { + return sv_2mortal(newSViv(imp_dbh->allow_multiple_statements ? 1 : 0)); } if (strEQ(key, "sqlite_unicode")) { #if PERL_UNICODE_DOES_NOT_WORK_WELL sqlite_trace(dbh, imp_dbh, 3, "Unicode support is disabled for this version of perl."); - return newSViv(0); + return sv_2mortal(newSViv(0)); #else - return newSViv(imp_dbh->unicode ? 1 : 0); + return sv_2mortal(newSViv(imp_dbh->unicode ? 1 : 0)); #endif } if (strEQ(key, "unicode")) { warn("\"unicode\" attribute will be deprecated. Use \"sqlite_unicode\" instead."); #if PERL_UNICODE_DOES_NOT_WORK_WELL sqlite_trace(dbh, imp_dbh, 3, "Unicode support is disabled for this version of perl."); - return newSViv(0); + return sv_2mortal(newSViv(0)); #else - return newSViv(imp_dbh->unicode ? 1 : 0); + return sv_2mortal(newSViv(imp_dbh->unicode ? 1 : 0)); #endif } @@ -441,6 +449,12 @@ sqlite_st_prepare(SV *sth, imp_sth_t *imp_sth, char *statement, SV *attribs) } return FALSE; /* -> undef in lib/DBD/SQLite.pm */ } + if (&extra) { + imp_sth->unprepared_statements = extra; + } + else { + imp_sth->unprepared_statements = NULL; + } DBIc_NUM_PARAMS(imp_sth) = sqlite3_bind_parameter_count(imp_sth->stmt); DBIc_NUM_FIELDS(imp_sth) = sqlite3_column_count(imp_sth->stmt); @@ -817,6 +831,10 @@ sqlite_st_FETCH_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv) croak_if_db_is_null(); croak_if_stmt_is_null(); + if (strEQ(key, "sqlite_unprepared_statements")) { + return sv_2mortal(newSVpv(imp_sth->unprepared_statements, 0)); + } + if (!DBIc_ACTIVE(imp_sth)) { return NULL; } @@ -891,6 +909,9 @@ sqlite_st_FETCH_attrib(SV *sth, imp_sth_t *imp_sth, SV *keysv) else if (strEQ(key, "NUM_OF_FIELDS")) { retsv = sv_2mortal(newSViv(i)); } + else if (strEQ(key, "NUM_OF_PARAMS")) { + retsv = sv_2mortal(newSViv(sqlite3_bind_parameter_count(imp_sth->stmt))); + } return retsv; } diff --git a/dbdimp.h b/dbdimp.h index 2344117..84e0c8e 100644 --- a/dbdimp.h +++ b/dbdimp.h @@ -29,6 +29,7 @@ struct imp_dbh_st { AV *functions; AV *aggregates; SV *collation_needed_callback; + bool allow_multiple_statements; }; /* Statement Handle */ @@ -44,6 +45,7 @@ struct imp_sth_st { int nrow; AV *params; AV *col_types; + char *unprepared_statements; }; #define dbd_init sqlite_init diff --git a/lib/DBD/SQLite.pm b/lib/DBD/SQLite.pm index cec461c..dec0c41 100644 --- a/lib/DBD/SQLite.pm +++ b/lib/DBD/SQLite.pm @@ -10,7 +10,7 @@ use vars qw{$err $errstr $drh $sqlite_version}; use vars qw{%COLLATION}; BEGIN { - $VERSION = '1.29'; + $VERSION = '1.30_01'; @ISA = 'DynaLoader'; # Initialize errors @@ -170,6 +170,24 @@ sub prepare { return $sth; } +sub do { + my ($dbh, $statement, $attr, @bind_values) = @_; + + my @copy = @{[@bind_values]}; + + my $rows = 0; + while ($statement) { + my $sth = $dbh->prepare($statement, $attr) or return undef; + $sth->execute(splice @copy, 0, $sth->{NUM_OF_PARAMS}) or return undef; + $rows += $sth->rows; + # XXX: not sure why but $dbh->{sqlite...} wouldn't work here + last unless $dbh->FETCH('sqlite_allow_multiple_statements'); + $statement = $sth->{sqlite_unprepared_statements}; + } + # always return true if no error + return ($rows == 0) ? "0E0" : $rows; +} + sub _get_version { return ( DBD::SQLite::db::FETCH($_[0], 'sqlite_version') ); } @@ -826,6 +844,12 @@ This C mode is independent from the autocommit mode of the internal SQLite library, which always begins by a C statement, and ends by a C or a . +=head2 Processing Multiple Statements At A Time + +L's statement handle is not supposed to process multiple statements at a time. So if you pass a string that contains multiple statements (a C) to a statement handle (via C or C), L only processes the first statement, and discards the rest. + +Since 1.30_01, you can retrieve those ignored (unprepared) statements via C<< $sth->{sqlite_unprepared_statements} >>. It usually contains nothing but white spaces, but if you really care, you can check this attribute to see if there's anything left undone. Also, if you set a C attribute of a database handle to true when you connect to a database, C method automatically checks the C attribute, and if it finds anything undone (even if what's left is just a single white space), it repeats the process again, to the end. + =head2 Performance SQLite is fast, very fast. Matt processed my 72MB log file with it, @@ -903,6 +927,20 @@ This attribute was originally named as C, and renamed to C for integrity since version 1.26_06. Old C attribute is still accessible but will be deprecated in the near future. +=item sqlite_allow_multiple_statements + +If you set this to true, C method will process multiple statements at one go. This may be handy, but with performance penalty. See above for details. + +=back + +=head2 Statement Handle Attributes + +=over 4 + +=item sqlite_unprepared_statements + +Returns an unprepared part of the statement you pass to C. Typically this contains nothing but white spaces after a semicolon. See above for details. + =back =head1 METHODS diff --git a/t/40_multiple_statements.t b/t/40_multiple_statements.t new file mode 100644 index 0000000..c7bbbd5 --- /dev/null +++ b/t/40_multiple_statements.t @@ -0,0 +1,133 @@ +#!/usr/bin/perl + +use strict; +BEGIN { + $| = 1; + $^W = 1; +} + +use t::lib::Test qw/connect_ok/; +use Test::More; +use Test::NoWarnings; + +plan tests => 21; + +{ + # DBD::SQLite prepares/does the first statement only; + # the following statements will be discarded silently. + + my $dbh = connect_ok( RaiseError => 1 ); + eval { $dbh->do(q/ + create table foo (id integer); + insert into foo (id) values (1); + insert into foo (id) values (2); + /)}; + ok !$@, "do succeeds anyway"; + diag $@ if $@; + my $got = $dbh->selectall_arrayref('select id from foo'); + ok !@$got, "but got nothing as the inserts were discarded"; +} + +{ + # As of 1.29_01, you can do bulk inserts with the help of + # "sqlite_allows_multiple_statements" and + # "sqlite_unprepared_statements" attributes. + my $dbh = connect_ok( + RaiseError => 1, + sqlite_allows_multiple_statements => 1, + ); + ok $dbh->{sqlite_allows_multiple_statements}, "allows multiple statements"; + eval { $dbh->do(q/ + create table foo (id integer); + insert into foo (id) values (1); + insert into foo (id) values (2); + /, { sqlite_allows_multiple_statements => 1 })}; + ok !$@, "do succeeds anyway"; + diag $@ if $@; + + my $got = $dbh->selectall_arrayref('select id from foo'); + ok $got->[0][0] == 1 + && $got->[1][0] == 2, "and got the inserted values"; +} + +{ + # Do it more explicitly + my $dbh = connect_ok( + RaiseError => 1, + sqlite_allows_multiple_statements => 1, + ); + ok $dbh->{sqlite_allows_multiple_statements}, "allows multiple statements"; + my $statement = q/ + create table foo (id integer); + insert into foo (id) values (1); + insert into foo (id) values (2); + /; + $dbh->begin_work; + eval { + while ($statement) { + my $sth = $dbh->prepare($statement); + $sth->execute; + $statement = $sth->{sqlite_unprepared_statements}; + } + }; + ok !$@, "executed multiple statements successfully"; + diag $@ if $@; + $@ ? $dbh->rollback : $dbh->commit; + + my $got = $dbh->selectall_arrayref('select id from foo'); + ok $got->[0][0] == 1 + && $got->[1][0] == 2, "and got the inserted values"; +} + +{ + # Placeholders + my $dbh = connect_ok( + RaiseError => 1, + sqlite_allows_multiple_statements => 1, + ); + ok $dbh->{sqlite_allows_multiple_statements}, "allows multiple statements"; + eval { $dbh->do(q/ + create table foo (id integer); + insert into foo (id) values (?); + insert into foo (id) values (?); + /, undef, 1, 2)}; + ok !$@, "do succeeds anyway"; + diag $@ if $@; + + my $got = $dbh->selectall_arrayref('select id from foo'); + ok $got->[0][0] == 1 + && $got->[1][0] == 2, "and got the inserted values"; +} + +{ + # Do it more explicitly + my $dbh = connect_ok( + RaiseError => 1, + sqlite_allows_multiple_statements => 1, + ); + ok $dbh->{sqlite_allows_multiple_statements}, "allows multiple statements"; + my $statement = q/ + create table foo (id integer); + insert into foo (id) values (?); + insert into foo (id) values (?); + /; + $dbh->begin_work; + eval { + my @params = (1, 2); + while ($statement) { + my $sth = $dbh->prepare($statement); + $sth->execute(splice @params, 0, $sth->{NUM_OF_PARAMS}); + $statement = $sth->{sqlite_unprepared_statements}; + } + }; + ok !$@, "executed multiple statements successfully"; + diag $@ if $@; + $@ ? $dbh->rollback : $dbh->commit; + + ok !$@, "executed multiple statements successfully"; + diag $@ if $@; + + my $got = $dbh->selectall_arrayref('select id from foo'); + ok $got->[0][0] == 1 + && $got->[1][0] == 2, "and got the inserted values"; +} diff --git a/t/lib/Test.pm b/t/lib/Test.pm index 80e50ce..193cb85 100644 --- a/t/lib/Test.pm +++ b/t/lib/Test.pm @@ -9,7 +9,7 @@ use Test::More (); use vars qw{$VERSION @ISA @EXPORT @CALL_FUNCS}; BEGIN { - $VERSION = '1.29'; + $VERSION = '1.30_01'; @ISA = 'Exporter'; @EXPORT = qw/connect_ok dies @CALL_FUNCS/;