fdb 5.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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