Module: SQLite3::FFI

Defined in:
lib/sqlite3/ffi/c_api.rb,
lib/sqlite3/ffi/utils.rb,
lib/sqlite3/ffi/version.rb,
lib/sqlite3/ffi/core_ext.rb,
lib/sqlite3/ffi/exception.rb,
lib/sqlite3/ffi/functions.rb,
lib/sqlite3/ffi/aggregator.rb

Defined Under Namespace

Modules: CApi, CoreExt Classes: AggregatorInstance, AggregatorWrapper

Constant Summary collapse

RB_ERRINFO =
:sqlite3_ffi_rb_errinfo
OBJECT_REGISTRY =
ObjectSpace::WeakMap.new
VERSION =
"0.1.3"
COMPARATOR =
::FFI::Function.new(:int, [:pointer, :int, :pointer, :int, :pointer]) do |ctx, a_len, a, b_len, b|
  comparator = unwrap(ctx)
  a_str = a.read_bytes(a_len).force_encoding(Encoding::UTF_8)
  b_str = b.read_bytes(b_len).force_encoding(Encoding::UTF_8)

  if Encoding.default_internal
    a_str = a_str.encode(Encoding.default_internal)
    b_str = b_str.encode(Encoding.default_internal)
  end

  comparator.compare(a_str, b_str)
end
TRACE =
::FFI::Function.new(:int, [:uint, :pointer, :pointer, :pointer]) do |_, ctx, _, x|
  unwrap(ctx).call(x.read_string)
  0
end
AUTH =
::FFI::Function.new(:int, [:pointer, :int, :string, :string, :string, :string]) do |ctx, op_id, s1, s2, s3, s4|
  result = unwrap(ctx).call(op_id, s1, s2, s3, s4)
  if result.is_a?(Integer)
    result
  elsif result == true
    CApi::SQLITE_OK
  elsif result == false
    CApi::SQLITE_DENY
  else
    CApi::SQLITE_IGNORE
  end
end
HASH_CALLBACK =
::FFI::Function.new(:int, [:pointer, :int, :pointer, :pointer]) do |ctx, count, data, columns|
  callback_ary = unwrap(ctx)
  new_hash = {}
  data.read_array_of_pointer(count).zip(columns.read_array_of_pointer(count)) do |value, column|
    new_hash[column.read_string] = value.null? ? nil : value.read_string
  end
  callback_ary << new_hash
  0
end
REGULAR_CALLBACK =
::FFI::Function.new(:int, [:pointer, :int, :pointer, :pointer]) do |ctx, count, data, columns|
  callback_ary = unwrap(ctx)
  new_ary = []
  data.read_array_of_pointer(count).each do |value|
    new_ary << (value.null? ? nil : value.read_string)
  end
  callback_ary << new_ary
  0
end
STATEMENT_TIMEOUT =
::FFI::Function.new(:int, [:pointer]) do |ctx|
  ctx = unwrap(ctx)
  current_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  if ctx.instance_variable_get(:@stmt_deadline).nil?
    ctx.instance_variable_set(:@stmt_deadline, current_time)
    0
  elsif current_time >= ctx.instance_variable_get(:@stmt_deadline)
    1
  else
    0
  end
end
BUSY_HANDLER =
::FFI::Function.new(:int, [:pointer, :int]) do |ctx, count|
  handler = unwrap(ctx).instance_variable_get(:@busy_handler)
  result = handler.(count)
  result == false ? 0 : 1
end
FUNC =
::FFI::Function.new(:void, [:pointer, :int, :pointer]) do |ctx, argc, argv|
  callable = unwrap(CApi.sqlite3_user_data(ctx))
  params = argv.read_array_of_pointer(argc).map { |v| FFI.sqlite3val2rb(v) }
  result = callable.(*params)
  FFI.set_sqlite3_func_result(ctx, result)
end
AGGREGATOR_STEP =
::FFI::Function.new(:void, [:pointer, :int, :pointer]) do |ctx, argc, argv|
  begin
    inst = aggregate_instance(ctx)
    handler_instance = inst.handler_instance
    params = argv.read_array_of_pointer(argc).map { |v| FFI.sqlite3val2rb(v) }
    handler_instance.step(*params)
  rescue => e
    FFI.rb_errinfo = e
  end
end
AGGREGATOR_FINAL =
::FFI::Function.new(:void, [:pointer]) do |ctx|
  begin
    inst = aggregate_instance(ctx)
    handler_instance = inst.handler_instance
    result = handler_instance.finalize
    FFI.set_sqlite3_func_result(ctx, result)
    aggregate_instance_destroy(ctx)
  rescue => e
    FFI.rb_errinfo = e
  end
