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

implemented sqlite_db_config interface (RT-128056)

This commit is contained in:
Kenichi Ishigaki 2018-12-22 05:09:06 +09:00
parent b8c5535548
commit a28532af37
6 changed files with 251 additions and 0 deletions

View file

@ -90,6 +90,7 @@ t/61_strlike.t
t/62_regexp_multibyte_char_class.t
t/63_param_values.t
t/64_limit.t
t/65_db_config.t
t/cookbook_variance.t
t/lib/SQLiteTest.pm
t/rt_106151_outermost_savepoint.t

View file

@ -354,6 +354,20 @@ limit(dbh, id, new_value = -1)
OUTPUT:
RETVAL
static int
db_config(dbh, id, new_value = -1)
SV *dbh
int id
int new_value
ALIAS:
DBD::SQLite::db::sqlite_db_config = 1
CODE:
{
RETVAL = sqlite_db_config(aTHX_ dbh, id, new_value);
}
OUTPUT:
RETVAL
MODULE = DBD::SQLite PACKAGE = DBD::SQLite::st
PROTOTYPES: DISABLE

View file

@ -2756,6 +2756,41 @@ sqlite_db_limit(pTHX_ SV *dbh, int id, int new_value)
return sqlite3_limit(imp_dbh->db, id, new_value);
}
int
sqlite_db_config(pTHX_ SV *dbh, int id, int new_value)
{
D_imp_dbh(dbh);
int ret;
int rc = -1;
switch (id) {
case SQLITE_DBCONFIG_LOOKASIDE:
sqlite_error(dbh, rc, "SQLITE_DBCONFIG_LOOKASIDE is not supported");
return FALSE;
case SQLITE_DBCONFIG_MAINDBNAME:
sqlite_error(dbh, rc, "SQLITE_DBCONFIG_MAINDBNAME is not supported");
return FALSE;
case SQLITE_DBCONFIG_ENABLE_FKEY:
case SQLITE_DBCONFIG_ENABLE_TRIGGER:
case SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER:
case SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION:
case SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE:
case SQLITE_DBCONFIG_ENABLE_QPSG:
case SQLITE_DBCONFIG_TRIGGER_EQP:
case SQLITE_DBCONFIG_RESET_DATABASE:
case SQLITE_DBCONFIG_DEFENSIVE:
rc = sqlite3_db_config(imp_dbh->db, id, new_value, &ret);
break;
default:
sqlite_error(dbh, rc, form("Unknown config id: %d", id));
return FALSE;
}
if ( rc != SQLITE_OK ) {
sqlite_error(dbh, rc, form("sqlite_config failed with error %s", sqlite3_errmsg(imp_dbh->db)));
return FALSE;
}
return ret;
}
#include "dbdimp_tokenizer.inc"
#include "dbdimp_virtual_table.inc"

View file

@ -135,6 +135,7 @@ HV* _sqlite_status(int reset);
HV* _sqlite_st_status(pTHX_ SV *sth, int reset);
int sqlite_db_create_module(pTHX_ SV *dbh, const char *name, const char *perl_class);
int sqlite_db_limit(pTHX_ SV *dbh, int id, int new_value);
int sqlite_db_config(pTHX_ SV *dbh, int id, int new_value);
int sqlite_db_do_sv(SV *dbh, imp_dbh_t *imp_dbh, SV *sv_statement);
void init_cxt();

View file

@ -60,6 +60,7 @@ sub driver {
DBD::SQLite::st->install_method('sqlite_st_status', { O => 0x0004 });
DBD::SQLite::db->install_method('sqlite_create_module');
DBD::SQLite::db->install_method('sqlite_limit');
DBD::SQLite::db->install_method('sqlite_db_config');
$methods_are_installed++;
}
@ -2304,6 +2305,20 @@ Returns a hash reference that holds a set of status information of SQLite statem
You may also pass 0 as an argument to reset the status.
=head2 $dbh->sqlite_db_config( $id, $new_integer_value )
You can change how the connected database should behave like this:
use DBD::SQLite::Constants qw/:database_connection_configuration_options/;
my $dbh = DBI->connect('dbi:SQLite::memory:');
# This disables language features that allow ordinary SQL
# to deliberately corrupt the database file
$dbh->sqlite_db_config( SQLITE_DBCONFIG_DEFENSIVE, 1 );
As of this writing, C<sqlite_db_config> only supports options that set an integer value. C<SQLITE_DBCONFIG_LOOKASIDE> and C<SQLITE_DBCONFIG_MAINDBNAME> are not supported. See also C<https://www.sqlite.org/capi3ref.html#sqlite3_db_config> for details.
=head2 $dbh->sqlite_create_module()
Registers a name for a I<virtual table module>. Module names must be

185
t/65_db_config.t Normal file
View file

