libcouchbase-mapo 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.gitmodules +3 -0
- data/.rspec +1 -0
- data/.travis.yml +38 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.md +445 -0
- data/Rakefile +76 -0
- data/ext/README.md +6 -0
- data/ext/Rakefile +19 -0
- data/lib/libcouchbase.rb +40 -0
- data/lib/libcouchbase/bucket.rb +825 -0
- data/lib/libcouchbase/callbacks.rb +69 -0
- data/lib/libcouchbase/connection.rb +886 -0
- data/lib/libcouchbase/design_docs.rb +92 -0
- data/lib/libcouchbase/error.rb +68 -0
- data/lib/libcouchbase/ext/libcouchbase.rb +1175 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdbase.rb +23 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdcounter.rb +36 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdendure.rb +26 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdfts.rb +24 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdget.rb +30 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdgetreplica.rb +49 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdhttp.rb +58 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdn1ql.rb +40 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdobseqno.rb +33 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdobserve.rb +30 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdstore.rb +40 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdstoredur.rb +45 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdsubdoc.rb +61 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdverbosity.rb +29 -0
- data/lib/libcouchbase/ext/libcouchbase/cmdviewquery.rb +61 -0
- data/lib/libcouchbase/ext/libcouchbase/contigbuf.rb +14 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st.rb +15 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st0.rb +23 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st1.rb +26 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st2.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/create_st3.rb +26 -0
- data/lib/libcouchbase/ext/libcouchbase/crst_u.rb +20 -0
- data/lib/libcouchbase/ext/libcouchbase/durability_opts_st_v.rb +11 -0
- data/lib/libcouchbase/ext/libcouchbase/durability_opts_t.rb +14 -0
- data/lib/libcouchbase/ext/libcouchbase/durabilityopt_sv0.rb +63 -0
- data/lib/libcouchbase/ext/libcouchbase/enums.rb +1007 -0
- data/lib/libcouchbase/ext/libcouchbase/fragbuf.rb +18 -0
- data/lib/libcouchbase/ext/libcouchbase/ftshandle.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/histogram.rb +34 -0
- data/lib/libcouchbase/ext/libcouchbase/http_request_t.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/keybuf.rb +20 -0
- data/lib/libcouchbase/ext/libcouchbase/multicmd_ctx.rb +30 -0
- data/lib/libcouchbase/ext/libcouchbase/mutation_token.rb +17 -0
- data/lib/libcouchbase/ext/libcouchbase/n1qlhandle.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/n1qlparams.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/respbase.rb +29 -0
- data/lib/libcouchbase/ext/libcouchbase/respcounter.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/respendure.rb +49 -0
- data/lib/libcouchbase/ext/libcouchbase/respfts.rb +40 -0
- data/lib/libcouchbase/ext/libcouchbase/respget.rb +44 -0
- data/lib/libcouchbase/ext/libcouchbase/resphttp.rb +48 -0
- data/lib/libcouchbase/ext/libcouchbase/respmcversion.rb +38 -0
- data/lib/libcouchbase/ext/libcouchbase/respn1ql.rb +41 -0
- data/lib/libcouchbase/ext/libcouchbase/respobseqno.rb +52 -0
- data/lib/libcouchbase/ext/libcouchbase/respobserve.rb +41 -0
- data/lib/libcouchbase/ext/libcouchbase/respserverbase.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/respstats.rb +38 -0
- data/lib/libcouchbase/ext/libcouchbase/respstore.rb +32 -0
- data/lib/libcouchbase/ext/libcouchbase/respstoredur.rb +38 -0
- data/lib/libcouchbase/ext/libcouchbase/respsubdoc.rb +35 -0
- data/lib/libcouchbase/ext/libcouchbase/respviewquery.rb +67 -0
- data/lib/libcouchbase/ext/libcouchbase/sdentry.rb +22 -0
- data/lib/libcouchbase/ext/libcouchbase/sdspec.rb +31 -0
- data/lib/libcouchbase/ext/libcouchbase/t.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase/valbuf.rb +22 -0
- data/lib/libcouchbase/ext/libcouchbase/valbuf_u_buf.rb +14 -0
- data/lib/libcouchbase/ext/libcouchbase/viewhandle.rb +7 -0
- data/lib/libcouchbase/ext/libcouchbase_libuv.rb +22 -0
- data/lib/libcouchbase/ext/tasks.rb +39 -0
- data/lib/libcouchbase/n1ql.rb +78 -0
- data/lib/libcouchbase/query_full_text.rb +147 -0
- data/lib/libcouchbase/query_n1ql.rb +123 -0
- data/lib/libcouchbase/query_view.rb +135 -0
- data/lib/libcouchbase/results_fiber.rb +281 -0
- data/lib/libcouchbase/results_native.rb +220 -0
- data/lib/libcouchbase/subdoc_request.rb +139 -0
- data/lib/libcouchbase/version.rb +5 -0
- data/libcouchbase.gemspec +68 -0
- data/spec/bucket_spec.rb +290 -0
- data/spec/connection_spec.rb +257 -0
- data/spec/design_docs_spec.rb +31 -0
- data/spec/error_spec.rb +26 -0
- data/spec/fts_spec.rb +135 -0
- data/spec/n1ql_spec.rb +206 -0
- data/spec/results_libuv_spec.rb +244 -0
- data/spec/results_native_spec.rb +259 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/design.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/data-0000.cbb +0 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/failover.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/meta.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/seqno.json +1 -0
- data/spec/seed/2016-10-25T043505Z/2016-10-25T043505Z-full/bucket-default/node-127.0.0.1%3A8091/snapshot_markers.json +1 -0
- data/spec/subdoc_spec.rb +192 -0
- data/spec/view_spec.rb +201 -0
- data/windows_build.md +36 -0
- metadata +265 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require 'ffi'
|
5
|
+
|
6
|
+
module Libcouchbase
|
7
|
+
module Callbacks
|
8
|
+
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def dispatch_callback(func_name, lookup, args)
|
15
|
+
instance_id = __send__(lookup, *args)
|
16
|
+
inst = @callback_lookup[instance_id]
|
17
|
+
inst.__send__(func_name, *args) if inst.respond_to?(func_name, true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_callback(function:, params: [:pointer, :int, :pointer], ret_val: :void, lookup: :default_lookup)
|
21
|
+
@callback_funcs[function] = ::FFI::Function.new(ret_val, params) do |*args|
|
22
|
+
dispatch_callback(function, lookup, args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Much like include to support inheritance properly
|
27
|
+
# We keep existing callbacks and inherit the lookup (as this will never clash)
|
28
|
+
def inherited(subclass)
|
29
|
+
subclass.instance_variable_set(:@callback_funcs, {}.merge(@callback_funcs))
|
30
|
+
subclass.instance_variable_set(:@callback_lookup, @callback_lookup)
|
31
|
+
subclass.instance_variable_set(:@callback_lock, @callback_lock)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# Provide accessor methods to the class level instance variables
|
36
|
+
attr_reader :callback_lookup, :callback_funcs, :callback_lock
|
37
|
+
|
38
|
+
|
39
|
+
# This function is used to work out the instance the callback is for
|
40
|
+
def default_lookup(req, *args)
|
41
|
+
req.address
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.included(base)
|
46
|
+
base.instance_variable_set(:@callback_funcs, {})
|
47
|
+
base.instance_variable_set(:@callback_lookup, ::Concurrent::Hash.new)
|
48
|
+
base.instance_variable_set(:@callback_lock, ::Mutex.new)
|
49
|
+
base.extend(ClassMethods)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def callback(name, instance_id = @ref)
|
54
|
+
klass = self.class
|
55
|
+
klass.callback_lock.synchronize do
|
56
|
+
klass.callback_lookup[instance_id] = self
|
57
|
+
end
|
58
|
+
klass.callback_funcs[name]
|
59
|
+
end
|
60
|
+
|
61
|
+
def cleanup_callbacks(instance_id = @ref)
|
62
|
+
klass = self.class
|
63
|
+
klass.callback_lock.synchronize do
|
64
|
+
inst = klass.callback_lookup[instance_id]
|
65
|
+
klass.callback_lookup.delete(instance_id) if inst == self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,886 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
|
6
|
+
# Not required on jruby - buckets are cleaned up by GC
|
7
|
+
unless defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
|
8
|
+
at_exit do
|
9
|
+
GC.start
|
10
|
+
connections = []
|
11
|
+
ObjectSpace.each_object(::Libcouchbase::Connection).each do |connection|
|
12
|
+
next unless connection.reactor.running?
|
13
|
+
connections << connection
|
14
|
+
begin
|
15
|
+
connection.destroy
|
16
|
+
rescue => e
|
17
|
+
end
|
18
|
+
end
|
19
|
+
sleep 2 if connections.length > 0
|
20
|
+
connections.each { |c| c.reactor.stop }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
module Libcouchbase
|
26
|
+
Response = Struct.new(:callback, :key, :cas, :value, :metadata)
|
27
|
+
HttpResponse = Struct.new(:callback, :status, :headers, :body, :request)
|
28
|
+
|
29
|
+
class Connection
|
30
|
+
include Callbacks
|
31
|
+
define_callback function: :bootstrap_callback, params: [:pointer, Ext::ErrorT.native_type]
|
32
|
+
|
33
|
+
# This is common for all standard request types
|
34
|
+
define_callback function: :callback_get
|
35
|
+
define_callback function: :callback_unlock
|
36
|
+
define_callback function: :callback_store
|
37
|
+
define_callback function: :callback_storedur
|
38
|
+
define_callback function: :callback_counter
|
39
|
+
define_callback function: :callback_touch
|
40
|
+
define_callback function: :callback_remove
|
41
|
+
define_callback function: :callback_cbflush
|
42
|
+
define_callback function: :callback_http
|
43
|
+
define_callback function: :callback_sdlookup # subdoc lookup
|
44
|
+
define_callback function: :callback_sdmutate
|
45
|
+
|
46
|
+
# These are passed with the request
|
47
|
+
define_callback function: :viewquery_callback
|
48
|
+
define_callback function: :n1ql_callback
|
49
|
+
define_callback function: :fts_callback
|
50
|
+
|
51
|
+
|
52
|
+
Request = Struct.new(:cmd, :defer, :key, :value) do
|
53
|
+
# We need to hold a reference to c-strings so they are not GC'd
|
54
|
+
def ref(string)
|
55
|
+
@refs ||= []
|
56
|
+
str = FFI::MemoryPointer.from_string(string)
|
57
|
+
@refs << str
|
58
|
+
str
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def initialize(hosts: Defaults.host, bucket: Defaults.bucket, username: Defaults.username, password: Defaults.password, thread: nil, **opts)
|
64
|
+
# build host string http://docs.couchbase.com/sdk-api/couchbase-c-client-2.5.6/group__lcb-init.html
|
65
|
+
hosts = Array(hosts).flatten.join(',')
|
66
|
+
connstr = "couchbase://#{hosts}/#{bucket}"
|
67
|
+
connstr = "#{connstr}?#{opts.map { |k, v| "#{k}=#{v}" }.join('&') }" unless opts.empty?
|
68
|
+
|
69
|
+
# It's good to know
|
70
|
+
@bucket = bucket
|
71
|
+
|
72
|
+
# Configure the event loop settings
|
73
|
+
@reactor = thread || ::Libuv::Reactor.current || ::Libuv::Reactor.new
|
74
|
+
@reactor.on_program_interrupt { destroy }
|
75
|
+
@io_ptr = FFI::MemoryPointer.new :pointer, 1
|
76
|
+
|
77
|
+
# Configure Libuv plugin
|
78
|
+
@io_opts = Ext::Libuv::UVOptions.new
|
79
|
+
@io_opts[:version] = 0
|
80
|
+
@io_opts[:loop] = @reactor.handle
|
81
|
+
@io_opts[:start_stop_noop] = 1 # We want to control the start and stopping of the loop
|
82
|
+
|
83
|
+
err = Ext::Libuv.create_libuv_io_opts(0, @io_ptr, @io_opts)
|
84
|
+
if err != :success
|
85
|
+
raise Error.lookup(err), 'failed to allocate IO plugin'
|
86
|
+
end
|
87
|
+
|
88
|
+
# Configure the connection to the database
|
89
|
+
@connection = Ext::CreateSt.new
|
90
|
+
@connection[:version] = 3
|
91
|
+
@connection[:v][:v3][:connstr] = FFI::MemoryPointer.from_string(connstr)
|
92
|
+
uname = (username && !username.to_s.empty?) ? username.to_s : bucket.to_s
|
93
|
+
@connection[:v][:v3][:username] = FFI::MemoryPointer.from_string(uname)
|
94
|
+
@connection[:v][:v3][:passwd] = FFI::MemoryPointer.from_string(password) if password
|
95
|
+
@connection[:v][:v3][:io] = @io_ptr.get_pointer(0)
|
96
|
+
@handle_ptr = FFI::MemoryPointer.new :pointer, 1
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
attr_reader :requests, :handle, :bucket, :reactor
|
101
|
+
|
102
|
+
def get_callback(cb)
|
103
|
+
callback(cb)
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
def connect(defer: nil, flush_enabled: false)
|
108
|
+
raise 'already connected' if @handle || @bootstrap_defer
|
109
|
+
@bootstrap_defer = defer || @reactor.defer
|
110
|
+
promise = @bootstrap_defer.promise
|
111
|
+
|
112
|
+
@reactor.schedule {
|
113
|
+
@flush_enabled = flush_enabled
|
114
|
+
|
115
|
+
@requests = {}
|
116
|
+
|
117
|
+
# Create a library handle
|
118
|
+
# the create call allocates the memory and updates our pointer
|
119
|
+
err = Ext.create(@handle_ptr, @connection)
|
120
|
+
if err != :success
|
121
|
+
@bootstrap_defer.reject(Error.lookup(err).new('failed to create instance'))
|
122
|
+
handle_destroyed
|
123
|
+
else
|
124
|
+
# We extract the pointer and create the handle structure
|
125
|
+
@ref = @handle_ptr.get_pointer(0).address
|
126
|
+
@handle = Ext::T.new @handle_ptr.get_pointer(0)
|
127
|
+
|
128
|
+
# Register the callbacks we are interested in
|
129
|
+
Ext.set_bootstrap_callback(@handle, callback(:bootstrap_callback))
|
130
|
+
|
131
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_get], callback(:callback_get))
|
132
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_unlock], callback(:callback_unlock))
|
133
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_store], callback(:callback_store))
|
134
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_storedur], callback(:callback_storedur))
|
135
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_counter], callback(:callback_counter))
|
136
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_touch], callback(:callback_touch))
|
137
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_remove], callback(:callback_remove))
|
138
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_http], callback(:callback_http))
|
139
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_sdlookup], callback(:callback_sdlookup))
|
140
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_sdmutate], callback(:callback_sdmutate))
|
141
|
+
Ext.install_callback3(@handle, Ext::CALLBACKTYPE[:callback_cbflush], callback(:callback_cbflush)) if @flush_enabled
|
142
|
+
|
143
|
+
# Configure safe retries
|
144
|
+
# LCB_RETRYOPT_CREATE = Proc.new { |mode, policy| ((mode << 16) | policy) }
|
145
|
+
# val = LCB_RETRYOPT_CREATE(LCB_RETRY_ON_SOCKERR, LCB_RETRY_CMDS_SAFE);
|
146
|
+
# ::Libcouchbase::Ext.cntl_setu32(handle, LCB_CNTL_RETRYMODE, val)
|
147
|
+
retry_config = (1 << 16) | 3
|
148
|
+
::Libcouchbase::Ext.cntl_setu32(@handle, 0x24, (1 << 16) | 3)
|
149
|
+
|
150
|
+
# Connect to the database
|
151
|
+
err = Ext.connect(@handle)
|
152
|
+
if err != :success
|
153
|
+
@bootstrap_defer.reject(Error.lookup(err).new('failed to schedule connect'))
|
154
|
+
Ext.destroy(@handle)
|
155
|
+
handle_destroyed
|
156
|
+
end
|
157
|
+
end
|
158
|
+
}
|
159
|
+
|
160
|
+
promise
|
161
|
+
end
|
162
|
+
|
163
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-cntl.html
|
164
|
+
def configure(setting, value)
|
165
|
+
# Ensure it is thread safe
|
166
|
+
defer = @reactor.defer
|
167
|
+
@reactor.schedule {
|
168
|
+
if @handle
|
169
|
+
err = Ext.cntl_string(@handle, setting.to_s, value.to_s)
|
170
|
+
if err == :success
|
171
|
+
defer.resolve(self)
|
172
|
+
else
|
173
|
+
defer.reject(Error.lookup(err).new("failed to configure #{setting}=#{value}"))
|
174
|
+
end
|
175
|
+
else
|
176
|
+
defer.reject(RuntimeError.new('not connected'))
|
177
|
+
end
|
178
|
+
}
|
179
|
+
|
180
|
+
defer.promise
|
181
|
+
end
|
182
|
+
|
183
|
+
def destroy
|
184
|
+
return @destroy_defer.promise if @destroy_defer
|
185
|
+
|
186
|
+
# Ensure it is thread safe
|
187
|
+
defer = @reactor.defer
|
188
|
+
if @handle
|
189
|
+
@reactor.schedule {
|
190
|
+
if @destroy_defer.nil?
|
191
|
+
@destroy_defer = defer
|
192
|
+
Ext.destroy(@handle)
|
193
|
+
handle_destroyed
|
194
|
+
defer.resolve(nil)
|
195
|
+
else
|
196
|
+
defer.resolve(@destroy_defer.promise)
|
197
|
+
end
|
198
|
+
}
|
199
|
+
else
|
200
|
+
defer.resolve(nil)
|
201
|
+
end
|
202
|
+
defer.promise
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_server_list
|
206
|
+
defer = @reactor.defer
|
207
|
+
|
208
|
+
# Ensure it is thread safe
|
209
|
+
@reactor.schedule {
|
210
|
+
if @handle
|
211
|
+
nodes = Ext.get_num_nodes(@handle)
|
212
|
+
list = []
|
213
|
+
count = 0
|
214
|
+
|
215
|
+
while count <= nodes
|
216
|
+
list << Ext.get_node(@handle, :node_data, count)
|
217
|
+
count += 1
|
218
|
+
end
|
219
|
+
|
220
|
+
defer.resolve(list.uniq)
|
221
|
+
else
|
222
|
+
defer.reject(RuntimeError.new('not connected'))
|
223
|
+
end
|
224
|
+
}
|
225
|
+
|
226
|
+
defer.promise
|
227
|
+
end
|
228
|
+
|
229
|
+
def get_num_replicas
|
230
|
+
defer = @reactor.defer
|
231
|
+
|
232
|
+
# Ensure it is thread safe
|
233
|
+
@reactor.schedule {
|
234
|
+
if @handle
|
235
|
+
defer.resolve(Ext.get_num_replicas(@handle))
|
236
|
+
else
|
237
|
+
defer.reject(RuntimeError.new('not connected'))
|
238
|
+
end
|
239
|
+
}
|
240
|
+
|
241
|
+
defer.promise
|
242
|
+
end
|
243
|
+
|
244
|
+
def get_num_nodes
|
245
|
+
defer = @reactor.defer
|
246
|
+
|
247
|
+
# Ensure it is thread safe
|
248
|
+
@reactor.schedule {
|
249
|
+
if @handle
|
250
|
+
defer.resolve(Ext.get_num_nodes(@handle))
|
251
|
+
else
|
252
|
+
defer.reject(RuntimeError.new('not connected'))
|
253
|
+
end
|
254
|
+
}
|
255
|
+
|
256
|
+
defer.promise
|
257
|
+
end
|
258
|
+
|
259
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-store.html
|
260
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-durability.html
|
261
|
+
def store(key, value,
|
262
|
+
defer: nil,
|
263
|
+
operation: :set,
|
264
|
+
expire_in: nil,
|
265
|
+
expire_at: nil,
|
266
|
+
ttl: nil,
|
267
|
+
persist_to: 0,
|
268
|
+
replicate_to: 0,
|
269
|
+
cas: nil,
|
270
|
+
flags: 0,
|
271
|
+
**opts)
|
272
|
+
raise 'not connected' unless @handle
|
273
|
+
defer ||= @reactor.defer
|
274
|
+
|
275
|
+
# Check if this should be a durable operation
|
276
|
+
durable = (persist_to | replicate_to) != 0
|
277
|
+
if durable
|
278
|
+
cmd = Ext::CMDSTOREDUR.new
|
279
|
+
cmd[:persist_to] = persist_to
|
280
|
+
cmd[:replicate_to] = replicate_to
|
281
|
+
else
|
282
|
+
cmd = Ext::CMDSTORE.new
|
283
|
+
end
|
284
|
+
cmd[:operation] = operation
|
285
|
+
cmd[:flags] = flags
|
286
|
+
|
287
|
+
str_value = begin
|
288
|
+
[value].to_json[1...-1]
|
289
|
+
rescue
|
290
|
+
[value.respond_to?(:to_str) ? value.to_str : value.to_s].to_json[1...-1]
|
291
|
+
end
|
292
|
+
|
293
|
+
req = Request.new(cmd, defer)
|
294
|
+
req.value = value
|
295
|
+
cmd_set_value(req, cmd, str_value)
|
296
|
+
key = cmd_set_key(req, cmd, key)
|
297
|
+
|
298
|
+
cmd[:cas] = cas if cas
|
299
|
+
expire_in ||= ttl
|
300
|
+
cmd[:exptime] = expire_in ? expires_in(expire_in) : expire_at.to_i
|
301
|
+
|
302
|
+
@reactor.schedule {
|
303
|
+
pointer = cmd.to_ptr
|
304
|
+
@requests[pointer.address] = req
|
305
|
+
check_error(key, defer, durable ? Ext.storedur3(@handle, pointer, cmd) : Ext.store3(@handle, pointer, cmd))
|
306
|
+
}
|
307
|
+
|
308
|
+
defer.promise
|
309
|
+
end
|
310
|
+
|
311
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-get.html
|
312
|
+
def get(key, defer: nil, lock: false, cas: nil, **opts)
|
313
|
+
raise 'not connected' unless @handle
|
314
|
+
defer ||= @reactor.defer
|
315
|
+
|
316
|
+
cmd = Ext::CMDGET.new
|
317
|
+
req = Request.new(cmd, defer)
|
318
|
+
key = cmd_set_key(req, cmd, key)
|
319
|
+
cmd[:cas] = cas if cas
|
320
|
+
|
321
|
+
# exptime == the lock expire time
|
322
|
+
if lock
|
323
|
+
time = lock == true ? 30 : lock.to_i
|
324
|
+
time = 30 if time > 30 || time < 0
|
325
|
+
|
326
|
+
# We only want to lock if time is between 1 and 30
|
327
|
+
if time > 0
|
328
|
+
cmd[:exptime] = time
|
329
|
+
cmd[:lock] = 1
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
@reactor.schedule {
|
334
|
+
pointer = cmd.to_ptr
|
335
|
+
@requests[pointer.address] = req
|
336
|
+
check_error key, defer, Ext.get3(@handle, pointer, cmd)
|
337
|
+
}
|
338
|
+
|
339
|
+
defer.promise
|
340
|
+
end
|
341
|
+
|
342
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-lock.html
|
343
|
+
def unlock(key, cas: , **opts)
|
344
|
+
raise 'not connected' unless @handle
|
345
|
+
defer ||= @reactor.defer
|
346
|
+
|
347
|
+
cmd = Ext::CMDBASE.new
|
348
|
+
req = Request.new(cmd, defer)
|
349
|
+
key = cmd_set_key(req, cmd, key)
|
350
|
+
cmd[:cas] = cas
|
351
|
+
|
352
|
+
@reactor.schedule {
|
353
|
+
pointer = cmd.to_ptr
|
354
|
+
@requests[pointer.address] = Request.new(cmd, defer, key)
|
355
|
+
check_error key, defer, Ext.unlock3(@handle, pointer, cmd)
|
356
|
+
}
|
357
|
+
|
358
|
+
defer.promise
|
359
|
+
end
|
360
|
+
|
361
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-remove.html
|
362
|
+
def remove(key, defer: nil, cas: nil, **opts)
|
363
|
+
raise 'not connected' unless @handle
|
364
|
+
defer ||= @reactor.defer
|
365
|
+
|
366
|
+
cmd = Ext::CMDBASE.new
|
367
|
+
req = Request.new(cmd, defer)
|
368
|
+
key = cmd_set_key(req, cmd, key)
|
369
|
+
cmd[:cas] = cas if cas
|
370
|
+
|
371
|
+
@reactor.schedule {
|
372
|
+
pointer = cmd.to_ptr
|
373
|
+
@requests[pointer.address] = req
|
374
|
+
check_error key, defer, Ext.remove3(@handle, pointer, cmd)
|
375
|
+
}
|
376
|
+
|
377
|
+
defer.promise
|
378
|
+
end
|
379
|
+
|
380
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-counter.html
|
381
|
+
def counter(key, delta: 1, initial: nil, expire_in: nil, ttl: nil, expire_at: nil, cas: nil, **opts)
|
382
|
+
raise 'not connected' unless @handle
|
383
|
+
defer ||= @reactor.defer
|
384
|
+
|
385
|
+
cmd = Ext::CMDCOUNTER.new
|
386
|
+
req = Request.new(cmd, defer)
|
387
|
+
key = cmd_set_key(req, cmd, key)
|
388
|
+
|
389
|
+
cmd[:cas] = cas if cas
|
390
|
+
expire_in ||= ttl
|
391
|
+
cmd[:exptime] = expire_in ? expires_in(expire_in) : expire_at.to_i
|
392
|
+
cmd[:delta] = delta
|
393
|
+
if initial
|
394
|
+
cmd[:initial] = initial
|
395
|
+
cmd[:create] = 1
|
396
|
+
end
|
397
|
+
|
398
|
+
@reactor.schedule {
|
399
|
+
pointer = cmd.to_ptr
|
400
|
+
@requests[pointer.address] = req
|
401
|
+
check_error key, defer, Ext.counter3(@handle, pointer, cmd)
|
402
|
+
}
|
403
|
+
|
404
|
+
defer.promise
|
405
|
+
end
|
406
|
+
|
407
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-touch.html
|
408
|
+
def touch(key, expire_in: nil, ttl: nil, expire_at: nil, cas: nil, **opts)
|
409
|
+
raise 'not connected' unless @handle
|
410
|
+
raise ArgumentError.new('requires either expire_in or expire_at to be set') unless expire_in || expire_at
|
411
|
+
defer ||= @reactor.defer
|
412
|
+
|
413
|
+
cmd = Ext::CMDBASE.new
|
414
|
+
req = Request.new(cmd, defer)
|
415
|
+
key = cmd_set_key(req, cmd, key)
|
416
|
+
|
417
|
+
cmd[:cas] = cas if cas
|
418
|
+
expire_in ||= ttl
|
419
|
+
cmd[:exptime] = expire_in ? expires_in(expire_in) : expire_at.to_i
|
420
|
+
|
421
|
+
@reactor.schedule {
|
422
|
+
pointer = cmd.to_ptr
|
423
|
+
@requests[pointer.address] = req
|
424
|
+
check_error key, defer, Ext.touch3(@handle, pointer, cmd)
|
425
|
+
}
|
426
|
+
|
427
|
+
defer.promise
|
428
|
+
end
|
429
|
+
|
430
|
+
def subdoc(request, expire_in: nil, ttl: nil, expire_at: nil, cas: nil, **opts)
|
431
|
+
raise 'not connected' unless @handle
|
432
|
+
defer ||= @reactor.defer
|
433
|
+
|
434
|
+
cmd = Ext::CMDSUBDOC.new
|
435
|
+
req = Request.new(cmd, defer, request.key, request)
|
436
|
+
key = cmd_set_key(req, cmd, request.key)
|
437
|
+
|
438
|
+
cmd[:multimode] = request.mode == :mutate ? Ext::CMDSUBDOC::SDMULTI_MODE_MUTATE : Ext::CMDSUBDOC::SDMULTI_MODE_LOOKUP
|
439
|
+
cmd[:specs], cmd[:nspecs] = request.to_specs_array
|
440
|
+
|
441
|
+
cmd[:cas] = cas if cas
|
442
|
+
expire_in ||= ttl
|
443
|
+
cmd[:exptime] = expire_in ? expires_in(expire_in) : expire_at.to_i
|
444
|
+
|
445
|
+
@reactor.schedule {
|
446
|
+
pointer = cmd.to_ptr
|
447
|
+
@requests[pointer.address] = req
|
448
|
+
check_error(key, defer, Ext.subdoc3(@handle, pointer, cmd), subdoc: true)
|
449
|
+
request.free_memory
|
450
|
+
}
|
451
|
+
|
452
|
+
defer.promise
|
453
|
+
end
|
454
|
+
|
455
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-flush.html
|
456
|
+
def flush(defer: nil)
|
457
|
+
raise 'not connected' unless @handle
|
458
|
+
raise 'flush not enabled' unless @flush_enabled
|
459
|
+
defer ||= @reactor.defer
|
460
|
+
|
461
|
+
cmd = Ext::CMDBASE.new
|
462
|
+
|
463
|
+
@reactor.schedule {
|
464
|
+
pointer = cmd.to_ptr
|
465
|
+
@requests[pointer.address] = Request.new(cmd, defer, :flush)
|
466
|
+
check_error :flush, defer, Ext.cbflush3(@handle, pointer, cmd)
|
467
|
+
}
|
468
|
+
|
469
|
+
defer.promise
|
470
|
+
end
|
471
|
+
|
472
|
+
|
473
|
+
CMDHTTP_F_STREAM = 1<<16 # Stream the response (not used, we're only making simple requests)
|
474
|
+
CMDHTTP_F_CASTMO = 1<<17 # If specified, the lcb_CMDHTTP::cas field becomes the timeout
|
475
|
+
CMDHTTP_F_NOUPASS = 1<<18 # If specified, do not inject authentication header into the request.
|
476
|
+
HttpBodyRequired = [:put, :post].freeze
|
477
|
+
|
478
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-http.html
|
479
|
+
def http(path,
|
480
|
+
type: :view,
|
481
|
+
method: :get,
|
482
|
+
body: nil,
|
483
|
+
content_type: 'application/json',
|
484
|
+
defer: nil,
|
485
|
+
timeout: nil,
|
486
|
+
username: nil,
|
487
|
+
password: nil,
|
488
|
+
no_auth: false,
|
489
|
+
**opts)
|
490
|
+
raise 'not connected' unless @handle
|
491
|
+
raise 'unsupported request type' unless Ext::HttpTypeT[type]
|
492
|
+
raise 'unsupported HTTP method' unless Ext::HttpMethodT[method]
|
493
|
+
body_content = if HttpBodyRequired.include? method
|
494
|
+
raise 'no HTTP body provided' unless body
|
495
|
+
if body.is_a? String
|
496
|
+
body
|
497
|
+
else
|
498
|
+
# This will raise an error if not valid json
|
499
|
+
JSON.generate([body])[1..-2]
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
defer ||= @reactor.defer
|
504
|
+
|
505
|
+
cmd = Ext::CMDHTTP.new
|
506
|
+
req = Request.new(cmd, defer)
|
507
|
+
req.value = {
|
508
|
+
path: path,
|
509
|
+
method: method,
|
510
|
+
body: body,
|
511
|
+
content_type: content_type,
|
512
|
+
type: type,
|
513
|
+
no_auth: no_auth
|
514
|
+
}
|
515
|
+
cmd_set_key(req, cmd, path)
|
516
|
+
|
517
|
+
if timeout
|
518
|
+
cmd[:cas] = timeout
|
519
|
+
cmd[:cmdflags] |= CMDHTTP_F_CASTMO
|
520
|
+
end
|
521
|
+
cmd[:cmdflags] |= CMDHTTP_F_NOUPASS if no_auth
|
522
|
+
cmd[:type] = type
|
523
|
+
cmd[:method] = method
|
524
|
+
|
525
|
+
if body_content
|
526
|
+
cmd[:body] = req.ref(body_content)
|
527
|
+
cmd[:nbody] = body_content.bytesize
|
528
|
+
end
|
529
|
+
cmd[:content_type] = req.ref(content_type) if content_type
|
530
|
+
cmd[:username] = req.ref(username) if username
|
531
|
+
cmd[:password] = req.ref(password) if password
|
532
|
+
|
533
|
+
|
534
|
+
@reactor.schedule {
|
535
|
+
pointer = cmd.to_ptr
|
536
|
+
@requests[pointer.address] = req
|
537
|
+
check_error path, defer, Ext.http3(@handle, pointer, cmd)
|
538
|
+
}
|
539
|
+
|
540
|
+
defer.promise
|
541
|
+
end
|
542
|
+
|
543
|
+
def query_view(design, view, **opts)
|
544
|
+
QueryView.new(self, @reactor, design, view, **opts)
|
545
|
+
end
|
546
|
+
|
547
|
+
def full_text_search(index, **opts)
|
548
|
+
opts[:indexName] = index
|
549
|
+
QueryFullText.new(self, @reactor, **opts)
|
550
|
+
end
|
551
|
+
|
552
|
+
def n1ql_query(n1ql, **opts)
|
553
|
+
QueryN1QL.new(self, @reactor, n1ql, **opts)
|
554
|
+
end
|
555
|
+
|
556
|
+
def parse_document(raw_string)
|
557
|
+
val = begin
|
558
|
+
JSON.parse("[#{raw_string}]", DECODE_OPTIONS)[0]
|
559
|
+
rescue
|
560
|
+
raw_string
|
561
|
+
end
|
562
|
+
val
|
563
|
+
end
|
564
|
+
|
565
|
+
|
566
|
+
private
|
567
|
+
|
568
|
+
|
569
|
+
def cmd_set_key(req, cmd, value)
|
570
|
+
key = value.to_s
|
571
|
+
cmd[:key][:type] = :kv_copy
|
572
|
+
str = req.ref(key)
|
573
|
+
req.key = value
|
574
|
+
cmd[:key][:contig][:bytes] = str
|
575
|
+
cmd[:key][:contig][:nbytes] = key.bytesize
|
576
|
+
key
|
577
|
+
end
|
578
|
+
|
579
|
+
def cmd_set_value(req, cmd, value)
|
580
|
+
val = value.to_s
|
581
|
+
cmd[:value][:vtype] = :kv_copy
|
582
|
+
str = req.ref(val)
|
583
|
+
cmd[:value][:u_buf][:contig][:bytes] = str
|
584
|
+
cmd[:value][:u_buf][:contig][:nbytes] = val.bytesize
|
585
|
+
end
|
586
|
+
|
587
|
+
# 30 days in seconds
|
588
|
+
MAX_EXPIRY = 2_592_000
|
589
|
+
|
590
|
+
def expires_in(time)
|
591
|
+
period = time.to_i
|
592
|
+
if period > MAX_EXPIRY
|
593
|
+
Time.now.to_i + period
|
594
|
+
else
|
595
|
+
period
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def check_error(key, defer, err, subdoc: false)
|
600
|
+
if err != :success
|
601
|
+
error = Error.lookup(err).new("request not scheduled for #{key}")
|
602
|
+
backtrace = caller
|
603
|
+
error.set_backtrace backtrace
|
604
|
+
defer.reject error
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
def handle_destroyed
|
609
|
+
@bootstrap_defer = nil
|
610
|
+
@handle = nil
|
611
|
+
|
612
|
+
# TODO:: cleanup IO opts?
|
613
|
+
cleanup_callbacks
|
614
|
+
|
615
|
+
@requests.each_value do |req|
|
616
|
+
err = Error::Sockshutdown.new('handle destroyed')
|
617
|
+
if req.is_a? Request
|
618
|
+
req.defer.reject(err)
|
619
|
+
else
|
620
|
+
# this is a view, n1ql or full text query
|
621
|
+
req.error(err)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
@requests = nil
|
625
|
+
end
|
626
|
+
|
627
|
+
def bootstrap_callback(handle, error_code)
|
628
|
+
error_name = Ext::ErrorT[error_code]
|
629
|
+
|
630
|
+
if error_code == Ext::ErrorT[:success]
|
631
|
+
@bootstrap_defer.resolve(self)
|
632
|
+
@bootstrap_defer = nil
|
633
|
+
else
|
634
|
+
@bootstrap_defer.reject(Error.lookup(error_code).new("bootstrap failed #{error_code}: #{error_name}"))
|
635
|
+
handle_destroyed
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
# ==================
|
640
|
+
# Response Callbacks
|
641
|
+
# ==================
|
642
|
+
DECODE_OPTIONS = {
|
643
|
+
symbolize_names: true
|
644
|
+
}.freeze
|
645
|
+
|
646
|
+
def callback_get(handle, type, response)
|
647
|
+
resp = Ext::RESPGET.new response
|
648
|
+
resp_callback_common(resp, :callback_get) do |req, cb|
|
649
|
+
raw_string = resp[:value].read_string(resp[:nvalue])
|
650
|
+
val = parse_document(raw_string)
|
651
|
+
Response.new(cb, req.key, resp[:cas], val, {flags: resp[:itmflags]})
|
652
|
+
end
|
653
|
+
end
|
654
|
+
|
655
|
+
def callback_store(handle, type, response)
|
656
|
+
resp = Ext::RESPSTORE.new response
|
657
|
+
resp_callback_common(resp, :callback_store) do |req, cb|
|
658
|
+
Response.new(cb, req.key, resp[:cas], req.value)
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
Durability = Struct.new(:nresponses, :exists_master, :persisted_master, :npersisted, :nreplicated, :error)
|
663
|
+
|
664
|
+
def callback_storedur(handle, type, response)
|
665
|
+
resp = Ext::RESPSTOREDUR.new response
|
666
|
+
resp_callback_common(resp, :callback_storedur) do |req, cb|
|
667
|
+
info = resp[:dur_resp]
|
668
|
+
dur = Durability.new(
|
669
|
+
info[:nresponses],
|
670
|
+
info[:exists_master],
|
671
|
+
info[:persisted_master],
|
672
|
+
info[:npersisted],
|
673
|
+
info[:nreplicated],
|
674
|
+
info[:rc]
|
675
|
+
)
|
676
|
+
Response.new(cb, req.key, resp[:cas], req.value, dur)
|
677
|
+
end
|
678
|
+
end
|
679
|
+
|
680
|
+
def callback_counter(handle, type, response)
|
681
|
+
resp = Ext::RESPCOUNTER.new response
|
682
|
+
resp_callback_common(resp, :callback_counter) do |req, cb|
|
683
|
+
Response.new(cb, req.key, resp[:cas], resp[:value])
|
684
|
+
end
|
685
|
+
end
|
686
|
+
|
687
|
+
def callback_touch(handle, type, response)
|
688
|
+
resp = Ext::RESPBASE.new response
|
689
|
+
resp_callback_common(resp, :callback_touch) do |req, cb|
|
690
|
+
Response.new(cb, req.key, resp[:cas])
|
691
|
+
end
|
692
|
+
end
|
693
|
+
|
694
|
+
def callback_remove(handle, type, response)
|
695
|
+
resp = Ext::RESPBASE.new response
|
696
|
+
resp_callback_common(resp, :callback_remove) do |req, cb|
|
697
|
+
Response.new(cb, req.key, resp[:cas])
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
def callback_unlock(handle, type, response)
|
702
|
+
resp = Ext::RESPBASE.new response
|
703
|
+
resp_callback_common(resp, :callback_unlock) do |req, cb|
|
704
|
+
Response.new(cb, req.key, resp[:cas])
|
705
|
+
end
|
706
|
+
end
|
707
|
+
|
708
|
+
def callback_sdlookup(handle, type, response)
|
709
|
+
resp = Ext::RESPSUBDOC.new response
|
710
|
+
resp_callback_common(resp, :callback_sdlookup) do |req, cb|
|
711
|
+
subdoc_common(resp, req, cb)
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
# Only counter returns a result
|
716
|
+
def callback_sdmutate(handle, type, response)
|
717
|
+
resp = Ext::RESPSUBDOC.new response
|
718
|
+
resp_callback_common(resp, :callback_sdmutate) do |req, cb|
|
719
|
+
subdoc_common(resp, req, cb)
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
def subdoc_common(resp, req, cb)
|
724
|
+
iterval = FFI::MemoryPointer.new(:ulong, 1)
|
725
|
+
cur_res = Ext::SDENTRY.new
|
726
|
+
values = []
|
727
|
+
index = 0
|
728
|
+
|
729
|
+
ignore = req.value.ignore
|
730
|
+
mutation = req.value.mode == :mutate
|
731
|
+
|
732
|
+
loop do
|
733
|
+
check = Ext.sdresult_next(resp, cur_res, iterval)
|
734
|
+
break if check == 0
|
735
|
+
|
736
|
+
if cur_res[:status] == :success
|
737
|
+
count = cur_res[:nvalue]
|
738
|
+
if count > 0
|
739
|
+
result = cur_res[:value].read_string(count)
|
740
|
+
else
|
741
|
+
result = true # success response
|
742
|
+
end
|
743
|
+
result = "[#{result}]"
|
744
|
+
values << JSON.parse(result, DECODE_OPTIONS)[0]
|
745
|
+
elsif cur_res[:status] == :subdoc_path_enoent && ignore[mutation ? cur_res[:index] : index]
|
746
|
+
values << nil
|
747
|
+
else
|
748
|
+
values << Error.lookup(cur_res[:status]).new("Subdoc #{cb} failed for #{req.key} index #{mutation ? cur_res[:index] : index}")
|
749
|
+
end
|
750
|
+
|
751
|
+
index += 1
|
752
|
+
end
|
753
|
+
|
754
|
+
# Return the single result instead of an array if single
|
755
|
+
is_single = resp[:rflags] & Ext::RESPFLAGS[:resp_f_sdsingle] > 0
|
756
|
+
if is_single
|
757
|
+
values = values.first
|
758
|
+
elsif values.empty? # multiple mutate arrays should return true (same as a single mutate)
|
759
|
+
values = true
|
760
|
+
end
|
761
|
+
|
762
|
+
Response.new(cb, req.key, resp[:cas], values)
|
763
|
+
end
|
764
|
+
|
765
|
+
def callback_cbflush(handle, type, response)
|
766
|
+
resp = Ext::RESPBASE.new response
|
767
|
+
resp_callback_common(resp, :callback_cbflush) do |req, cb|
|
768
|
+
Response.new(cb)
|
769
|
+
end
|
770
|
+
end
|
771
|
+
|
772
|
+
def callback_http(handle, type, response)
|
773
|
+
resp = Ext::RESPHTTP.new response
|
774
|
+
resp_callback_common(resp, :callback_http) do |req, cb|
|
775
|
+
headers = {}
|
776
|
+
head_ptr = resp[:headers]
|
777
|
+
if not head_ptr.null?
|
778
|
+
head_ptr.get_array_of_string(0).each_slice(2) do |key, value|
|
779
|
+
headers[key] = value
|
780
|
+
end
|
781
|
+
end
|
782
|
+
body = body_text(resp)
|
783
|
+
|
784
|
+
if (200...300).include? resp[:htstatus]
|
785
|
+
HttpResponse.new(cb, resp[:htstatus], headers, body, req.value)
|
786
|
+
else
|
787
|
+
err = Error::HttpResponseError.new "non success response for #{req.key}"
|
788
|
+
err.code = resp[:htstatus]
|
789
|
+
err.headers = headers
|
790
|
+
err.body = body
|
791
|
+
req.defer.reject(err)
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
def resp_callback_common(resp, callback)
|
797
|
+
req = @requests.delete(resp[:cookie].address)
|
798
|
+
if req
|
799
|
+
begin
|
800
|
+
# Errors will be provided in the response
|
801
|
+
if resp[:rc] == :success || resp[:rc] == :subdoc_multi_failure
|
802
|
+
req.defer.resolve(yield(req, callback))
|
803
|
+
else
|
804
|
+
lookup = resp[:rc]
|
805
|
+
req.defer.reject(Error.lookup(lookup).new("#{callback} failed for #{req.key} with #{lookup}"))
|
806
|
+
end
|
807
|
+
rescue => e
|
808
|
+
req.defer.reject(e)
|
809
|
+
end
|
810
|
+
else
|
811
|
+
@reactor.log IOError.new("received #{callback} for unknown request")
|
812
|
+
end
|
813
|
+
end
|
814
|
+
# ======================
|
815
|
+
# End Response Callbacks
|
816
|
+
# ======================
|
817
|
+
|
818
|
+
# http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-view-api.html
|
819
|
+
def viewquery_callback(handle, type, row)
|
820
|
+
row_data = Ext::RESPVIEWQUERY.new row
|
821
|
+
view = @requests[row_data[:cookie].address]
|
822
|
+
|
823
|
+
if row_data[:rc] == :success
|
824
|
+
if (row_data[:rflags] & Ext::RESPFLAGS[:resp_f_final]) > 0
|
825
|
+
# We can assume this is JSON
|
826
|
+
view.received_final(JSON.parse(row_data[:value].read_string(row_data[:nvalue]), DECODE_OPTIONS))
|
827
|
+
else
|
828
|
+
view.received(row_data)
|
829
|
+
end
|
830
|
+
else
|
831
|
+
error_klass = Error.lookup(row_data[:rc])
|
832
|
+
if error_klass == Error::HttpError
|
833
|
+
http_resp = row_data[:htresp]
|
834
|
+
view.error error_klass.new(body_text(http_resp))
|
835
|
+
else
|
836
|
+
view.error error_klass.new
|
837
|
+
end
|
838
|
+
end
|
839
|
+
end
|
840
|
+
|
841
|
+
# N1QL query response
|
842
|
+
# @see http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-n1ql-api.html
|
843
|
+
def n1ql_callback(handle, type, row)
|
844
|
+
query_callback_common Ext::RESPN1QL.new(row)
|
845
|
+
end
|
846
|
+
|
847
|
+
# Full text search
|
848
|
+
# @see http://docs.couchbase.com/sdk-api/couchbase-c-client-2.6.2/group__lcb-cbft-api.html
|
849
|
+
def fts_callback(handle, type, row)
|
850
|
+
query_callback_common Ext::RESPFTS.new(row)
|
851
|
+
end
|
852
|
+
|
853
|
+
# Common code to process both N1QL and FTS callbacks
|
854
|
+
def query_callback_common(row_data)
|
855
|
+
view = @requests[row_data[:cookie].address]
|
856
|
+
|
857
|
+
if row_data[:rc] == :success
|
858
|
+
value = JSON.parse(row_data[:row].read_string(row_data[:nrow]), DECODE_OPTIONS)
|
859
|
+
|
860
|
+
if (row_data[:rflags] & Ext::RESPFLAGS[:resp_f_final]) > 0
|
861
|
+
# We can assume this is JSON
|
862
|
+
view.received_final(value)
|
863
|
+
else
|
864
|
+
view.received(value)
|
865
|
+
end
|
866
|
+
else
|
867
|
+
error_klass = Error.lookup(row_data[:rc])
|
868
|
+
if error_klass == Error::HttpError
|
869
|
+
http_resp = row_data[:htresp]
|
870
|
+
view.error error_klass.new(body_text(http_resp))
|
871
|
+
else
|
872
|
+
view.error error_klass.new
|
873
|
+
end
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
# Extracts the body content of a HTTP response
|
878
|
+
def body_text(http_resp)
|
879
|
+
if http_resp[:nbody] > 0
|
880
|
+
http_resp[:body].read_string(http_resp[:nbody])
|
881
|
+
else
|
882
|
+
''
|
883
|
+
end
|
884
|
+
end
|
885
|
+
end
|
886
|
+
end
|