sqlite3-ffi 0.1.7 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0099c7235d3e7b13c26861041a474d279952101488139c9b11c40053658bb095'
4
- data.tar.gz: e809f4772c73162b7417c79ae56674606c689e2a1fddb3181ac637d958066f04
3
+ metadata.gz: 402ba8b0d0f49fd0f6229dba55575c8c5c22e460a2c2883095782f7f0f370ae6
4
+ data.tar.gz: 22c9aad92904a410a4a697aa8dddcb84519e4669cb8976c57ba6d1f2c04c37d4
5
5
  SHA512:
6
- metadata.gz: 916507cb952494f227d6182cf92d9e910cf3534f01995706efff64523eb86750fe7601238b9e4d6c32813cff3a097a073b541bad0632dc74f87e2b35e589ccca
7
- data.tar.gz: 78586b3eb030fdcb58e4afbcace803efcf78c3e02d3939d73524186148f55cc6b7699c5f15b42df774f6007681ac22bbf82a434887874b1092b3a72ec66efadf
6
+ metadata.gz: 9ef8d936ee00247132f2d0c632491bffc4802915b539267ffc8131c13dfcee6cb85eb568d68d439894eed3a511813cb9e7f19baf82cbeff9fd7d896bdc4b8845
7
+ data.tar.gz: 0a454150d3d90edbf20cd59f3eb69c75d0825bb406efb290aded8b4376e32ea14e45e6317252134c691fddbb1e065a00f9ae8981d852861131ad1a4c80e0bf16
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 0.2.0 (2026-10-13)
2
+
3
+ - Synced with sqlite3-ruby 2.10.0
4
+ - Dropped support for Ruby < 3.2
5
+
6
+ ## 0.1.8 (2026-08-11)
7
+
8
+ - Synced with sqlite3-ruby 2.9.6
9
+
1
10
  ## 0.1.7 (2026-06-07)
2
11
 
3
12
  - Synced with sqlite3-ruby 2.9.5
@@ -171,8 +171,6 @@ module SQLite3
171
171
  end
172
172
  end
173
173
 
174
- @tracefunc = nil
175
- @authorizer = nil
176
174
  @progress_handler = nil
177
175
  @collations = {}
178
176
  @functions = []
@@ -458,9 +456,7 @@ module SQLite3
458
456
  #
459
457
  # See also #create_aggregate_handler for a more object-oriented approach to
460
458
  # aggregate functions.
461
- def create_aggregate(name, arity, step = nil, finalize = nil,
462
- text_rep = Constants::TextRep::ANY, &block)
463
-
459
+ def create_aggregate(name, arity, step = nil, finalize = nil, text_rep = Constants::TextRep::ANY, &block)
464
460
  proxy = Class.new do
465
461
  def self.step(&block)
466
462
  define_method(:step_with_ctx, &block)
@@ -90,6 +90,9 @@ module SQLite3
90
90
  SQLITE_DBCONFIG_ENABLE_COMMENTS = 1022
91
91
  SQLITE_DBCONFIG_MAX = 1022
92
92
 
93
+ SQLITE_DESERIALIZE_FREEONCLOSE = 1
94
+ SQLITE_DESERIALIZE_RESIZEABLE = 2
95
+
93
96
  SQLITE_OPEN_READONLY = 0x00000001
94
97
  SQLITE_OPEN_READWRITE = 0x00000002
95
98
  SQLITE_OPEN_CREATE = 0x00000004
@@ -145,7 +148,7 @@ module SQLite3
145
148
  attach_function :sqlite3_bind_parameter_count, [:pointer], :int
146
149
  attach_function :sqlite3_bind_parameter_index, [:pointer, :string], :int
147
150
  attach_function :sqlite3_bind_parameter_name, [:pointer, :int], :string
148
- attach_function :sqlite3_bind_text, [:pointer, :int, :string, :int, :pointer], :int
151
+ attach_function :sqlite3_bind_text, [:pointer, :int, :pointer, :int, :pointer], :int
149
152
  attach_function :sqlite3_bind_text16, [:pointer, :int, :pointer, :int, :pointer], :int
150
153
  attach_function :sqlite3_busy_handler, [:pointer, :pointer, :pointer], :int
151
154
  attach_function :sqlite3_busy_timeout, [:pointer, :int], :int
@@ -182,8 +185,10 @@ module SQLite3
182
185
  attach_function :sqlite3_last_insert_rowid, [:pointer], :int64
183
186
  attach_function :sqlite3_libversion, [], :string
184
187
  attach_function :sqlite3_libversion_number, [], :int
