sqlite3-ffi 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 402ba8b0d0f49fd0f6229dba55575c8c5c22e460a2c2883095782f7f0f370ae6
4
- data.tar.gz: 22c9aad92904a410a4a697aa8dddcb84519e4669cb8976c57ba6d1f2c04c37d4
3
+ metadata.gz: 88fbf37c663c6a41c1f491c411813c2dec850f22a00bb04fb970876c0b00cf94
4
+ data.tar.gz: 04327ad75659e1ca926446b21f72ecc974aec0dea1fe4c1176adab29e1d0c63e
5
5
  SHA512:
6
- metadata.gz: 9ef8d936ee00247132f2d0c632491bffc4802915b539267ffc8131c13dfcee6cb85eb568d68d439894eed3a511813cb9e7f19baf82cbeff9fd7d896bdc4b8845
7
- data.tar.gz: 0a454150d3d90edbf20cd59f3eb69c75d0825bb406efb290aded8b4376e32ea14e45e6317252134c691fddbb1e065a00f9ae8981d852861131ad1a4c80e0bf16
6
+ metadata.gz: '0584a32cb0eff8e49cd76ffaa48756951f83cbaa897360289ab2c92e7a96adcb8ae3bda2b2558145a3fb788f3358e2e4834fed00134a2635e8a49b89c6ecaf81'
7
+ data.tar.gz: 0d9fcea2a566d141dc167b1ba760dbbc313e0a9ff9865cb761d1e9e803c5119807ce2055d5976618a818a9561ff8c97bfa7d792ccec9ed49df679f8db7c0a94b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.2.1 (2026-10-27)
2
+
3
+ - Synced with sqlite3-ruby 2.10.2
4
+
1
5
  ## 0.2.0 (2026-10-13)
2
6
 
3
7
  - Synced with sqlite3-ruby 2.10.0
@@ -579,6 +579,25 @@ module SQLite3
579
579
  self
580
580
  end
581
581
 
582
+ # The "handler" that #define_aggregator hands to define_aggregator2, which
583
+ # only needs something that responds to +new+ (a fresh aggregator for each
584
+ # aggregate invocation) and +arity+.
585
+ class AggregatorFactory # :nodoc:
586
+ def initialize(template)
587
+ @template = template
588
+ end
589
+
590
+ def new
591
+ @template.clone
592
+ end
593
+
594
+ # this is what sqlite3_obj_method_arity did before
595
+ def arity
596
+ @template.method(:step).arity
597
+ end
598
+ end
599
+ private_constant :AggregatorFactory
600
+
582
601
  # Define an aggregate function named +name+ using a object template
583
602
  # object +aggregator+. +aggregator+ must respond to +step+ and +finalize+.
584
603
  # +step+ will be called with row information and +finalize+ must return the
@@ -592,36 +611,7 @@ module SQLite3
592
611
  def define_aggregator(name, aggregator)
593
612
  # Previously, this has been implemented in C. Now this is just yet
594
613
  # another compatibility shim
595
- proxy = Class.new do
596
- @template = aggregator
597
- @name = name
598
-
599
- def self.template
600
- @template
601
- end
602
-
603
- def self.name
604
- @name
605
- end
606
-
607
- def self.arity
608
- # this is what sqlite3_obj_method_arity did before
609
- @template.method(:step).arity
610
- end
611
-
612
- def initialize
613
- @klass = self.class.template.clone
614
- end
615
-
616
- def step(*args)
617
- @klass.step(*args)
618
- end
619
-
620
- def finalize
621
- @klass.finalize
622
- end
623
- end
624
- define_aggregator2(proxy, name)
614
+ define_aggregator2(AggregatorFactory.new(aggregator), name)
625
615
  self
626
616
  end
627
617
 
@@ -1,17 +1,8 @@
1
1
  module SQLite3
2
2
  module FFI
3
- class AggregatorWrapper
4
- attr_reader :handler_klass, :instances
3
+ AggregatorWrapper = Struct.new(:handler_klass, :instances)
5
4
 
6
- def initialize(handler_klass)
7
- @handler_klass = handler_klass
8
- @instances = []
9
- end
10
- end
11
-
12
- class AggregatorInstance
13
- attr_accessor :handler_instance
14
- end
5
+ AggregatorInstance = Struct.new(:handler_instance)
15
6
 
16
7
  def self.aggregate_instance(ctx)
17
8
  aw = FFI.unwrap(CApi.sqlite3_user_data(ctx))
@@ -64,26 +55,22 @@ module SQLite3
64
55
  end
65
56
 
66
57
  AGGREGATOR_STEP = ::FFI::Function.new(:void, [:pointer, :int, :pointer]) do |ctx, argc, argv|
67
- begin
58
+ FFI.protect_callback do
68
59
  inst = aggregate_instance(ctx)
69
60
  handler_instance = inst.handler_instance
70
61
  params = argv.read_array_of_pointer(argc).map { |v| FFI.sqlite3val2rb(v) }
71
62
  handler_instance.step(*params)
72
- rescue => e
73
- FFI.rb_errinfo = e
74
63
  end
75
64
  end
76
65
 
77
66
  AGGREGATOR_FINAL = ::FFI::Function.new(:void, [:pointer]) do |ctx|
78
- begin
67
+ FFI.protect_callback do
79
68
  inst = aggregate_instance(ctx)
80
69
  handler_instance = inst.handler_instance
81
70
  result = handler_instance.finalize
