sqlite3-ffi 0.1.0
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.md +3 -0
- data/LICENSE.txt +24 -0
- data/README.md +55 -0
- data/lib/sqlite3/constants.rb +198 -0
- data/lib/sqlite3/database.rb +797 -0
- data/lib/sqlite3/errors.rb +88 -0
- data/lib/sqlite3/ffi/aggregator.rb +89 -0
- data/lib/sqlite3/ffi/backup.rb +59 -0
- data/lib/sqlite3/ffi/c_api.rb +214 -0
- data/lib/sqlite3/ffi/core_ext.rb +14 -0
- data/lib/sqlite3/ffi/database.rb +261 -0
- data/lib/sqlite3/ffi/exception.rb +111 -0
- data/lib/sqlite3/ffi/functions.rb +79 -0
- data/lib/sqlite3/ffi/sqlite3.rb +62 -0
- data/lib/sqlite3/ffi/statement.rb +242 -0
- data/lib/sqlite3/ffi/utils.rb +83 -0
- data/lib/sqlite3/ffi/version.rb +5 -0
- data/lib/sqlite3/ffi.rb +1 -0
- data/lib/sqlite3/fork_safety.rb +66 -0
- data/lib/sqlite3/pragmas.rb +599 -0
- data/lib/sqlite3/resultset.rb +96 -0
- data/lib/sqlite3/sqlite3_native.rb +14 -0
- data/lib/sqlite3/statement.rb +190 -0
- data/lib/sqlite3/value.rb +54 -0
- data/lib/sqlite3/version.rb +4 -0
- data/lib/sqlite3/version_info.rb +17 -0
- data/lib/sqlite3.rb +19 -0
- metadata +87 -0
@@ -0,0 +1,242 @@
|
|
1
|
+
module SQLite3
|
2
|
+
class Statement
|
3
|
+
def prepare(db, sql)
|
4
|
+
sql = FFI.string_value(sql)
|
5
|
+
|
6
|
+
@db = db # so can check if discarded
|
7
|
+
db = db.instance_variable_get(:@db)
|
8
|
+
stmt = ::FFI::MemoryPointer.new(:pointer)
|
9
|
+
tail = ::FFI::MemoryPointer.new(:pointer)
|
10
|
+
status = FFI::CApi.sqlite3_prepare_v2(db, sql, sql.bytesize, stmt, tail)
|
11
|
+
FFI.check_prepare(db, status, sql)
|
12
|
+
|
13
|
+
@stmt = stmt.read_pointer
|
14
|
+
@db.instance_variable_set(:@stmt_deadline, nil)
|
15
|
+
|
16
|
+
tail.read_pointer.read_string.force_encoding(Encoding::UTF_8)
|
17
|
+
end
|
18
|
+
|
19
|
+
def close
|
20
|
+
require_open_stmt
|
21
|
+
|
22
|
+
FFI::CApi.sqlite3_finalize(@stmt)
|
23
|
+
@stmt = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def closed?
|
27
|
+
@stmt.nil? || @stmt.null?
|
28
|
+
end
|
29
|
+
|
30
|
+
def step
|
31
|
+
require_live_db
|
32
|
+
require_open_stmt
|
33
|
+
|
34
|
+
return nil if @done
|
35
|
+
|
36
|
+
stmt = @stmt
|
37
|
+
|
38
|
+
value = FFI::CApi.sqlite3_step(stmt)
|
39
|
+
if FFI.rb_errinfo
|
40
|
+
exception = FFI.rb_errinfo
|
41
|
+
FFI.rb_errinfo = nil
|
42
|
+
raise exception
|
43
|
+
end
|
44
|
+
|
45
|
+
length = FFI::CApi.sqlite3_column_count(stmt)
|
46
|
+
list = []
|
47
|
+
|
48
|
+
case value
|
49
|
+
when FFI::CApi::SQLITE_ROW
|
50
|
+
length.times do |i|
|
51
|
+
case FFI::CApi.sqlite3_column_type(stmt, i)
|
52
|
+
when FFI::CApi::SQLITE_INTEGER
|
53
|
+
val = FFI::CApi.sqlite3_column_int64(stmt, i)
|
54
|
+
when FFI::CApi::SQLITE_FLOAT
|
55
|
+
val = FFI::CApi.sqlite3_column_double(stmt, i)
|
56
|
+
when FFI::CApi::SQLITE_TEXT
|
57
|
+
len = FFI::CApi.sqlite3_column_bytes(stmt, i)
|
58
|
+
val = FFI::CApi.sqlite3_column_text(stmt, i).read_bytes(len).force_encoding(Encoding::UTF_8)
|
59
|
+
if Encoding.default_internal
|
60
|
+
val = val.encode(Encoding.default_internal)
|
61
|
+
end
|
62
|
+
val.freeze
|
63
|
+
when FFI::CApi::SQLITE_BLOB
|
64
|
+
len = FFI::CApi.sqlite3_column_bytes(stmt, i)
|
65
|
+
val = FFI::CApi.sqlite3_column_blob(stmt, i).read_bytes(len)
|
66
|
+
val.freeze
|
67
|
+
when FFI::CApi::SQLITE_NULL
|
68
|
+
val = nil
|
69
|
+
else
|
70
|
+
raise RuntimeError, "bad type"
|
71
|
+
end
|
72
|
+
list << val
|
73
|
+
end
|
74
|
+
when FFI::CApi::SQLITE_DONE
|
75
|
+
@done = true
|
76
|
+
return nil
|
77
|
+
else
|
78
|
+
FFI::CApi.sqlite3_reset(stmt)
|
79
|
+
@done = false
|
80
|
+
FFI.check(FFI::CApi.sqlite3_db_handle(stmt), value)
|
81
|
+
end
|
82
|
+
|
83
|
+
list.freeze
|
84
|
+
list
|
85
|
+
end
|
86
|
+
|
87
|
+
def bind_param(key, value)
|
88
|
+
require_live_db
|
89
|
+
require_open_stmt
|
90
|
+
|
91
|
+
case key
|
92
|
+
when Symbol, String
|
93
|
+
key = key.to_s
|
94
|
+
key = ":" + key if key[0] != ":"
|
95
|
+
index = FFI::CApi.sqlite3_bind_parameter_index(@stmt, key)
|
96
|
+
else
|
97
|
+
index = key.to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
if index == 0
|
101
|
+
raise SQLite3::Exception, "no such bind parameter"
|
102
|
+
end
|
103
|
+
|
104
|
+
case value
|
105
|
+
when String
|
106
|
+
if value.is_a?(Blob) || value.encoding == Encoding::BINARY
|
107
|
+
status = FFI::CApi.sqlite3_bind_blob(@stmt, index, value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
|
108
|
+
elsif value.encoding == Encoding::UTF_16LE || value.encoding == Encoding::UTF_16BE
|
109
|
+
status = FFI::CApi.sqlite3_bind_text16(@stmt, index, value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
|
110
|
+
else
|
111
|
+
if value.encoding != Encoding::UTF_8 && value.encoding != Encoding::US_ASCII
|
112
|
+
value = value.encode(Encoding::UTF_8)
|
113
|
+
end
|
114
|
+
status = FFI::CApi.sqlite3_bind_text(@stmt, index, value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
|
115
|
+
end
|
116
|
+
when Float
|
117
|
+
status = FFI::CApi.sqlite3_bind_double(@stmt, index, value)
|
118
|
+
when Integer
|
119
|
+
status = FFI::CApi.sqlite3_bind_int64(@stmt, index, value)
|
120
|
+
when NilClass
|
121
|
+
status = FFI::CApi.sqlite3_bind_null(@stmt, index)
|
122
|
+
else
|
123
|
+
raise RuntimeError, "can't prepare #{value.class.name}"
|
124
|
+
end
|
125
|
+
|
126
|
+
FFI.check(FFI::CApi.sqlite3_db_handle(@stmt), status)
|
127
|
+
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
def reset!
|
132
|
+
require_live_db
|
133
|
+
require_open_stmt
|
134
|
+
|
135
|
+
FFI::CApi.sqlite3_reset(@stmt)
|
136
|
+
@done = false
|
137
|
+
self
|
138
|
+
end
|
139
|
+
|
140
|
+
def clear_bindings!
|
141
|
+
require_live_db
|
142
|
+
require_open_stmt
|
143
|
+
|
144
|
+
FFI::CApi.sqlite3_clear_bindings(@stmt)
|
145
|
+
@done = false
|
146
|
+
end
|
147
|
+
|
148
|
+
def done?
|
149
|
+
@done
|
150
|
+
end
|
151
|
+
|
152
|
+
def column_count
|
153
|
+
require_live_db
|
154
|
+
require_open_stmt
|
155
|
+
|
156
|
+
FFI::CApi.sqlite3_column_count(@stmt)
|
157
|
+
end
|
158
|
+
|
159
|
+
def column_name(index)
|
160
|
+
require_live_db
|
161
|
+
require_open_stmt
|
162
|
+
|
163
|
+
name = FFI::CApi.sqlite3_column_name(@stmt, index)
|
164
|
+
name ? FFI.interned_utf8_cstr(name) : nil
|
165
|
+
end
|
166
|
+
|
167
|
+
def column_decltype(index)
|
168
|
+
require_live_db
|
169
|
+
require_open_stmt
|
170
|
+
|
171
|
+
FFI::CApi.sqlite3_column_decltype(@stmt, index)
|
172
|
+
end
|
173
|
+
|
174
|
+
def bind_parameter_count
|
175
|
+
require_live_db
|
176
|
+
require_open_stmt
|
177
|
+
|
178
|
+
FFI::CApi.sqlite3_bind_parameter_count(@stmt)
|
179
|
+
end
|
180
|
+
|
181
|
+
STMT_STAT_SYMBOLS = {
|
182
|
+
fullscan_steps: FFI::CApi::SQLITE_STMTSTATUS_FULLSCAN_STEP,
|
183
|
+
sorts: FFI::CApi::SQLITE_STMTSTATUS_SORT,
|
184
|
+
autoindexes: FFI::CApi::SQLITE_STMTSTATUS_AUTOINDEX,
|
185
|
+
vm_steps: FFI::CApi::SQLITE_STMTSTATUS_VM_STEP
|
186
|
+
}
|
187
|
+
private_constant :STMT_STAT_SYMBOLS
|
188
|
+
|
189
|
+
def stats_as_hash
|
190
|
+
require_live_db
|
191
|
+
require_open_stmt
|
192
|
+
|
193
|
+
STMT_STAT_SYMBOLS.to_h do |k, stat_type|
|
194
|
+
[k, FFI::CApi.sqlite3_stmt_status(@stmt, stat_type, 0)]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def stat_for(key)
|
199
|
+
require_live_db
|
200
|
+
require_open_stmt
|
201
|
+
|
202
|
+
if !key.is_a?(Symbol)
|
203
|
+
raise TypeError, "non-symbol given"
|
204
|
+
end
|
205
|
+
|
206
|
+
stat_type = STMT_STAT_SYMBOLS[key]
|
207
|
+
if !stat_type
|
208
|
+
raise ArgumentError, "unknown key: #{key}"
|
209
|
+
end
|
210
|
+
|
211
|
+
FFI::CApi.sqlite3_stmt_status(@stmt, stat_type, 0)
|
212
|
+
end
|
213
|
+
|
214
|
+
def sql
|
215
|
+
require_live_db
|
216
|
+
require_open_stmt
|
217
|
+
|
218
|
+
FFI::CApi.sqlite3_sql(@stmt).force_encoding(Encoding::UTF_8).freeze
|
219
|
+
end
|
220
|
+
|
221
|
+
def expanded_sql
|
222
|
+
require_live_db
|
223
|
+
require_open_stmt
|
224
|
+
|
225
|
+
FFI::CApi.sqlite3_expanded_sql(@stmt).force_encoding(Encoding::UTF_8).freeze
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def require_open_stmt
|
231
|
+
if closed?
|
232
|
+
raise SQLite3::Exception, "cannot use a closed statement"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def require_live_db
|
237
|
+
if @db.instance_variable_get(:@discarded)
|
238
|
+
raise SQLite3::Exception, "cannot use a statement associated with a discarded database"
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module SQLite3
|
2
|
+
module FFI
|
3
|
+
def self.sqlite3val2rb(val)
|
4
|
+
case CApi.sqlite3_value_type(val)
|
5
|
+
when CApi::SQLITE_INTEGER
|
6
|
+
CApi.sqlite3_value_int64(val)
|
7
|
+
when CApi::SQLITE_FLOAT
|
8
|
+
CApi.sqlite3_value_double(val)
|
9
|
+
when CApi::SQLITE_TEXT
|
10
|
+
len = CApi.sqlite3_value_bytes(val)
|
11
|
+
CApi.sqlite3_value_text(val).read_bytes(len).force_encoding(Encoding::UTF_8).freeze
|
12
|
+
when CApi::SQLITE_BLOB
|
13
|
+
len = CApi.sqlite3_value_bytes(val)
|
14
|
+
CApi.sqlite3_value_text(val).read_bytes(len).freeze
|
15
|
+
when CApi::SQLITE_NULL
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
raise RuntimeError, "bad type"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.set_sqlite3_func_result(ctx, result)
|
23
|
+
case result
|
24
|
+
when NilClass
|
25
|
+
CApi.sqlite3_result_null(ctx)
|
26
|
+
when Integer
|
27
|
+
CApi.sqlite3_result_int64(ctx, result)
|
28
|
+
when Float
|
29
|
+
CApi.sqlite3_result_double(ctx, result)
|
30
|
+
when String
|
31
|
+
if result.is_a?(Blob) || result.encoding == Encoding::BINARY
|
32
|
+
CApi.sqlite3_result_blob(ctx, result, result.bytesize, CApi::SQLITE_TRANSIENT)
|
33
|
+
else
|
34
|
+
CApi.sqlite3_result_text(ctx, result, result.bytesize, CApi::SQLITE_TRANSIENT)
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise RuntimeError, "can't return #{result.class.name}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# TODO intern
|
42
|
+
def self.interned_utf8_cstr(str)
|
43
|
+
str.freeze
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.string_value(obj)
|
47
|
+
unless obj.respond_to?(:to_str)
|
48
|
+
val =
|
49
|
+
case obj
|
50
|
+
when nil
|
51
|
+
"nil"
|
52
|
+
when true, false
|
53
|
+
obj.to_s
|
54
|
+
else
|
55
|
+
obj.class.name
|
56
|
+
end
|
57
|
+
raise TypeError, "no implicit conversion of #{val} into String"
|
58
|
+
end
|
59
|
+
obj.to_str
|
60
|
+
end
|
61
|
+
|
62
|
+
RB_ERRINFO = :sqlite3_ffi_rb_errinfo
|
63
|
+
|
64
|
+
def self.rb_errinfo
|
65
|
+
Thread.current[RB_ERRINFO]
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.rb_errinfo=(e)
|
69
|
+
Thread.current[RB_ERRINFO] = e
|
70
|
+
end
|
71
|
+
|
72
|
+
OBJECT_REGISTRY = ObjectSpace::WeakMap.new
|
73
|
+
|
74
|
+
def self.wrap(obj)
|
75
|
+
OBJECT_REGISTRY[obj.object_id] = obj
|
76
|
+
::FFI::Pointer.new(obj.object_id)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.unwrap(ptr)
|
80
|
+
OBJECT_REGISTRY[ptr.to_i] || (raise "object not found")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/sqlite3/ffi.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "../sqlite3"
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "weakref"
|
4
|
+
|
5
|
+
module SQLite3
|
6
|
+
# based on Rails's active_support/fork_tracker.rb
|
7
|
+
module ForkSafety
|
8
|
+
module CoreExt # :nodoc:
|
9
|
+
def _fork
|
10
|
+
pid = super
|
11
|
+
if pid == 0
|
12
|
+
ForkSafety.discard
|
13
|
+
end
|
14
|
+
pid
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
@databases = []
|
19
|
+
@mutex = Mutex.new
|
20
|
+
@suppress = false
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def hook! # :nodoc:
|
24
|
+
::Process.singleton_class.prepend(CoreExt)
|
25
|
+
end
|
26
|
+
|
27
|
+
def track(database) # :nodoc:
|
28
|
+
@mutex.synchronize do
|
29
|
+
@databases << WeakRef.new(database)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def discard # :nodoc:
|
34
|
+
warned = @suppress
|
35
|
+
@databases.each do |db|
|
36
|
+
next unless db.weakref_alive?
|
37
|
+
|
38
|
+
begin
|
39
|
+
unless db.closed? || db.readonly?
|
40
|
+
unless warned
|
41
|
+
# If you are here, you may want to read
|
42
|
+
# https://github.com/sparklemotion/sqlite3-ruby/pull/558
|
43
|
+
warn("Writable sqlite database connection(s) were inherited from a forked process. " \
|
44
|
+
"This is unsafe and the connections are being closed to prevent possible data " \
|
45
|
+
"corruption. Please close writable sqlite database connections before forking.",
|
46
|
+
uplevel: 0)
|
47
|
+
warned = true
|
48
|
+
end
|
49
|
+
db.close
|
50
|
+
end
|
51
|
+
rescue WeakRef::RefError
|
52
|
+
# GC may run while this method is executing, and that's OK
|
53
|
+
end
|
54
|
+
end
|
55
|
+
@databases.clear
|
56
|
+
end
|
57
|
+
|
58
|
+
# Call to suppress the fork-related warnings.
|
59
|
+
def suppress_warnings!
|
60
|
+
@suppress = true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
SQLite3::ForkSafety.hook!
|