libcouchbase 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +11 -8
  3. data/ext/libcouchbase/CMakeLists.txt +1 -1
  4. data/ext/libcouchbase/README.markdown +38 -6
  5. data/ext/libcouchbase/RELEASE_NOTES.markdown +151 -0
  6. data/ext/libcouchbase/cmake/Modules/GenerateConfigDotH.cmake +2 -2
  7. data/ext/libcouchbase/cmake/Modules/GetVersionInfo.cmake +3 -3
  8. data/ext/libcouchbase/cmake/source_files.cmake +1 -0
  9. data/ext/libcouchbase/contrib/cJSON/cJSON.c +686 -288
  10. data/ext/libcouchbase/contrib/cJSON/cJSON.h +0 -0
  11. data/ext/libcouchbase/contrib/cbsasl/src/hash.c +17 -17
  12. data/ext/libcouchbase/contrib/cliopts/cliopts.c +76 -0
  13. data/ext/libcouchbase/contrib/cliopts/cliopts.h +66 -15
  14. data/ext/libcouchbase/contrib/genhash/genhash.c +1 -2
  15. data/ext/libcouchbase/contrib/lcb-jsoncpp/lcb-jsoncpp.cpp +4 -3
  16. data/ext/libcouchbase/example/instancepool/main.cc +12 -2
  17. data/ext/libcouchbase/example/libeventdirect/main.c +99 -25
  18. data/ext/libcouchbase/example/minimal/minimal.c +7 -5
  19. data/ext/libcouchbase/example/observe/durability.c +102 -0
  20. data/ext/libcouchbase/example/observe/observe.c +19 -6
  21. data/ext/libcouchbase/example/subdoc/subdoc-xattrs.c +1 -2
  22. data/ext/libcouchbase/include/libcouchbase/cntl-private.h +6 -8
  23. data/ext/libcouchbase/include/libcouchbase/cntl.h +84 -64
  24. data/ext/libcouchbase/include/libcouchbase/couchbase.h +295 -78
  25. data/ext/libcouchbase/include/libcouchbase/deprecated.h +2 -2
  26. data/ext/libcouchbase/include/libcouchbase/error.h +1 -1
  27. data/ext/libcouchbase/include/libcouchbase/iops.h +9 -9
  28. data/ext/libcouchbase/include/libcouchbase/ixmgmt.h +2 -2
  29. data/ext/libcouchbase/include/libcouchbase/n1ql.h +69 -7
  30. data/ext/libcouchbase/include/libcouchbase/vbucket.h +17 -0
  31. data/ext/libcouchbase/include/libcouchbase/views.h +3 -3
  32. data/ext/libcouchbase/include/memcached/protocol_binary.h +62 -1
  33. data/ext/libcouchbase/packaging/deb/control +1 -1
  34. data/ext/libcouchbase/packaging/rpm/libcouchbase.spec.in +37 -36
  35. data/ext/libcouchbase/src/bootstrap.cc +22 -8
  36. data/ext/libcouchbase/src/bucketconfig/bc_cccp.cc +1 -1
  37. data/ext/libcouchbase/src/bucketconfig/bc_http.cc +0 -1
  38. data/ext/libcouchbase/src/bucketconfig/confmon.cc +13 -8
  39. data/ext/libcouchbase/src/callbacks.c +2 -0
  40. data/ext/libcouchbase/src/cntl.cc +28 -17
  41. data/ext/libcouchbase/src/dns-srv.cc +1 -2
  42. data/ext/libcouchbase/src/dump.cc +4 -0
  43. data/ext/libcouchbase/src/errmap.h +89 -16
  44. data/ext/libcouchbase/src/handler.cc +28 -11
  45. data/ext/libcouchbase/src/http/http-priv.h +4 -1
  46. data/ext/libcouchbase/src/http/http.cc +3 -0
  47. data/ext/libcouchbase/src/instance.cc +1 -1
  48. data/ext/libcouchbase/src/internal.h +1 -0
  49. data/ext/libcouchbase/src/lcbio/connect.cc +2 -3
  50. data/ext/libcouchbase/src/lcbio/manager.cc +2 -2
  51. data/ext/libcouchbase/src/lcbio/ssl.h +10 -0
  52. data/ext/libcouchbase/src/mc/mcreq.c +8 -0
  53. data/ext/libcouchbase/src/mcserver/mcserver.cc +14 -1
  54. data/ext/libcouchbase/src/n1ql/ixmgmt.cc +0 -3
  55. data/ext/libcouchbase/src/n1ql/n1ql.cc +22 -29
  56. data/ext/libcouchbase/src/n1ql/params.cc +46 -1
  57. data/ext/libcouchbase/src/newconfig.cc +4 -4
  58. data/ext/libcouchbase/src/operations/durability-seqno.cc +4 -0
  59. data/ext/libcouchbase/src/operations/durability.cc +3 -0
  60. data/ext/libcouchbase/src/operations/ping.cc +315 -0
  61. data/ext/libcouchbase/src/operations/stats.cc +10 -0
  62. data/ext/libcouchbase/src/operations/subdoc.cc +13 -1
  63. data/ext/libcouchbase/src/retrychk.cc +1 -0
  64. data/ext/libcouchbase/src/settings.c +2 -0
  65. data/ext/libcouchbase/src/settings.h +13 -7
  66. data/ext/libcouchbase/src/ssl/ssl_c.c +28 -2
  67. data/ext/libcouchbase/src/ssl/ssl_common.c +3 -0
  68. data/ext/libcouchbase/src/ssl/ssl_e.c +15 -1
  69. data/ext/libcouchbase/src/ssl/ssl_iot_common.h +3 -1
  70. data/ext/libcouchbase/src/timings.c +0 -1
  71. data/ext/libcouchbase/src/vbucket/vbucket.c +49 -1
  72. data/ext/libcouchbase/tests/iotests/mock-environment.cc +58 -40
  73. data/ext/libcouchbase/tests/iotests/mock-environment.h +23 -4
  74. data/ext/libcouchbase/tests/iotests/mock-unit-test.h +8 -8
  75. data/ext/libcouchbase/tests/iotests/t_behavior.cc +5 -5
  76. data/ext/libcouchbase/tests/iotests/t_durability.cc +50 -0
  77. data/ext/libcouchbase/tests/iotests/t_eerrs.cc +4 -2
  78. data/ext/libcouchbase/tests/iotests/t_errmap.cc +6 -3
  79. data/ext/libcouchbase/tests/iotests/t_lock.cc +5 -6
  80. data/ext/libcouchbase/tests/iotests/t_misc.cc +44 -0
  81. data/ext/libcouchbase/tests/iotests/t_serverops.cc +1 -0
  82. data/ext/libcouchbase/tests/iotests/t_subdoc.cc +28 -0
  83. data/ext/libcouchbase/tests/iotests/t_views.cc +22 -10
  84. data/ext/libcouchbase/tools/CMakeLists.txt +21 -1
  85. data/ext/libcouchbase/tools/cbc-handlers.h +23 -3
  86. data/ext/libcouchbase/tools/cbc-n1qlback.cc +1 -1
  87. data/ext/libcouchbase/tools/cbc-pillowfight.cc +126 -26
  88. data/ext/libcouchbase/tools/cbc-proxy.cc +403 -0
  89. data/ext/libcouchbase/tools/cbc-subdoc.cc +826 -0
  90. data/ext/libcouchbase/tools/cbc.cc +149 -37
  91. data/ext/libcouchbase/tools/common/options.h +5 -2
  92. data/ext/libcouchbase/tools/linenoise/linenoise.c +15 -15
  93. data/lib/libcouchbase.rb +4 -0
  94. data/lib/libcouchbase/bucket.rb +51 -0
  95. data/lib/libcouchbase/connection.rb +100 -13
  96. data/lib/libcouchbase/ext/libcouchbase.rb +40 -0
  97. data/lib/libcouchbase/ext/libcouchbase/cmdsubdoc.rb +13 -1
  98. data/lib/libcouchbase/ext/libcouchbase/enums.rb +2 -1
  99. data/lib/libcouchbase/ext/libcouchbase/sdspec.rb +5 -0
  100. data/lib/libcouchbase/subdoc_request.rb +129 -0
  101. data/lib/libcouchbase/version.rb +1 -1
  102. data/spec/bucket_spec.rb +15 -1
  103. data/spec/connection_spec.rb +1 -1
  104. data/spec/subdoc_spec.rb +192 -0
  105. metadata +13 -4
  106. data/ext/libcouchbase/.travis.yml +0 -19