82
71
  FFI.set_sqlite3_func_result(ctx, result)
83
- aggregate_instance_destroy(ctx)
84
- rescue => e
85
- FFI.rb_errinfo = e
86
72
  end
73
+ aggregate_instance_destroy(ctx)
87
74
  end
88
75
  end
89
76
  end
@@ -8,12 +8,12 @@ module SQLite3
8
8
  raise ArgumentError, "cannot backup to a closed database"
9
9
  end
10
10
 
11
- ddb = dstdb.instance_variable_get(:@db)
11
+ ddb = dstdb.instance_variable_get(:@ctx).db
12
12
  @p =
13
13
  FFI::CApi.sqlite3_backup_init(
14
14
  ddb,
15
15
  FFI.string_value(dstname),
16
- srcdb.instance_variable_get(:@db),
16
+ srcdb.instance_variable_get(:@ctx).db,
17
17
  FFI.string_value(srcname)
18
18
  )
19
19
 
@@ -24,8 +24,15 @@ module SQLite3
24
24
 
25
25
  def step(n_page)
26
26
  require_open_backup
27
+ pages = n_page.to_i
27
28
 
28
- FFI::CApi.sqlite3_backup_step(@p, n_page)
29
+ frame = FFI::CallbackFrame.new
30
+ FFI.push_callback_frame(frame)
31
+ status = FFI::CApi.sqlite3_backup_step(@p, pages)
32
+ if (callback_state = FFI.pop_callback_frame(frame))
33
+ raise callback_state
34
+ end
35
+ status
29
36
  end
30
37
 
31
38
  def finish
@@ -6,21 +6,21 @@ module SQLite3
6
6
  end
7
7
 
8
8
  def closed?
9
- @db.nil?
9
+ @ctx&.db.nil?
10
10
  end
11
11
 
12
12
  def total_changes
13
13
  require_open_db
14
14
 
15
- FFI::CApi.sqlite3_total_changes(@db)
15
+ FFI::CApi.sqlite3_total_changes(@ctx.db)
16
16
  end
17
17
 
18
18
  def trace(blk = nil, &block)
19
19
  require_open_db
20
20
 
21
21
  blk ||= block
22
- @tracefunc = blk
23
- FFI::CApi.sqlite3_trace_v2(@db, FFI::CApi::SQLITE_TRACE_STMT, blk.nil? ? nil : FFI::TRACE, FFI.wrap(blk))
22
+ @ctx.trace_handler = blk
23
+ FFI::CApi.sqlite3_trace_v2(@ctx.db, FFI::CApi::SQLITE_TRACE_STMT, blk.nil? ? nil : FFI::TRACE, FFI.wrap(@ctx))
24
24
  self
25
25
  end
26
26
 
@@ -28,31 +28,32 @@ module SQLite3
28
28
  require_open_db
29
29
 
30
30
  blk ||= block
31
- @busy_handler = blk
32
- status = FFI::CApi.sqlite3_busy_handler(@db, FFI::BUSY_HANDLER, FFI.wrap(self))
33
- FFI.check(@db, status)
31
+ @ctx.busy_handler = blk
32
+ status = FFI::CApi.sqlite3_busy_handler(@ctx.db, FFI::BUSY_HANDLER, FFI.wrap(@ctx))
33
+ FFI.check(@ctx.db, status)
34
34
  self
35
35
  end
36
36
 
37
37
  def statement_timeout=(milliseconds)
38
- @stmt_timeout = milliseconds.to_i
38
+ @ctx.stmt_timeout = milliseconds.to_i
39
39
  n = milliseconds.to_i == 0 ? -1 : 1000
40
- FFI::CApi.sqlite3_progress_handler(@db, n, FFI::STATEMENT_TIMEOUT, FFI.wrap(self))
40
+ FFI::CApi.sqlite3_progress_handler(@ctx.db, n, FFI::STATEMENT_TIMEOUT, FFI.wrap(@ctx))
41
41
  self
42
42
  end
43
43
 
44
44
  def last_insert_row_id
45
45
  require_open_db
46
46
 
47
- FFI::CApi.sqlite3_last_insert_rowid(@db)
47
+ FFI::CApi.sqlite3_last_insert_rowid(@ctx.db)
48
48
  end
49
49
 
50
50
  def define_function_with_flags(name, flags, &block)
51
51
  require_open_db
52
52
 
53
- status = FFI::CApi.sqlite3_create_function(@db, FFI.string_value(name), block.arity, flags, FFI.wrap(block), FFI::FUNC, nil, nil)
54
- FFI.check(@db, status)
53
+ status = FFI::CApi.sqlite3_create_function(@ctx.db, FFI.string_value(name), block.arity, flags, FFI.wrap(block), FFI::FUNC, nil, nil)
54
+ FFI.check(@ctx.db, status)
55
55
  @functions << block
56
+ @ctx.functions = @functions
56
57
  self
57
58
  end
58
59
 
@@ -63,20 +64,20 @@ module SQLite3
63
64
  def interrupt
64
65
  require_open_db
65
66
 
66
- FFI::CApi.sqlite3_interrupt(@db)
67
+ FFI::CApi.sqlite3_interrupt(@ctx.db)
67
68
  self
68
69
  end
69
70
 
70
71
  def errmsg
71
72
  require_open_db
72
73
 
73
- FFI::CApi.sqlite3_errmsg(@db)
74
+ FFI::CApi.sqlite3_errmsg(@ctx.db)
74
75
  end
75
76
 
