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.
@@ -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