diff --git a/dbdimp.c b/dbdimp.c index 9ce9498..f0cbf17 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -240,6 +240,7 @@ sqlite_db_login6(SV *dbh, imp_dbh_t *imp_dbh, char *dbname, char *user, char *pa imp_dbh->handle_binary_nulls = FALSE; imp_dbh->allow_multiple_statements = FALSE; imp_dbh->use_immediate_transaction = FALSE; + imp_dbh->see_if_its_a_number = FALSE; sqlite3_busy_timeout(imp_dbh->db, SQL_TIMEOUT); @@ -439,6 +440,10 @@ sqlite_db_STORE_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv, SV *valuesv) imp_dbh->use_immediate_transaction = !(! SvTRUE(valuesv)); return TRUE; } + if (strEQ(key, "sqlite_see_if_its_a_number")) { + imp_dbh->see_if_its_a_number = !(! 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.")); @@ -477,6 +482,9 @@ sqlite_db_FETCH_attrib(SV *dbh, imp_dbh_t *imp_dbh, SV *keysv) if (strEQ(key, "sqlite_use_immediate_transaction")) { return sv_2mortal(newSViv(imp_dbh->use_immediate_transaction ? 1 : 0)); } + if (strEQ(key, "sqlite_see_if_its_a_number")) { + return sv_2mortal(newSViv(imp_dbh->see_if_its_a_number ? 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."); @@ -637,15 +645,17 @@ sqlite_st_execute(SV *sth, imp_sth_t *imp_sth) else { STRLEN len; const char *data; -#if 0 - int numtype; -#endif + int numtype = 0; + if (imp_dbh->unicode) { sv_utf8_upgrade(value); } data = SvPV(value, len); -#if 0 - numtype = sqlite_is_number(aTHX_ data); + + if (imp_dbh->see_if_its_a_number) { + numtype = sqlite_is_number(aTHX_ data); + } + if (numtype == 1) { #if defined(USE_64_BIT_INT) rc = sqlite3_bind_int64(imp_sth->stmt, i+1, atoi(data)); @@ -657,11 +667,8 @@ sqlite_st_execute(SV *sth, imp_sth_t *imp_sth) rc = sqlite3_bind_double(imp_sth->stmt, i+1, atof(data)); } else { -#endif rc = sqlite3_bind_text(imp_sth->stmt, i+1, data, len, SQLITE_TRANSIENT); -#if 0 } -#endif } if (value) { @@ -816,7 +823,6 @@ sqlite_st_fetch(SV *sth, imp_sth_t *imp_sth) } switch(col_type) { case SQLITE_INTEGER: -#if 1 sqlite_trace(sth, imp_sth, 5, form("fetch column %d as integer", i)); #if defined(USE_64_BIT_INT) sv_setiv(AvARRAY(av)[i], sqlite3_column_int64(imp_sth->stmt, i)); @@ -824,14 +830,11 @@ sqlite_st_fetch(SV *sth, imp_sth_t *imp_sth) sv_setnv(AvARRAY(av)[i], (double)sqlite3_column_int64(imp_sth->stmt, i)); #endif break; -#endif case SQLITE_FLOAT: -#if 1 /* fetching as float may lose precision info in the perl world */ sqlite_trace(sth, imp_sth, 5, form("fetch column %d as float", i)); sv_setnv(AvARRAY(av)[i], sqlite3_column_double(imp_sth->stmt, i)); break; -#endif case SQLITE_TEXT: sqlite_trace(sth, imp_sth, 5, form("fetch column %d as text", i)); val = (char*)sqlite3_column_text(imp_sth->stmt, i); diff --git a/dbdimp.h b/dbdimp.h index 16e20b7..9d611e1 100644 --- a/dbdimp.h +++ b/dbdimp.h @@ -35,6 +35,7 @@ struct imp_dbh_st { SV *collation_needed_callback; bool allow_multiple_statements; bool use_immediate_transaction; + bool see_if_its_a_number; }; /* Statement Handle */ diff --git a/lib/DBD/SQLite.pm b/lib/DBD/SQLite.pm index 1900a64..f71f38b 100644 --- a/lib/DBD/SQLite.pm +++ b/lib/DBD/SQLite.pm @@ -752,7 +752,7 @@ like this while executing: SELECT bar FROM foo GROUP BY bar HAVING count(*) > "5"; -There are two workarounds for this. +There are three workarounds for this. =over 4 @@ -778,6 +778,32 @@ This is somewhat weird, but works anyway. }); $sth->execute(5); +=item Set C database handle attribute + +As of version 1.32_02, you can use C +to let DBD::SQLite to see if the bind values are numbers or not. + + $dbh->{sqlite_see_if_its_a_number} = 1; + my $sth = $dbh->prepare(q{ + SELECT bar FROM foo GROUP BY bar HAVING count(*) > ?; + }); + $sth->execute(5); + +You can set it to true when you connect to a database. + + my $dbh = DBI->connect('dbi:SQLite:foo', undef, undef, { + AutoCommit => 1, + RaiseError => 1, + sqlite_see_if_its_a_number => 1, + }); + +This is the most straightforward solution, but as noted above, +existing data in your databases created by DBD::SQLite have not +always been stored as numbers, so this *might* cause other obscure +problems. Use this sparingly when you handle existing databases. +If you handle databases created by other tools like native C +command line tool, this attribute would help you. + =back =head2 Placeholders @@ -1033,6 +1059,12 @@ If you set this to true, DBD::SQLite tries to issue a C (instead of C) when necessary. See above for details. +=item sqlite_see_if_its_a_number + +If you set this to true, DBD::SQLite tries to see if the bind values +are number or not, and does not quote if they are numbers. See above +for details. + =back =head2 Statement Handle Attributes diff --git a/t/rt_29058_group_by.t b/t/rt_29058_group_by.t index 0d7174a..e1d6ab2 100644 --- a/t/rt_29058_group_by.t +++ b/t/rt_29058_group_by.t @@ -6,7 +6,7 @@ BEGIN { } use t::lib::Test; -use Test::More tests => 7; +use Test::More tests => 8; use Test::NoWarnings; use DBI qw(:sql_types); @@ -51,6 +51,16 @@ $sth->execute; $ar = $sth->fetchall_arrayref; is( scalar(@$ar), 2, 'Got 2 results' ); +# known workaround 3 +{ + local $dbh->{sqlite_see_if_its_a_number} = 1; + my $sth = $dbh->selectall_arrayref( + 'SELECT bar FROM foo GROUP BY bar HAVING count(*) > ?', + undef, 1 + ); + is( scalar(@$ar), 2, 'Got 2 results' ); +} + # and this is what should be tested #TODO: { local $TODO = 'This test is currently broken again. Wait for a better fix, or use known workarounds shown above'; diff --git a/t/rt_29629_sqlite_where_length.t b/t/rt_29629_sqlite_where_length.t index 335dc1e..481047e 100644 --- a/t/rt_29629_sqlite_where_length.t +++ b/t/rt_29629_sqlite_where_length.t @@ -7,7 +7,7 @@ BEGIN { } use t::lib::Test; -use Test::More tests => 17; +use Test::More tests => 19; use Test::NoWarnings; use DBI qw(:sql_types); @@ -74,8 +74,15 @@ is( $sth->fetchrow_arrayref->[0], 1, "result of: $statement : [2]" ); # known workarounds 2: add "+0" to let sqlite convert the binded param into number -$statement =~ s/\?/\?\+0/; -$sth = $dbh->prepare($statement); -ok( $sth->execute(2), "execute: $statement : [2]" ); -is( $sth->fetchrow_arrayref->[0], 1, "result of: $statement : [2]" ); +(my $tweaked_statement = $statement) =~ s/\?/\?\+0/; +$sth = $dbh->prepare($tweaked_statement); +ok( $sth->execute(2), "execute: $tweaked_statement : [2]" ); +is( $sth->fetchrow_arrayref->[0], 1, "result of: $tweaked_statement : [2]" ); +# workaround 3: use sqlite_see_if_its_a_number attribute +{ + local $dbh->{sqlite_see_if_its_a_number} = 1; + $sth = $dbh->prepare($statement); + ok( $sth->execute(2), "execute: $statement : [2]" ); + is( $sth->fetchrow_arrayref->[0], 1, "result of: $statement : [2]" ); +}