surrealdb-embedded 0.7.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/lib/surrealdb/connections/embedded.rb +182 -0
- data/lib/surrealdb/embedded.rb +21 -0
- data/lib/surrealdb/native/ffi.rb +50 -0
- data/lib/surrealdb/native/platform.rb +65 -0
- metadata +77 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7b75d55cc942993d3d7ea7bb41a0cb728d744d948a7483c79fb90e1db921f2a9
|
|
4
|
+
data.tar.gz: 5d5a09ad19b759d6335aeab6df38e747b1ba36967a3f5314dafbb75766510c66
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4436eead8dd1e61e2a1e3673681dc55d750b0b0d1c4a0b22bd24944ee76a0325dc815ded55b9b5de7dd6748978ebd89cba0f6e3a9180d58a6a2bfe3a6ec78b0c
|
|
7
|
+
data.tar.gz: 80da57f4362cd5c6db546585a7686bfca397ed96f23ef1eaecdc09673144d6b49df271fbe9e2617cba93d480adf63b5b59ea845feeeee4d33c7e96a8e3c46690
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SurrealDB
|
|
4
|
+
module Connections
|
|
5
|
+
# Embedded transport for SurrealDB via FFI to libsurrealdb_c.
|
|
6
|
+
#
|
|
7
|
+
# Supports mem://, surrealkv://, and file:// URL schemes. Uses the same
|
|
8
|
+
# CBOR RPC protocol as the WebSocket/HTTP transports, but calls directly
|
|
9
|
+
# into the C library instead of going over the network.
|
|
10
|
+
#
|
|
11
|
+
# Requires `require "surrealdb/embedded"` before use.
|
|
12
|
+
#
|
|
13
|
+
# ## Thread Safety
|
|
14
|
+
#
|
|
15
|
+
# The underlying C library handles its own threading via a Tokio runtime.
|
|
16
|
+
# All FFI calls use `blocking: true` to release the GVL. However, the
|
|
17
|
+
# Ruby-side request lifecycle is not atomic -- use one Client per thread.
|
|
18
|
+
class Embedded < Base
|
|
19
|
+
def initialize(url, **options)
|
|
20
|
+
super
|
|
21
|
+
@timeout = options.fetch(:timeout, SurrealDB.configuration.timeout)
|
|
22
|
+
@strict = options.fetch(:strict, false)
|
|
23
|
+
@rpc_ptr = nil
|
|
24
|
+
@live_handlers = {}
|
|
25
|
+
@mutex = Mutex.new
|
|
26
|
+
@notification_thread = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def connect
|
|
30
|
+
err_ptr = FFI::MemoryPointer.new(:pointer)
|
|
31
|
+
surreal_ptr = FFI::MemoryPointer.new(:pointer)
|
|
32
|
+
|
|
33
|
+
opts = Native::SrOption.new
|
|
34
|
+
opts[:strict] = @strict
|
|
35
|
+
opts[:query_timeout] = @timeout
|
|
36
|
+
opts[:transaction_timeout] = @timeout
|
|
37
|
+
|
|
38
|
+
ret = Native.sr_surreal_rpc_new(err_ptr, surreal_ptr, @url, opts)
|
|
39
|
+
check_error!(ret, err_ptr)
|
|
40
|
+
|
|
41
|
+
@rpc_ptr = surreal_ptr.read_pointer
|
|
42
|
+
@connected = true
|
|
43
|
+
start_notification_stream
|
|
44
|
+
log(:debug, "Embedded connection opened: #{@url}")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def close
|
|
48
|
+
return unless @connected
|
|
49
|
+
|
|
50
|
+
@connected = false
|
|
51
|
+
stop_notification_stream
|
|
52
|
+
Native.sr_surreal_rpc_free(@rpc_ptr) if @rpc_ptr
|
|
53
|
+
@rpc_ptr = nil
|
|
54
|
+
log(:debug, 'Embedded connection closed')
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def send_request(method, params = [])
|
|
58
|
+
raise ConnectionError, 'not connected' unless @connected
|
|
59
|
+
|
|
60
|
+
_id, encoded = @rpc.encode_request(method, params)
|
|
61
|
+
|
|
62
|
+
err_ptr = FFI::MemoryPointer.new(:pointer)
|
|
63
|
+
res_ptr = FFI::MemoryPointer.new(:pointer)
|
|
64
|
+
|
|
65
|
+
ret = Native.sr_surreal_rpc_execute(
|
|
66
|
+
@rpc_ptr, err_ptr, res_ptr,
|
|
67
|
+
encoded, encoded.bytesize
|
|
68
|
+
)
|
|
69
|
+
check_error!(ret, err_ptr)
|
|
70
|
+
|
|
71
|
+
read_and_free_response(res_ptr, ret)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def supports_live_queries?
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @param live_query_id [String]
|
|
79
|
+
# @param handler [Proc, Queue]
|
|
80
|
+
def on_notification(live_query_id, handler)
|
|
81
|
+
@mutex.synchronize { @live_handlers[live_query_id] = handler }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @param live_query_id [String]
|
|
85
|
+
def remove_notification_handler(live_query_id)
|
|
86
|
+
@mutex.synchronize { @live_handlers.delete(live_query_id) }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def read_and_free_response(res_ptr, length)
|
|
92
|
+
raw_ptr = res_ptr.read_pointer
|
|
93
|
+
begin
|
|
94
|
+
bytes = raw_ptr.read_bytes(length)
|
|
95
|
+
response = @rpc.decode_response(bytes)
|
|
96
|
+
Protocol::Response.extract_result(response)
|
|
97
|
+
ensure
|
|
98
|
+
Native.sr_free_byte_arr(raw_ptr, length)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def check_error!(ret, err_ptr)
|
|
103
|
+
return if ret >= 0
|
|
104
|
+
|
|
105
|
+
err_str_ptr = err_ptr.read_pointer
|
|
106
|
+
message = err_str_ptr.null? ? "unknown error (code #{ret})" : err_str_ptr.read_string
|
|
107
|
+
Native.sr_free_string(err_str_ptr) unless err_str_ptr.null?
|
|
108
|
+
|
|
109
|
+
if ret == Native::SR_FATAL
|
|
110
|
+
@connected = false
|
|
111
|
+
raise ConnectionError, "fatal error (connection poisoned): #{message}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
raise ServerError, message
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def start_notification_stream
|
|
118
|
+
err_ptr = FFI::MemoryPointer.new(:pointer)
|
|
119
|
+
stream_ptr = FFI::MemoryPointer.new(:pointer)
|
|
120
|
+
|
|
121
|
+
ret = Native.sr_surreal_rpc_notifications(@rpc_ptr, err_ptr, stream_ptr)
|
|
122
|
+
return if ret.negative?
|
|
123
|
+
|
|
124
|
+
@stream_ptr = stream_ptr.read_pointer
|
|
125
|
+
@notification_thread = Thread.new do
|
|
126
|
+
Thread.current.report_on_exception = false
|
|
127
|
+
notification_loop
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def notification_loop
|
|
132
|
+
while @connected
|
|
133
|
+
res_ptr = FFI::MemoryPointer.new(:pointer)
|
|
134
|
+
ret = Native.sr_rpc_stream_next(@stream_ptr, res_ptr)
|
|
135
|
+
|
|
136
|
+
break if ret == Native::SR_CLOSED || !@connected
|
|
137
|
+
|
|
138
|
+
next unless ret.positive?
|
|
139
|
+
|
|
140
|
+
raw_ptr = res_ptr.read_pointer
|
|
141
|
+
begin
|
|
142
|
+
bytes = raw_ptr.read_bytes(ret)
|
|
143
|
+
dispatch_notification(bytes)
|
|
144
|
+
ensure
|
|
145
|
+
Native.sr_free_byte_arr(raw_ptr, ret)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
log(:warn, "Notification stream error: #{e.class}: #{e.message}")
|
|
150
|
+
ensure
|
|
151
|
+
Native.sr_rpc_stream_free(@stream_ptr) if @stream_ptr
|
|
152
|
+
@stream_ptr = nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def dispatch_notification(bytes) # rubocop:disable Metrics/CyclomaticComplexity
|
|
156
|
+
raw = ::CBOR.decode(bytes)
|
|
157
|
+
resolved = CBOR::Decoder.resolve(raw)
|
|
158
|
+
result = resolved.is_a?(Hash) ? (resolved['result'] || resolved) : resolved
|
|
159
|
+
live_id = result['id'] if result.is_a?(Hash)
|
|
160
|
+
return unless live_id
|
|
161
|
+
|
|
162
|
+
handler = @mutex.synchronize { @live_handlers[live_id] }
|
|
163
|
+
return unless handler
|
|
164
|
+
|
|
165
|
+
case handler
|
|
166
|
+
when Queue then handler.push(result)
|
|
167
|
+
when Proc then handler.call(result)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def stop_notification_stream
|
|
172
|
+
@notification_thread&.join(2)
|
|
173
|
+
@notification_thread&.kill if @notification_thread&.alive?
|
|
174
|
+
@notification_thread = nil
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def log(level, message)
|
|
178
|
+
SurrealDB.configuration.logger&.send(level, message)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Opt-in entrypoint for the embedded SurrealDB connection.
|
|
4
|
+
# Loads the FFI bindings and the Embedded connection class.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# require "surrealdb"
|
|
8
|
+
# require "surrealdb/embedded"
|
|
9
|
+
#
|
|
10
|
+
# SurrealDB.connect("mem://") do |db|
|
|
11
|
+
# db.use("test", "test")
|
|
12
|
+
# # ...
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# Requires the `ffi` gem and either:
|
|
16
|
+
# - SURREALDB_LIB_PATH env var pointing to libsurrealdb_c
|
|
17
|
+
# - libsurrealdb_c installed in the system library path
|
|
18
|
+
|
|
19
|
+
require_relative 'native/platform'
|
|
20
|
+
require_relative 'native/ffi'
|
|
21
|
+
require_relative 'connections/embedded'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
module SurrealDB
|
|
6
|
+
module Native
|
|
7
|
+
extend FFI::Library
|
|
8
|
+
|
|
9
|
+
ffi_lib Platform.library_path
|
|
10
|
+
|
|
11
|
+
# sr_option_t is passed by value to sr_surreal_rpc_new.
|
|
12
|
+
class SrOption < FFI::Struct
|
|
13
|
+
layout :strict, :bool,
|
|
14
|
+
:query_timeout, :uint8,
|
|
15
|
+
:transaction_timeout, :uint8
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Return code constants from surrealdb.h
|
|
19
|
+
SR_NONE = 0 # success / no more items
|
|
20
|
+
SR_CLOSED = -1 # stream closed
|
|
21
|
+
SR_ERROR = -2 # recoverable error
|
|
22
|
+
SR_FATAL = -3 # connection poisoned, must not reuse
|
|
23
|
+
|
|
24
|
+
# Connection lifecycle
|
|
25
|
+
attach_function :sr_surreal_rpc_new,
|
|
26
|
+
[:pointer, :pointer, :string, SrOption.by_value], :int,
|
|
27
|
+
blocking: true
|
|
28
|
+
attach_function :sr_surreal_rpc_free,
|
|
29
|
+
[:pointer], :void
|
|
30
|
+
|
|
31
|
+
# CBOR request/response
|
|
32
|
+
attach_function :sr_surreal_rpc_execute,
|
|
33
|
+
%i[pointer pointer pointer pointer int], :int,
|
|
34
|
+
blocking: true
|
|
35
|
+
|
|
36
|
+
# Live query notification stream
|
|
37
|
+
attach_function :sr_surreal_rpc_notifications,
|
|
38
|
+
%i[pointer pointer pointer], :int,
|
|
39
|
+
blocking: true
|
|
40
|
+
attach_function :sr_rpc_stream_next,
|
|
41
|
+
%i[pointer pointer], :int,
|
|
42
|
+
blocking: true
|
|
43
|
+
attach_function :sr_rpc_stream_free,
|
|
44
|
+
[:pointer], :void
|
|
45
|
+
|
|
46
|
+
# Memory cleanup
|
|
47
|
+
attach_function :sr_free_string, [:pointer], :void
|
|
48
|
+
attach_function :sr_free_byte_arr, %i[pointer int], :void
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SurrealDB
|
|
4
|
+
module Native
|
|
5
|
+
# Resolves the platform-specific path to the libsurrealdb_c shared library.
|
|
6
|
+
module Platform
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
# @return [String] absolute path or library name for FFI.ffi_lib
|
|
10
|
+
# @raise [LoadError] if the library cannot be found
|
|
11
|
+
def library_path
|
|
12
|
+
from_env || from_system
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @return [String] shared library file extension for the current OS
|
|
16
|
+
def library_extension
|
|
17
|
+
case host_os
|
|
18
|
+
when :macos then 'dylib'
|
|
19
|
+
when :windows then 'dll'
|
|
20
|
+
else 'so'
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @return [String] expected library filename
|
|
25
|
+
def library_name
|
|
26
|
+
case host_os
|
|
27
|
+
when :windows then 'surrealdb_c.dll'
|
|
28
|
+
else "libsurrealdb_c.#{library_extension}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Symbol] :linux, :macos, or :windows
|
|
33
|
+
def host_os
|
|
34
|
+
case RbConfig::CONFIG['host_os']
|
|
35
|
+
when /darwin/i then :macos
|
|
36
|
+
when /mingw|mswin|cygwin/i then :windows
|
|
37
|
+
else :linux
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @return [Symbol] :x86_64 or :aarch64
|
|
42
|
+
def host_cpu
|
|
43
|
+
case RbConfig::CONFIG['host_cpu']
|
|
44
|
+
when /x86_64|amd64/i then :x86_64
|
|
45
|
+
when /aarch64|arm64/i then :aarch64
|
|
46
|
+
else RbConfig::CONFIG['host_cpu'].to_sym
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def from_env
|
|
51
|
+
path = ENV.fetch('SURREALDB_LIB_PATH', nil)
|
|
52
|
+
return nil if path.nil? || path.empty?
|
|
53
|
+
raise LoadError, "SURREALDB_LIB_PATH points to missing file: #{path}" unless File.exist?(path)
|
|
54
|
+
|
|
55
|
+
path
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def from_system
|
|
59
|
+
library_name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private_class_method :from_env, :from_system
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: surrealdb-embedded
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.7.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- SurrealDB
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: ffi
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.15'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.15'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: surrealdb
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - '='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.7.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - '='
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.7.0
|
|
40
|
+
description: Adds embedded database support (mem://, surrealkv://, file://) to the
|
|
41
|
+
SurrealDB Ruby SDK via FFI bindings to libsurrealdb_c. Requires libsurrealdb_c to
|
|
42
|
+
be installed or SURREALDB_LIB_PATH to be set.
|
|
43
|
+
email:
|
|
44
|
+
- stu.schwartz@surrealdb.com
|
|
45
|
+
executables: []
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- lib/surrealdb/connections/embedded.rb
|
|
50
|
+
- lib/surrealdb/embedded.rb
|
|
51
|
+
- lib/surrealdb/native/ffi.rb
|
|
52
|
+
- lib/surrealdb/native/platform.rb
|
|
53
|
+
homepage: https://github.com/surrealdb/surrealdb.rb
|
|
54
|
+
licenses:
|
|
55
|
+
- Apache-2.0
|
|
56
|
+
metadata:
|
|
57
|
+
homepage_uri: https://github.com/surrealdb/surrealdb.rb
|
|
58
|
+
source_code_uri: https://github.com/surrealdb/surrealdb.rb
|
|
59
|
+
rubygems_mfa_required: 'true'
|
|
60
|
+
rdoc_options: []
|
|
61
|
+
require_paths:
|
|
62
|
+
- lib
|
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.2'
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '0'
|
|
73
|
+
requirements: []
|
|
74
|
+
rubygems_version: 3.7.1
|
|
75
|
+
specification_version: 4
|
|
76
|
+
summary: Embedded SurrealDB engine for the Ruby SDK
|
|
77
|
+
test_files: []
|