76
77
  def errcode
77
78
  require_open_db
78
79
 
79
- FFI::CApi.sqlite3_errcode(@db)
80
+ FFI::CApi.sqlite3_errcode(@ctx.db)
80
81
  end
81
82
 
82
83
  def complete?(sql)
@@ -86,35 +87,52 @@ module SQLite3
86
87
  def changes
87
88
  require_open_db
88
89
 
89
- FFI::CApi.sqlite3_changes(@db)
90
+ FFI::CApi.sqlite3_changes(@ctx.db)
90
91
  end
91
92
 
92
93
  def authorizer=(authorizer)
93
94
  require_open_db
94
95
 
95
- status = FFI::CApi.sqlite3_set_authorizer(@db, authorizer.nil? ? nil : FFI::AUTH, FFI.wrap(authorizer))
96
- FFI.check(@db, status)
97
- @authorizer = authorizer
96
+ status = FFI::CApi.sqlite3_set_authorizer(@ctx.db, authorizer.nil? ? nil : FFI::AUTH, FFI.wrap(@ctx))
97
+ FFI.check(@ctx.db, status)
98
+ @ctx.authorizer = authorizer
98
99
  end
99
100
 
100
101
  def busy_timeout=(timeout)
101
102
  require_open_db
102
103
 
103
- FFI.check(@db, FFI::CApi.sqlite3_busy_timeout(@db, timeout))
104
+ FFI.check(@ctx.db, FFI::CApi.sqlite3_busy_timeout(@ctx.db, timeout))
104
105
  end
105
106
 
106
107
  def extended_result_codes=(enable)
107
108
  require_open_db
108
109
 
109
- FFI::CApi.sqlite3_extended_result_codes(@db, enable ? 1 : 0)
110
+ FFI::CApi.sqlite3_extended_result_codes(@ctx.db, enable ? 1 : 0)
110
111
  self
111
112
  end
112
113
 
113
114
  def collation(name, comparator)
114
115
  require_open_db
115
116
 
116
- status = FFI::CApi.sqlite3_create_collation(@db, name, FFI::CApi::SQLITE_UTF8, FFI.wrap(comparator), comparator.nil? ? nil : FFI::COMPARATOR)
117
- FFI.check(@db, status)
117
+ name = FFI.string_value(name)
118
+ key = name.dup
119
+ key.each_byte.with_index do |v, i|
120
+ if v >= 65 && v <= 90 # 'A' and 'Z'
121
+ key.setbyte(i, v + 32) # 'a' - 'A'
122
+ end
123
+ end
124
+
125
+ @_collations ||= {}
126
+ registry = @_collations
127
+
128
+ status = FFI::CApi.sqlite3_create_collation(@ctx.db, name, FFI::CApi::SQLITE_UTF8, FFI.wrap(comparator), comparator.nil? ? nil : FFI::COMPARATOR)
129
+ FFI.check(@ctx.db, status)
130
+
131
+ registry[key] = comparator
132
+ comparators = []
133
+ comparators += registry.values
134
+ @ctx.collations = comparators
135
+
118
136
  @collations[name] = comparator
119
137
  self
120
138
  end
@@ -130,7 +148,7 @@ module SQLite3
130
148
  else
131
149
  onoffparam = onoff.to_i
132
150
  end
133
- FFI.check(@db, FFI::CApi.sqlite3_enable_load_extension(@db, onoffparam))
151
+ FFI.check(@ctx.db, FFI::CApi.sqlite3_enable_load_extension(@ctx.db, onoffparam))
134
152
  self
135
153
  end
136
154
  end
@@ -138,26 +156,33 @@ module SQLite3
138
156
  def transaction_active?
139
157
  require_open_db
140
158
 
141
- FFI::CApi.sqlite3_get_autocommit(@db).zero?
159
+ FFI::CApi.sqlite3_get_autocommit(@ctx.db).zero?
142
160
  end
143
161
 
144
162
  def exec_batch(sql, results_as_hash)
145
163
  require_open_db
146
164
 
147
- @stmt_deadline = nil
165
+ sql_cstr = FFI.string_value(sql)
166
+ @ctx.stmt_deadline = nil
148
167
 
149
168
  callback_ary = []
150
169
  err_msg = ::FFI::MemoryPointer.new(:pointer)
151
170
  callback = results_as_hash == true ? FFI::HASH_CALLBACK : FFI::REGULAR_CALLBACK
152
- status = FFI::CApi.sqlite3_exec(@db, FFI.string_value(sql), callback, FFI.wrap(callback_ary), err_msg)
153
- FFI.check_msg(@db, status, err_msg)
171
+ frame = FFI::CallbackFrame.new
172
+ FFI.push_callback_frame(frame)
173
+ status = FFI::CApi.sqlite3_exec(@ctx.db, sql_cstr, callback, FFI.wrap(callback_ary), err_msg)
174
+ if (state = FFI.pop_callback_frame(frame))
175
+ FFI::CApi.sqlite3_free(err_msg.read_pointer)
176
+ raise state
177
+ end
178
+ FFI.check_msg(@ctx.db, status, err_msg)
154
179
  callback_ary
155
180
  end
156
181
 
157
182
  def db_filename(db_name)
158
183
  require_open_db
159
184
 
160
- fname = FFI::CApi.sqlite3_db_filename(@db, db_name)
185
+ fname = FFI::CApi.sqlite3_db_filename(@ctx.db, db_name)
161
186
  fname.null? ? nil : fname.read_string.force_encoding(Encoding::UTF_8)