end

Class Method Summary collapse

Class Method Details

.aggregate_instance(ctx) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/sqlite3/ffi/aggregator.rb', line 16

def self.aggregate_instance(ctx)
  aw = FFI.unwrap(CApi.sqlite3_user_data(ctx))
  handler_klass = aw.handler_klass
  inst_ptr = CApi.sqlite3_aggregate_context(ctx, 8)

  if inst_ptr.null?
    fatal "SQLite is out-of-merory"
  end

  if inst_ptr.read_pointer.null?
    instances = aw.instances

    inst = AggregatorInstance.new
    inst.handler_instance = handler_klass.new
    instances << inst
    inst_ptr.write_pointer(FFI.wrap(inst))
  else
    inst = FFI.unwrap(inst_ptr.read_pointer)
  end

  if inst.nil?
    fatal "SQLite called us back on an already destroyed aggregate instance"
  end

  inst
end

.aggregate_instance_destroy(ctx) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/sqlite3/ffi/aggregator.rb', line 43

def self.aggregate_instance_destroy(ctx)
  aw = FFI.unwrap(CApi.sqlite3_user_data(ctx))
  instances = aw.instances
  inst_ptr = CApi.sqlite3_aggregate_context(ctx, 0)

  if inst_ptr.null? || inst_ptr.read_pointer.null?
    return
  end

  inst = FFI.unwrap(inst_ptr.read_pointer)

  if inst.nil?
    fatal "attempt to destroy aggregate instance twice"
  end

  inst.handler_instance = nil
  if instances.delete(inst).nil?
    fatal "must be in instances at that point"
  end

  inst_ptr.write_pointer(::FFI::Pointer.new(0))
end

.check(db, status) ⇒ Object



3
4
5
# File 'lib/sqlite3/ffi/exception.rb', line 3

def self.check(db, status)
  raise_(db, status)
end

.check_msg(db, status, msg) ⇒ Object



7
8
9
# File 'lib/sqlite3/ffi/exception.rb', line 7

def self.check_msg(db, status, msg)
  raise_msg(db, status, msg)
end

.check_prepare(db, status, sql) ⇒ Object



11
12
13
# File 'lib/sqlite3/ffi/exception.rb', line 11

def self.check_prepare(db, status, sql)
  raise_with_sql(db, status, sql)
end

.interned_utf8_cstr(str) ⇒ Object



41
42
43
# File 'lib/sqlite3/ffi/utils.rb', line 41

def self.interned_utf8_cstr(str)
  -str
end

.raise_(db, status) ⇒ Object



76
77
78
79
80
81
82
83
84
# File 'lib/sqlite3/ffi/exception.rb', line 76

def self.raise_(db, status)
  klass = status2klass(status)
  return if klass.nil?

  exception = klass.new(CApi.sqlite3_errmsg(db))
  exception.instance_variable_set(:@code, status)

  raise exception
end

.raise_msg(db, status, msg) ⇒ Object



86
87
88
89
90
91
92
93
94
95
# File 'lib/sqlite3/ffi/exception.rb', line 86

def self.raise_msg(db, status, msg)
  klass = status2klass(status)
  return if klass.nil?

  exception = klass.new(msg.read_pointer.read_string)
  exception.instance_variable_set(:@code, status)
  CApi.sqlite3_free(msg.read_pointer)

  raise exception
end

.raise_with_sql(db, status, sql) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/sqlite3/ffi/exception.rb', line 97

def self.raise_with_sql(db, status, sql)
  klass = status2klass(status)
  return if klass.nil?

  exception = klass.new(CApi.sqlite3_errmsg(db))
  exception.instance_variable_set(:@code, status)
  if sql
    exception.instance_variable_set(:@sql, sql)
    exception.instance_variable_set(:@sql_offset, CApi.sqlite3_error_offset(db))
  end

  raise exception
end

.rb_errinfoObject



63
64
65
# File 'lib/sqlite3/ffi/utils.rb', line 63

def self.rb_errinfo
  Thread.current[RB_ERRINFO]
end

.rb_errinfo=(e) ⇒ Object



67
68
69
# File 'lib/sqlite3/ffi/utils.rb', line 67

def self.rb_errinfo=(e)
  Thread.current[RB_ERRINFO] = e
end

.set_sqlite3_func_result(ctx, result) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/sqlite3/ffi/utils.rb', line 22

