sqlite3 1.3.7 → 1.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.rdoc +45 -1
- data/Gemfile +15 -0
- data/Manifest.txt +2 -0
- data/README.rdoc +29 -6
- data/ext/sqlite3/database.c +95 -22
- data/ext/sqlite3/extconf.rb +27 -7
- data/ext/sqlite3/sqlite3.c +112 -0
- data/ext/sqlite3/sqlite3_ruby.h +3 -4
- data/ext/sqlite3/statement.c +27 -18
- data/faq/faq.yml +1 -1
- data/lib/sqlite3/database.rb +34 -22
- data/lib/sqlite3/pragmas.rb +357 -49
- data/lib/sqlite3/resultset.rb +1 -1
- data/lib/sqlite3/version.rb +2 -2
- data/lib/sqlite3.rb +5 -0
- data/setup.rb +2 -2
- data/tasks/gem.rake +12 -6
- data/tasks/native.rake +22 -7
- data/tasks/vendor_sqlite3.rake +66 -20
- data/test/helper.rb +8 -4
- data/test/test_database.rb +95 -4
- data/test/test_database_readonly.rb +9 -2
- data/test/test_encoding.rb +34 -0
- data/test/test_integration.rb +35 -14
- data/test/test_integration_pending.rb +1 -1
- data/test/test_sqlite3.rb +12 -0
- data/test/test_statement.rb +14 -3
- metadata +120 -118
- data/.gemtest +0 -0
data/tasks/vendor_sqlite3.rake
CHANGED
|
@@ -2,17 +2,17 @@ require "rake/clean"
|
|
|
2
2
|
require "rake/extensioncompiler"
|
|
3
3
|
require "mini_portile"
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
CLOBBER.include("ports")
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
$recipes[:sqlite3].files << "http://sqlite.org/sqlite-autoconf-#{URL_VERSION}.tar.gz"
|
|
7
|
+
directory "ports"
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
def define_sqlite_task(platform, host)
|
|
10
|
+
recipe = MiniPortile.new "sqlite3", BINARY_VERSION
|
|
11
|
+
recipe.files << "http://sqlite.org#{URL_PATH}/sqlite-autoconf-#{URL_VERSION}.tar.gz"
|
|
12
|
+
recipe.host = host
|
|
12
13
|
|
|
13
|
-
desc "
|
|
14
|
-
task :sqlite3 => ["ports"] do |t|
|
|
15
|
-
recipe = $recipes[:sqlite3]
|
|
14
|
+
desc "Compile sqlite3 for #{platform} (#{host})"
|
|
15
|
+
task "ports:sqlite3:#{platform}" => ["ports"] do |t|
|
|
16
16
|
checkpoint = "ports/.#{recipe.name}-#{recipe.version}-#{recipe.host}.installed"
|
|
17
17
|
|
|
18
18
|
unless File.exist?(checkpoint)
|
|
@@ -22,30 +22,76 @@ namespace :ports do
|
|
|
22
22
|
recipe.cook
|
|
23
23
|
touch checkpoint
|
|
24
24
|
end
|
|
25
|
-
|
|
26
|
-
recipe.activate
|
|
27
25
|
end
|
|
26
|
+
|
|
27
|
+
recipe
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# native sqlite3 compilation
|
|
31
|
+
recipe = define_sqlite_task(RUBY_PLATFORM, RbConfig::CONFIG["host"])
|
|
32
|
+
|
|
33
|
+
# force compilation of sqlite3 when working natively under MinGW
|
|
30
34
|
if RUBY_PLATFORM =~ /mingw/
|
|
31
|
-
|
|
35
|
+
RUBY_EXTENSION.config_options << "--with-opt-dir=#{recipe.path}"
|
|
36
|
+
|
|
37
|
+
# also prepend DevKit into compilation phase
|
|
38
|
+
Rake::Task["compile"].prerequisites.unshift "devkit", "ports:sqlite3:#{RUBY_PLATFORM}"
|
|
39
|
+
Rake::Task["native"].prerequisites.unshift "devkit", "ports:sqlite3:#{RUBY_PLATFORM}"
|
|
32
40
|
end
|
|
33
41
|
|
|
42
|
+
# trick to test local compilation of sqlite3
|
|
34
43
|
if ENV["USE_MINI_PORTILE"] == "true"
|
|
35
|
-
|
|
44
|
+
# fake recipe so we can build a directory to it
|
|
45
|
+
recipe = MiniPortile.new "sqlite3", BINARY_VERSION
|
|
46
|
+
recipe.host = RbConfig::CONFIG["host"]
|
|
47
|
+
|
|
48
|
+
RUBY_EXTENSION.config_options << "--with-opt-dir=#{recipe.path}"
|
|
49
|
+
|
|
50
|
+
# compile sqlite3 first
|
|
51
|
+
Rake::Task["compile"].prerequisites.unshift "ports:sqlite3:#{RUBY_PLATFORM}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# iterate over all cross-compilation platforms and define the proper
|
|
55
|
+
# sqlite3 recipe for it.
|
|
56
|
+
if RUBY_EXTENSION.cross_compile
|
|
57
|
+
config_path = File.expand_path("~/.rake-compiler/config.yml")
|
|
58
|
+
if File.exist?(config_path)
|
|
59
|
+
# obtains platforms from rake-compiler's config.yml
|
|
60
|
+
config_file = YAML.load_file(config_path)
|
|
61
|
+
|
|
62
|
+
Array(RUBY_EXTENSION.cross_platform).each do |platform|
|
|
63
|
+
# obtain platform from rbconfig file
|
|
64
|
+
config_key = config_file.keys.sort.find { |key|
|
|
65
|
+
key.start_with?("rbconfig-#{platform}-")
|
|
66
|
+
}
|
|
67
|
+
rbfile = config_file[config_key]
|
|
68
|
+
|
|
69
|
+
# skip if rbconfig cannot be read
|
|
70
|
+
next unless File.exist?(rbfile)
|
|
71
|
+
|
|
72
|
+
host = IO.read(rbfile).match(/CONFIG\["CC"\] = "(.*)"/)[1].sub(/\-gcc/, '')
|
|
73
|
+
recipe = define_sqlite_task(platform, host)
|
|
74
|
+
|
|
75
|
+
RUBY_EXTENSION.cross_config_options << {
|
|
76
|
+
platform => "--with-opt-dir=#{recipe.path}"
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# pre-compile sqlite3 port when cross-compiling
|
|
80
|
+
task :cross => "ports:sqlite3:#{platform}"
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
warn "rake-compiler configuration doesn't exist, but is required for ports"
|
|
84
|
+
end
|
|
36
85
|
end
|
|
37
86
|
|
|
38
87
|
task :cross do
|
|
39
88
|
["CC", "CXX", "LDFLAGS", "CPPFLAGS", "RUBYOPT"].each do |var|
|
|
40
89
|
ENV.delete(var)
|
|
41
90
|
end
|
|
42
|
-
host = ENV.fetch("HOST", Rake::ExtensionCompiler.mingw_host)
|
|
43
|
-
$recipes.each do |_, recipe|
|
|
44
|
-
recipe.host = host
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# hook compile task with dependencies
|
|
48
|
-
Rake::Task["compile"].prerequisites.unshift "ports:sqlite3"
|
|
49
91
|
end
|
|
50
92
|
|
|
51
|
-
|
|
93
|
+
desc "Build windows binary gems per rake-compiler-dock."
|
|
94
|
+
task "gem:windows" do
|
|
95
|
+
require "rake_compiler_dock"
|
|
96
|
+
RakeCompilerDock.sh "bundle && rake cross native gem MAKE='nice make -j`nproc`'"
|
|
97
|
+
end
|
data/test/helper.rb
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
require 'sqlite3'
|
|
2
|
-
require '
|
|
2
|
+
require 'minitest/autorun'
|
|
3
3
|
|
|
4
4
|
unless RUBY_VERSION >= "1.9"
|
|
5
5
|
require 'iconv'
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
module SQLite3
|
|
9
|
-
class TestCase < Test
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
class TestCase < Minitest::Test
|
|
10
|
+
alias :assert_not_equal :refute_equal
|
|
11
|
+
alias :assert_not_nil :refute_nil
|
|
12
|
+
alias :assert_raise :assert_raises
|
|
13
|
+
|
|
14
|
+
def assert_nothing_raised
|
|
15
|
+
yield
|
|
12
16
|
end
|
|
13
17
|
end
|
|
14
18
|
end
|
data/test/test_database.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require 'helper'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
require 'pathname'
|
|
2
4
|
|
|
3
5
|
module SQLite3
|
|
4
6
|
class TestDatabase < SQLite3::TestCase
|
|
@@ -6,12 +8,43 @@ module SQLite3
|
|
|
6
8
|
|
|
7
9
|
def setup
|
|
8
10
|
@db = SQLite3::Database.new(':memory:')
|
|
11
|
+
super
|
|
9
12
|
end
|
|
10
13
|
|
|
11
14
|
def test_segv
|
|
12
15
|
assert_raises(TypeError) { SQLite3::Database.new 1 }
|
|
13
16
|
end
|
|
14
17
|
|
|
18
|
+
def test_db_filename
|
|
19
|
+
tf = nil
|
|
20
|
+
assert_equal '', @db.filename('main')
|
|
21
|
+
tf = Tempfile.new 'thing'
|
|
22
|
+
@db = SQLite3::Database.new tf.path
|
|
23
|
+
assert_equal File.expand_path(tf.path), File.expand_path(@db.filename('main'))
|
|
24
|
+
ensure
|
|
25
|
+
tf.unlink if tf
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_filename
|
|
29
|
+
tf = nil
|
|
30
|
+
assert_equal '', @db.filename
|
|
31
|
+
tf = Tempfile.new 'thing'
|
|
32
|
+
@db = SQLite3::Database.new tf.path
|
|
33
|
+
assert_equal File.expand_path(tf.path), File.expand_path(@db.filename)
|
|
34
|
+
ensure
|
|
35
|
+
tf.unlink if tf
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_filename_with_attachment
|
|
39
|
+
tf = nil
|
|
40
|
+
assert_equal '', @db.filename
|
|
41
|
+
tf = Tempfile.new 'thing'
|
|
42
|
+
@db.execute "ATTACH DATABASE '#{tf.path}' AS 'testing'"
|
|
43
|
+
assert_equal File.expand_path(tf.path), File.expand_path(@db.filename('testing'))
|
|
44
|
+
ensure
|
|
45
|
+
tf.unlink if tf
|
|
46
|
+
end
|
|
47
|
+
|
|
15
48
|
def test_bignum
|
|
16
49
|
num = 4907021672125087844
|
|
17
50
|
db.execute 'CREATE TABLE "employees" ("token" integer(8), "name" varchar(20) NOT NULL)'
|
|
@@ -22,9 +55,9 @@ module SQLite3
|
|
|
22
55
|
|
|
23
56
|
def test_blob
|
|
24
57
|
@db.execute("CREATE TABLE blobs ( id INTEGER, hash BLOB(10) )")
|
|
25
|
-
|
|
26
|
-
@db.execute("INSERT INTO blobs VALUES (0, ?)", [
|
|
27
|
-
assert_equal [[0,
|
|
58
|
+
blob = Blob.new("foo\0bar")
|
|
59
|
+
@db.execute("INSERT INTO blobs VALUES (0, ?)", [blob])
|
|
60
|
+
assert_equal [[0, blob, blob.length, blob.length*2]], @db.execute("SELECT id, hash, length(hash), length(hex(hash)) FROM blobs")
|
|
28
61
|
end
|
|
29
62
|
|
|
30
63
|
def test_get_first_row
|
|
@@ -60,6 +93,14 @@ module SQLite3
|
|
|
60
93
|
assert_equal [[30]], @db.execute("select number from items")
|
|
61
94
|
end
|
|
62
95
|
|
|
96
|
+
def test_batch_last_comment_is_processed
|
|
97
|
+
# FIXME: nil as a successful return value is kinda dumb
|
|
98
|
+
assert_nil @db.execute_batch <<-eosql
|
|
99
|
+
CREATE TABLE items (id integer PRIMARY KEY AUTOINCREMENT);
|
|
100
|
+
-- omg
|
|
101
|
+
eosql
|
|
102
|
+
end
|
|
103
|
+
|
|
63
104
|
def test_new
|
|
64
105
|
db = SQLite3::Database.new(':memory:')
|
|
65
106
|
assert db
|
|
@@ -101,12 +142,33 @@ module SQLite3
|
|
|
101
142
|
assert thing.closed?
|
|
102
143
|
end
|
|
103
144
|
|
|
145
|
+
def test_block_closes_self_even_raised
|
|
146
|
+
thing = nil
|
|
147
|
+
begin
|
|
148
|
+
SQLite3::Database.new(':memory:') do |db|
|
|
149
|
+
thing = db
|
|
150
|
+
raise
|
|
151
|
+
end
|
|
152
|
+
rescue
|
|
153
|
+
end
|
|
154
|
+
assert thing.closed?
|
|
155
|
+
end
|
|
156
|
+
|
|
104
157
|
def test_prepare
|
|
105
158
|
db = SQLite3::Database.new(':memory:')
|
|
106
159
|
stmt = db.prepare('select "hello world"')
|
|
107
160
|
assert_instance_of(SQLite3::Statement, stmt)
|
|
108
161
|
end
|
|
109
162
|
|
|
163
|
+
def test_block_prepare_does_not_double_close
|
|
164
|
+
db = SQLite3::Database.new(':memory:')
|
|
165
|
+
r = db.prepare('select "hello world"') do |stmt|
|
|
166
|
+
stmt.close
|
|
167
|
+
:foo
|
|
168
|
+
end
|
|
169
|
+
assert_equal :foo, r
|
|
170
|
+
end
|
|
171
|
+
|
|
110
172
|
def test_total_changes
|
|
111
173
|
db = SQLite3::Database.new(':memory:')
|
|
112
174
|
db.execute("create table foo ( a integer primary key, b text )")
|
|
@@ -227,18 +289,47 @@ module SQLite3
|
|
|
227
289
|
assert_equal [2.2, 'foo', nil], called_with
|
|
228
290
|
end
|
|
229
291
|
|
|
292
|
+
def test_call_func_blob
|
|
293
|
+
called_with = nil
|
|
294
|
+
@db.define_function("hello") do |a, b|
|
|
295
|
+
called_with = [a, b, a.length]
|
|
296
|
+
nil
|
|
297
|
+
end
|
|
298
|
+
blob = Blob.new("a\0fine\0kettle\0of\0fish")
|
|
299
|
+
@db.execute("select hello(?, length(?))", [blob, blob])
|
|
300
|
+
assert_equal [blob, blob.length, 21], called_with
|
|
301
|
+
end
|
|
302
|
+
|
|
230
303
|
def test_function_return
|
|
231
304
|
@db.define_function("hello") { |a| 10 }
|
|
232
305
|
assert_equal [10], @db.execute("select hello('world')").first
|
|
233
306
|
end
|
|
234
307
|
|
|
235
308
|
def test_function_return_types
|
|
236
|
-
[10, 2.2, nil, "foo"].each do |thing|
|
|
309
|
+
[10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing|
|
|
237
310
|
@db.define_function("hello") { |a| thing }
|
|
238
311
|
assert_equal [thing], @db.execute("select hello('world')").first
|
|
239
312
|
end
|
|
240
313
|
end
|
|
241
314
|
|
|
315
|
+
def test_function_gc_segfault
|
|
316
|
+
@db.create_function("bug", -1) { |func, *values| func.result = values.join }
|
|
317
|
+
# With a lot of data and a lot of threads, try to induce a GC segfault.
|
|
318
|
+
params = Array.new(127, "?" * 28000)
|
|
319
|
+
proc = Proc.new {
|
|
320
|
+
db.execute("select bug(#{Array.new(params.length, "?").join(",")})", params)
|
|
321
|
+
}
|
|
322
|
+
m = Mutex.new
|
|
323
|
+
30.times.map { Thread.new { m.synchronize { proc.call } } }.each(&:join)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def test_function_return_type_round_trip
|
|
327
|
+
[10, 2.2, nil, "foo", Blob.new("foo\0bar")].each do |thing|
|
|
328
|
+
@db.define_function("hello") { |a| a }
|
|
329
|
+
assert_equal [thing], @db.execute("select hello(hello(?))", [thing]).first
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
242
333
|
def test_define_function_closed
|
|
243
334
|
@db.close
|
|
244
335
|
assert_raise(SQLite3::Exception) do
|
|
@@ -3,7 +3,7 @@ require 'helper'
|
|
|
3
3
|
module SQLite3
|
|
4
4
|
class TestDatabaseReadonly < SQLite3::TestCase
|
|
5
5
|
def setup
|
|
6
|
-
File.unlink 'test-readonly.db' if File.
|
|
6
|
+
File.unlink 'test-readonly.db' if File.exist?('test-readonly.db')
|
|
7
7
|
@db = SQLite3::Database.new('test-readonly.db')
|
|
8
8
|
@db.execute("CREATE TABLE foos (id integer)")
|
|
9
9
|
@db.close
|
|
@@ -11,7 +11,7 @@ module SQLite3
|
|
|
11
11
|
|
|
12
12
|
def teardown
|
|
13
13
|
@db.close unless @db.closed?
|
|
14
|
-
File.unlink 'test-readonly.db'
|
|
14
|
+
File.unlink 'test-readonly.db' if File.exist?('test-readonly.db')
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def test_open_readonly_database
|
|
@@ -19,6 +19,13 @@ module SQLite3
|
|
|
19
19
|
assert @db.readonly?
|
|
20
20
|
end
|
|
21
21
|
|
|
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
|
+
|
|
22
29
|
def test_insert_readonly_database
|
|
23
30
|
@db = SQLite3::Database.new('test-readonly.db', :readonly => true)
|
|
24
31
|
assert_raise(SQLite3::ReadOnlyException) do
|
data/test/test_encoding.rb
CHANGED
|
@@ -11,6 +11,40 @@ module SQLite3
|
|
|
11
11
|
@db.execute(@create);
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
def test_select_encoding_on_utf_16
|
|
15
|
+
str = "foo"
|
|
16
|
+
utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE"
|
|
17
|
+
db = SQLite3::Database.new(':memory:'.encode(utf16))
|
|
18
|
+
db.execute @create
|
|
19
|
+
db.execute "insert into ex (id, data) values (1, \"#{str}\")"
|
|
20
|
+
|
|
21
|
+
stmt = db.prepare 'select * from ex where data = ?'
|
|
22
|
+
['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each do |enc|
|
|
23
|
+
stmt.bind_param 1, str.encode(enc)
|
|
24
|
+
assert_equal 1, stmt.to_a.length
|
|
25
|
+
stmt.reset!
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def test_insert_encoding
|
|
30
|
+
str = "foo"
|
|
31
|
+
utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE"
|
|
32
|
+
db = SQLite3::Database.new(':memory:'.encode(utf16))
|
|
33
|
+
db.execute @create
|
|
34
|
+
stmt = db.prepare @insert
|
|
35
|
+
|
|
36
|
+
['US-ASCII', utf16, 'EUC-JP', 'UTF-8'].each_with_index do |enc,i|
|
|
37
|
+
stmt.bind_param 1, i
|
|
38
|
+
stmt.bind_param 2, str.encode(enc)
|
|
39
|
+
stmt.to_a
|
|
40
|
+
stmt.reset!
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
db.execute('select data from ex').flatten.each do |s|
|
|
44
|
+
assert_equal str, s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
14
48
|
def test_default_internal_is_honored
|
|
15
49
|
warn_before = $-w
|
|
16
50
|
$-w = false
|
data/test/test_integration.rb
CHANGED
|
@@ -21,12 +21,14 @@ class TC_Database_Integration < SQLite3::TestCase
|
|
|
21
21
|
|
|
22
22
|
def test_table_info_with_defaults_for_version_3_3_8_and_higher
|
|
23
23
|
@db.transaction do
|
|
24
|
-
@db.execute "create table defaults_test ( a string default NULL, b string default 'Hello' )"
|
|
24
|
+
@db.execute "create table defaults_test ( a string default NULL, b string default 'Hello', c string default '--- []\n' )"
|
|
25
25
|
data = @db.table_info( "defaults_test" )
|
|
26
26
|
assert_equal({"name" => "a", "type" => "string", "dflt_value" => nil, "notnull" => 0, "cid" => 0, "pk" => 0},
|
|
27
27
|
data[0])
|
|
28
28
|
assert_equal({"name" => "b", "type" => "string", "dflt_value" => "Hello", "notnull" => 0, "cid" => 1, "pk" => 0},
|
|
29
29
|
data[1])
|
|
30
|
+
assert_equal({"name" => "c", "type" => "string", "dflt_value" => "--- []\n", "notnull" => 0, "cid" => 2, "pk" => 0},
|
|
31
|
+
data[2])
|
|
30
32
|
end
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -236,7 +238,7 @@ class TC_Database_Integration < SQLite3::TestCase
|
|
|
236
238
|
|
|
237
239
|
def test_execute2_with_block_with_bind_with_match
|
|
238
240
|
called = 0
|
|
239
|
-
@db.execute2( "select * from foo where a = ?", 1 ) do
|
|
241
|
+
@db.execute2( "select * from foo where a = ?", 1 ) do
|
|
240
242
|
called += 1
|
|
241
243
|
end
|
|
242
244
|
assert_equal 2, called
|
|
@@ -497,6 +499,10 @@ class TC_Database_Integration < SQLite3::TestCase
|
|
|
497
499
|
|
|
498
500
|
value = @db.get_first_value( "select accumulate(a) from foo" )
|
|
499
501
|
assert_equal 6, value
|
|
502
|
+
|
|
503
|
+
# calling #get_first_value twice don't add up to the latest result
|
|
504
|
+
value = @db.get_first_value( "select accumulate(a) from foo" )
|
|
505
|
+
assert_equal 6, value
|
|
500
506
|
end
|
|
501
507
|
|
|
502
508
|
def test_create_aggregate_with_block
|
|
@@ -528,21 +534,36 @@ class TC_Database_Integration < SQLite3::TestCase
|
|
|
528
534
|
assert_equal 0, value
|
|
529
535
|
end
|
|
530
536
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
537
|
+
class AggregateHandler
|
|
538
|
+
class << self
|
|
539
|
+
def arity; 1; end
|
|
540
|
+
def text_rep; SQLite3::Constants::TextRep::ANY; end
|
|
541
|
+
def name; "multiply"; end
|
|
542
|
+
end
|
|
543
|
+
def step(ctx, a)
|
|
544
|
+
ctx[:buffer] ||= 1
|
|
545
|
+
ctx[:buffer] *= a.to_i
|
|
546
|
+
end
|
|
547
|
+
def finalize(ctx); ctx.result = ctx[:buffer]; end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def test_aggregate_initialized_twice
|
|
551
|
+
initialized = 0
|
|
552
|
+
handler = Class.new(AggregateHandler) do
|
|
553
|
+
define_method(:initialize) do
|
|
554
|
+
initialized += 1
|
|
555
|
+
super()
|
|
541
556
|
end
|
|
542
|
-
def finalize(ctx); ctx.result = ctx[:buffer]; end
|
|
543
557
|
end
|
|
544
558
|
|
|
545
|
-
@db.create_aggregate_handler
|
|
559
|
+
@db.create_aggregate_handler handler
|
|
560
|
+
@db.get_first_value( "select multiply(a) from foo" )
|
|
561
|
+
@db.get_first_value( "select multiply(a) from foo" )
|
|
562
|
+
assert_equal 2, initialized
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def test_create_aggregate_handler
|
|
566
|
+
@db.create_aggregate_handler AggregateHandler
|
|
546
567
|
value = @db.get_first_value( "select multiply(a) from foo" )
|
|
547
568
|
assert_equal 6, value
|
|
548
569
|
end
|
data/test/test_sqlite3.rb
CHANGED
|
@@ -5,5 +5,17 @@ module SQLite3
|
|
|
5
5
|
def test_libversion
|
|
6
6
|
assert_not_nil SQLite3.libversion
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
def test_threadsafe
|
|
10
|
+
assert_not_nil SQLite3.threadsafe
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_threadsafe?
|
|
14
|
+
if SQLite3.threadsafe > 0
|
|
15
|
+
assert SQLite3.threadsafe?
|
|
16
|
+
else
|
|
17
|
+
refute SQLite3.threadsafe?
|
|
18
|
+
end
|
|
19
|
+
end
|
|
8
20
|
end
|
|
9
21
|
end
|
data/test/test_statement.rb
CHANGED
|
@@ -24,6 +24,19 @@ module SQLite3
|
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def test_insert_duplicate_records
|
|
28
|
+
@db.execute 'CREATE TABLE "things" ("name" varchar(20) CONSTRAINT "index_things_on_name" UNIQUE)'
|
|
29
|
+
stmt = @db.prepare("INSERT INTO things(name) VALUES(?)")
|
|
30
|
+
stmt.execute('ruby')
|
|
31
|
+
|
|
32
|
+
exception = assert_raises(SQLite3::ConstraintException) { stmt.execute('ruby') }
|
|
33
|
+
# SQLite 3.8.2 returns new error message:
|
|
34
|
+
# UNIQUE constraint failed: *table_name*.*column_name*
|
|
35
|
+
# Older versions of SQLite return:
|
|
36
|
+
# column *column_name* is not unique
|
|
37
|
+
assert_match(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/, exception.message)
|
|
38
|
+
end
|
|
39
|
+
|
|
27
40
|
###
|
|
28
41
|
# This method may not exist depending on how sqlite3 was compiled
|
|
29
42
|
def test_database_name
|
|
@@ -227,9 +240,7 @@ module SQLite3
|
|
|
227
240
|
stmt.execute('employee-1')
|
|
228
241
|
stmt.execute('employee-1') rescue SQLite3::ConstraintException
|
|
229
242
|
stmt.reset!
|
|
230
|
-
|
|
231
|
-
stmt.execute('employee-2')
|
|
232
|
-
}
|
|
243
|
+
assert stmt.execute('employee-2')
|
|
233
244
|
end
|
|
234
245
|
|
|
235
246
|
def test_clear_bindings
|