162
187
  end
163
188
 
@@ -168,7 +193,7 @@ module SQLite3
168
193
  z_schema = db_name.nil? ? nil : FFI.string_value_cstr(db_name)
169
194
 
170
195
  size = ::FFI::MemoryPointer.new(:int64)
171
- data = FFI::CApi.sqlite3_serialize(@db, z_schema, size, 0)
196
+ data = FFI::CApi.sqlite3_serialize(@ctx.db, z_schema, size, 0)
172
197
  size = size.read_int64
173
198
 
174
199
  if data.null? && size < 0
@@ -200,9 +225,9 @@ module SQLite3
200
225
  p_data.put_bytes(0, data)
201
226
  p_data.put_bytes(sz_db, "\x00"*(sz_buf - sz_db))
202
227
 
203
- status = FFI::CApi.sqlite3_deserialize(@db, z_schema, p_data, sz_db, sz_db, FFI::CApi::SQLITE_DESERIALIZE_FREEONCLOSE | FFI::CApi::SQLITE_DESERIALIZE_RESIZEABLE)
228
+ status = FFI::CApi.sqlite3_deserialize(@ctx.db, z_schema, p_data, sz_db, sz_db, FFI::CApi::SQLITE_DESERIALIZE_FREEONCLOSE | FFI::CApi::SQLITE_DESERIALIZE_RESIZEABLE)
204
229
 
205
- FFI.check(@db, status)
230
+ FFI.check(@ctx.db, status)
206
231
  self
207
232
  end
208
233
  end
@@ -210,13 +235,13 @@ module SQLite3
210
235
  private
211
236
 
212
237
  def require_open_db
213
- if @db.nil?
238
+ if @ctx&.db.nil?
214
239
  raise SQLite3::Exception, "cannot use a closed database"
215
240
  end
216
241
  end
217
242
 
218
243
  def require_closed_db
219
- if !@db.nil?
244
+ if !@ctx&.db.nil?
220
245
  raise SQLite3::Exception, "cannot open a database that is already open"
221
246
  end
222
247
  end
@@ -224,38 +249,38 @@ module SQLite3
224
249
  def discard_db
225
250
  sfile = ::FFI::MemoryPointer.new(:pointer)
226
251
 
227
- FFI::CApi.sqlite3_db_release_memory(@db)
252
+ FFI::CApi.sqlite3_db_release_memory(@ctx.db)
228
253
 
229
254
  if FFI::CApi::HAVE_SQLITE3_DB_NAME
230
255
  j_db = 0
231
- while !(db_name = FFI::CApi.sqlite3_db_name(@db, j_db)).null?
232
- status = FFI::CApi.sqlite3_file_control(@db, db_name, FFI::CApi::SQLITE_FCNTL_FILE_POINTER, sfile)
256
+ while !(db_name = FFI::CApi.sqlite3_db_name(@ctx.db, j_db)).null?
257
+ status = FFI::CApi.sqlite3_file_control(@ctx.db, db_name, FFI::CApi::SQLITE_FCNTL_FILE_POINTER, sfile)
233
258
  if status == 0
234
259
  # TODO
235
260
  end
236
261
  j_db += 1
237
262
  end
238
263
  else
239
- status = FFI::CApi.sqlite3_file_control(@db, nil, FFI::CApi::SQLITE_FCNTL_FILE_POINTER, sfile)
264
+ status = FFI::CApi.sqlite3_file_control(@ctx.db, nil, FFI::CApi::SQLITE_FCNTL_FILE_POINTER, sfile)
240
265
  if status == 0
241
266
  # TODO
242
267
  end
243
268
  end
244
269
 
245
- status = FFI::CApi.sqlite3_file_control(@db, nil, FFI::CApi::SQLITE_FCNTL_JOURNAL_POINTER, sfile)
270
+ status = FFI::CApi.sqlite3_file_control(@ctx.db, nil, FFI::CApi::SQLITE_FCNTL_JOURNAL_POINTER, sfile)
246
271
  if status == 0
247
272
  # TODO
248
273
  end
249
274
 
250
- @db = nil
251
- @discarded = true
275
+ @ctx.db = nil
276
+ @ctx.discarded = true
252
277
  end
253
278
 
254
279
  def close_or_discard_db
255
- unless @db.nil?
256
- if @readonly || @owner == Process.pid
257
- @db.free
258
- @db = nil
280
+ unless @ctx&.db.nil?
281
+ if @ctx.readonly || @ctx.owner == Process.pid
282
+ @ctx.db.free
283
+ @ctx.db = nil
259
284
  else
260
285
  discard_db
261
286
  end
@@ -282,23 +307,23 @@ module SQLite3
282
307
 
283
308
  def open_v2(file, flags, zvfs)
284
309
  require_closed_db
285
- @owner = Process.pid
310
+ @ctx = FFI::Context.new
311
+ @ctx.owner = Process.pid
286
312
  db = ::FFI::MemoryPointer.new(:pointer)
287
313
  status = FFI::CApi.sqlite3_open_v2(FFI.string_value_cstr(file), db, flags, zvfs.nil? ? nil : FFI.string_value_cstr(zvfs))
288
- @db = db.read_pointer
289
- @db = ::FFI::AutoPointer.new(db.read_pointer, FFI::CApi.method(:sqlite3_close_v2))
290
- FFI.check(@db, status)
314
+ @ctx.db = ::FFI::AutoPointer.new(db.read_pointer, FFI::CApi.method(:sqlite3_close_v2))
315
+ FFI.check(@ctx.db, status)
291
316
  if (flags & FFI::CApi::SQLITE_OPEN_READONLY) != 0
