Skip to content

Commit b161d65

Browse files
committed
Merge pull request #180 from jimflood/readwrite_option
add 'readwrite' flag to Database.new options for readwrite without cr…
2 parents 2473747 + 9247a94 commit b161d65

File tree

6 files changed

+205
-2
lines changed

6 files changed

+205
-2
lines changed

ext/sqlite3/database.c

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self)
4747
VALUE opts;
4848
VALUE zvfs;
4949
#ifdef HAVE_SQLITE3_OPEN_V2
50+
VALUE flags;
5051
int mode = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
5152
#endif
5253
int status;
@@ -79,11 +80,37 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self)
7980
}
8081
#endif
8182

83+
/* The three primary flag values for sqlite3_open_v2 are:
84+
* SQLITE_OPEN_READONLY
85+
* SQLITE_OPEN_READWRITE
86+
* SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE -- always used for sqlite3_open and sqlite3_open16
87+
*/
8288
if (Qtrue == rb_hash_aref(opts, ID2SYM(rb_intern("readonly")))) {
8389
#ifdef HAVE_SQLITE3_OPEN_V2
8490
mode = SQLITE_OPEN_READONLY;
8591
#else
8692
rb_raise(rb_eNotImpError, "sqlite3-ruby was compiled against a version of sqlite that does not support readonly databases");
93+
#endif
94+
}
95+
if (Qtrue == rb_hash_aref(opts, ID2SYM(rb_intern("readwrite")))) {
96+
#ifdef HAVE_SQLITE3_OPEN_V2
97+
if (mode == SQLITE_OPEN_READONLY) {
98+
rb_raise(rb_eRuntimeError, "conflicting options: readonly and readwrite");
99+
}
100+
mode = SQLITE_OPEN_READWRITE;
101+
#else
102+
rb_raise(rb_eNotImpError, "sqlite3-ruby was compiled against a version of sqlite that does not support readwrite without create");
103+
#endif
104+
}
105+
flags = rb_hash_aref(opts, ID2SYM(rb_intern("flags")));
106+
if (flags != Qnil) {
107+
#ifdef HAVE_SQLITE3_OPEN_V2
108+
if ((mode & SQLITE_OPEN_CREATE) == 0) {
109+
rb_raise(rb_eRuntimeError, "conflicting options: flags with readonly and/or readwrite");
110+
}
111+
mode = (int)NUM2INT(flags);
112+
#else
113+
rb_raise(rb_eNotImpError, "sqlite3-ruby was compiled against a version of sqlite that does not support flags on open");
87114
#endif
88115
}
89116
#ifdef HAVE_SQLITE3_OPEN_V2
@@ -116,7 +143,7 @@ static VALUE initialize(int argc, VALUE *argv, VALUE self)
116143
rb_iv_set(self, "@results_as_hash", rb_hash_aref(opts, sym_results_as_hash));
117144
rb_iv_set(self, "@type_translation", rb_hash_aref(opts, sym_type_translation));
118145
#ifdef HAVE_SQLITE3_OPEN_V2
119-
rb_iv_set(self, "@readonly", mode == SQLITE_OPEN_READONLY ? Qtrue : Qfalse);
146+
rb_iv_set(self, "@readonly", (mode & SQLITE_OPEN_READONLY) ? Qtrue : Qfalse);
120147
#else
121148
rb_iv_set(self, "@readonly", Qfalse);
122149
#endif

ext/sqlite3/sqlite3.c

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,42 @@ static VALUE threadsafe_p(VALUE UNUSED(klass))
7373
return INT2NUM(sqlite3_threadsafe());
7474
}
7575