data/lib/libcouchbase.rb CHANGED
@@ -8,6 +8,9 @@ module Libcouchbase
8
8
  require 'libcouchbase/callbacks'
9
9
  require 'libcouchbase/connection'
10
10
 
11
+ DefaultOpts = Struct.new(:host, :bucket, :username, :password)
12
+ Defaults = DefaultOpts.new('127.0.0.1', 'default')
13
+
11
14
  class Results
12
15
  include Enumerable
13
16
 
@@ -33,4 +36,5 @@ module Libcouchbase
33
36
  autoload :ResultsEM, 'libcouchbase/results_fiber'
34
37
  autoload :ResultsLibuv, 'libcouchbase/results_fiber'
35
38
  autoload :ResultsNative, 'libcouchbase/results_native'
39
+ autoload :SubdocRequest, 'libcouchbase/subdoc_request'
36
40
  end
@@ -164,6 +164,15 @@ module Libcouchbase
164
164
  get(key, quiet: true)
165
165
  end
166
166
 
167
+ # A helper method for returning a default value if one doesn't exist for the key
168
+ def fetch(key, value = nil, async: false, **opts)
169
+ cached_obj = get(key, quiet: true, async: false, extended: false)
170
+ return cached_obj if cached_obj
171
+ value = value || yield
172
+ set(key, value, opts.merge(async: false, extended: false))
173
+ value
174
+ end
175
+
167
176
  # Add the item to the database, but fail if the object exists already