292
- @readonly = true
317
+ @ctx.readonly = true
293
318
  end
294
319
  self
295
320
  end
296
321
 
297
322
  def disable_quirk_mode
298
- return false if @db.nil?
323
+ return false if @ctx&.db.nil?
299
324
 
300
- FFI::CApi.sqlite3_db_config(@db, FFI::CApi::SQLITE_DBCONFIG_DQS_DDL, :int, 0, :pointer, nil)
301
- FFI::CApi.sqlite3_db_config(@db, FFI::CApi::SQLITE_DBCONFIG_DQS_DML, :int, 0, :pointer, nil)
325
+ FFI::CApi.sqlite3_db_config(@ctx.db, FFI::CApi::SQLITE_DBCONFIG_DQS_DDL, :int, 0, :pointer, nil)
326
+ FFI::CApi.sqlite3_db_config(@ctx.db, FFI::CApi::SQLITE_DBCONFIG_DQS_DML, :int, 0, :pointer, nil)
302
327
  true
303
328
  end
304
329
 
@@ -312,26 +337,27 @@ module SQLite3
312
337
  require_open_db
313
338
 
314
339
  err_msg = ::FFI::MemoryPointer.new(:pointer)
315
- status = FFI::CApi.sqlite3_load_extension(@db, FFI.string_value(file), nil, err_msg)
316
- FFI.check_msg(@db, status, err_msg)
340
+ status = FFI::CApi.sqlite3_load_extension(@ctx.db, FFI.string_value(file), nil, err_msg)
341
+ FFI.check_msg(@ctx.db, status, err_msg)
317
342
  self
318
343
  end
319
344
  end
320
345
 
321
346
  def open16(file)
322
347
  require_closed_db
323
- @owner = Process.pid
348
+ @ctx = FFI::Context.new
349
+ @ctx.owner = Process.pid
324
350
  db = ::FFI::MemoryPointer.new(:pointer)
325
351
  status = FFI::CApi.sqlite3_open16(utf16_string_value_ptr(file), db)
326
- @db = ::FFI::AutoPointer.new(db.read_pointer, FFI::CApi.method(:sqlite3_close_v2))
352
+ @ctx.db = ::FFI::AutoPointer.new(db.read_pointer, FFI::CApi.method(:sqlite3_close_v2))
327
353
  if status != FFI::CApi::SQLITE_OK
328
354
  # https://github.com/ffi/ffi/issues/1121
329
- # msg = FFI::CApi.sqlite3_mprintf("%s", :string, FFI::CApi.sqlite3_errmsg(@db))
355
+ # msg = FFI::CApi.sqlite3_mprintf("%s", :string, FFI::CApi.sqlite3_errmsg(@ctx.db))
330
356
  # msg = ::FFI::MemoryPointer.new(:pointer).write_pointer(msg)
331
- msg = FFI::CApi.sqlite3_errmsg(@db)
332
- @db.free
333
- @db = nil
334
- FFI.check_msg(@db, status, msg)
357
+ msg = FFI::CApi.sqlite3_errmsg(@ctx.db)
358
+ @ctx.db.free
359
+ @ctx.db = nil
360
+ FFI.check_msg(@ctx.db, status, msg)
335
361
  end
336
362
  status
337
363
  end
@@ -349,12 +375,18 @@ module SQLite3
349
375
  raise ArgumentError, "Aggregator arity=#{arity} out of range -1..127"
350
376
  end
351
377
 
352
- aw = FFI::AggregatorWrapper.new(aggregator)
353
- 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)
354
- FFI.check(@db, status)
378
+ @_aggregators ||= []
379
+ aggregators = @_aggregators
380
+
381
+ aw = FFI::AggregatorWrapper.new
382
+ aw.handler_klass = aggregator
383
+ aw.instances = []
384
+
385
+ status = FFI::CApi.sqlite3_create_function(@ctx.db, FFI.string_value(ruby_name), arity, FFI::CApi::SQLITE_UTF8, FFI.wrap(aw), nil, FFI::AGGREGATOR_STEP, FFI::AGGREGATOR_FINAL)
386
+ FFI.check(@ctx.db, status)
355
387
 
356
- @aggregators ||= []
357
- @aggregators << aw
388
+ aggregators << aw
389
+ @ctx.aggregators = aggregators
358
390
 
359
391
  self
360
392
  end
@@ -107,5 +107,64 @@ module SQLite3
107
107
 
108
108
  raise exception
109
109
  end