188
+ attach_function :sqlite3_malloc64, [:int64], :pointer
189
+ attach_function :sqlite3_mprintf, [:string, :varargs], :pointer
185
190
  attach_function :sqlite3_open16, [:pointer, :pointer], :int
186
- attach_function :sqlite3_open_v2, [:string, :pointer, :int, :pointer], :int
191
+ attach_function :sqlite3_open_v2, [:string, :pointer, :int, :string], :int
187
192
  attach_function :sqlite3_prepare_v2, [:pointer, :string, :int, :pointer, :pointer], :int
188
193
  attach_function :sqlite3_progress_handler, [:pointer, :int, :pointer, :pointer], :void
189
194
  attach_function :sqlite3_reset, [:pointer], :int
@@ -196,6 +201,7 @@ module SQLite3
196
201
  attach_function :sqlite3_sql, [:pointer], :string
197
202
  attach_function :sqlite3_status64, [:int, :pointer, :pointer, :int], :int
198
203
  attach_function :sqlite3_step, [:pointer], :int
204
+ attach_function :sqlite3_stmt_busy, [:pointer], :int
199
205
  attach_function :sqlite3_stmt_status, [:pointer, :int, :int], :int
200
206
  attach_function :sqlite3_threadsafe, [], :int
201
207
  attach_function :sqlite3_total_changes, [:pointer], :int
@@ -215,6 +221,13 @@ module SQLite3
215
221
  false
216
222
  end
217
223
 
224
+ HAVE_SQLITE3_DESERIALIZE = begin
225
+ attach_function :sqlite3_deserialize, [:pointer, :string, :pointer, :int64, :int64, :uint], :int
226
+ true
227
+ rescue ::FFI::NotFoundError
228
+ false
229
+ end
230
+
218
231
  HAVE_SQLITE3_ENABLE_LOAD_EXTENSION = begin
219
232
  attach_function :sqlite3_enable_load_extension, [:pointer, :int], :int
220
233
  true
@@ -228,6 +241,13 @@ module SQLite3
228
241
  rescue ::FFI::NotFoundError
229
242
  false
230
243
  end
244
+
245
+ HAVE_SQLITE3_SERIALIZE = begin
246
+ attach_function :sqlite3_serialize, [:pointer, :string, :pointer, :uint], :pointer
247
+ true
248
+ rescue ::FFI::NotFoundError
249
+ false
250
+ end
231
251
  end
232
252
  end
233
253
  end
@@ -144,6 +144,8 @@ module SQLite3
144
144
  def exec_batch(sql, results_as_hash)
145
145
  require_open_db
146
146
 
147
+ @stmt_deadline = nil
148
+
147
149
  callback_ary = []
148
150
  err_msg = ::FFI::MemoryPointer.new(:pointer)
149
151
  callback = results_as_hash == true ? FFI::HASH_CALLBACK : FFI::REGULAR_CALLBACK
@@ -159,6 +161,52 @@ module SQLite3
159
161
  fname.null? ? nil : fname.read_string.force_encoding(Encoding::UTF_8)
160
162
  end
161
163
 
164
+ if FFI::CApi::HAVE_SQLITE3_SERIALIZE
165
+ def serialize(db_name = nil)
166
+ require_open_db
167
+
168
+ z_schema = db_name.nil? ? nil : FFI.string_value_cstr(db_name)
169
+
170
+ size = ::FFI::MemoryPointer.new(:int64)
171
+ data = FFI::CApi.sqlite3_serialize(@db, z_schema, size, 0)
172
+ size = size.read_int64
173
+
174
+ if data.null? && size < 0
175
+ raise SQLite3::Exception, "unable to serialize database \"%s\"" % [z_schema || "main"]
176
+ end
177
+ if data.null? && size > 0
178
+ raise NoMemoryError, "failed to allocate %d bytes for serialization" % [size]
179
+ end
180
+
181
+ result = data.read_bytes(size)
182
+ FFI::CApi.sqlite3_free(data)
183
+ result
184
+ end
185
+ end
186
+
187
+ if FFI::CApi::HAVE_SQLITE3_DESERIALIZE && FFI::CApi.sqlite3_libversion_number >= 3054000
188
+ def deserialize(data, db_name = nil)
189
+ require_open_db
190
+
191
+ data = FFI.string_value(data)
192
+ z_schema = db_name.nil? ? nil : FFI.string_value_cstr(db_name)
193
+ sz_db = data.bytesize
194
+
195
+ sz_buf = sz_db + 20
196
+ p_data = FFI::CApi.sqlite3_malloc64(sz_buf)
197
+ if p_data.null?
198
+ raise NoMemoryError, "failed to allocate %d bytes for deserialization" % [sz_buf]
199
+ end
200
+ p_data.put_bytes(0, data)
201
+ p_data.put_bytes(sz_db, "\x00"*(sz_buf - sz_db))
202
+
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)
204
+
205
+ FFI.check(@db, status)
206
+ self
207
+ end
208
+ end
209
+
162
210
  private