168
177
  #
169
178
  # @param key [String, Symbol] Key used to reference the value.
@@ -534,6 +543,48 @@ module Libcouchbase
534
543
  result @connection.touch(**opts), async
535
544
  end
536
545
 
546
+ # Perform subdocument operations on a key.
547
+ #
548
+ # Yields a request builder to a block and applies the operations performed
549
+ #
550
+ # @param [String, Symbol] key
551
+ #
552
+ # @yieldparam [Libcouchbase::SubdocRequest] the subdocument request object used to define the request
553
+ #
554
+ # @example Perform a subdocument operation using a block
555
+ # c.subdoc(:foo) { |subdoc|
556
+ # subdoc.get('sub.key')
557
+ # subdoc.exists?('other.key')
558
+ # subdoc.get_count('some.array')
559
+ # } # => ["sub key val", true, 23]
560
+ #
561
+ # @example perform a subdocument operation using execute!
562
+ # c.subdoc(:foo).get(:bob).execute! # => { age: 13, working: false }
563
+ #
564
+ # @example perform multiple subdocument operations using execute!
565
+ # c.subdoc(:foo)
566
+ # .get(:bob).get(:jane).execute! # => [{ age: 13, working: false }, { age: 47, working: true }]
567
+ #
568
+ # @example perform a subdocument mutation operation
569
+ # c.subdoc(:foo).counter('bob.age', 1).execute! # => 14
570
+ def subdoc(key, quiet: @quiet, **opts)
571
+ if block_given?
572
+ sd = SubdocRequest.new(key, quiet)
573
+ yield sd
574
+ subdoc_execute!(sd, opts)
575
+ else
576
+ SubdocRequest.new(key, quiet, bucket: self, exec_opts: opts)
577
+ end
578
+ end
579
+
580
+ def subdoc_execute!(sd, extended: false, async: false, **opts)
581
+ promise = @connection.subdoc(sd, opts).then { |resp|
582
+ raise resp.value if resp.value.is_a?(::Exception)
583
+ extended ? resp : resp.value
584
+ }
585
+ result promise, async
586
+ end
587
+
537
588
  # Fetch design docs stored in current bucket
538
589
  #
539
590
  # @return [Libcouchbase::DesignDocs]
@@ -40,6 +40,8 @@ module Libcouchbase
40
40
  define_callback function: :callback_remove
41
41
  define_callback function: :callback_cbflush
42
42
  define_callback function: :callback_http
43
+ define_callback function: :callback_sdlookup # subdoc lookup
44
+ define_callback function: :callback_sdmutate
43
45
 
44
46
  # These are passed with the request
45
47
  define_callback function: :viewquery_callback
@@ -58,7 +60,7 @@ module Libcouchbase
58
60
  end
59
61
 
60
62
 
61
- def initialize(hosts: '127.0.0.1', bucket: 'default', password: nil, thread: nil, **opts)
63
+ def initialize(hosts: Defaults.host, bucket: Defaults.bucket, username: Defaults.username, password: Defaults.password, thread: nil, **opts)
62
64
  # build host string http://docs.couchbase.com/sdk-api/couchbase-c-client-2.5.6/group__lcb-init.html