110
+
111
+ CallbackFrame = Struct.new(:state, :prev, :holder)
112
+
113
+ CallbackFrames = Struct.new(:top)
114
+
115
+ def self.current_callback_frames_holder
116
+ holder = Thread.current[:__sqlite3_callback_frames__]
117
+ holder.is_a?(CallbackFrames) ? holder : nil
118
+ end
119
+
120
+ def self.current_callback_frames
121
+ holder = current_callback_frames_holder
122
+ holder.nil? ? nil : holder
123
+ end
124
+
125
+ def self.push_callback_frame(frame)
126
+ holder = current_callback_frames_holder
127
+
128
+ if holder.nil?
129
+ holder = CallbackFrames.new
130
+ Thread.current[:__sqlite3_callback_frames__] = holder
131
+ end
132
+ frames = holder
133
+
134
+ frame.state = nil
135
+ frame.prev = frames.top
136
+ frame.holder = holder
137
+ frames.top = frame
138
+ end
139
+
140
+ def self.pop_callback_frame(frame)
141
+ frame.holder.top = frame.prev
142
+ frame.state
143
+ end
144
+
145
+ def self.protect_callback
146
+ frames = current_callback_frames
147
+ frame = frames ? frames.top : nil
148
+ result = nil
149
+ state = nil
150
+
151
+ if frame && frame.state
152
+ failed = true
153
+ return [nil, failed]
154
+ end
155
+
156
+ begin
157
+ result = yield
158
+ rescue => e
159
+ state = e
160
+ end
161
+ failed = !state.nil?
162
+
163
+ if frame
164
+ frame.state = state
165
+ end
166
+
167
+ [result, failed]
168
+ end
110
169
  end
111
170
  end
@@ -1,63 +1,85 @@
1
1
  module SQLite3
2
2
  module FFI
3
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)
4
+ result, failed =
5
+ protect_callback do
6
+ comparator = unwrap(ctx)
7
+ a_str = a.read_bytes(a_len).force_encoding(Encoding::UTF_8)
8
+ b_str = b.read_bytes(b_len).force_encoding(Encoding::UTF_8)
7
9
 
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
10
+ if Encoding.default_internal
11
+ a_str = a_str.encode(Encoding.default_internal)
12
+ b_str = b_str.encode(Encoding.default_internal)
13
+ end
14
+
15
+ comparator.compare(a_str, b_str)
16
+ end
12
17
 
13
- comparator.compare(a_str, b_str)
18
+ failed ? 0 : result
14
19
  end
15
20
 
16
21
  TRACE = ::FFI::Function.new(:int, [:uint, :pointer, :pointer, :pointer]) do |_, ctx, _, x|
17
- unwrap(ctx).call(x.read_string)
22
+ protect_callback do
23
+ unwrap(ctx).trace_handler.call(x.read_string)
24
+ end
18
25
  0
19
26
  end
20
27
 
21
28
  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
29
+ result, failed =
30
+ protect_callback do
31
+ result = unwrap(ctx).authorizer.call(op_id, s1, s2, s3, s4)
32
+ if result.is_a?(Integer)
33
+ result
34
+ elsif result == true
35
+ CApi::SQLITE_OK
36
+ elsif result == false
37
+ CApi::SQLITE_DENY
38
+ else
39
+ CApi::SQLITE_IGNORE
40
+ end
41
+ end
42
+
43
+ failed ? CApi::SQLITE_DENY : result
32
44
  end
33
45
 
34
46
  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
47
+ _, failed =
48
+ protect_callback do
49
+ callback_ary = unwrap(ctx)
50
+ new_hash = {}
51
+ data.read_array_of_pointer(count).zip(columns.read_array_of_pointer(count)) do |value, column|
52
+ new_hash[column.read_string] = value.null? ? nil : value.read_string
53
+ end
54
+ callback_ary << new_hash
55
+ nil
56
+ end
57
+
58
+ failed ? 1 : 0
42
59
  end
43
60
 
44
61
  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
62
+ _, failed =
63
+ protect_callback do
64
+ callback_ary = unwrap(ctx)
65
+ new_ary = []
66
+ data.read_array_of_pointer(count).each do |value|
67
+ new_ary << (value.null? ? nil : value.read_string)
68
+ end
69
+ callback_ary << new_ary
70
+ nil
71
+ end
72
+
73
+ failed ? 1 : 0
52
74
  end
53
75
 
54
76
  STATEMENT_TIMEOUT = ::FFI::Function.new(:int, [:pointer]) do |ctx|
55
77
  ctx = unwrap(ctx)
56
78
  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 + ctx.instance_variable_get(:@stmt_timeout).to_i / 1000.0)
79
+ if ctx.stmt_deadline.nil?
80
+ ctx.stmt_deadline = current_time + ctx.stmt_timeout.to_i / 1000.0
59
81
  0
60
- elsif current_time >= ctx.instance_variable_get(:@stmt_deadline)
82
+ elsif current_time >= ctx.stmt_deadline
61
83
  1
62
84
  else
63
85
  0
@@ -65,16 +87,27 @@ module SQLite3
65
87
  end
66
88
 
67
89
  BUSY_HANDLER = ::FFI::Function.new(:int, [:pointer, :int]) do |ctx, count|
68
- handler = unwrap(ctx).instance_variable_get(:@busy_handler)
69
- result = handler.(count)
70
- result == false ? 0 : 1
90
+ result, failed =
91
+ protect_callback do
92
+ handler = unwrap(ctx).busy_handler
93
+ handler.(count)
94
+ end
95
+
96
+ if failed || result == false
97
+ 0
98
+ else
99
+ 1
100
+ end
71
101
  end
72
102
 
73
103
  FUNC = ::FFI::Function.new(:void, [:pointer, :int, :pointer]) do |ctx, argc, argv|
74
- callable = unwrap(CApi.sqlite3_user_data(ctx))
75
- params = argv.read_array_of_pointer(argc).map { |v| FFI.sqlite3val2rb(v) }
76
- result = callable.(*params)
77
- FFI.set_sqlite3_func_result(ctx, result)
104
+ protect_callback do
105
+ callable = unwrap(CApi.sqlite3_user_data(ctx))
106
+ params = argv.read_array_of_pointer(argc).map { |v| FFI.sqlite3val2rb(v) }
107
+ result = callable.(*params)
108
+ FFI.set_sqlite3_func_result(ctx, result)
109
+ nil
110
+ end
78
111
  end