163
211
 
164
212
  def require_open_db
@@ -167,6 +215,12 @@ module SQLite3
167
215
  end
168
216
  end
169
217
 
218
+ def require_closed_db
219
+ if !@db.nil?
220
+ raise SQLite3::Exception, "cannot open a database that is already open"
221
+ end
222
+ end
223
+
170
224
  def discard_db
171
225
  sfile = ::FFI::MemoryPointer.new(:pointer)
172
226
 
@@ -209,13 +263,28 @@ module SQLite3
209
263
  end
210
264
 
211
265
  def utf16_string_value_ptr(str)
212
- str + "\x00\x00".encode(Encoding::UTF_16LE)
266
+ str = FFI.string_value(str)
267
+
268
+ utf16str = str.dup
269
+ if utf16str.encoding != Encoding::UTF_16LE && utf16str.encoding != Encoding::UTF_16BE
270
+ utf16str.force_encoding(Encoding::UTF_16LE)
271
+ end
272
+ codepoints = utf16str.codepoints
273
+ if codepoints.include?(0)
274
+ raise ArgumentError, "string contains null char"
275
+ end
276
+
277
+ str = str.dup
278
+ str << 0
279
+ str << 0
280
+ str
213
281
  end
214
282
 
215
283
  def open_v2(file, flags, zvfs)
284
+ require_closed_db
216
285
  @owner = Process.pid
217
286
  db = ::FFI::MemoryPointer.new(:pointer)
218
- status = FFI::CApi.sqlite3_open_v2(FFI.string_value(file), db, flags, zvfs)
287
+ status = FFI::CApi.sqlite3_open_v2(FFI.string_value_cstr(file), db, flags, zvfs.nil? ? nil : FFI.string_value_cstr(zvfs))
219
288
  @db = db.read_pointer
220
289
  @db = ::FFI::AutoPointer.new(db.read_pointer, FFI::CApi.method(:sqlite3_close_v2))
221
290
  FFI.check(@db, status)
@@ -250,11 +319,20 @@ module SQLite3
250
319
  end
251
320
 
252
321
  def open16(file)
322
+ require_closed_db
253
323
  @owner = Process.pid
254
324
  db = ::FFI::MemoryPointer.new(:pointer)
255
325
  status = FFI::CApi.sqlite3_open16(utf16_string_value_ptr(file), db)
256
326
  @db = ::FFI::AutoPointer.new(db.read_pointer, FFI::CApi.method(:sqlite3_close_v2))
257
- FFI.check(@db, status)
327
+ if status != FFI::CApi::SQLITE_OK
328
+ # https://github.com/ffi/ffi/issues/1121
329
+ # msg = FFI::CApi.sqlite3_mprintf("%s", :string, FFI::CApi.sqlite3_errmsg(@db))
330
+ # 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)
335
+ end
258
336
  status
259
337
  end
260
338
 
@@ -87,9 +87,9 @@ module SQLite3
87
87
  klass = status2klass(status)
88
88
  return if klass.nil?
89
89
 
90
- exception = klass.new(msg.read_pointer.read_string)
90
+ exception = klass.new(msg.is_a?(String) ? msg : msg.read_pointer.read_string)
91
91
  exception.instance_variable_set(:@code, status)
92
- CApi.sqlite3_free(msg.read_pointer)
92
+ CApi.sqlite3_free(msg.read_pointer) unless msg.is_a?(String)
93
93
 
94
94
  raise exception
95
95
  end
@@ -55,7 +55,7 @@ module SQLite3
55
55
  ctx = unwrap(ctx)
56
56
  current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
57
57
  if ctx.instance_variable_get(:@stmt_deadline).nil?
58
- ctx.instance_variable_set(:@stmt_deadline, current_time)
58
+ ctx.instance_variable_set(:@stmt_deadline, current_time + ctx.instance_variable_get(:@stmt_timeout).to_i / 1000.0)
59
59
  0
60
60
  elsif current_time >= ctx.instance_variable_get(:@stmt_deadline)
61
61
  1