63
65
  hosts = Array(hosts).flatten.join(',')
64
66
  connstr = "couchbase://#{hosts}/#{bucket}"
@@ -95,7 +97,7 @@ module Libcouchbase
95
97
  @connection = Ext::CreateSt.new
96
98
  @connection[:version] = 3
97
99
  @connection[:v][:v3][:connstr] = FFI::MemoryPointer.from_string(connstr)
98
- @connection[:v][:v3][:username] = FFI::MemoryPointer.from_string(bucket.to_s)
100
+ @connection[:v][:v3][:username] = FFI::MemoryPointer.from_string(username&.to_s || bucket.to_s)
99
101
  @connection[:v][:v3][:passwd] = FFI::MemoryPointer.from_string(password) if password
100
102
  @connection[:v][:v3][:io] = @io_ptr.get_pointer(0)
101
103
  @handle_ptr = FFI::MemoryPointer.new :pointer, 1
@@ -133,15 +135,17 @@ module Libcouchbase
133
135
  # Register the callbacks we are interested in
134
136
  Ext.set_bootstrap_callback(@handle, callback(:bootstrap_callback))
135
137
 
136
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_get], callback(:callback_get))
137
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_unlock], callback(:callback_unlock))
138
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_store], callback(:callback_store))
139
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_storedur],callback(:callback_storedur))
140
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_counter], callback(:callback_counter))
141
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_touch], callback(:callback_touch))
142
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_remove], callback(:callback_remove))
143
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_http], callback(:callback_http))
144
- Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_cbflush], callback(:callback_cbflush)) if @flush_enabled
138
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_get], callback(:callback_get))
139
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_unlock], callback(:callback_unlock))
140
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_store], callback(:callback_store))
141
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_storedur], callback(:callback_storedur))
142
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_counter], callback(:callback_counter))
143
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_touch], callback(:callback_touch))
144
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_remove], callback(:callback_remove))
145
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_http], callback(:callback_http))
146
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_sdlookup], callback(:callback_sdlookup))
147
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_sdmutate], callback(:callback_sdmutate))
148
+ Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_cbflush], callback(:callback_cbflush)) if @flush_enabled
145
149
 
146
150
  # Connect to the database
147
151
  err = Ext.connect(@handle)
@@ -453,6 +457,31 @@ module Libcouchbase
453
457
  defer.promise
454
458
  end
455
459
 
460
+ def subdoc(request, expire_in: nil, ttl: nil, expire_at: nil, cas: nil, **opts)
461
+ raise 'not connected' unless @handle
462
+ defer ||= @reactor.defer
463
+
464
+ cmd = Ext::CMDSUBDOC.new
465
+ req = Request.new(cmd, defer, request.key, request)
466
+ key = cmd_set_key(req, cmd, request.key)
467
+
468
+ cmd[:multimode] = request.mode == :mutate ? Ext::CMDSUBDOC::SDMULTI_MODE_MUTATE : Ext::CMDSUBDOC::SDMULTI_MODE_LOOKUP
469
+ cmd[:specs], cmd[:nspecs] = request.to_specs_array
470
+
471
+ cmd[:cas] = cas if cas
472
+ expire_in ||= ttl
473
+ cmd[:exptime] = expire_in ? expires_in(expire_in) : expire_at.to_i
474
+
475
+ @reactor.schedule {
476
+ pointer = cmd.to_ptr
477
+ @requests[pointer.address] = req
478
+ check_error(key, defer, Ext.subdoc3(@handle, pointer, cmd), subdoc: true)
479
+ request.free_memory
480
+ }
481
+
482
+ defer.promise
483
+ end
484
+
456
485
  # http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-flush.html
457
486
  def flush(defer: nil)
458
487
  raise 'not connected' unless @handle
@@ -608,7 +637,7 @@ module Libcouchbase
608
637
  end
609
638
  end
610
639
 
611
- def check_error(key, defer, err)
640
+ def check_error(key, defer, err, subdoc: false)
612
641
  if err != :success
613
642
  error = Error.lookup(err).new("request not scheduled for #{key}")
614
643
  backtrace = caller
@@ -717,6 +746,63 @@ module Libcouchbase
717
746
  end
718
747
  end
719
748
 
