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:
parent
dacdce66d5
commit
5040024ca9
6 changed files with 208 additions and 7 deletions
7
Changes
7
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)
|
||||
|
||||
|
|
31
dbdimp.c
31
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;
|
||||
}
|
||||
|
|
2
dbdimp.h
2
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
|
||||
|
|
|
@ -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
133
t/40_multiple_statements.t
Normal 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";
|
||||
}
|
|
@ -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/;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue