fdb 5.1.5
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/LICENSE +207 -0
- data/lib/fdb.rb +71 -0
- data/lib/fdbdirectory.rb +689 -0
- data/lib/fdbimpl.rb +1017 -0
- data/lib/fdblocality.rb +83 -0
- data/lib/fdboptions.rb +129 -0
- data/lib/fdbsubspace.rb +71 -0
- data/lib/fdbtuple.rb +307 -0
- metadata +71 -0
data/lib/fdbimpl.rb
ADDED
@@ -0,0 +1,1017 @@
|
|
1
|
+
#encoding: BINARY
|
2
|
+
|
3
|
+
#
|
4
|
+
# fdbimpl.rb
|
5
|
+
#
|
6
|
+
# This source file is part of the FoundationDB open source project
|
7
|
+
#
|
8
|
+
# Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
9
|
+
#
|
10
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
11
|
+
# you may not use this file except in compliance with the License.
|
12
|
+
# You may obtain a copy of the License at
|
13
|
+
#
|
14
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
15
|
+
#
|
16
|
+
# Unless required by applicable law or agreed to in writing, software
|
17
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
18
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
19
|
+
# See the License for the specific language governing permissions and
|
20
|
+
# limitations under the License.
|
21
|
+
#
|
22
|
+
|
23
|
+
# FoundationDB Ruby API
|
24
|
+
|
25
|
+
# Documentation for this API can be found at
|
26
|
+
# https://www.foundationdb.org/documentation/api-ruby.html
|
27
|
+
|
28
|
+
require 'ffi'
|
29
|
+
|
30
|
+
require 'thread'
|
31
|
+
require 'monitor'
|
32
|
+
|
33
|
+
require_relative 'fdboptions'
|
34
|
+
|
35
|
+
module FDB
|
36
|
+
module FDBC
|
37
|
+
require 'rbconfig'
|
38
|
+
|
39
|
+
if RbConfig::CONFIG['host_cpu'] != "x86_64"
|
40
|
+
raise LoadError, "FoundationDB API only supported on x86_64 (not #{RbConfig::CONFIG['host_cpu']})"
|
41
|
+
end
|
42
|
+
|
43
|
+
case RbConfig::CONFIG['host_os']
|
44
|
+
when /linux/
|
45
|
+
dlobj = 'libfdb_c.so'
|
46
|
+
when /darwin/
|
47
|
+
dlobj = 'libfdb_c.dylib'
|
48
|
+
when /mswin|mingw|cygwin/
|
49
|
+
dlobj = 'fdb_c.dll'
|
50
|
+
if Gem.loaded_specs['ffi'].version < Gem::Version.create('1.7.0.dev')
|
51
|
+
raise LoadError, "You must install ffi >= 1.7.0.dev on 64-bit Windows (see https://github.com/ffi/ffi/issues/259)"
|
52
|
+
end
|
53
|
+
else
|
54
|
+
raise LoadError, "FoundationDB API is not supported on #{RbConfig::CONFIG['host_os']}"
|
55
|
+
end
|
56
|
+
|
57
|
+
extend FFI::Library
|
58
|
+
begin
|
59
|
+
ffi_lib [File.join( File.dirname(__FILE__), dlobj ), dlobj]
|
60
|
+
rescue LoadError => e
|
61
|
+
raise $!, "#{$!} (is the FoundationDB client library installed?)"
|
62
|
+
end
|
63
|
+
|
64
|
+
typedef :int, :fdb_error
|
65
|
+
typedef :int, :fdb_bool
|
66
|
+
|
67
|
+
attach_function :fdb_get_error, [ :fdb_error ], :string
|
68
|
+
|
69
|
+
attach_function :fdb_network_set_option, [ :int, :pointer, :int ], :fdb_error
|
70
|
+
attach_function :fdb_setup_network, [ ], :fdb_error
|
71
|
+
attach_function :fdb_run_network, [ ], :fdb_error, :blocking => true
|
72
|
+
attach_function :fdb_stop_network, [ ], :fdb_error
|
73
|
+
|
74
|
+
attach_function :fdb_future_cancel, [ :pointer ], :void
|
75
|
+
attach_function :fdb_future_release_memory, [ :pointer ], :void
|
76
|
+
attach_function :fdb_future_destroy, [ :pointer ], :void
|
77
|
+
attach_function :fdb_future_block_until_ready, [ :pointer ], :fdb_error, :blocking => true
|
78
|
+
attach_function :fdb_future_is_ready, [ :pointer ], :fdb_bool
|
79
|
+
|
80
|
+
callback :fdb_future_callback, [ :pointer, :pointer ], :void
|
81
|
+
attach_function :fdb_future_set_callback, [ :pointer, :fdb_future_callback, :pointer ], :fdb_error
|
82
|
+
|
83
|
+
attach_function :fdb_future_get_error, [ :pointer ], :fdb_error
|
84
|
+
attach_function :fdb_future_get_version, [ :pointer, :pointer ], :fdb_error
|
85
|
+
attach_function :fdb_future_get_key, [ :pointer, :pointer, :pointer ], :fdb_error
|
86
|
+
attach_function :fdb_future_get_cluster, [ :pointer, :pointer ], :fdb_error
|
87
|
+
attach_function :fdb_future_get_database, [ :pointer, :pointer ], :fdb_error
|
88
|
+
attach_function :fdb_future_get_value, [ :pointer, :pointer, :pointer, :pointer ], :fdb_error
|
89
|
+
attach_function :fdb_future_get_keyvalue_array, [ :pointer, :pointer, :pointer, :pointer ], :fdb_error
|
90
|
+
attach_function :fdb_future_get_string_array, [ :pointer, :pointer, :pointer ], :fdb_error
|
91
|
+
|
92
|
+
attach_function :fdb_create_cluster, [ :string ], :pointer
|
93
|
+
attach_function :fdb_cluster_destroy, [ :pointer ], :void
|
94
|
+
attach_function :fdb_cluster_set_option, [ :pointer, :int, :pointer, :int ], :fdb_error
|
95
|
+
|
96
|
+
attach_function :fdb_cluster_create_database, [ :pointer, :pointer, :int ], :pointer
|
97
|
+
attach_function :fdb_database_destroy, [ :pointer ], :void
|
98
|
+
attach_function :fdb_database_set_option, [ :pointer, :int, :pointer, :int ], :fdb_error
|
99
|
+
|
100
|
+
attach_function :fdb_database_create_transaction, [ :pointer, :pointer ], :fdb_error
|
101
|
+
attach_function :fdb_transaction_destroy, [ :pointer ], :void
|
102
|
+
attach_function :fdb_transaction_cancel, [ :pointer ], :void
|
103
|
+
attach_function :fdb_transaction_atomic_op, [ :pointer, :pointer, :int, :pointer, :int, :int ], :void
|
104
|
+
attach_function :fdb_transaction_add_conflict_range, [ :pointer, :pointer, :int, :pointer, :int, :int ], :int
|
105
|
+
attach_function :fdb_transaction_get_addresses_for_key, [ :pointer, :pointer, :int ], :pointer
|
106
|
+
attach_function :fdb_transaction_set_option, [ :pointer, :int, :pointer, :int ], :fdb_error
|
107
|
+
attach_function :fdb_transaction_set_read_version, [ :pointer, :int64 ], :void
|
108
|
+
attach_function :fdb_transaction_get_read_version, [ :pointer ], :pointer
|
109
|
+
attach_function :fdb_transaction_get, [ :pointer, :pointer, :int, :int ], :pointer
|
110
|
+
attach_function :fdb_transaction_get_key, [ :pointer, :pointer, :int, :int, :int, :int ], :pointer
|
111
|
+
attach_function :fdb_transaction_get_range, [ :pointer, :pointer, :int, :int, :int, :pointer, :int, :int, :int, :int, :int, :int, :int, :int, :int ], :pointer
|
112
|
+
attach_function :fdb_transaction_set, [ :pointer, :pointer, :int, :pointer, :int ], :void
|
113
|
+
attach_function :fdb_transaction_clear, [ :pointer, :pointer, :int ], :void
|
114
|
+
attach_function :fdb_transaction_clear_range, [ :pointer, :pointer, :int, :pointer, :int ], :void
|
115
|
+
attach_function :fdb_transaction_watch, [ :pointer, :pointer, :int ], :pointer
|
116
|
+
attach_function :fdb_transaction_commit, [ :pointer ], :pointer
|
117
|
+
attach_function :fdb_transaction_get_committed_version, [ :pointer, :pointer ], :fdb_error
|
118
|
+
attach_function :fdb_transaction_get_versionstamp, [ :pointer ], :pointer
|
119
|
+
attach_function :fdb_transaction_on_error, [ :pointer, :fdb_error ], :pointer
|
120
|
+
attach_function :fdb_transaction_reset, [ :pointer ], :void
|
121
|
+
|
122
|
+
attach_function :fdb_select_api_version_impl, [ :int, :int ], :fdb_error
|
123
|
+
attach_function :fdb_get_max_api_version, [ ], :int
|
124
|
+
|
125
|
+
class KeyValueStruct < FFI::Struct
|
126
|
+
pack 4
|
127
|
+
layout :key, :pointer,
|
128
|
+
:key_length, :int,
|
129
|
+
:value, :pointer,
|
130
|
+
:value_length, :int
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.check_error(code)
|
134
|
+
raise Error.new(code) if code.nonzero?
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
@@cb_mutex = Mutex.new
|
140
|
+
def self.cb_mutex
|
141
|
+
@@cb_mutex
|
142
|
+
end
|
143
|
+
|
144
|
+
class CallbackEntry
|
145
|
+
attr_accessor :callback
|
146
|
+
attr_accessor :index
|
147
|
+
|
148
|
+
def initialize
|
149
|
+
@callback = nil
|
150
|
+
@index = nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
@@ffi_callbacks = []
|
155
|
+
def self.ffi_callbacks
|
156
|
+
@@ffi_callbacks
|
157
|
+
end
|
158
|
+
|
159
|
+
[ "Network", "Cluster", "Database", "Transaction" ].each do |scope|
|
160
|
+
klass = FDB.const_set("#{scope}Options", Class.new)
|
161
|
+
klass.class_eval do
|
162
|
+
define_method(:initialize) do |setfunc|
|
163
|
+
instance_variable_set("@setfunc", setfunc)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
class_variable_get("@@#{scope}Option").each_pair do |k,v|
|
167
|
+
p =
|
168
|
+
case v[2]
|
169
|
+
when NilClass then
|
170
|
+
Proc.new do || @setfunc.call(v[0], nil) end
|
171
|
+
when String then
|
172
|
+
Proc.new do |opt=nil| @setfunc.call(v[0], (opt.nil? ? opt : opt.encode('UTF-8')) ) end
|
173
|
+
when Fixnum then
|
174
|
+
Proc.new do |opt| @setfunc.call(v[0], [opt].pack("q<")) end
|
175
|
+
else
|
176
|
+
raise ArgumentError, "Don't know how to set options of type #{v[2].class}"
|
177
|
+
end
|
178
|
+
klass.send :define_method, "set_#{k.downcase}", p
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def self.key_to_bytes(k)
|
183
|
+
if k.respond_to? 'as_foundationdb_key'
|
184
|
+
k.as_foundationdb_key
|
185
|
+
else
|
186
|
+
k
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.value_to_bytes(v)
|
191
|
+
if v.respond_to? 'as_foundationdb_value'
|
192
|
+
v.as_foundationdb_value
|
193
|
+
else
|
194
|
+
v
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def self.strinc(key)
|
199
|
+
key = key.gsub(/\xff*\z/, '')
|
200
|
+
raise ArgumentError, 'Key must contain at least one byte not equal to 0xFF.' if key.length == 0
|
201
|
+
|
202
|
+
key[0..-2] + (key[-1].ord + 1).chr
|
203
|
+
end
|
204
|
+
|
205
|
+
@@options = NetworkOptions.new lambda { |code, param|
|
206
|
+
FDBC.check_error FDBC.fdb_network_set_option(code, param, param.nil? ? 0 : param.bytesize)
|
207
|
+
}
|
208
|
+
def FDB.options
|
209
|
+
@@options
|
210
|
+
end
|
211
|
+
|
212
|
+
@@network_thread = nil
|
213
|
+
@@network_thread_monitor = Monitor.new
|
214
|
+
|
215
|
+
def self.init()
|
216
|
+
@@network_thread_monitor.synchronize do
|
217
|
+
if !@@network_thread.nil?
|
218
|
+
raise Error.new(2000)
|
219
|
+
end
|
220
|
+
|
221
|
+
begin
|
222
|
+
@@network_thread = Thread.new do
|
223
|
+
@@network_thread_monitor.synchronize do
|
224
|
+
# Don't start running until init releases this
|
225
|
+
end
|
226
|
+
# puts "Starting FDB network"
|
227
|
+
begin
|
228
|
+
FDBC.check_error FDBC.fdb_run_network
|
229
|
+
rescue Error => e
|
230
|
+
$stderr.puts "Unhandled error in FoundationDB network thread: #{e.to_s}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
FDBC.check_error FDBC.fdb_setup_network
|
235
|
+
rescue
|
236
|
+
@@network_thread.kill
|
237
|
+
@@network_thread = nil
|
238
|
+
raise
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
|
245
|
+
def self.stop()
|
246
|
+
FDBC.check_error FDBC.fdb_stop_network
|
247
|
+
end
|
248
|
+
|
249
|
+
at_exit do
|
250
|
+
if !@@network_thread.nil?
|
251
|
+
# puts "Stopping FDB network"
|
252
|
+
stop
|
253
|
+
@@network_thread.join
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
@@open_clusters = {}
|
258
|
+
@@open_databases = {}
|
259
|
+
@@cache_lock = Mutex.new
|
260
|
+
|
261
|
+
def self.open( cluster_file = nil, database_name = "DB" )
|
262
|
+
@@network_thread_monitor.synchronize do
|
263
|
+
if ! @@network_thread
|
264
|
+
init
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
@@cache_lock.synchronize do
|
269
|
+
if ! @@open_clusters.has_key? cluster_file
|
270
|
+
@@open_clusters[cluster_file] = create_cluster( cluster_file )
|
271
|
+
end
|
272
|
+
|
273
|
+
if ! @@open_databases.has_key? [cluster_file, database_name]
|
274
|
+
@@open_databases[[cluster_file, database_name]] = @@open_clusters[cluster_file].open_database(database_name)
|
275
|
+
end
|
276
|
+
|
277
|
+
@@open_databases[[cluster_file, database_name]]
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
class Error < StandardError
|
282
|
+
attr_reader :code
|
283
|
+
|
284
|
+
def initialize(code)
|
285
|
+
@code = code
|
286
|
+
@description = nil
|
287
|
+
end
|
288
|
+
|
289
|
+
def description
|
290
|
+
if !@description
|
291
|
+
@description = FDBC.fdb_get_error(@code)
|
292
|
+
end
|
293
|
+
@description
|
294
|
+
end
|
295
|
+
|
296
|
+
def to_s
|
297
|
+
"#{description} (#{@code})"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
class Future
|
302
|
+
def self.finalize(ptr)
|
303
|
+
proc do
|
304
|
+
#puts "Destroying future #{ptr}"
|
305
|
+
FDBC.fdb_future_destroy(ptr)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def initialize(fpointer)
|
310
|
+
@fpointer = fpointer
|
311
|
+
ObjectSpace.define_finalizer(self, FDB::Future.finalize(@fpointer))
|
312
|
+
end
|
313
|
+
|
314
|
+
def cancel
|
315
|
+
FDBC.fdb_future_cancel(@fpointer)
|
316
|
+
end
|
317
|
+
|
318
|
+
def ready?
|
319
|
+
return !FDBC.fdb_future_is_ready(@fpointer).zero?
|
320
|
+
end
|
321
|
+
|
322
|
+
def block_until_ready
|
323
|
+
FDBC.check_error FDBC.fdb_future_block_until_ready(@fpointer)
|
324
|
+
end
|
325
|
+
|
326
|
+
def on_ready(&block)
|
327
|
+
def callback_wrapper(f, &block)
|
328
|
+
begin
|
329
|
+
yield f
|
330
|
+
rescue Exception
|
331
|
+
begin
|
332
|
+
$stderr.puts "Discarding uncaught exception from user callback:"
|
333
|
+
$stderr.puts "#{$@.first}: #{$!.message} (#{$!.class})", $@.drop(1).map{|s| "\t#{s}"}
|
334
|
+
rescue Exception
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
entry = CallbackEntry.new
|
340
|
+
|
341
|
+
FDB.cb_mutex.synchronize {
|
342
|
+
pos = FDB.ffi_callbacks.length
|
343
|
+
entry.index = pos
|
344
|
+
FDB.ffi_callbacks << entry
|
345
|
+
}
|
346
|
+
|
347
|
+
entry.callback = FFI::Function.new(:void, [:pointer, :pointer]) do |ign1, ign2|
|
348
|
+
FDB.cb_mutex.synchronize {
|
349
|
+
FDB.ffi_callbacks[-1].index = entry.index
|
350
|
+
FDB.ffi_callbacks[entry.index] = FDB.ffi_callbacks[-1]
|
351
|
+
FDB.ffi_callbacks.pop
|
352
|
+
}
|
353
|
+
callback_wrapper(self, &block)
|
354
|
+
end
|
355
|
+
FDBC.fdb_future_set_callback(@fpointer, entry.callback, nil)
|
356
|
+
end
|
357
|
+
|
358
|
+
def self.wait_for_any(*futures)
|
359
|
+
if futures.empty?
|
360
|
+
raise ArgumentError, "wait_for_any requires at least one future"
|
361
|
+
end
|
362
|
+
|
363
|
+
mx = Mutex.new
|
364
|
+
cv = ConditionVariable.new
|
365
|
+
|
366
|
+
ready_idx = -1
|
367
|
+
|
368
|
+
futures.each_with_index do |f, i|
|
369
|
+
f.on_ready do |f|
|
370
|
+
mx.synchronize {
|
371
|
+
if ready_idx < 0
|
372
|
+
ready_idx = i
|
373
|
+
cv.signal
|
374
|
+
end
|
375
|
+
}
|
376
|
+
end
|
377
|
+
end
|
378
|
+
|
379
|
+
mx.synchronize {
|
380
|
+
if ready_idx < 0
|
381
|
+
cv.wait mx
|
382
|
+
end
|
383
|
+
}
|
384
|
+
|
385
|
+
ready_idx
|
386
|
+
end
|
387
|
+
|
388
|
+
private
|
389
|
+
|
390
|
+
def release_memory
|
391
|
+
FDBC.fdb_future_release_memory(@fpointer)
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
class FutureNil < Future
|
396
|
+
def wait
|
397
|
+
block_until_ready
|
398
|
+
FDBC.check_error FDBC.fdb_future_get_error(@fpointer)
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
class LazyFuture < Future
|
403
|
+
instance_methods.each { |m| undef_method m unless (m =~ /^__/ or (LazyFuture.instance_methods - Object.instance_methods).include? m or m == :object_id)}
|
404
|
+
|
405
|
+
def respond_to?(message)
|
406
|
+
message = message.to_sym
|
407
|
+
message == :__result__ or
|
408
|
+
message == :to_ptr or
|
409
|
+
value.respond_to? message
|
410
|
+
end
|
411
|
+
|
412
|
+
def method_missing( *args, &block )
|
413
|
+
value.__send__( *args, &block )
|
414
|
+
end
|
415
|
+
|
416
|
+
def initialize(fpointer)
|
417
|
+
super(fpointer)
|
418
|
+
@set = false
|
419
|
+
@value = nil
|
420
|
+
end
|
421
|
+
|
422
|
+
def value
|
423
|
+
if !@set
|
424
|
+
block_until_ready
|
425
|
+
|
426
|
+
begin
|
427
|
+
getter
|
428
|
+
release_memory
|
429
|
+
rescue Error => e
|
430
|
+
if e.code != 1102 # future_released
|
431
|
+
raise
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
@set = true
|
436
|
+
end
|
437
|
+
|
438
|
+
@value
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
class LazyString < LazyFuture
|
443
|
+
def to_ptr
|
444
|
+
FFI::MemoryPointer.from_string(value)
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
class Version < LazyFuture
|
449
|
+
def getter
|
450
|
+
version = FFI::MemoryPointer.new :int64
|
451
|
+
FDBC.check_error FDBC.fdb_future_get_version(@fpointer, version)
|
452
|
+
@value = version.read_long_long
|
453
|
+
end
|
454
|
+
private :getter
|
455
|
+
end
|
456
|
+
|
457
|
+
class FutureKeyValueArray < Future
|
458
|
+
def wait
|
459
|
+
block_until_ready
|
460
|
+
|
461
|
+
kvs = FFI::MemoryPointer.new :pointer
|
462
|
+
count = FFI::MemoryPointer.new :int
|
463
|
+
more = FFI::MemoryPointer.new :int
|
464
|
+
FDBC.check_error FDBC.fdb_future_get_keyvalue_array(@fpointer, kvs, count, more)
|
465
|
+
kvs = kvs.read_pointer
|
466
|
+
|
467
|
+
[(0..count.read_int-1).map{|i|
|
468
|
+
x = FDBC::KeyValueStruct.new(kvs + (i * FDBC::KeyValueStruct.size))
|
469
|
+
KeyValue.new(x[:key].read_bytes(x[:key_length]),
|
470
|
+
x[:value].read_bytes(x[:value_length]))
|
471
|
+
}, count.read_int, more.read_int]
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
class FutureStringArray < LazyFuture
|
476
|
+
def getter
|
477
|
+
strings = FFI::MemoryPointer.new :pointer
|
478
|
+
count = FFI::MemoryPointer.new :int
|
479
|
+
FDBC.check_error FDBC.fdb_future_get_string_array(@fpointer, strings, count)
|
480
|
+
|
481
|
+
@value = strings.read_pointer.get_array_of_string(0, count.read_int).compact
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
class FormerFuture
|
486
|
+
def ready?
|
487
|
+
true
|
488
|
+
end
|
489
|
+
|
490
|
+
def block_until_ready
|
491
|
+
end
|
492
|
+
|
493
|
+
def on_ready(&block)
|
494
|
+
begin
|
495
|
+
yield self
|
496
|
+
rescue Exception
|
497
|
+
begin
|
498
|
+
$stderr.puts "Discarding uncaught exception from user callback:"
|
499
|
+
$stderr.puts "#{$@.first}: #{$!.message} (#{$!.class})", $@.drop(1).map{|s| "\t#{s}"}
|
500
|
+
rescue Exception
|
501
|
+
end
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def self.create_cluster(cluster=nil)
|
507
|
+
f = FDBC.fdb_create_cluster(cluster)
|
508
|
+
cpointer = FFI::MemoryPointer.new :pointer
|
509
|
+
FDBC.check_error FDBC.fdb_future_block_until_ready(f)
|
510
|
+
FDBC.check_error FDBC.fdb_future_get_cluster(f, cpointer)
|
511
|
+
Cluster.new cpointer.get_pointer(0)
|
512
|
+
end
|
513
|
+
|
514
|
+
class Cluster < FormerFuture
|
515
|
+
attr_reader :options
|
516
|
+
|
517
|
+
def self.finalize(ptr)
|
518
|
+
proc do
|
519
|
+
# puts "Destroying cluster #{ptr}"
|
520
|
+
FDBC.fdb_cluster_destroy(ptr)
|
521
|
+
end
|
522
|
+
end
|
523
|
+
|
524
|
+
def initialize(cpointer)
|
525
|
+
@cpointer = cpointer
|
526
|
+
@options = ClusterOptions.new lambda { |code, param|
|
527
|
+
FDBC.check_error FDBC.fdb_cluster_set_option(cpointer, code, param, param.nil? ? 0 : param.bytesize)
|
528
|
+
}
|
529
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@cpointer))
|
530
|
+
end
|
531
|
+
|
532
|
+
def open_database(name="DB")
|
533
|
+
f = FDBC.fdb_cluster_create_database(@cpointer, name, name.bytesize)
|
534
|
+
dpointer = FFI::MemoryPointer.new :pointer
|
535
|
+
FDBC.check_error FDBC.fdb_future_block_until_ready(f)
|
536
|
+
FDBC.check_error FDBC.fdb_future_get_database(f, dpointer)
|
537
|
+
Database.new dpointer.get_pointer(0)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
class Database < FormerFuture
|
542
|
+
attr_reader :options
|
543
|
+
|
544
|
+
def self.finalize(ptr)
|
545
|
+
proc do
|
546
|
+
# puts "Destroying database #{ptr}"
|
547
|
+
FDBC.fdb_database_destroy(ptr)
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
def initialize(dpointer)
|
552
|
+
@dpointer = dpointer
|
553
|
+
@options = DatabaseOptions.new lambda { |code, param|
|
554
|
+
FDBC.check_error FDBC.fdb_database_set_option(dpointer, code, param, param.nil? ? 0 : param.bytesize)
|
555
|
+
}
|
556
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@dpointer))
|
557
|
+
end
|
558
|
+
|
559
|
+
def create_transaction
|
560
|
+
tr = FFI::MemoryPointer.new :pointer
|
561
|
+
FDBC.check_error FDBC.fdb_database_create_transaction(@dpointer, tr)
|
562
|
+
Transaction.new(tr.read_pointer, self)
|
563
|
+
end
|
564
|
+
|
565
|
+
def transact
|
566
|
+
tr = create_transaction
|
567
|
+
committed = false
|
568
|
+
begin
|
569
|
+
ret = yield tr
|
570
|
+
# puts ret
|
571
|
+
tr.commit.wait
|
572
|
+
committed = true
|
573
|
+
rescue Error => e
|
574
|
+
# puts "Rescued #{e}"
|
575
|
+
tr.on_error(e).wait
|
576
|
+
end until committed
|
577
|
+
ret
|
578
|
+
end
|
579
|
+
|
580
|
+
def set(key, value)
|
581
|
+
transact do |tr|
|
582
|
+
tr[key] = value
|
583
|
+
end
|
584
|
+
end
|
585
|
+
alias []= set
|
586
|
+
|
587
|
+
def get(key)
|
588
|
+
transact do |tr|
|
589
|
+
tr[key].value
|
590
|
+
end
|
591
|
+
end
|
592
|
+
alias [] get
|
593
|
+
|
594
|
+
def get_range(bkeysel, ekeysel, options={}, &block)
|
595
|
+
transact do |tr|
|
596
|
+
a = tr.get_range(bkeysel, ekeysel, options).to_a
|
597
|
+
if block_given?
|
598
|
+
a.each &block
|
599
|
+
else
|
600
|
+
a
|
601
|
+
end
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
def clear(key)
|
606
|
+
transact do |tr|
|
607
|
+
tr.clear(key)
|
608
|
+
end
|
609
|
+
end
|
610
|
+
|
611
|
+
def clear_range(bkey, ekey)
|
612
|
+
transact do |tr|
|
613
|
+
tr.clear_range(bkey, ekey)
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def get_key(keysel)
|
618
|
+
transact do |tr|
|
619
|
+
tr.get_key(keysel).value
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
def get_range_start_with(prefix, options={}, &block)
|
624
|
+
transact do |tr|
|
625
|
+
a = tr.get_range_start_with(prefix, options).to_a
|
626
|
+
if block_given?
|
627
|
+
a.each &block
|
628
|
+
else
|
629
|
+
a
|
630
|
+
end
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
def clear_range_start_with(prefix)
|
635
|
+
transact do |tr|
|
636
|
+
tr.clear_range_start_with(prefix)
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
def get_and_watch(key)
|
641
|
+
transact do |tr|
|
642
|
+
value = tr.get(key)
|
643
|
+
watch = tr.watch(key)
|
644
|
+
[value.value, watch]
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
def set_and_watch(key, value)
|
649
|
+
transact do |tr|
|
650
|
+
tr.set(key, value)
|
651
|
+
tr.watch(key)
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
def clear_and_watch(key)
|
656
|
+
transact do |tr|
|
657
|
+
tr.clear(key)
|
658
|
+
tr.watch(key)
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
def watch(key)
|
663
|
+
transact do |tr|
|
664
|
+
tr.watch(key)
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
class KeySelector
|
670
|
+
attr_reader :key, :or_equal, :offset
|
671
|
+
|
672
|
+
def initialize(key, or_equal, offset)
|
673
|
+
@key = key
|
674
|
+
@or_equal = or_equal
|
675
|
+
@offset = offset
|
676
|
+
end
|
677
|
+
|
678
|
+
def self.last_less_than(key)
|
679
|
+
self.new(key, 0, 0)
|
680
|
+
end
|
681
|
+
|
682
|
+
def self.last_less_or_equal(key)
|
683
|
+
self.new(key, 1, 0)
|
684
|
+
end
|
685
|
+
|
686
|
+
def self.first_greater_than(key)
|
687
|
+
self.new(key, 1, 1)
|
688
|
+
end
|
689
|
+
|
690
|
+
def self.first_greater_or_equal(key)
|
691
|
+
self.new(key, 0, 1)
|
692
|
+
end
|
693
|
+
|
694
|
+
def +(offset)
|
695
|
+
KeySelector.new(@key, @or_equal, @offset + offset)
|
696
|
+
end
|
697
|
+
|
698
|
+
def -(offset)
|
699
|
+
KeySelector.new(@key, @or_equal, @offset - offset)
|
700
|
+
end
|
701
|
+
end
|
702
|
+
|
703
|
+
class TransactionRead
|
704
|
+
attr_reader :db
|
705
|
+
attr_reader :tpointer
|
706
|
+
|
707
|
+
def self.finalize(ptr)
|
708
|
+
proc do
|
709
|
+
#puts "Destroying transaction #{ptr}"
|
710
|
+
FDBC.fdb_transaction_destroy(ptr)
|
711
|
+
end
|
712
|
+
end
|
713
|
+
|
714
|
+
def initialize(tpointer, db, is_snapshot)
|
715
|
+
@tpointer = tpointer
|
716
|
+
@db = db
|
717
|
+
@is_snapshot = is_snapshot
|
718
|
+
|
719
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@tpointer))
|
720
|
+
end
|
721
|
+
|
722
|
+
def transact
|
723
|
+
yield self
|
724
|
+
end
|
725
|
+
|
726
|
+
def get_read_version
|
727
|
+
Version.new(FDBC.fdb_transaction_get_read_version @tpointer)
|
728
|
+
end
|
729
|
+
|
730
|
+
def get(key)
|
731
|
+
key = FDB.key_to_bytes(key)
|
732
|
+
Value.new(FDBC.fdb_transaction_get(@tpointer, key, key.bytesize, @is_snapshot))
|
733
|
+
end
|
734
|
+
alias [] get
|
735
|
+
|
736
|
+
def get_key(keysel)
|
737
|
+
key = FDB.key_to_bytes(keysel.key)
|
738
|
+
Key.new(FDBC.fdb_transaction_get_key(@tpointer, key, key.bytesize, keysel.or_equal, keysel.offset, @is_snapshot))
|
739
|
+
end
|
740
|
+
|
741
|
+
def to_selector(key_or_selector)
|
742
|
+
if key_or_selector.kind_of? KeySelector
|
743
|
+
key_or_selector
|
744
|
+
else
|
745
|
+
KeySelector.first_greater_or_equal key_or_selector
|
746
|
+
end
|
747
|
+
end
|
748
|
+
private :to_selector
|
749
|
+
|
750
|
+
@@RangeEnum = Class.new do
|
751
|
+
include Enumerable
|
752
|
+
|
753
|
+
def initialize(get_range, bsel, esel, limit, reverse, streaming_mode)
|
754
|
+
@get_range = get_range
|
755
|
+
|
756
|
+
@bsel = bsel
|
757
|
+
@esel = esel
|
758
|
+
|
759
|
+
@limit = limit
|
760
|
+
@reverse = reverse
|
761
|
+
@mode = streaming_mode
|
762
|
+
|
763
|
+
@future = @get_range.call(@bsel, @esel, @limit, @mode, 1, @reverse)
|
764
|
+
end
|
765
|
+
|
766
|
+
def to_a
|
767
|
+
o = self.dup
|
768
|
+
o.instance_eval do
|
769
|
+
if @mode == @@StreamingMode["ITERATOR"][0]
|
770
|
+
if @limit.zero?
|
771
|
+
@mode = @@StreamingMode["WANT_ALL"][0]
|
772
|
+
else
|
773
|
+
@mode = @@StreamingMode["EXACT"][0]
|
774
|
+
end
|
775
|
+
end
|
776
|
+
end
|
777
|
+
Enumerable.instance_method(:to_a).bind(o).call
|
778
|
+
end
|
779
|
+
|
780
|
+
def each
|
781
|
+
bsel = @bsel
|
782
|
+
esel = @esel
|
783
|
+
limit = @limit
|
784
|
+
|
785
|
+
iteration = 1 # the first read was fired off when the RangeEnum was initialized
|
786
|
+
future = @future
|
787
|
+
|
788
|
+
done = false
|
789
|
+
|
790
|
+
while !done
|
791
|
+
if future
|
792
|
+
kvs, count, more = future.wait
|
793
|
+
index = 0
|
794
|
+
future = nil
|
795
|
+
|
796
|
+
return if count.zero?
|
797
|
+
end
|
798
|
+
|
799
|
+
result = kvs[index]
|
800
|
+
index += 1
|
801
|
+
|
802
|
+
if index == count
|
803
|
+
if more.zero? || limit == count
|
804
|
+
done = true
|
805
|
+
else
|
806
|
+
iteration += 1
|
807
|
+
if limit.nonzero?
|
808
|
+
limit -= count
|
809
|
+
end
|
810
|
+
if @reverse.nonzero?
|
811
|
+
esel = KeySelector.first_greater_or_equal(kvs.last.key)
|
812
|
+
else
|
813
|
+
bsel = KeySelector.first_greater_than(kvs.last.key)
|
814
|
+
end
|
815
|
+
future = @get_range.call(bsel, esel, limit, @mode, iteration, @reverse)
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
yield result
|
820
|
+
end
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
def get_range(bkeysel, ekeysel, options={}, &block)
|
825
|
+
defaults = { :limit => 0, :reverse => false, :streaming_mode => :iterator }
|
826
|
+
options = defaults.merge options
|
827
|
+
bsel = to_selector bkeysel
|
828
|
+
esel = to_selector ekeysel
|
829
|
+
|
830
|
+
if options[:streaming_mode].kind_of? Symbol
|
831
|
+
streaming_mode = @@StreamingMode[options[:streaming_mode].to_s.upcase]
|
832
|
+
raise ArgumentError, "#{options[:streaming_mode]} is not a valid streaming mode" if !streaming_mode
|
833
|
+
streaming_mode = streaming_mode[0]
|
834
|
+
else
|
835
|
+
streaming_mode = options[:streaming_mode]
|
836
|
+
end
|
837
|
+
|
838
|
+
r = @@RangeEnum.new(lambda {|bsel, esel, limit, streaming_mode, iteration, reverse|
|
839
|
+
begin_key = FDB.key_to_bytes(bsel.key)
|
840
|
+
end_key = FDB.key_to_bytes(esel.key)
|
841
|
+
FDB::FutureKeyValueArray.new(FDBC.fdb_transaction_get_range(@tpointer, begin_key, begin_key.bytesize, bsel.or_equal, bsel.offset, end_key, end_key.bytesize, esel.or_equal, esel.offset, limit, 0, streaming_mode, iteration, @is_snapshot, reverse))
|
842
|
+
}, bsel, esel, options[:limit], options[:reverse] ? 1 : 0, streaming_mode)
|
843
|
+
|
844
|
+
if !block_given?
|
845
|
+
r
|
846
|
+
else
|
847
|
+
r.each &block
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
def get_range_start_with(prefix, options={}, &block)
|
852
|
+
prefix = FDB.key_to_bytes(prefix)
|
853
|
+
prefix = prefix.dup.force_encoding "BINARY"
|
854
|
+
get_range(prefix, FDB.strinc(prefix), options, &block)
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
TransactionRead.class_variable_set("@@StreamingMode", @@StreamingMode)
|
859
|
+
|
860
|
+
class Transaction < TransactionRead
|
861
|
+
attr_reader :snapshot, :options
|
862
|
+
|
863
|
+
def initialize(tpointer, db)
|
864
|
+
super(tpointer, db, 0)
|
865
|
+
@snapshot = TransactionRead.new(tpointer, db, 1)
|
866
|
+
|
867
|
+
@options = TransactionOptions.new lambda { |code, param|
|
868
|
+
FDBC.check_error FDBC.fdb_transaction_set_option(@tpointer, code, param, param.nil? ? 0 : param.bytesize)
|
869
|
+
}
|
870
|
+
|
871
|
+
ObjectSpace.undefine_finalizer self
|
872
|
+
end
|
873
|
+
|
874
|
+
def set(key, value)
|
875
|
+
key = FDB.key_to_bytes(key)
|
876
|
+
value = FDB.value_to_bytes(value)
|
877
|
+
FDBC.fdb_transaction_set(@tpointer, key, key.bytesize, value, value.bytesize)
|
878
|
+
end
|
879
|
+
alias []= set
|
880
|
+
|
881
|
+
def add_read_conflict_range(bkey, ekey)
|
882
|
+
bkey = FDB.key_to_bytes(bkey)
|
883
|
+
ekey = FDB.key_to_bytes(ekey)
|
884
|
+
FDBC.check_error FDBC.fdb_transaction_add_conflict_range(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, @@ConflictRangeType["READ"][0])
|
885
|
+
end
|
886
|
+
|
887
|
+
def add_read_conflict_key(key)
|
888
|
+
key = FDB.key_to_bytes(key)
|
889
|
+
add_read_conflict_range(key, key + "\x00")
|
890
|
+
end
|
891
|
+
|
892
|
+
def add_write_conflict_range(bkey, ekey)
|
893
|
+
bkey = FDB.key_to_bytes(bkey)
|
894
|
+
ekey = FDB.key_to_bytes(ekey)
|
895
|
+
FDBC.check_error FDBC.fdb_transaction_add_conflict_range(@tpointer, bkey, bkey.bytesize, ekey, ekey.bytesize, @@ConflictRangeType["WRITE"][0])
|
896
|
+
end
|
897
|
+
|
898
|
+
def add_write_conflict_key(key)
|
899
|
+
key = FDB.key_to_bytes(key)
|
900
|
+
add_write_conflict_range(key, key + "\x00")
|
901
|
+
end
|
902
|
+
|
903
|
+
def commit
|
904
|
+
FutureNil.new(FDBC.fdb_transaction_commit(@tpointer))
|
905
|
+
end
|
906
|
+
|
907
|
+
def watch(key)
|
908
|
+
key = FDB.key_to_bytes(key)
|
909
|
+
FutureNil.new(FDBC.fdb_transaction_watch(@tpointer, key, key.bytesize))
|
910
|
+
end
|
911
|
+
|
912
|
+
def on_error(e)
|
913
|
+
raise e if !e.kind_of? Error
|
914
|
+
FutureNil.new(FDBC.fdb_transaction_on_error(@tpointer, e.code))
|
915
|
+
end
|
916
|
+
|
917
|
+
def clear(key)
|
918
|
+
key = FDB.key_to_bytes(key)
|
919
|
+
FDBC.fdb_transaction_clear(@tpointer, key, key.bytesize)
|
920
|
+
end
|
921
|
+
|
922
|
+
def clear_range(bkey, ekey)
|
923
|
+
bkey = FDB.key_to_bytes(bkey)
|
924
|
+
ekey = FDB.key_to_bytes(ekey)
|
925
|
+
FDBC.fdb_transaction_clear_range(@tpointer, bkey || "", bkey && bkey.bytesize || 0, ekey || "", ekey && ekey.bytesize || 0)
|
926
|
+
end
|
927
|
+
|
928
|
+
def clear_range_start_with(prefix)
|
929
|
+
prefix = FDB.key_to_bytes(prefix)
|
930
|
+
prefix = prefix.dup.force_encoding "BINARY"
|
931
|
+
clear_range(prefix, FDB.strinc(prefix))
|
932
|
+
end
|
933
|
+
|
934
|
+
def set_read_version(version)
|
935
|
+
FDBC.fdb_transaction_set_read_version(@tpointer, version)
|
936
|
+
end
|
937
|
+
|
938
|
+
def get_committed_version
|
939
|
+
version = FFI::MemoryPointer.new :int64
|
940
|
+
FDBC.check_error FDBC.fdb_transaction_get_committed_version(@tpointer, version)
|
941
|
+
version.read_long_long
|
942
|
+
end
|
943
|
+
|
944
|
+
def get_versionstamp
|
945
|
+
Key.new(FDBC.fdb_transaction_get_versionstamp(@tpointer))
|
946
|
+
end
|
947
|
+
|
948
|
+
def reset
|
949
|
+
FDBC.fdb_transaction_reset @tpointer
|
950
|
+
end
|
951
|
+
|
952
|
+
def cancel
|
953
|
+
FDBC.fdb_transaction_cancel @tpointer
|
954
|
+
end
|
955
|
+
|
956
|
+
def atomic_op(code, key, param)
|
957
|
+
key = FDB.key_to_bytes(key)
|
958
|
+
param = FDB.value_to_bytes(param)
|
959
|
+
FDBC.fdb_transaction_atomic_op(@tpointer, key, key.bytesize, param, param.bytesize, code)
|
960
|
+
end
|
961
|
+
private :atomic_op
|
962
|
+
end
|
963
|
+
|
964
|
+
@@MutationType.each_pair do |k,v|
|
965
|
+
p = Proc.new do |key, param| atomic_op(v[0], key, param) end
|
966
|
+
Transaction.class_eval do
|
967
|
+
define_method("#{k.downcase}") do |key, param|
|
968
|
+
atomic_op(v[0], key, param)
|
969
|
+
end
|
970
|
+
end
|
971
|
+
Database.class_eval do
|
972
|
+
define_method("#{k.downcase}") do |key, param|
|
973
|
+
transact do |tr|
|
974
|
+
tr.send("#{k.downcase}", key, param)
|
975
|
+
end
|
976
|
+
end
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
Transaction.class_variable_set("@@ConflictRangeType", @@ConflictRangeType)
|
981
|
+
|
982
|
+
class Value < LazyString
|
983
|
+
def getter
|
984
|
+
present = FFI::MemoryPointer.new :int
|
985
|
+
value = FFI::MemoryPointer.new :pointer
|
986
|
+
length = FFI::MemoryPointer.new :int
|
987
|
+
FDBC.check_error FDBC.fdb_future_get_value(@fpointer, present, value, length)
|
988
|
+
if present.read_int > 0
|
989
|
+
@value = value.read_pointer.read_bytes(length.read_int)
|
990
|
+
end
|
991
|
+
end
|
992
|
+
private :getter
|
993
|
+
end
|
994
|
+
|
995
|
+
class Key < LazyString
|
996
|
+
def getter
|
997
|
+
key = FFI::MemoryPointer.new :pointer
|
998
|
+
key_length = FFI::MemoryPointer.new :int
|
999
|
+
FDBC.check_error FDBC.fdb_future_get_key(@fpointer, key, key_length)
|
1000
|
+
if key_length.read_int.zero?
|
1001
|
+
@value = ''
|
1002
|
+
else
|
1003
|
+
@value = key.read_pointer.read_bytes(key_length.read_int)
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
private :getter
|
1007
|
+
end
|
1008
|
+
|
1009
|
+
class KeyValue
|
1010
|
+
attr_reader :key, :value
|
1011
|
+
|
1012
|
+
def initialize(key, value)
|
1013
|
+
@key = key
|
1014
|
+
@value = value
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
end
|