libcouchbase 1.0.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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