@ -0,0 +1,185 @@
#!/usr/bin/perl
use strict;
BEGIN {
$| = 1;
$^W = 1;
}
use lib "t/lib";
use SQLiteTest qw/connect_ok @CALL_FUNCS/;
use Test::More;
use DBD::SQLite::Constants qw/:database_connection_configuration_options/;
#plan tests => 7 * @CALL_FUNCS;
# LOOKASIDE
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'LOOKASIDE is not supported', 2 if !SQLITE_DBCONFIG_LOOKASIDE;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
eval { $dbh->$func(SQLITE_DBCONFIG_LOOKASIDE, 1, 'db_config') };
ok $@, 'LOOKASIDE is not supported';
like $@ => qr/LOOKASIDE is not supported/;
}
}
# MAINDBNAME
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'MAINDBNAME is not supported', 2 if !SQLITE_DBCONFIG_MAINDBNAME;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
eval { $dbh->$func(SQLITE_DBCONFIG_MAINDBNAME, 1, 'db_config') };
ok $@, 'MAINDBNAME is not supported';
like $@ => qr/MAINDBNAME is not supported/;
}
}
# ENABLE_FKEY
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'ENABLE_FKEY is not supported', 3 if !SQLITE_DBCONFIG_ENABLE_FKEY;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_FKEY, 1, 'db_config');
is $ret => 1, 'enable foreign key';
# TODO: add foreign key check
$ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_FKEY, 0, 'db_config');
is $ret => 0, 'disable foreign key';
}
}
# ENABLE_TRIGGER
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'ENABLE_TRIGGER is not supported', 3 if !SQLITE_DBCONFIG_ENABLE_TRIGGER;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_TRIGGER, 1, 'db_config');
is $ret => 1, 'enable trigger';
# TODO: add trigger check
$ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_TRIGGER, 0, 'db_config');
is $ret => 0, 'disable trigger';
}
}
# ENABLE_FTS3_TOKENIZER
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'ENABLE_FTS3_TOKENIZER is not supported', 3 if !SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, 'db_config');
is $ret => 1, 'enable fts3_tokenizer';
# TODO: add fts3_tokenizer check
$ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 0, 'db_config');
is $ret => 0, 'disable fts3_tokenizer';
}
}
# ENABLE_LOAD_EXTENSION
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'ENABLE_LOAD_EXTENSION is not supported', 3 if !SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, 'db_config');
is $ret => 1, 'enable load_extension';
# TODO: add load_extension check
$ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 0, 'db_config');
is $ret => 0, 'disable load_extension';
}
}
# ENABLE_NO_CKPT_ON_CLOSE
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'NO_CKPT_ON_CLOSE is not supported', 3 if !SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, 'db_config');
is $ret => 1, 'no checkpoint on close';
# TODO: add checkpoint check
$ret = $dbh->$func(SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 0, 'db_config');
is $ret => 0, 'checkpoint on close';
}
}
# ENABLE_QPSG
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'ENABLE_QPSG is not supported', 3 if !SQLITE_DBCONFIG_ENABLE_QPSG;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_QPSG, 1, 'db_config');
is $ret => 1, 'enable query planner stability guarantee';
# TODO: add qpsg check
$ret = $dbh->$func(SQLITE_DBCONFIG_ENABLE_QPSG, 0, 'db_config');
is $ret => 0, 'disable query planner stability guarantee';
}
}
# TRIGGER_EQP
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'TRIGGER_EQP is not supported', 3 if !SQLITE_DBCONFIG_TRIGGER_EQP;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_TRIGGER_EQP, 1, 'db_config');
is $ret => 1, 'trigger explain query plan';
# TODO: add trigger check
$ret = $dbh->$func(SQLITE_DBCONFIG_TRIGGER_EQP, 0, 'db_config');
is $ret => 0, 'no trigger explain query plan';
}
}
# RESET_DATABASE
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'RESET_DATABASE is not supported', 3 if !SQLITE_DBCONFIG_RESET_DATABASE;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $ret = $dbh->$func(SQLITE_DBCONFIG_RESET_DATABASE, 1, 'db_config');
is $ret => 1, 'enable reset database';
# TODO: add reset check
$ret = $dbh->$func(SQLITE_DBCONFIG_RESET_DATABASE, 0, 'db_config');
is $ret => 0, 'disable reset database';
}
}
# DEFENSIVE
for my $func (@CALL_FUNCS) {
SKIP: {
skip 'DEFENSIVE is not supported', 8 if !SQLITE_DBCONFIG_DEFENSIVE;
my $dbh = connect_ok(RaiseError => 1, PrintError => 0);
my $sql = 'CREATE TABLE foo (id, text)';
$dbh->do($sql);
$dbh->do('PRAGMA writable_schema=ON');
my $row = $dbh->selectrow_hashref('SELECT * FROM sqlite_master WHERE name = ?', {Slice => +{}}, 'foo');
is $row->{sql} => $sql, 'found sql';
my $ret = $dbh->$func(SQLITE_DBCONFIG_DEFENSIVE, 1, 'db_config');
is $ret => 1;
eval { $dbh->do('UPDATE sqlite_master SET name = ? WHERE name = ?', undef, 'bar', 'foo'); };
ok $@, "updating sqlite_master is prohibited";
like $@ => qr/table sqlite_master may not be modified/;
$ret = $dbh->$func(SQLITE_DBCONFIG_DEFENSIVE, 0, 'db_config');
is $ret => 0;
$ret = $dbh->do('UPDATE sqlite_master SET name = ? WHERE name = ?', undef, 'bar', 'foo');
ok $ret, 'updating sqlite_master is succeeded';
$row = $dbh->selectrow_hashref('SELECT * FROM sqlite_master WHERE name = ?', {Slice => +{}}, 'foo');
ok !$row, 'sql not found';
}
}
done_testing;