@@ -37,7 +37,7 @@ module SQLite3
37
37
  end
38
38
 
39
39
  def self.libversion
40
- FFI::CApi.sqlite3_libversion
40
+ FFI::CApi.sqlite3_libversion_number
41
41
  end
42
42
 
43
43
  def self.status(parameter, reset_flag = false)
@@ -4,6 +4,7 @@ module SQLite3
4
4
  sql = FFI.string_value(sql)
5
5
 
6
6
  @db = db # so can check if discarded
7
+ @db.instance_variable_set(:@stmt_deadline, nil)
7
8
  db = db.instance_variable_get(:@db)
8
9
  stmt = ::FFI::MemoryPointer.new(:pointer)
9
10
  tail = ::FFI::MemoryPointer.new(:pointer)
@@ -11,7 +12,6 @@ module SQLite3
11
12
  FFI.check_prepare(db, status, sql)
12
13
 
13
14
  @stmt = ::FFI::AutoPointer.new(stmt.read_pointer, FFI::CApi.method(:sqlite3_finalize))
14
- @db.instance_variable_set(:@stmt_deadline, nil)
15
15
 
16
16
  tail.read_pointer.read_string.force_encoding(Encoding::UTF_8)
17
17
  end
@@ -35,6 +35,10 @@ module SQLite3
35
35
 
36
36
  stmt = @stmt
37
37
 
38
+ if FFI::CApi.sqlite3_stmt_busy(stmt) == 0
39
+ @db.instance_variable_set(:@stmt_deadline, nil)
40
+ end
41
+
38
42
  value = FFI::CApi.sqlite3_step(stmt)
39
43
  if FFI.rb_errinfo
40
44
  exception = FFI.rb_errinfo
@@ -111,7 +115,7 @@ module SQLite3
111
115
  if value.encoding != Encoding::UTF_8 && value.encoding != Encoding::US_ASCII
112
116
  value = value.encode(Encoding::UTF_8)
113
117
  end