79
112
  end
80
113
  end
@@ -1,17 +1,25 @@
1
1
  module SQLite3
2
2
  class Statement
3
3
  def prepare(db, sql)
4
- sql = FFI.string_value(sql)
4
+ @db_ctx = db.instance_variable_get(:@ctx)
5
+ @db_ctx.stmt_deadline = nil
5
6
 
6
- @db = db # so can check if discarded
7
- @db.instance_variable_set(:@stmt_deadline, nil)
8
- db = db.instance_variable_get(:@db)
7
+ sql_cstr = FFI.string_value(sql)
8
+ sql_len = FFI.rstring_lenint(sql)
9
+
10
+ db = @db_ctx.db
9
11
  stmt = ::FFI::MemoryPointer.new(:pointer)
10
12
  tail = ::FFI::MemoryPointer.new(:pointer)
11
- status = FFI::CApi.sqlite3_prepare_v2(db, sql, sql.bytesize, stmt, tail)
12
- FFI.check_prepare(db, status, sql)
13
-
13
+ frame = FFI::CallbackFrame.new
14
+ FFI.push_callback_frame(frame)
15
+ status = FFI::CApi.sqlite3_prepare_v2(db, sql_cstr, sql_len, stmt, tail)
16
+ # sqlite3_finalize is no-op on NULL pointer
14
17
  @stmt = ::FFI::AutoPointer.new(stmt.read_pointer, FFI::CApi.method(:sqlite3_finalize))
18
+ if (callback_state = FFI.pop_callback_frame(frame))
19
+ raise callback_state
20
+ end
21
+
22
+ FFI.check_prepare(db, status, sql)
15
23
 
16
24
  tail.read_pointer.read_string.force_encoding(Encoding::UTF_8)
17
25
  end
@@ -36,14 +44,19 @@ module SQLite3
36
44
  stmt = @stmt
37
45
 
38
46
  if FFI::CApi.sqlite3_stmt_busy(stmt) == 0
39
- @db.instance_variable_set(:@stmt_deadline, nil)
47
+ @db_ctx.stmt_deadline = nil
40
48
  end
41
49
 
50
+ frame = FFI::CallbackFrame.new
51
+ FFI.push_callback_frame(frame)
42
52
  value = FFI::CApi.sqlite3_step(stmt)
43
- if FFI.rb_errinfo
44
- exception = FFI.rb_errinfo
45
- FFI.rb_errinfo = nil
46
- raise exception
53
+ if (callback_state = FFI.pop_callback_frame(frame))
54
+ raise callback_state
55
+ end
56
+
57
+ if value == FFI::CApi::SQLITE_DONE
58
+ @done = true
59
+ return nil
47
60
  end
48
61
 
49
62
  length = FFI::CApi.sqlite3_column_count(stmt)
@@ -75,9 +88,6 @@ module SQLite3
75
88
  end
76
89
  list << val
77
90
  end
78
- when FFI::CApi::SQLITE_DONE
79
- @done = true
80
- return nil
81
91
  else
82
92
  FFI::CApi.sqlite3_reset(stmt)
83
93
  @done = false
@@ -108,14 +118,14 @@ module SQLite3
108
118
  case value
109
119
  when String
110
120
  if value.is_a?(Blob) || value.encoding == Encoding::BINARY