749
+ def callback_sdlookup(handle, type, response)
750
+ resp = Ext::RESPSUBDOC.new response
751
+ resp_callback_common(resp, :callback_sdlookup) do |req, cb|
752
+ subdoc_common(resp, req, cb)
753
+ end
754
+ end
755
+
756
+ # Only counter returns a result
757
+ def callback_sdmutate(handle, type, response)
758
+ resp = Ext::RESPSUBDOC.new response
759
+ resp_callback_common(resp, :callback_sdmutate) do |req, cb|
760
+ subdoc_common(resp, req, cb)
761
+ end
762
+ end
763
+
764
+ def subdoc_common(resp, req, cb)
765
+ iterval = FFI::MemoryPointer.new(:ulong, 1)
766
+ cur_res = Ext::SDENTRY.new
767
+ values = []
768
+ index = 0
769
+
770
+ ignore = req.value.ignore
771
+ mutation = req.value.mode == :mutate
772
+
773
+ loop do
774
+ check = Ext.sdresult_next(resp, cur_res, iterval)
775
+ break if check == 0
776
+
777
+ if cur_res[:status] == :success
778
+ count = cur_res[:nvalue]
779
+ if count > 0
780
+ result = cur_res[:value].read_string(count)
781
+ else
782
+ result = true # success response
783
+ end
784
+ result = "[#{result}]"
785
+ values << JSON.parse(result, DECODE_OPTIONS)[0]
786
+ elsif cur_res[:status] == :subdoc_path_enoent && ignore[mutation ? cur_res[:index] : index]
787
+ values << nil
788
+ else
789
+ values << Error.lookup(cur_res[:status]).new("Subdoc #{cb} failed for #{req.key} index #{mutation ? cur_res[:index] : index}")
790
+ end
791
+
792
+ index += 1
793
+ end
794
+
795
+ # Return the single result instead of an array if single
796
+ is_single = resp[:rflags] & Ext::RESPFLAGS[:resp_f_sdsingle] > 0
797
+ if is_single
798
+ values = values.first
799
+ elsif values.empty? # multiple mutate arrays should return true (same as a single mutate)
800
+ values = true
801
+ end
802
+
803
+ Response.new(cb, req.key, resp[:cas], values)
804
+ end
805
+
720
806
  def callback_cbflush(handle, type, response)
721
807
  resp = Ext::RESPBASE.new response
722
808
  resp_callback_common(resp, :callback_cbflush) do |req, cb|
@@ -756,7 +842,8 @@ module Libcouchbase
756
842
  req = @requests.delete(resp[:cookie].address)
757
843
  if req
758
844
  begin
759
- if resp[:rc] == :success
845
+ # Errors will be provided in the response
846
+ if resp[:rc] == :success || resp[:rc] == :subdoc_multi_failure
760
847
  req.defer.resolve(yield(req, callback))
761
848
  else
762
849
  req.defer.reject(Error.lookup(resp[:rc]).new("#{callback} failed for #{req.key}"))
@@ -1016,6 +1016,46 @@ module Libcouchbase::Ext
1016
1016
  #
1017
1017
  attach_function :n1p_posparam, :lcb_n1p_posparam, [N1QLPARAMS.by_ref, :string, :ulong], ErrorT
1018
1018
 
1019
+ # (Not documented)
1020
+ #
1021
+ # @method n1p_readonly(params, readonly)
1022
+ # @param [N1QLPARAMS] params
1023
+ # @param [Integer] readonly if non-zero, the query will be read-only
1024
+ # @return [ErrorT]
1025
+ # @scope class
1026
+ #
1027
+ attach_function :n1p_readonly, :lcb_n1p_readonly, [N1QLPARAMS.by_ref, :int], ErrorT
1028
+
1029
+ # (Not documented)
1030
+ #
1031
+ # @method n1p_readonly(params, readonly)
1032
+ # @param [N1QLPARAMS] params
1033
+ # @param [Integer] Sets maximum buffered channel size. Use 0 or a negative number to disable.
1034
+ # @return [ErrorT]
1035
+ # @scope class
1036
+ #
1037
+ attach_function :n1p_scancap, :lcb_n1p_scancap, [N1QLPARAMS.by_ref, :int], ErrorT
1038
+
1039
+ # (Not documented)
1040
+ #
1041
+ # @method n1p_readonly(params, readonly)
1042
+ # @param [N1QLPARAMS] params
1043
+ # @param [Integer] maximum number of items each execution operator can buffer between operators.
1044
+ # @return [ErrorT]
1045
+ # @scope class
1046
+ #
1047
+ attach_function :n1p_pipelinecap, :lcb_n1p_pipelinecap, [N1QLPARAMS.by_ref, :int], ErrorT
1048
+
1049
+ # (Not documented)
1050
+ #
1051
+ # @method n1p_readonly(params, readonly)
1052
+ # @param [N1QLPARAMS] params
1053
+ # @param [Integer] number of items execution operators can batch for fetch from the KV
1054
+ # @return [ErrorT]
1055
+ # @scope class
1056
+ #
1057
+ attach_function :n1p_pipelinebatch, :lcb_n1p_pipelinebatch, [N1QLPARAMS.by_ref, :int], ErrorT
1058
+
1019
1059
  # (Not documented)