76+
void init_sqlite3_constants()
77+
{
78+
VALUE mSqlite3Constants;
79+
VALUE mSqlite3Open;
80+
81+
mSqlite3Constants = rb_define_module_under(mSqlite3, "Constants");
82+
83+
/* sqlite3_open_v2 flags for Database::new */
84+
mSqlite3Open = rb_define_module_under(mSqlite3Constants, "Open");
85+
86+
/* symbols = IO.readlines('sqlite3.h').map { |n| /\A#define\s+(SQLITE_OPEN_\w+)\s/ =~ n && $1 }.compact
87+
* pad = symbols.map(&:length).max - 9
88+
* symbols.each { |s| printf %Q{ rb_define_const(mSqlite3Open, %-#{pad}s INT2FIX(#{s}));\n}, '"' + s[12..-1] + '",' }
89+
*/
90+
rb_define_const(mSqlite3Open, "READONLY", INT2FIX(SQLITE_OPEN_READONLY));
91+
rb_define_const(mSqlite3Open, "READWRITE", INT2FIX(SQLITE_OPEN_READWRITE));
92+
rb_define_const(mSqlite3Open, "CREATE", INT2FIX(SQLITE_OPEN_CREATE));
93+
rb_define_const(mSqlite3Open, "DELETEONCLOSE", INT2FIX(SQLITE_OPEN_DELETEONCLOSE));
94+
rb_define_const(mSqlite3Open, "EXCLUSIVE", INT2FIX(SQLITE_OPEN_EXCLUSIVE));
95+
rb_define_const(mSqlite3Open, "AUTOPROXY", INT2FIX(SQLITE_OPEN_AUTOPROXY));
96+
rb_define_const(mSqlite3Open, "URI", INT2FIX(SQLITE_OPEN_URI));
97+
rb_define_const(mSqlite3Open, "MEMORY", INT2FIX(SQLITE_OPEN_MEMORY));
98+
rb_define_const(mSqlite3Open, "MAIN_DB", INT2FIX(SQLITE_OPEN_MAIN_DB));
99+
rb_define_const(mSqlite3Open, "TEMP_DB", INT2FIX(SQLITE_OPEN_TEMP_DB));
100+
rb_define_const(mSqlite3Open, "TRANSIENT_DB", INT2FIX(SQLITE_OPEN_TRANSIENT_DB));
101+
rb_define_const(mSqlite3Open, "MAIN_JOURNAL", INT2FIX(SQLITE_OPEN_MAIN_JOURNAL));
102+
rb_define_const(mSqlite3Open, "TEMP_JOURNAL", INT2FIX(SQLITE_OPEN_TEMP_JOURNAL));
103+
rb_define_const(mSqlite3Open, "SUBJOURNAL", INT2FIX(SQLITE_OPEN_SUBJOURNAL));
104+
rb_define_const(mSqlite3Open, "MASTER_JOURNAL", INT2FIX(SQLITE_OPEN_MASTER_JOURNAL));
105+
rb_define_const(mSqlite3Open, "NOMUTEX", INT2FIX(SQLITE_OPEN_NOMUTEX));
106+
rb_define_const(mSqlite3Open, "FULLMUTEX", INT2FIX(SQLITE_OPEN_FULLMUTEX));
107+
rb_define_const(mSqlite3Open, "SHAREDCACHE", INT2FIX(SQLITE_OPEN_SHAREDCACHE));
108+
rb_define_const(mSqlite3Open, "PRIVATECACHE", INT2FIX(SQLITE_OPEN_PRIVATECACHE));
109+
rb_define_const(mSqlite3Open, "WAL", INT2FIX(SQLITE_OPEN_WAL));
110+
}
111+
76112
void Init_sqlite3_native()
77113
{
78114
/*
@@ -93,6 +129,7 @@ void Init_sqlite3_native()
93129
sqlite3_initialize();
94130
#endif
95131

132+
init_sqlite3_constants();
96133
init_sqlite3_database();
97134
init_sqlite3_statement();
98135
#ifdef HAVE_SQLITE3_BACKUP_INIT

test/test_database.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def test_segv
1515
end
1616

1717
def test_db_filename
18+
tf = nil
1819
assert_equal '', @db.filename('main')
1920
tf = Tempfile.new 'thing'
2021
@db = SQLite3::Database.new tf.path
@@ -24,6 +25,7 @@ def test_db_filename
2425
end
2526

2627
def test_filename
28+
tf = nil
2729
assert_equal '', @db.filename
2830
tf = Tempfile.new 'thing'
2931
@db = SQLite3::Database.new tf.path
@@ -33,6 +35,7 @@ def test_filename
3335
end
3436

3537
def test_filename_with_attachment
38+
tf = nil
3639
assert_equal '', @db.filename
3740
tf = Tempfile.new 'thing'
3841
@db.execute "ATTACH DATABASE '#{tf.path}' AS 'testing'"

test/test_database_flags.rb

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
require 'helper'
2+
3+
module SQLite3
4+
class TestDatabaseFlags < SQLite3::TestCase
5+
def setup
6+
File.unlink 'test-flags.db' if File.exists?('test-flags.db')
7+
@db = SQLite3::Database.new('test-flags.db')
8+
@db.execute("CREATE TABLE foos (id integer)")
9+
@db.close
10+
end
11+
12+
def teardown
13+
@db.close unless @db.closed?
14+
File.unlink 'test-flags.db' if File.exists?('test-flags.db')
15+
end
16+
17+
def test_open_database_flags_constants
18+
defined_to_date = [:READONLY, :READWRITE, :CREATE,
19+
:DELETEONCLOSE, :EXCLUSIVE, :AUTOPROXY, :URI, :MEMORY,
20+
:MAIN_DB, :TEMP_DB, :TRANSIENT_DB,
21+
:MAIN_JOURNAL, :TEMP_JOURNAL, :SUBJOURNAL, :MASTER_JOURNAL,
22+
:NOMUTEX, :FULLMUTEX,
23+
:SHAREDCACHE, :PRIVATECACHE, :WAL]
24+
assert (defined_to_date - SQLite3::Constants::Open.constants).empty?
25+
end
26+
27+
def test_open_database_flags_conflicts_with_readonly
28+
assert_raise(RuntimeError) do
29+
@db = SQLite3::Database.new('test-flags.db', :flags => 2, :readonly => true)
30+
end
31+
end
32+
33+
def test_open_database_flags_conflicts_with_readwrite
34+
assert_raise(RuntimeError) do
35+
@db = SQLite3::Database.new('test-flags.db', :flags => 2, :readwrite => true)
36+
end
37+
end
38+
39+
def test_open_database_readonly_flags
40+
@db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY)
41+
assert @db.readonly?
42+
end
43+
44+
def test_open_database_readwrite_flags
45+
@db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE)
46+
assert !@db.readonly?
47+
end
48+
49+
def test_open_database_readonly_flags_cant_open
50+
File.unlink 'test-flags.db'
51+
assert_raise(SQLite3::CantOpenException) do
52+
@db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READONLY)
53+
end
54+
end
55+
56+
def test_open_database_readwrite_flags_cant_open
57+
File.unlink 'test-flags.db'
58+
assert_raise(SQLite3::CantOpenException) do
59+
@db = SQLite3::Database.new('test-flags.db', :flags => SQLite3::Constants::Open::READWRITE)
60+
end
61+
end
62+
63+
def test_open_database_misuse_flags
64+
assert_raise(SQLite3::MisuseException) do
65+
flags = SQLite3::Constants::Open::READONLY | SQLite3::Constants::Open::READWRITE # <== incompatible flags
66+
@db = SQLite3::Database.new('test-flags.db', :flags => flags)
67+
end
68+
end
69+
70+
def test_open_database_create_flags
71+
File.unlink 'test-flags.db'
72+
flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE
73+
@db = SQLite3::Database.new('test-flags.db', :flags => flags) do |db|
74+
db.execute("CREATE TABLE foos (id integer)")
75+
db.execute("INSERT INTO foos (id) VALUES (12)")
76+
end
77+
assert File.exists?('test-flags.db')
78+
end
79+
80+
def test_open_database_exotic_flags
81+
flags = SQLite3::Constants::Open::READWRITE | SQLite3::Constants::Open::CREATE
82+
exotic_flags = SQLite3::Constants::Open::NOMUTEX | SQLite3::Constants::Open::PRIVATECACHE
83+
@db = SQLite3::Database.new('test-flags.db', :flags => flags | exotic_flags)
84+
@db.execute("INSERT INTO foos (id) VALUES (12)")
85+
assert @db.changes == 1
86+
end
87+
end
88+
end

test/test_database_readonly.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,21 @@ def setup
1111

1212
def teardown
1313
@db.close unless @db.closed?
14-
File.unlink 'test-readonly.db'
14+
File.unlink 'test-readonly.db' if File.exists?('test-readonly.db')
1515
end
1616

1717
def test_open_readonly_database
1818
@db = SQLite3::Database.new('test-readonly.db', :readonly => true)
1919
assert @db.readonly?
2020
end
2121

22+
def test_open_readonly_not_exists_database
23+
File.unlink 'test-readonly.db'
24+
assert_raise(SQLite3::CantOpenException) do
25+
@db = SQLite3::Database.new('test-readonly.db', :readonly => true)
26+
end
27+
end
28+
2229
def test_insert_readonly_database
2330
@db = SQLite3::Database.new('test-readonly.db', :readonly => true)
2431
assert_raise(SQLite3::ReadOnlyException) do

test/test_database_readwrite.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require 'helper'
2+
3+
module SQLite3
4+
class TestDatabaseReadwrite < SQLite3::TestCase
5+
def setup
6+
File.unlink 'test-readwrite.db' if File.exists?('test-readwrite.db')
7+
@db = SQLite3::Database.new('test-readwrite.db')
8+
@db.execute("CREATE TABLE foos (id integer)")
9+
@db.close
10+
end
11+
12+
def teardown
13+
@db.close unless @db.closed?
14+
File.unlink 'test-readwrite.db' if File.exists?('test-readwrite.db')
15+
end
16+
17+
def test_open_readwrite_database
18+
@db = SQLite3::Database.new('test-readwrite.db', :readwrite => true)
19+
assert !@db.readonly?
20+
end
21+
22+
def test_open_readwrite_readonly_database
23+
assert_raise(RuntimeError) do
24+
@db = SQLite3::Database.new('test-readwrite.db', :readwrite => true, :readonly => true)
25+
end
26+
end
27+
28+
def test_open_readwrite_not_exists_database
29+
File.unlink 'test-readwrite.db'
30+
assert_raise(SQLite3::CantOpenException) do
31+
@db = SQLite3::Database.new('test-readwrite.db', :readonly => true)
32+
end
33+
end
34+
35+
def test_insert_readwrite_database
36+
@db = SQLite3::Database.new('test-readwrite.db', :readwrite => true)
37+
@db.execute("INSERT INTO foos (id) VALUES (12)")
38+
assert @db.changes == 1
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)