def self.set_sqlite3_func_result(ctx, result)
  case result
  when NilClass
    CApi.sqlite3_result_null(ctx)
  when Integer
    CApi.sqlite3_result_int64(ctx, result)
  when Float
    CApi.sqlite3_result_double(ctx, result)
  when String
    if result.is_a?(Blob) || result.encoding == Encoding::BINARY
      CApi.sqlite3_result_blob(ctx, result, result.bytesize, CApi::SQLITE_TRANSIENT)
    else
      CApi.sqlite3_result_text(ctx, result, result.bytesize, CApi::SQLITE_TRANSIENT)
    end
  else
    raise RuntimeError, "can't return #{result.class.name}"
  end
end

.sqlite3val2rb(val) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/sqlite3/ffi/utils.rb', line 3

def self.sqlite3val2rb(val)
  case CApi.sqlite3_value_type(val)
  when CApi::SQLITE_INTEGER
    CApi.sqlite3_value_int64(val)
  when CApi::SQLITE_FLOAT
    CApi.sqlite3_value_double(val)
  when CApi::SQLITE_TEXT
    len = CApi.sqlite3_value_bytes(val)
    CApi.sqlite3_value_text(val).read_bytes(len).force_encoding(Encoding::UTF_8).freeze
  when CApi::SQLITE_BLOB
    len = CApi.sqlite3_value_bytes(val)
    CApi.sqlite3_value_text(val).read_bytes(len).freeze
  when CApi::SQLITE_NULL
    nil
  else
    raise RuntimeError, "bad type"
  end
end

.status2klass(status) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/sqlite3/ffi/exception.rb', line 15

def self.status2klass(status)
  case status & 0xff
  when 0
    nil
  when CApi::SQLITE_ERROR
    SQLite3::SQLException
  when CApi::SQLITE_INTERNAL
    SQLite3::InternalException
  when CApi::SQLITE_PERM
    SQLite3::PermissionException
  when CApi::SQLITE_ABORT
    SQLite3::AbortException
  when CApi::SQLITE_BUSY
    SQLite3::BusyException
  when CApi::SQLITE_LOCKED
    SQLite3::LockedException
  when CApi::SQLITE_NOMEM
    SQLite3::MemoryException
  when CApi::SQLITE_READONLY
    SQLite3::ReadOnlyException
  when CApi::SQLITE_INTERRUPT
    SQLite3::InterruptException
  when CApi::SQLITE_IOERR
    SQLite3::IOException
  when CApi::SQLITE_CORRUPT
    SQLite3::CorruptException
  when CApi::SQLITE_NOTFOUND
    SQLite3::NotFoundException
  when CApi::SQLITE_FULL
    SQLite3::FullException
  when CApi::SQLITE_CANTOPEN
    SQLite3::CantOpenException
  when CApi::SQLITE_PROTOCOL
    SQLite3::ProtocolException
  when CApi::SQLITE_EMPTY
    SQLite3::EmptyException
  when CApi::SQLITE_SCHEMA
    SQLite3::SchemaChangedException
  when CApi::SQLITE_TOOBIG
    SQLite3::TooBigException
  when CApi::SQLITE_CONSTRAINT
    SQLite3::ConstraintException
  when CApi::SQLITE_MISMATCH
    SQLite3::MismatchException
  when CApi::SQLITE_MISUSE
    SQLite3::MisuseException
  when CApi::SQLITE_NOLFS
    SQLite3::UnsupportedException
  when CApi::SQLITE_AUTH
    SQLite3::AuthorizationException
  when CApi::SQLITE_FORMAT
    SQLite3::FormatException
  when CApi::SQLITE_RANGE
    SQLite3::RangeException
  when CApi::SQLITE_NOTADB
    SQLite3::NotADatabaseException
  else
    SQLite3::Exception
  end
end

.string_value(obj) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/sqlite3/ffi/utils.rb', line 45

def self.string_value(obj)
  unless obj.respond_to?(:to_str)
    val =
      case obj
      when nil
        "nil"
      when true, false
        obj.to_s
      else
        obj.class.name
      end
    raise TypeError, "no implicit conversion of #{val} into String"
  end
  obj.to_str
end

.unwrap(ptr) ⇒ Object



78
79
80
# File 'lib/sqlite3/ffi/utils.rb', line 78

def self.unwrap(ptr)
  OBJECT_REGISTRY[ptr.to_i] || (raise "object not found")
end

.wrap(obj) ⇒ Object



73
74
75
76
# File 'lib/sqlite3/ffi/utils.rb', line 73

def self.wrap(obj)
  OBJECT_REGISTRY[obj.object_id] = obj
  ::FFI::Pointer.new(obj.object_id)
end