1020
1060
  #
1021
1061
  # @method n1p_setopt(params, name, n_name, value, n_value)
@@ -35,14 +35,26 @@ module Libcouchbase::Ext
35
35
  # This field may be left empty, in which case the mode is implicitly
36
36
  # derived from the _first_ command issued.
37
37
  class CMDSUBDOC < FFI::Struct
38
+ # CMD flags
39
+ UPSERT_DOC = (1<<16) # document is to be created if it does not exist.
40
+ INSERT_DOC = (1<<17) # document must be created anew. Fail if it exists
41
+ ACCESS_DELETED = (1<<18) # Access a potentially deleted document.
42
+
43
+ SDMULTI_MODE_INVALID = 0
44
+ SDMULTI_MODE_LOOKUP = 1
45
+ SDMULTI_MODE_MUTATE = 2
46
+
38
47
  layout :cmdflags, :uint,
39
48
  :exptime, :uint,
40
49
  :cas, :ulong_long,
41
50
  :key, KEYBUF.by_value,
42
51
  :hashkey, KEYBUF.by_value,
43
- :specs, SDSPEC.by_ref,
52
+ :specs, :pointer, # ==> SDSPEC.by_ref,
44
53
  :nspecs, :ulong,
45
54
  :error_index, :pointer,
55
+
56
+ # This can either be SDMULTI_MODE_LOOKUP or SDMULTI_MODE_MUTATE
57
+ # This field may be left empty, in which case the mode is implicitly derived from the _first_ command issued.
46
58
  :multimode, :uint
47
59
  end
48
60
 
@@ -124,7 +124,8 @@ module Libcouchbase::Ext
124
124
  :resp_f_clientgen, 2,
125
125
  :resp_f_nmvgen, 4,
126
126
  :resp_f_extdata, 8,
127
- :resp_f_sdsingle, 16
127
+ :resp_f_sdsingle, 16,
128
+ :resp_f_errinfo, 0x20
128
129
  ]
129
130
 
130
131
  # (Not documented)
@@ -17,6 +17,11 @@ module Libcouchbase::Ext
17
17
  # @ref LCB_SDSPEC_SET_VALUE. The contents of the value should be valid
18
18
  # until the operation is scheduled (i.e. lcb_subdoc3())
19
19
  class SDSPEC < FFI::Struct
20
+ MKINTERMEDIATES = (1<<16) # Create intermediate paths
21
+ XATTRPATH = (1<<18) # Access document XATTR path
22
+ XATTR_MACROVALUES = (1<<19) # Access document virtual/materialized path
23
+ XATTR_DELETED_OK = (1<<20) # Access Xattrs of deleted documents
24
+
20
25
  layout :sdcmd, :uint,
21
26
  :options, :uint,
