1
0
Fork 0
mirror of https://github.com/DBD-SQLite/DBD-SQLite synced 2025-06-07 14:19:10 -04:00

experimental sqlite_allow_multiple_statements/sqlite_unprepared_statements attribute to allow processing a SQL dump

This commit is contained in:
Kenichi Ishigaki 2010-01-11 07:34:30 +00:00
parent dacdce66d5
commit 5040024ca9
6 changed files with 208 additions and 7 deletions

View file

@ -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)

View file

@ -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;
}

View file

@ -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

View file

@ -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<AutoCommit> mode is independent from the autocommit mode
of the internal SQLite library, which always begins by a C<BEGIN>
statement, and ends by a C<COMMIT> or a <ROLLBACK>.
=head2 Processing Multiple Statements At A Time
L<DBI>'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<dump>) to a statement handle (via C<prepare> or C<do>), L<DBD::SQLite> 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<sqlite_allow_multiple_statements> attribute of a database handle to true when you connect to a database, C<do> method automatically checks the C<sqlite_unprepared_statements> 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<unicode>, and renamed to
C<sqlite_unicode> for integrity since version 1.26_06. Old C<unicode>
attribute is still accessible but will be deprecated in the near future.
=item sqlite_allow_multiple_statements
If you set this to true, C<do> 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<prepare>. Typically this contains nothing but white spaces after a semicolon. See above for details.
=back
=head1 METHODS

133
t/40_multiple_statements.t Normal file
View file

@ -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";
}

View file

@ -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/;