114
- status = FFI::CApi.sqlite3_bind_text(@stmt, index, value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
118
+ status = FFI::CApi.sqlite3_bind_text(@stmt, index, +value, value.bytesize, FFI::CApi::SQLITE_TRANSIENT)
115
119
  end
116
120
  when Float
117
121
  status = FFI::CApi.sqlite3_bind_double(@stmt, index, value)
@@ -168,7 +172,16 @@ module SQLite3
168
172
  require_live_db
169
173
  require_open_stmt
170
174
 
171
- FFI::CApi.sqlite3_column_decltype(@stmt, index)
175
+ name = FFI::CApi.sqlite3_column_decltype(@stmt, index)
176
+ name ? name.force_encoding(Encoding::UTF_8) : nil
177
+ end
178
+
179
+ def column_decltype_downcased(index)
180
+ require_live_db
181
+ require_open_stmt
182
+
183
+ name = FFI::CApi.sqlite3_column_decltype(@stmt, index)
184
+ name ? FFI.interned_downcased_utf8_cstr(name) : nil
172
185
  end
173
186
 
174
187
  def bind_parameter_count
@@ -39,7 +39,11 @@ module SQLite3
39
39
  end
40
40
 
41
41
  def self.interned_utf8_cstr(str)
42
- -str
42
+ -str.force_encoding(Encoding::UTF_8)
43
+ end
44
+
45
+ def self.interned_downcased_utf8_cstr(str)
46
+ -str.force_encoding(Encoding::UTF_8).downcase(:ascii)
43
47
  end
44
48
 
45
49
  def self.string_value(obj)
@@ -58,6 +62,14 @@ module SQLite3
58
62
  obj.to_str
59
63
  end
60
64
 
65
+ def self.string_value_cstr(obj)
66
+ obj = string_value(obj)
67
+ if obj.include?("\x00")
68
+ raise ArgumentError, "string contains null byte"
69
+ end
70
+ obj
71
+ end
72
+
61
73
  RB_ERRINFO = :sqlite3_ffi_rb_errinfo
62
74
 
63
75
  def self.rb_errinfo
@@ -1,5 +1,5 @@
1
1
  module SQLite3
2
2
  module FFI
3
- VERSION = "0.1.7"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -43,20 +43,26 @@ module SQLite3
43
43
  @stmt.step
44
44
  end
45
45
 
46
- # Required by the Enumerable mixin. Provides an internal iterator over the
47
- # rows of the result set.
46
+ # With a block given, iterates over the rows of the result set, passing each
47
+ # to the block. Returns self.
48
+ # With no block given, returns a new Enumerator.
48
49
  def each
50
+ return enum_for(__method__) unless block_given?
49
51
  while (node = self.next)
50
52
  yield node
51
53
  end
54
+ self
52
55
  end
53
56
 
54
- # Provides an internal iterator over the rows of the result set where
55
- # each row is yielded as a hash.
57
+ # With a block given, iterates over the rows of the result set, passing each
58
+ # to the block as a hash. Returns self.
59
+ # With no block given, returns a new Enumerator.
56
60
  def each_hash
61
+ return enum_for(__method__) unless block_given?
57
62
  while (node = next_hash)
58
63
  yield node
59
64
  end
65
+ self
60
66
  end
61
67
 
62
68
  # Closes the statement that spawned this result set.
@@ -42,6 +42,24 @@ module SQLite3
42
42
  # See Database#execute for a description of the valid placeholder
43
43
  # syntaxes.
44
44
  #
45
+ # Type mapping from Ruby to SQLite3:
46
+ # - +nil+ → NULL
47
+ # - Integer → INTEGER (or REAL when outside the signed int64 range)
48
+ # - Float → REAL
49
+ # - SQLite3::Blob → BLOB
50
+ # - String with Encoding::ASCII_8BIT
51
+ # (a.k.a. BINARY) → BLOB
52
+ # - String with Encoding::UTF_16LE
53
+ # or Encoding::UTF_16BE → TEXT (bound as UTF-16)
54
+ # - String (all other encodings) → TEXT (re-encoded to UTF-8 if needed)
55
+ # - Any other type → raises RuntimeError
56
+ #
57
+ # Note: a String with Encoding::ASCII_8BIT (BINARY) is always bound as a
58
+ # BLOB, even when its bytes are all valid ASCII. If you want such a string
59
+ # compared or stored as TEXT, re-encode it before binding:
60
+ #
61
+ # stmt.bind_params(my_binary_str.encode(Encoding::UTF_8))
62
+ #
45
63
  # Example:
46
64
  #
47
65
  # stmt = db.prepare( "select * from table where a=? and b=?" )
@@ -112,9 +130,9 @@ module SQLite3
112
130
  !done?
113
131
  end
114
132
 
115
- # Return an array of the column names for this statement. Note that this
116
- # may execute the statement in order to obtain the metadata; this makes it
117
- # a (potentially) expensive operation.
133
+ # Return a frozen array of the column names for this statement, as frozen
134
+ # UTF-8 strings. Note that this may execute the statement in order to
135
+ # obtain the metadata; this makes it a (potentially) expensive operation.
118
136
  def columns
119
137
  get_metadata unless @columns
120
138
  @columns
@@ -128,9 +146,11 @@ module SQLite3
128
146
  end
129
147
  end
130
148
 
131
- # Return an array of the data types for each column in this statement. Note
132
- # that this may execute the statement in order to obtain the metadata; this
133
- # makes it a (potentially) expensive operation.
149
+ # Return a frozen array of the declared types of the columns in this
150
+ # statement, as frozen UTF-8 strings with ASCII letters downcased. A column
151
+ # with no declared type, such as an expression, has a +nil+ type. Note that
152
+ # this may execute the statement in order to obtain the metadata; this makes
153
+ # it a (potentially) expensive operation.
134
154
  def types
135
155
  must_be_open!
136
156
  get_metadata unless @types
@@ -180,11 +200,10 @@ module SQLite3
180
200
  def get_metadata
181
201
  @columns = Array.new(column_count) do |column|
182
202
  column_name column
183
- end
203
+ end.freeze
184
204
  @types = Array.new(column_count) do |column|
185
- val = column_decltype(column)
186
- val&.downcase
187
- end
205
+ column_decltype_downcased(column)
206
+ end.freeze
188
207
  end
189
208
  end
190
209
  end
@@ -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.9.5"
3
+ VERSION = "2.10.0"
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.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamis Buck
@@ -70,14 +70,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
70
  requirements:
71
71
  - - ">="
72
72
  - !ruby/object:Gem::Version
73
- version: '3.1'
73
+ version: '3.2'
74
74
  required_rubygems_version: !ruby/object:Gem::Requirement
75
75
  requirements:
76
76
  - - ">="
77
77
  - !ruby/object:Gem::Version
78
78
  version: '0'
79
79
  requirements: []
80
- rubygems_version: 4.0.13
80
+ rubygems_version: 4.0.20
81
81
  specification_version: 4
82
82
  summary: A drop-in replacement for the sqlite3 gem for JRuby
83
83
  test_files: []