111
- status = FFI::CApi.sqlite3_bind_blob(@stmt, index, value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
121
+ status = FFI::CApi.sqlite3_bind_blob(@stmt, index, value, FFI.rstring_lenint(value), FFI::CApi::SQLITE_TRANSIENT)
112
122
  elsif value.encoding == Encoding::UTF_16LE || value.encoding == Encoding::UTF_16BE
113
- status = FFI::CApi.sqlite3_bind_text16(@stmt, index, value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
123
+ status = FFI::CApi.sqlite3_bind_text16(@stmt, index, value, FFI.rstring_lenint(value), FFI::CApi::SQLITE_TRANSIENT)
114
124
  else
115
125
  if value.encoding != Encoding::UTF_8 && value.encoding != Encoding::US_ASCII
116
126
  value = value.encode(Encoding::UTF_8)
117
127
  end
118
- status = FFI::CApi.sqlite3_bind_text(@stmt, index, +value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
128
+ status = FFI::CApi.sqlite3_bind_text(@stmt, index, +value, FFI.rstring_lenint(value), FFI::CApi::SQLITE_TRANSIENT)
119
129
  end
120
130
  when Float
121
131
  status = FFI::CApi.sqlite3_bind_double(@stmt, index, value)
@@ -268,7 +278,7 @@ module SQLite3
268
278
  end
269
279
 
270
280
  def require_live_db
271
- if @db.instance_variable_get(:@discarded)
281
+ if @db_ctx.discarded
272
282
  raise SQLite3::Exception, "cannot use a statement associated with a discarded database"
273
283
  end
274
284
  end
@@ -1,5 +1,21 @@
1
1
  module SQLite3
2
2
  module FFI
3
+ Context =
4
+ Struct.new(
5
+ :db,
6
+ :busy_handler,
7
+ :functions,
8
+ :collations,
9
+ :aggregators,
10
+ :trace_handler,
11
+ :authorizer,
12
+ :stmt_timeout,
13
+ :stmt_deadline,
14
+ :owner,
15
+ :readonly,
16
+ :discarded
17
+ )
18
+
3
19
  def self.sqlite3val2rb(val)
4
20
  case CApi.sqlite3_value_type(val)
5
21
  when CApi::SQLITE_INTEGER
@@ -29,9 +45,9 @@ module SQLite3
29
45
  CApi.sqlite3_result_double(ctx, result)
30
46
  when String
31
47
  if result.is_a?(Blob) || result.encoding == Encoding::BINARY
32
- CApi.sqlite3_result_blob(ctx, result, result.bytesize, CApi::SQLITE_TRANSIENT)
48
+ CApi.sqlite3_result_blob(ctx, result, rstring_lenint(result), CApi::SQLITE_TRANSIENT)
33
49
  else
34
- CApi.sqlite3_result_text(ctx, result, result.bytesize, CApi::SQLITE_TRANSIENT)
50
+ CApi.sqlite3_result_text(ctx, result, rstring_lenint(result), CApi::SQLITE_TRANSIENT)
35
51
  end
36
52
  else
37
53
  raise RuntimeError, "can't return #{result.class.name}"
@@ -70,14 +86,14 @@ module SQLite3
70
86
  obj
71
87
  end
72
88
 
73
- RB_ERRINFO = :sqlite3_ffi_rb_errinfo
74
-
75
- def self.rb_errinfo
76
- Thread.current[RB_ERRINFO]
77
- end
89
+ INT_MAX = 2**31 - 1
78
90
 
79
- def self.rb_errinfo=(e)
80
- Thread.current[RB_ERRINFO] = e
91
+ def self.rstring_lenint(obj)
92
+ n = obj.bytesize
93
+ if n > INT_MAX
94
+ raise RangeError, "integer %p too big to convert to int" % [n]
95
+ end
96
+ n
81
97
  end
82
98
 
83
99
  OBJECT_REGISTRY = ObjectSpace::WeakMap.new
@@ -1,5 +1,5 @@
1
1
  module SQLite3
2
2
  module FFI
3
- VERSION = "0.2.0"
3
+ VERSION = "0.2.1"
4
4
  end
5
5
  end
@@ -16,8 +16,12 @@ module SQLite3
16
16
  @stmt = stmt
17
17
  end
18
18
 
19
- # Reset the cursor, so that a result set which has reached end-of-file
20
- # can be rewound and reiterated.
19
+ # Resets the cursor to the first row, so that you can iterate over the
20
+ # result set again after it reaches the end.
21
+ #
22
+ # This method binds each argument to the statement with
23
+ # Statement#bind_params. The values that you bound before stay bound
24
+ # unless you bind new values to the same parameters.
21
25
  def reset(*bind_params)
22
26
  @stmt.reset!
23
27
  @stmt.bind_params(*bind_params)
@@ -65,6 +65,10 @@ module SQLite3
65
65
  # stmt = db.prepare( "select * from table where a=? and b=?" )
66
66
  # stmt.bind_params( 15, "hello" )
67
67
  #
68
+ # The values that this method binds stay bound when you execute or reset
69
+ # the statement. To change a value, bind a new value to the same
70
+ # parameter. To clear all values, call #clear_bindings!.
71
+ #
68
72
  # See also #execute, #bind_param, Statement#bind_param, and
69
73
  # Statement#bind_params.
70
74
  def bind_params(*bind_vars)
@@ -83,7 +87,17 @@ module SQLite3
83
87
  # statement's virtual machine. If a block was given, the new ResultSet will
84
88
  # be yielded to it; otherwise, the ResultSet will be returned.
85
89
  #
86
- # Any parameters will be bound to the statement using #bind_params.
90
+ # This method binds each argument to the statement with #bind_params. The
91
+ # values stay bound after this method returns. If a later call to #execute
92
+ # does not give a value for a parameter, the statement uses the value from
93
+ # the earlier call. To set all parameters to NULL before you execute the
94
+ # statement again, call #clear_bindings!:
95
+ #
96
+ # stmt = db.prepare( "select :a, :b" )
97
+ # stmt.execute( a: 1, b: 2 ).to_a # => [[1, 2]]
98
+ # stmt.execute( a: 3 ).to_a # => [[3, 2]]
99
+ # stmt.clear_bindings!
100
+ # stmt.execute( a: 3 ).to_a # => [[3, nil]]
87
101
  #
88
102
  # Example:
89
103
  #
@@ -92,7 +106,7 @@ module SQLite3
92
106
  # ...
93
107
  # end
94
108
  #
95
- # See also #bind_params, #execute!.
109
+ # See also #bind_params, #execute!, #clear_bindings!.
96
110
  def execute(*bind_vars)
97
111
  reset! if active? || done?
98
112
 
@@ -109,7 +123,9 @@ module SQLite3
109
123
  # rows returned by executing the statement. Otherwise, each row will be
110
124
  # yielded to the block.
111
125
  #
112
- # Any parameters will be bound to the statement using #bind_params.
126
+ # This method binds each argument to the statement with #bind_params. The
127
+ # values stay bound after this method returns. For more information, see
128
+ # #execute.
113
129
  #
114
130
  # Example:
115
131
  #
@@ -1,4 +1,4 @@
1
1
  module SQLite3
2
2
  # (String) the version of the sqlite3 gem, e.g. "2.1.1"
3
- VERSION = "2.10.0"
3
+ VERSION = "2.10.2"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlite3-ffi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamis Buck