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.
@@ -0,0 +1,261 @@
1
+ module SQLite3
2
+ class Database
3
+ def close
4
+ close_or_discard_db
5
+ @aggregators = nil
6
+ self
7
+ end
8
+
9
+ def closed?
10
+ @db.nil?
11
+ end
12
+
13
+ def total_changes
14
+ require_open_db
15
+
16
+ FFI::CApi.sqlite3_total_changes(@db)
17
+ end
18
+
19
+ def trace(blk = nil, &block)
20
+ require_open_db
21
+
22
+ blk ||= block
23
+ @tracefunc = blk
24
+ FFI::CApi.sqlite3_trace_v2(@db, FFI::CApi::SQLITE_TRACE_STMT, blk.nil? ? nil : FFI::TRACE, FFI.wrap(blk))
25
+ self
26
+ end
27
+
28
+ def busy_handler(blk = nil, &block)
29
+ require_open_db
30
+
31
+ blk ||= block
32
+ @busy_handler = blk
33
+ status = FFI::CApi.sqlite3_busy_handler(@db, FFI::BUSY_HANDLER, FFI.wrap(self))
34
+ FFI.check(@db, status)
35
+ self
36
+ end
37
+
38
+ def statement_timeout=(milliseconds)
39
+ @stmt_timeout = milliseconds.to_i
40
+ n = milliseconds.to_i == 0 ? -1 : 1000
41
+ FFI::CApi.sqlite3_progress_handler(@db, n, FFI::STATEMENT_TIMEOUT, FFI.wrap(self))
42
+ self
43
+ end
44
+
45
+ def last_insert_row_id
46
+ require_open_db
47
+
48
+ FFI::CApi.sqlite3_last_insert_rowid(@db)
49
+ end
50
+
51
+ def define_function_with_flags(name, flags, &block)
52
+ require_open_db
53
+
54
+ status = FFI::CApi.sqlite3_create_function(@db, FFI.string_value(name), block.arity, flags, FFI.wrap(block), FFI::FUNC, nil, nil)
55
+ FFI.check(@db, status)
56
+ @functions[name] = block
57
+ self
58
+ end
59
+
60
+ def define_function(name, &block)
61
+ define_function_with_flags(name, FFI::CApi::SQLITE_UTF8, &block)
62
+ end
63
+
64
+ def interrupt
65
+ require_open_db
66
+
67
+ FFI::CApi.sqlite3_interrupt(@db)
68
+ self
69
+ end
70
+
71
+ def errmsg
72
+ require_open_db
73
+
74
+ FFI::CApi.sqlite3_errmsg(@db)
75
+ end
76
+
77
+ def errcode
78
+ require_open_db
79
+
80
+ FFI::CApi.sqlite3_errcode(@db)
81
+ end
82
+
83
+ def complete?(sql)
84
+ FFI::CApi.sqlite3_complete(FFI.string_value(sql)) != 0
85
+ end
86
+
87
+ def changes
88
+ require_open_db
89
+
90
+ FFI::CApi.sqlite3_changes(@db)
91
+ end
92
+
93
+ def authorizer=(authorizer)
94
+ require_open_db
95
+
96
+ status = FFI::CApi.sqlite3_set_authorizer(@db, authorizer.nil? ? nil : FFI::AUTH, FFI.wrap(authorizer))
97
+ FFI.check(@db, status)
98
+ @authorizer = authorizer
99
+ end
100
+
101
+ def busy_timeout=(timeout)
102
+ require_open_db
103
+
104
+ FFI.check(@db, FFI::CApi.sqlite3_busy_timeout(@db, timeout))
105
+ end
106
+
107
+ def extended_result_codes=(enable)
108
+ require_open_db
109
+
110
+ FFI::CApi.sqlite3_extended_result_codes(@db, enable ? 1 : 0)
111
+ self
112
+ end
113
+
114
+ def collation(name, comparator)
115
+ require_open_db
116
+
117
+ status = FFI::CApi.sqlite3_create_collation(@db, name, FFI::CApi::SQLITE_UTF8, FFI.wrap(comparator), comparator.nil? ? nil : FFI::COMPARATOR)
118
+ FFI.check(@db, status)
119
+ @collations[name] = comparator
120
+ self
121
+ end
122
+
123
+ if FFI::CApi::HAVE_SQLITE3_ENABLE_LOAD_EXTENSION && FFI::CApi::HAVE_SQLITE3_LOAD_EXTENSION
124
+ def enable_load_extension(onoff)
125
+ require_open_db
126
+
127
+ if onoff == true
128
+ onoffparam = 1
129
+ elsif onoff == false
130
+ onoffparam = 0
131
+ else
132
+ onoffparam = onoff.to_i
133
+ end
134
+ FFI.check(@db, FFI::CApi.sqlite3_enable_load_extension(@db, onoffparam))
135
+ self
136
+ end
137
+ end
138
+
139
+ def transaction_active?
140
+ require_open_db
141
+
142
+ FFI::CApi.sqlite3_get_autocommit(@db).zero?
143
+ end
144
+
145
+ def exec_batch(sql, results_as_hash)
146
+ require_open_db
147
+
148
+ callback_ary = []
149
+ err_msg = ::FFI::MemoryPointer.new(:pointer)
150
+ callback = results_as_hash == true ? FFI::HASH_CALLBACK : FFI::REGULAR_CALLBACK
151
+ status = FFI::CApi.sqlite3_exec(@db, FFI.string_value(sql), callback, FFI.wrap(callback_ary), err_msg)
152
+ FFI.check_msg(@db, status, err_msg)
153
+ callback_ary
154
+ end
155
+
156
+ def db_filename(db_name)
157
+ require_open_db
158
+
159
+ fname = FFI::CApi.sqlite3_db_filename(@db, db_name)
160
+ fname.null? ? nil : fname.read_string.force_encoding(Encoding::UTF_8)
161
+ end
162
+
163
+ private
164
+
165
+ def require_open_db
166
+ if @db.nil?
167
+ raise SQLite3::Exception, "cannot use a closed database"
168
+ end
169
+ end
170
+
171
+ def discard_db(db)
172
+ FFI::CApi.sqlite3_db_release_memory(db)
173
+ # TODO sqlite3_file_control
174
+ @db = nil
175
+ @discarded = true
176
+ end
177
+
178
+ def close_or_discard_db
179
+ unless @db.nil?
180
+ if @readonly || @owner == Process.pid
181
+ FFI::CApi.sqlite3_close_v2(@db)
182
+ @db = nil
183
+ else
184
+ discard_db(@db)
185
+ end
186
+ end
187
+ end
188
+
189
+ def utf16_string_value_ptr(str)
190
+ str + "\x00\x00".encode(Encoding::UTF_16LE)
191
+ end
192
+
193
+ def open_v2(file, flags, zvfs)
194
+ db = ::FFI::MemoryPointer.new(:pointer)
195
+ status = FFI::CApi.sqlite3_open_v2(FFI.string_value(file), db, flags, zvfs)
196
+ @db = db.read_pointer
197
+ FFI.check(@db, status)
198
+ if (flags & FFI::CApi::SQLITE_OPEN_READONLY) != 0
199
+ @readonly = true
200
+ end
201
+ @owner = Process.pid
202
+ self
203
+ end
204
+
205
+ def disable_quirk_mode
206
+ return false if @db.nil?
207
+
208
+ FFI::CApi.sqlite3_db_config(@db, FFI::CApi::SQLITE_DBCONFIG_DQS_DDL, :int, 0, :pointer, nil)
209
+ FFI::CApi.sqlite3_db_config(@db, FFI::CApi::SQLITE_DBCONFIG_DQS_DML, :int, 0, :pointer, nil)
210
+ true
211
+ end
212
+
213
+ def discard
214
+ discard_db(@db)
215
+ @aggregators = nil
216
+ self
217
+ end
218
+
219
+ if FFI::CApi::HAVE_SQLITE3_LOAD_EXTENSION
220
+ def load_extension_internal(file)
221
+ require_open_db
222
+
223
+ err_msg = FFI::MemoryPointer.new(:pointer)
224
+ status = FFI::CApi.sqlite3_load_extension(@db, FFI.string_value(file), 0, err_msg)
225
+ FFI.check_msg(@db, status, err_msg)
226
+ self
227
+ end
228
+ end
229
+
230
+ def open16(file)
231
+ db = ::FFI::MemoryPointer.new(:pointer)
232
+ status = FFI::CApi.sqlite3_open16(utf16_string_value_ptr(file), db)
233
+ @db = db.read_pointer
234
+ FFI.check(@db, status)
235
+ status
236
+ end
237
+
238
+ def define_aggregator2(aggregator, ruby_name)
239
+ require_open_db
240
+
241
+ if aggregator.respond_to?(:arity)
242
+ arity = aggregator.arity
243
+ else
244
+ arity = -1
245
+ end
246
+
247
+ if arity < -1 || arity > 127
248
+ raise ArgumentError, "Aggregator arity=#{arity} out of range -1..127"
249
+ end
250
+
251
+ aw = FFI::AggregatorWrapper.new(aggregator)
252
+ status = FFI::CApi.sqlite3_create_function(@db, FFI.string_value(ruby_name), arity, FFI::CApi::SQLITE_UTF8, FFI.wrap(aw), nil, FFI::AGGREGATOR_STEP, FFI::AGGREGATOR_FINAL)
253
+ FFI.check(@db, status)
254
+
255
+ @aggregators ||= []
256
+ @aggregators << aw
257
+
258
+ self
259
+ end
260
+ end
261
+ end
@@ -0,0 +1,111 @@
1
+ module SQLite3
2
+ module FFI
3
+ def self.check(db, status)
4
+ raise_(db, status)
5
+ end
6
+
7
+ def self.check_msg(db, status, msg)
8
+ raise_msg(db, status, msg)
9
+ end
10
+
11
+ def self.check_prepare(db, status, sql)
12
+ raise_with_sql(db, status, sql)
13
+ end
14
+
15
+ def self.status2klass(status)
16
+ case status & 0xff
17
+ when 0
18
+ nil
19
+ when CApi::SQLITE_ERROR
20
+ SQLite3::SQLException
21
+ when CApi::SQLITE_INTERNAL
22
+ SQLite3::InternalException
23
+ when CApi::SQLITE_PERM
24
+ SQLite3::PermissionException
25
+ when CApi::SQLITE_ABORT
26
+ SQLite3::AbortException
27
+ when CApi::SQLITE_BUSY
28
+ SQLite3::BusyException
29
+ when CApi::SQLITE_LOCKED
30
+ SQLite3::LockedException
31
+ when CApi::SQLITE_NOMEM
32
+ SQLite3::MemoryException
33
+ when CApi::SQLITE_READONLY
34
+ SQLite3::ReadOnlyException
35
+ when CApi::SQLITE_INTERRUPT
36
+ SQLite3::InterruptException
37
+ when CApi::SQLITE_IOERR
38
+ SQLite3::IOException
39
+ when CApi::SQLITE_CORRUPT
40
+ SQLite3::CorruptException
41
+ when CApi::SQLITE_NOTFOUND
42
+ SQLite3::NotFoundException
43
+ when CApi::SQLITE_FULL
44
+ SQLite3::FullException
45
+ when CApi::SQLITE_CANTOPEN
46
+ SQLite3::CantOpenException
47
+ when CApi::SQLITE_PROTOCOL
48
+ SQLite3::ProtocolException
49
+ when CApi::SQLITE_EMPTY
50
+ SQLite3::EmptyException
51
+ when CApi::SQLITE_SCHEMA
52
+ SQLite3::SchemaChangedException
53
+ when CApi::SQLITE_TOOBIG
54
+ SQLite3::TooBigException
55
+ when CApi::SQLITE_CONSTRAINT
56
+ SQLite3::ConstraintException
57
+ when CApi::SQLITE_MISMATCH
58
+ SQLite3::MismatchException
59
+ when CApi::SQLITE_MISUSE
60
+ SQLite3::MisuseException
61
+ when CApi::SQLITE_NOLFS
62
+ SQLite3::UnsupportedException
63
+ when CApi::SQLITE_AUTH
64
+ SQLite3::AuthorizationException
65
+ when CApi::SQLITE_FORMAT
66
+ SQLite3::FormatException
67
+ when CApi::SQLITE_RANGE
68
+ SQLite3::RangeException
69
+ when CApi::SQLITE_NOTADB
70
+ SQLite3::NotADatabaseException
71
+ else
72
+ SQLite3::Exception
73
+ end
74
+ end
75
+
76
+ def self.raise_(db, status)
77
+ klass = status2klass(status)
78
+ return if klass.nil?
79
+
80
+ exception = klass.new(CApi.sqlite3_errmsg(db))
81
+ exception.instance_variable_set(:@code, status)
82
+
83
+ raise exception
84
+ end
85
+
86
+ def self.raise_msg(db, status, msg)
87
+ klass = status2klass(status)
88
+ return if klass.nil?
89
+
90
+ exception = klass.new(msg.read_pointer.read_string)
91
+ exception.instance_variable_set(:@code, status)
92
+ CApi.sqlite3_free(msg.read_pointer)
93
+
94
+ raise exception
95
+ end
96
+
97
+ def self.raise_with_sql(db, status, sql)
98
+ klass = status2klass(status)
99
+ return if klass.nil?
100
+
101
+ exception = klass.new(CApi.sqlite3_errmsg(db))
102
+ exception.instance_variable_set(:@code, status)
103
+ if sql
104
+ exception.instance_variable_set(:@sql, sql)
105
+ exception.instance_variable_set(:@sql_offset, CApi.sqlite3_error_offset(db))
106
+ end
107
+
108
+ raise exception
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,79 @@
1
+ module SQLite3
2
+ module FFI
3
+ COMPARATOR = ::FFI::Function.new(:int, [:pointer, :int, :pointer, :int, :pointer]) do |ctx, a_len, a, b_len, b|
4
+ comparator = unwrap(ctx)
5
+ a_str = a.read_bytes(a_len).force_encoding(Encoding::UTF_8)
6
+ b_str = b.read_bytes(b_len).force_encoding(Encoding::UTF_8)
7
+
8
+ if Encoding.default_internal
9
+ a_str = a_str.encode(Encoding.default_internal)
10
+ b_str = b_str.encode(Encoding.default_internal)
11
+ end
12
+
13
+ comparator.compare(a_str, b_str)
14
+ end
15
+
16
+ TRACE = ::FFI::Function.new(:int, [:uint, :pointer, :pointer, :pointer]) do |_, ctx, _, x|
17
+ unwrap(ctx).call(x.read_string)
18
+ 0
19
+ end
20
+
21
+ AUTH = ::FFI::Function.new(:int, [:pointer, :int, :string, :string, :string, :string]) do |ctx, op_id, s1, s2, s3, s4|
22
+ result = unwrap(ctx).call(op_id, s1, s2, s3, s4)
23
+ if result.is_a?(Integer)
24
+ result
25
+ elsif result == true
26
+ CApi::SQLITE_OK
27
+ elsif result == false
28
+ CApi::SQLITE_DENY
29
+ else
30
+ CApi::SQLITE_IGNORE
31
+ end
32
+ end
33
+
34
+ HASH_CALLBACK = ::FFI::Function.new(:int, [:pointer, :int, :pointer, :pointer]) do |ctx, count, data, columns|
35
+ callback_ary = unwrap(ctx)
36
+ new_hash = {}
37
+ data.read_array_of_pointer(count).zip(columns.read_array_of_pointer(count)) do |value, column|
38
+ new_hash[column.read_string] = value.null? ? nil : value.read_string
39
+ end
40
+ callback_ary << new_hash
41
+ 0
42
+ end
43
+
44
+ REGULAR_CALLBACK = ::FFI::Function.new(:int, [:pointer, :int, :pointer, :pointer]) do |ctx, count, data, columns|
45
+ callback_ary = unwrap(ctx)
46
+ new_ary = []
47
+ data.read_array_of_pointer(count).each do |value|
48
+ new_ary << (value.null? ? nil : value.read_string)
49
+ end
50
+ callback_ary << new_ary
51
+ 0
52
+ end
53
+
54
+ STATEMENT_TIMEOUT = ::FFI::Function.new(:int, [:pointer]) do |ctx|
55
+ ctx = unwrap(ctx)
56
+ current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
+ if ctx.instance_variable_get(:@stmt_deadline).nil?
58
+ ctx.instance_variable_set(:@stmt_deadline, current_time)
59
+ elsif current_time >= ctx.instance_variable_get(:@stmt_deadline)
60
+ 1
61
+ else
62
+ 0
63
+ end
64
+ end
65
+
66
+ BUSY_HANDLER = ::FFI::Function.new(:int, [:pointer, :int]) do |ctx, count|
67
+ handler = unwrap(ctx).instance_variable_get(:@busy_handler)
68
+ result = handler.(count)
69
+ result == false ? 0 : 1
70
+ end
71
+
72
+ FUNC = ::FFI::Function.new(:void, [:pointer, :int, :pointer]) do |ctx, argc, argv|
73
+ callable = unwrap(CApi.sqlite3_user_data(ctx))
74
+ params = argv.read_array_of_pointer(argc).map { |v| FFI.sqlite3val2rb(v) }
75
+ result = callable.(*params)
76
+ FFI.set_sqlite3_func_result(ctx, result)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,62 @@
1
+ module SQLite3
2
+ class Blob < String
3
+ end
4
+
5
+ module Constants
6
+ module Open
7
+ READONLY = FFI::CApi::SQLITE_OPEN_READONLY
8
+ READWRITE = FFI::CApi::SQLITE_OPEN_READWRITE
9
+ CREATE = FFI::CApi::SQLITE_OPEN_CREATE
10
+ DELETEONCLOSE = FFI::CApi::SQLITE_OPEN_DELETEONCLOSE
11
+ EXCLUSIVE = FFI::CApi::SQLITE_OPEN_EXCLUSIVE
12
+ MAIN_DB = FFI::CApi::SQLITE_OPEN_MAIN_DB
13
+ TEMP_DB = FFI::CApi::SQLITE_OPEN_TEMP_DB
14
+ TRANSIENT_DB = FFI::CApi::SQLITE_OPEN_TRANSIENT_DB
15
+ MAIN_JOURNAL = FFI::CApi::SQLITE_OPEN_MAIN_JOURNAL
16
+ TEMP_JOURNAL = FFI::CApi::SQLITE_OPEN_TEMP_JOURNAL
17
+ SUBJOURNAL = FFI::CApi::SQLITE_OPEN_SUBJOURNAL
18
+ MASTER_JOURNAL = FFI::CApi::SQLITE_OPEN_SUPER_JOURNAL
19
+ SUPER_JOURNAL = FFI::CApi::SQLITE_OPEN_SUPER_JOURNAL
20
+ NOMUTEX = FFI::CApi::SQLITE_OPEN_NOMUTEX
21
+ FULLMUTEX = FFI::CApi::SQLITE_OPEN_FULLMUTEX
22
+ AUTOPROXY = FFI::CApi::SQLITE_OPEN_AUTOPROXY
23
+ SHAREDCACHE = FFI::CApi::SQLITE_OPEN_SHAREDCACHE
24
+ PRIVATECACHE = FFI::CApi::SQLITE_OPEN_PRIVATECACHE
25
+ WAL = FFI::CApi::SQLITE_OPEN_WAL
26
+ URI = FFI::CApi::SQLITE_OPEN_URI
27
+ MEMORY = FFI::CApi::SQLITE_OPEN_MEMORY
28
+ end
29
+ end
30
+
31
+ def self.threadsafe
32
+ FFI::CApi.sqlite3_threadsafe
33
+ end
34
+
35
+ def self.sqlcipher?
36
+ false
37
+ end
38
+
39
+ def self.libversion
40
+ FFI::CApi.sqlite3_libversion
41
+ end
42
+
43
+ def self.status(parameter, reset_flag = false)
44
+ op = parameter.to_i
45
+ reset = reset_flag ? 1 : 0
46
+
47
+ p_current = ::FFI::MemoryPointer.new(:int64)
48
+ p_highwater = ::FFI::MemoryPointer.new(:int64)
49
+ FFI::CApi.sqlite3_status64(op, p_current, p_highwater, reset)
50
+
51
+ {
52
+ current: p_current.read_int64,
53
+ highwater: p_highwater.read_int64
54
+ }
55
+ end
56
+
57
+ SQLITE_VERSION = FFI::CApi.sqlite3_libversion
58
+ SQLITE_VERSION_NUMBER = FFI::CApi.sqlite3_libversion_number
59
+ SQLITE_LOADED_VERSION = FFI::CApi.sqlite3_libversion
60
+ SQLITE_PACKAGED_LIBRARIES = false
61
+ SQLITE_PRECOMPILED_LIBRARIES = false
62
+ end