22
27
  :path, KEYBUF.by_value,
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true, encoding: ASCII-8BIT
2
+
3
+ module Libcouchbase; end;
4
+ class Libcouchbase::SubdocRequest
5
+
6
+ def initialize(key, quiet, bucket: nil, exec_opts: nil)
7
+ @key = key.to_s
8
+ raise ArgumentError.new("invalid document key #{key.inspect}") unless @key.length > 0
9
+ @refs = []
10
+ @mode = nil
11
+ @quiet = quiet
12
+ @specs = []
13
+ @ignore = []
14
+
15
+ @bucket = bucket
16
+ @exec_opts = exec_opts
17
+ end
18
+
19
+ attr_reader :mode, :key, :ignore
20
+
21
+ # Internal use only
22
+ def to_specs_array
23
+ return @mem if @mem # effectively freezes this object
24
+ number = @specs.length
25
+ @mem = FFI::MemoryPointer.new(::Libcouchbase::Ext::SDSPEC, number, false)
26
+ @specs.each_with_index do |spec, index|
27
+ struct_bytes = spec.to_ptr.get_bytes(0, ::Libcouchbase::Ext::SDSPEC.size) # (offset, length)
28
+ @mem[index].put_bytes(0, struct_bytes) # (offset, byte_string)
29
+ end
30
+ @specs = nil
31
+ [@mem, number]
32
+ end
33
+
34
+ # Internal use only
35
+ def free_memory
36
+ @refs = nil
37
+ @mem = nil
38
+ end
39
+
40
+ # When not used in block form
41
+ def execute!(**opts)
42
+ opts = @exec_opts.merge(opts)
43
+ @exec_opts = nil
44
+ bucket = @bucket
45
+ @bucket = nil
46
+ bucket.subdoc_execute!(self, **opts)
47
+ end
48
+
49
+
50
+ # =========
51
+ # Lookups
52
+ # =========
53
+
54
+ [ :get, :exists, :get_count ].each do |cmd|
55
+ command = :"sdcmd_#{cmd}"
56
+ define_method cmd do |path, quiet: nil, **opts|
57
+ quiet = @quiet if quiet.nil?
58
+ new_spec(quiet, path, command, :lookup)
59
+ self
60
+ end
61
+ end
62
+ alias_method :exists?, :exists
63
+
64
+
65
+ # ===========
66
+ # Mutations
67
+ # ===========
68
+
69
+ def remove(path, quiet: nil, **opts)
70
+ quiet = @quiet if quiet.nil?
71
+ new_spec(quiet, path, :sdcmd_remove, :mutate)
72
+ self
73
+ end
74
+
75
+ [
76
+ :replace, :dict_add, :dict_upsert, :array_add_first, :array_add_last,
77
+ :array_add_unique, :array_insert, :counter
78
+ ].each do |cmd|
79
+ command = :"sdcmd_#{cmd}"
80
+ define_method cmd do |path, value, create_intermediates: true, **opts|
81
+ spec = new_spec(false, path, command, :mutate, create_intermediates)
82
+ set_value(spec, value)
83
+ self
84
+ end
85
+ end
86
+
87
+
88
+ protected
89
+
90
+
91
+ # http://docs.couchbase.com/sdk-api/couchbase-c-client-2.8.2/group__lcb-subdoc.html#ga53e89dd6b480e81b82fb305d04d92e18
92
+ def new_spec(quiet, path, cmd, mode, create_intermediates = false)
93
+ @mode ||= mode
94
+ raise "unable to perform #{cmd} as mode is currently #{@mode}" if @mode != mode
95
+
96
+ spec = ::Libcouchbase::Ext::SDSPEC.new
97
+ spec[:sdcmd] = ::Libcouchbase::Ext::SUBDOCOP[cmd]
98
+ spec[:options] = ::Libcouchbase::Ext::SDSPEC::MKINTERMEDIATES if create_intermediates
99
+
100
+ loc = path.to_s
101
+ str = ref(loc)
102
+ spec[:path][:type] = :kv_copy
103
+ spec[:path][:contig][:bytes] = str
104
+ spec[:path][:contig][:nbytes] = loc.bytesize
105
+
106
+ @ignore << quiet
107
+ @specs << spec
108
+ spec
109
+ end
110
+
111
+ # http://docs.couchbase.com/sdk-api/couchbase-c-client-2.8.2/group__lcb-subdoc.html#ga61009762f6b23ae2a9685ddb888dc406
112
+ def set_value(spec, value)
113
+ # Create a JSON version of the value.
114
+ # We throw it into an array so strings and numbers etc are valid, then we remove the array.
115
+ val = [value].to_json[1...-1]
116
+ str = ref(val)
117
+ spec[:value][:vtype] = :kv_copy
118
+ spec[:value][:u_buf][:contig][:bytes] = str
119
+ spec[:value][:u_buf][:contig][:nbytes] = val.bytesize
120
+ value
121
+ end
122
+
123
+ # We need to hold a reference to c-strings so they are not GC'd
124
+ def ref(string)
125
+ str = ::FFI::MemoryPointer.from_string(string)
126
+ @refs << str
127
+ str
128
+ end
129
+ end