better-riak-client 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +16 -0
- data/README.markdown +198 -0
- data/RELEASE_NOTES.md +211 -0
- data/better-riak-client.gemspec +61 -0
- data/erl_src/riak_kv_test014_backend.beam +0 -0
- data/erl_src/riak_kv_test014_backend.erl +189 -0
- data/erl_src/riak_kv_test_backend.beam +0 -0
- data/erl_src/riak_kv_test_backend.erl +697 -0
- data/erl_src/riak_search_test_backend.beam +0 -0
- data/erl_src/riak_search_test_backend.erl +175 -0
- data/lib/riak/bucket.rb +221 -0
- data/lib/riak/client/beefcake/messages.rb +213 -0
- data/lib/riak/client/beefcake/object_methods.rb +111 -0
- data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
- data/lib/riak/client/decaying.rb +36 -0
- data/lib/riak/client/excon_backend.rb +162 -0
- data/lib/riak/client/feature_detection.rb +88 -0
- data/lib/riak/client/http_backend/configuration.rb +211 -0
- data/lib/riak/client/http_backend/key_streamer.rb +43 -0
- data/lib/riak/client/http_backend/object_methods.rb +106 -0
- data/lib/riak/client/http_backend/request_headers.rb +34 -0
- data/lib/riak/client/http_backend/transport_methods.rb +201 -0
- data/lib/riak/client/http_backend.rb +340 -0
- data/lib/riak/client/net_http_backend.rb +82 -0
- data/lib/riak/client/node.rb +115 -0
- data/lib/riak/client/protobuffs_backend.rb +173 -0
- data/lib/riak/client/search.rb +91 -0
- data/lib/riak/client.rb +540 -0
- data/lib/riak/cluster.rb +151 -0
- data/lib/riak/core_ext/blank.rb +53 -0
- data/lib/riak/core_ext/deep_dup.rb +13 -0
- data/lib/riak/core_ext/extract_options.rb +7 -0
- data/lib/riak/core_ext/json.rb +15 -0
- data/lib/riak/core_ext/slice.rb +18 -0
- data/lib/riak/core_ext/stringify_keys.rb +10 -0
- data/lib/riak/core_ext/symbolize_keys.rb +10 -0
- data/lib/riak/core_ext/to_param.rb +31 -0
- data/lib/riak/core_ext.rb +7 -0
- data/lib/riak/encoding.rb +6 -0
- data/lib/riak/failed_request.rb +81 -0
- data/lib/riak/i18n.rb +5 -0
- data/lib/riak/json.rb +52 -0
- data/lib/riak/link.rb +94 -0
- data/lib/riak/locale/en.yml +53 -0
- data/lib/riak/locale/fr.yml +52 -0
- data/lib/riak/map_reduce/filter_builder.rb +103 -0
- data/lib/riak/map_reduce/phase.rb +98 -0
- data/lib/riak/map_reduce.rb +225 -0
- data/lib/riak/map_reduce_error.rb +7 -0
- data/lib/riak/node/configuration.rb +293 -0
- data/lib/riak/node/console.rb +133 -0
- data/lib/riak/node/control.rb +207 -0
- data/lib/riak/node/defaults.rb +83 -0
- data/lib/riak/node/generation.rb +106 -0
- data/lib/riak/node/log.rb +34 -0
- data/lib/riak/node/version.rb +43 -0
- data/lib/riak/node.rb +38 -0
- data/lib/riak/robject.rb +318 -0
- data/lib/riak/search.rb +3 -0
- data/lib/riak/serializers.rb +74 -0
- data/lib/riak/stamp.rb +77 -0
- data/lib/riak/test_server.rb +89 -0
- data/lib/riak/util/escape.rb +76 -0
- data/lib/riak/util/headers.rb +53 -0
- data/lib/riak/util/multipart/stream_parser.rb +62 -0
- data/lib/riak/util/multipart.rb +52 -0
- data/lib/riak/util/tcp_socket_extensions.rb +58 -0
- data/lib/riak/util/translation.rb +19 -0
- data/lib/riak/version.rb +3 -0
- data/lib/riak/walk_spec.rb +105 -0
- data/lib/riak.rb +21 -0
- metadata +348 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'riak/core_ext/deep_dup'
|
2
|
+
|
3
|
+
module Riak
|
4
|
+
class Node
|
5
|
+
# Settings based on Riak 1.1.
|
6
|
+
ENV_DEFAULTS = {
|
7
|
+
:riak_core => {
|
8
|
+
:ring_creation_size => 64
|
9
|
+
},
|
10
|
+
:riak_kv => {
|
11
|
+
:storage_backend => :riak_kv_bitcask_backend,
|
12
|
+
:map_js_vm_count => 8,
|
13
|
+
:reduce_js_vm_count => 6,
|
14
|
+
:hook_js_vm_count => 2,
|
15
|
+
:mapper_batch_size => 5,
|
16
|
+
:js_max_vm_mem => 8,
|
17
|
+
:js_thread_stack => 16,
|
18
|
+
:riak_kv_stat => true,
|
19
|
+
:legacy_stats => true,
|
20
|
+
:vnode_vclocks => true,
|
21
|
+
:http_url_encoding => :on,
|
22
|
+
:legacy_keylisting => false,
|
23
|
+
:mapred_system => :pipe,
|
24
|
+
:mapred_2i_pipe => true,
|
25
|
+
:listkeys_backpressure => true,
|
26
|
+
:add_paths => []
|
27
|
+
},
|
28
|
+
:riak_search => {
|
29
|
+
:enabled => true
|
30
|
+
},
|
31
|
+
:luwak => {
|
32
|
+
:enabled => true
|
33
|
+
},
|
34
|
+
:merge_index => {
|
35
|
+
:buffer_rollover_size => 1048576,
|
36
|
+
:max_compact_segments => 20
|
37
|
+
},
|
38
|
+
:eleveldb => {},
|
39
|
+
:bitcask => {},
|
40
|
+
:lager => {
|
41
|
+
:crash_log_size => 10485760,
|
42
|
+
:crash_log_msg_size => 65536,
|
43
|
+
:crash_log_date => "$D0",
|
44
|
+
:crash_log_count => 5,
|
45
|
+
:error_logger_redirect => true
|
46
|
+
},
|
47
|
+
:riak_sysmon => {
|
48
|
+
:process_limit => 30,
|
49
|
+
:port_limit => 30,
|
50
|
+
:gc_ms_limit => 100,
|
51
|
+
:heap_word_limit => 40111000,
|
52
|
+
:busy_port => true,
|
53
|
+
:busy_dist_port => true
|
54
|
+
},
|
55
|
+
:sasl => {
|
56
|
+
:sasl_error_logger => false
|
57
|
+
},
|
58
|
+
:riak_control => {
|
59
|
+
:enabled => false,
|
60
|
+
:auth => :userlist,
|
61
|
+
:userlist => {"user" => "pass"},
|
62
|
+
:admin => true
|
63
|
+
}
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
# Based on Riak 1.1.
|
67
|
+
VM_DEFAULTS = {
|
68
|
+
"+K" => true,
|
69
|
+
"+A" => 64,
|
70
|
+
"-smp" => "enable",
|
71
|
+
"+W" => "w",
|
72
|
+
"-env ERL_MAX_PORTS" => 4096,
|
73
|
+
"-env ERL_FULLSWEEP_AFTER" => 0
|
74
|
+
}.freeze
|
75
|
+
|
76
|
+
protected
|
77
|
+
# Populates the defaults
|
78
|
+
def set_defaults
|
79
|
+
@env = ENV_DEFAULTS.deep_dup
|
80
|
+
@vm = VM_DEFAULTS.deep_dup
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Riak
|
4
|
+
class Node
|
5
|
+
# Does the node exist on disk?
|
6
|
+
def exist?
|
7
|
+
manifest.exist?
|
8
|
+
end
|
9
|
+
|
10
|
+
# Deletes the node and regenerates it.
|
11
|
+
def recreate
|
12
|
+
delete
|
13
|
+
create
|
14
|
+
end
|
15
|
+
|
16
|
+
# Generates the node.
|
17
|
+
def create
|
18
|
+
unless exist?
|
19
|
+
create_directories
|
20
|
+
write_scripts
|
21
|
+
write_vm_args
|
22
|
+
write_app_config
|
23
|
+
write_manifest
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Clears data from known data directories. Stops the node if it is
|
28
|
+
# running.
|
29
|
+
def drop
|
30
|
+
was_started = started?
|
31
|
+
stop if was_started
|
32
|
+
data.children.each do |item|
|
33
|
+
if item.directory?
|
34
|
+
item.children.each {|c| c.rmtree }
|
35
|
+
else
|
36
|
+
item.delete
|
37
|
+
end
|
38
|
+
end
|
39
|
+
start if was_started
|
40
|
+
end
|
41
|
+
|
42
|
+
# Removes the node from disk and freezes the object.
|
43
|
+
def destroy
|
44
|
+
delete
|
45
|
+
freeze
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
def delete
|
50
|
+
stop unless stopped?
|
51
|
+
root.rmtree if root.exist?
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_directories
|
55
|
+
root.mkpath
|
56
|
+
NODE_DIRECTORIES.each {|d| send(d).mkpath }
|
57
|
+
end
|
58
|
+
|
59
|
+
def write_vm_args
|
60
|
+
(etc + 'vm.args').open('w') do |f|
|
61
|
+
vm.each do |k,v|
|
62
|
+
f.puts "#{k} #{v}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def write_app_config
|
68
|
+
(etc + 'app.config').open('w') do |f|
|
69
|
+
f.write to_erlang_config(env) + '.'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_scripts
|
74
|
+
[control_script, admin_script].each {|s| write_script(s.basename, s) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_script(name, target)
|
78
|
+
source_script = source + name
|
79
|
+
target.open('wb') do |f|
|
80
|
+
source_script.readlines.each do |line|
|
81
|
+
line.sub!(/(RUNNER_SCRIPT_DIR=)(.*)/, '\1' + bin.to_s)
|
82
|
+
line.sub!(/(RUNNER_ETC_DIR=)(.*)/, '\1' + etc.to_s)
|
83
|
+
line.sub!(/(RUNNER_USER=)(.*)/, '\1')
|
84
|
+
line.sub!(/(RUNNER_LOG_DIR=)(.*)/, '\1' + log.to_s)
|
85
|
+
line.sub!(/(PIPE_DIR=)(.*)/, '\1' + pipe.to_s + "/") # PIPE_DIR must have a trailing slash
|
86
|
+
line.sub!(/(PLATFORM_DATA_DIR=)(.*)/, '\1' + data.to_s)
|
87
|
+
line.sub!('grep "$RUNNER_BASE_DIR/.*/[b]eam"', 'grep "$RUNNER_ETC_DIR/app.config"')
|
88
|
+
if line.strip == "RUNNER_BASE_DIR=${RUNNER_SCRIPT_DIR%/*}"
|
89
|
+
line = "RUNNER_BASE_DIR=#{source.parent.to_s}\n"
|
90
|
+
end
|
91
|
+
f.write line
|
92
|
+
end
|
93
|
+
end
|
94
|
+
target.chmod 0755
|
95
|
+
end
|
96
|
+
|
97
|
+
def write_manifest
|
98
|
+
# TODO: For now this only saves the information that was used when
|
99
|
+
# configuring the node. Later we'll verify/warn if the settings
|
100
|
+
# used differ on subsequent generations.
|
101
|
+
@configuration[:env] = @env
|
102
|
+
@configuration[:vm] = @vm
|
103
|
+
manifest.open('w') {|f| YAML.dump(@configuration, f) }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Riak
|
2
|
+
class Node
|
3
|
+
LAGER_LEVELS = [
|
4
|
+
:debug,
|
5
|
+
:info,
|
6
|
+
:notice,
|
7
|
+
:warning,
|
8
|
+
:error,
|
9
|
+
:critical,
|
10
|
+
:alert,
|
11
|
+
:emergency
|
12
|
+
]
|
13
|
+
|
14
|
+
def read_console_log(*levels)
|
15
|
+
console_log = log + 'console.log'
|
16
|
+
if console_log.exist?
|
17
|
+
levels = levels.map { |level| expand_log_level(level) }.compact.flatten
|
18
|
+
pattern = /(#{levels.map { |level| "\\[#{level}\\]" }.join("|")})/
|
19
|
+
console_log.readlines.grep(pattern)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def expand_log_level(level)
|
24
|
+
case level
|
25
|
+
when Range
|
26
|
+
first = LAGER_LEVELS.index(level.begin.to_sym) || 0
|
27
|
+
last = LAGER_LEVELS.index(level.end.to_sym) || -1
|
28
|
+
LAGER_LEVELS[first..last]
|
29
|
+
when Symbol
|
30
|
+
level
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Riak
|
4
|
+
class Node
|
5
|
+
# @return [String] the version of the Riak node
|
6
|
+
def version
|
7
|
+
@version ||= configure_version
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Pathname] the location of Riak installation, aka RUNNER_BASE_DIR
|
11
|
+
def base_dir
|
12
|
+
@base_dir ||= configure_base_dir
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
# Detects the Riak version from the generated release
|
17
|
+
def configure_version
|
18
|
+
if base_dir
|
19
|
+
versions = (base_dir + 'releases' + 'start_erl.data').read
|
20
|
+
versions.split(" ")[1]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Determines the base_dir from source control script
|
25
|
+
def configure_base_dir
|
26
|
+
# Use the script from the source directory so we don't require
|
27
|
+
# it to be generated first.
|
28
|
+
(source + control_script_name).each_line.find {|l| l =~ /^RUNNER_BASE_DIR=(.*)/ }
|
29
|
+
|
30
|
+
# There should only be one matching line, so the contents of $1
|
31
|
+
# will be the matched path. If there's nothing matched, we
|
32
|
+
# return nil.
|
33
|
+
case $1
|
34
|
+
when '${RUNNER_SCRIPT_DIR%/*}'
|
35
|
+
source.parent
|
36
|
+
when String
|
37
|
+
Pathname.new($1).expand_path
|
38
|
+
else
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/riak/node.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'riak/util/translation'
|
2
|
+
require 'riak/node/defaults'
|
3
|
+
require 'riak/node/configuration'
|
4
|
+
require 'riak/node/generation'
|
5
|
+
require 'riak/node/control'
|
6
|
+
require 'riak/node/version'
|
7
|
+
require 'riak/node/log'
|
8
|
+
|
9
|
+
module Riak
|
10
|
+
# A Node encapsulates the generation and management of standalone
|
11
|
+
# Riak nodes. It is used by the {TestServer} to start and manage an
|
12
|
+
# in-memory node for supporting integration test suites.
|
13
|
+
class Node
|
14
|
+
include Util::Translation
|
15
|
+
|
16
|
+
# Creates a new Riak node. Unlike {#new}, this will also generate
|
17
|
+
# the node if it does not exist on disk. Equivalent to {::new}
|
18
|
+
# followed by {#create}.
|
19
|
+
# @see #new
|
20
|
+
def self.create(configuration={})
|
21
|
+
new(configuration).tap do |node|
|
22
|
+
node.create
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Creates the template for a Riak node. To generate the node after
|
27
|
+
# initialization, use {#create}.
|
28
|
+
def initialize(configuration={})
|
29
|
+
set_defaults
|
30
|
+
configure configuration
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def debug(msg)
|
35
|
+
$stderr.puts msg if ENV["DEBUG_RIAK_NODE"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/riak/robject.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'time'
|
3
|
+
require 'yaml'
|
4
|
+
require 'riak/util/translation'
|
5
|
+
require 'riak/util/escape'
|
6
|
+
require 'riak/bucket'
|
7
|
+
require 'riak/link'
|
8
|
+
require 'riak/walk_spec'
|
9
|
+
require 'riak/serializers'
|
10
|
+
|
11
|
+
module Riak
|
12
|
+
# Represents the data and metadata stored in a bucket/key pair in
|
13
|
+
# the Riak database, the base unit of data manipulation.
|
14
|
+
class RObject
|
15
|
+
include Util::Translation
|
16
|
+
extend Util::Translation
|
17
|
+
include Util::Escape
|
18
|
+
extend Util::Escape
|
19
|
+
|
20
|
+
# @return [Bucket] the bucket in which this object is contained
|
21
|
+
attr_accessor :bucket
|
22
|
+
|
23
|
+
# @return [String] the key of this object within its bucket
|
24
|
+
attr_accessor :key
|
25
|
+
|
26
|
+
# @return [String] the MIME content type of the object
|
27
|
+
attr_accessor :content_type
|
28
|
+
|
29
|
+
# @return [String] the Riak vector clock for the object
|
30
|
+
attr_accessor :vclock
|
31
|
+
|
32
|
+
# @return [Set<Link>] a Set of {Riak::Link} objects for relationships between this object and other resources
|
33
|
+
attr_accessor :links
|
34
|
+
|
35
|
+
# @return [String] the ETag header from the most recent HTTP response, useful for caching and reloading
|
36
|
+
attr_accessor :etag
|
37
|
+
|
38
|
+
# @return [Time] the Last-Modified header from the most recent HTTP response, useful for caching and reloading
|
39
|
+
attr_accessor :last_modified
|
40
|
+
|
41
|
+
# @return [Hash] a hash of any X-Riak-Meta-* headers that were in the HTTP response, keyed on the trailing portion
|
42
|
+
attr_accessor :meta
|
43
|
+
|
44
|
+
# @return [Hash<Set>] a hash of secondary indexes, where the
|
45
|
+
# key is the index name and the value is a Set of index
|
46
|
+
# entries for that index
|
47
|
+
attr_accessor :indexes
|
48
|
+
|
49
|
+
# @return [Boolean] whether to attempt to prevent stale writes using conditional PUT semantics, If-None-Match: * or If-Match: {#etag}
|
50
|
+
# @see http://wiki.basho.com/display/RIAK/REST+API#RESTAPI-Storeaneworexistingobjectwithakey Riak Rest API Docs
|
51
|
+
attr_accessor :prevent_stale_writes
|
52
|
+
|
53
|
+
# Defines a callback to be invoked when there is conflict.
|
54
|
+
#
|
55
|
+
# @yield The conflict callback.
|
56
|
+
# @yieldparam [RObject] robject The conflicted RObject
|
57
|
+
# @yieldreturn [RObject, nil] Either the resolved RObject or nil if your
|
58
|
+
# callback cannot resolve it. The next registered
|
59
|
+
# callback will be given the chance to resolve it.
|
60
|
+
#
|
61
|
+
# @note Ripple registers its own document-level conflict handler, so if you're
|
62
|
+
# using ripple, you will probably want to use that instead.
|
63
|
+
def self.on_conflict(&conflict_hook)
|
64
|
+
on_conflict_hooks << conflict_hook
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Array<Proc>] the list of registered conflict callbacks.
|
68
|
+
def self.on_conflict_hooks
|
69
|
+
@on_conflict_hooks ||= []
|
70
|
+
end
|
71
|
+
|
72
|
+
# Attempts to resolve conflict using the registered conflict callbacks.
|
73
|
+
#
|
74
|
+
# @return [RObject] the RObject
|
75
|
+
# @note There is no guarantee the returned RObject will have been resolved
|
76
|
+
def attempt_conflict_resolution
|
77
|
+
return self unless conflict?
|
78
|
+
|
79
|
+
self.class.on_conflict_hooks.each do |hook|
|
80
|
+
result = hook.call(self)
|
81
|
+
return result if result.is_a?(RObject)
|
82
|
+
end
|
83
|
+
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Loads a list of RObjects that were emitted from a MapReduce
|
88
|
+
# query.
|
89
|
+
# @param [Client] client A Riak::Client with which the results will be associated
|
90
|
+
# @param [Array<Hash>] response A list of results a MapReduce job. Each entry should contain these keys: bucket, key, vclock, values
|
91
|
+
# @return [Array<RObject>] An array of RObject instances
|
92
|
+
def self.load_from_mapreduce(client, response)
|
93
|
+
response.map do |item|
|
94
|
+
RObject.new(client[unescape(item['bucket'])], unescape(item['key'])).load_from_mapreduce(item)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Create a new object manually
|
99
|
+
# @param [Bucket] bucket the bucket in which the object exists
|
100
|
+
# @param [String] key the key at which the object resides. If nil, a key will be assigned when the object is saved.
|
101
|
+
# @yield self the new RObject
|
102
|
+
# @see Bucket#get
|
103
|
+
def initialize(bucket, key=nil)
|
104
|
+
@bucket, @key = bucket, key
|
105
|
+
@links, @meta = Set.new, {}
|
106
|
+
@indexes = new_index_hash
|
107
|
+
yield self if block_given?
|
108
|
+
end
|
109
|
+
|
110
|
+
def indexes=(hash)
|
111
|
+
@indexes = hash.inject(new_index_hash) do |h, (k,v)|
|
112
|
+
h[k].merge([*v])
|
113
|
+
h
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Load object data from a map/reduce response item.
|
118
|
+
# This method is used by RObject::load_from_mapreduce to instantiate the necessary
|
119
|
+
# objects.
|
120
|
+
# @param [Hash] response a response from {Riak::MapReduce}
|
121
|
+
# @return [RObject] self
|
122
|
+
def load_from_mapreduce(response)
|
123
|
+
self.vclock = response['vclock']
|
124
|
+
if response['values'].size == 1
|
125
|
+
value = response['values'].first
|
126
|
+
load_map_reduce_value(value)
|
127
|
+
else
|
128
|
+
@conflict = true
|
129
|
+
@siblings = response['values'].map do |v|
|
130
|
+
RObject.new(self.bucket, self.key) do |robj|
|
131
|
+
robj.vclock = self.vclock
|
132
|
+
robj.load_map_reduce_value(v)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Object] the unmarshaled form of {#raw_data} stored in riak at this object's key
|
140
|
+
def data
|
141
|
+
if @raw_data && !@data
|
142
|
+
raw = @raw_data.respond_to?(:read) ? @raw_data.read : @raw_data
|
143
|
+
@data = deserialize(raw)
|
144
|
+
@raw_data = nil
|
145
|
+
end
|
146
|
+
@data
|
147
|
+
end
|
148
|
+
|
149
|
+
# @param [Object] unmarshaled form of the data to be stored in riak. Object will be serialized using {#serialize} if a known content_type is used. Setting this overrides values stored with {#raw_data=}
|
150
|
+
# @return [Object] the object stored
|
151
|
+
def data=(new_data)
|
152
|
+
if new_data.respond_to?(:read)
|
153
|
+
raise ArgumentError.new(t("invalid_io_object"))
|
154
|
+
end
|
155
|
+
|
156
|
+
@raw_data = nil
|
157
|
+
@data = new_data
|
158
|
+
end
|
159
|
+
|
160
|
+
# @return [String] raw data stored in riak for this object's key
|
161
|
+
def raw_data
|
162
|
+
if @data && !@raw_data
|
163
|
+
@raw_data = serialize(@data)
|
164
|
+
@data = nil
|
165
|
+
end
|
166
|
+
@raw_data
|
167
|
+
end
|
168
|
+
|
169
|
+
# @param [String, IO-like] the raw data to be stored in riak at this key, will not be marshaled or manipulated prior to storage. Overrides any data stored by {#data=}
|
170
|
+
# @return [String] the data stored
|
171
|
+
def raw_data=(new_raw_data)
|
172
|
+
@data = nil
|
173
|
+
@raw_data = new_raw_data
|
174
|
+
end
|
175
|
+
|
176
|
+
# Store the object in Riak
|
177
|
+
# @param [Hash] options query parameters
|
178
|
+
# @option options [Fixnum] :r the "r" parameter (Read quorum for the implicit read performed when validating the store operation)
|
179
|
+
# @option options [Fixnum] :w the "w" parameter (Write quorum)
|
180
|
+
# @option options [Fixnum] :dw the "dw" parameter (Durable-write quorum)
|
181
|
+
# @option options [Boolean] :returnbody (true) whether to return the result of a successful write in the body of the response. Set to false for fire-and-forget updates, set to true to immediately have access to the object's stored representation.
|
182
|
+
# @return [Riak::RObject] self
|
183
|
+
# @raise [ArgumentError] if the content_type is not defined
|
184
|
+
def store(options={})
|
185
|
+
raise ArgumentError, t("content_type_undefined") unless @content_type.present?
|
186
|
+
@bucket.client.store_object(self, options)
|
187
|
+
self
|
188
|
+
end
|
189
|
+
|
190
|
+
# Reload the object from Riak. Will use conditional GETs when possible.
|
191
|
+
# @param [Hash] options query parameters
|
192
|
+
# @option options [Fixnum] :r the "r" parameter (Read quorum)
|
193
|
+
# @option options [Boolean] :force will force a reload request if
|
194
|
+
# the vclock is not present, useful for reloading the object after
|
195
|
+
# a store (not passed in the query params)
|
196
|
+
# @return [Riak::RObject] self
|
197
|
+
def reload(options={})
|
198
|
+
force = options.delete(:force)
|
199
|
+
return self unless @key && (@vclock || force)
|
200
|
+
self.etag = self.last_modified = nil if force
|
201
|
+
bucket.client.reload_object(self, options)
|
202
|
+
end
|
203
|
+
|
204
|
+
alias :fetch :reload
|
205
|
+
|
206
|
+
# Delete the object from Riak and freeze this instance. Will work whether or not the object actually
|
207
|
+
# exists in the Riak database.
|
208
|
+
# @see Bucket#delete
|
209
|
+
def delete(options={})
|
210
|
+
return if key.blank?
|
211
|
+
options[:vclock] = vclock if vclock
|
212
|
+
@bucket.delete(key, options)
|
213
|
+
freeze
|
214
|
+
end
|
215
|
+
|
216
|
+
attr_writer :siblings, :conflict
|
217
|
+
|
218
|
+
# Returns sibling objects when in conflict.
|
219
|
+
# @return [Array<RObject>] an array of conflicting sibling objects for this key
|
220
|
+
# @return [Array<self>] a single-element array containing object when not
|
221
|
+
# in conflict
|
222
|
+
def siblings
|
223
|
+
return [self] unless conflict?
|
224
|
+
@siblings
|
225
|
+
end
|
226
|
+
|
227
|
+
# @return [true,false] Whether this object has conflicting sibling objects (divergent vclocks)
|
228
|
+
def conflict?
|
229
|
+
@conflict.present?
|
230
|
+
end
|
231
|
+
|
232
|
+
# Serializes the internal object data for sending to Riak. Differs based on the content-type.
|
233
|
+
# This method is called internally when storing the object.
|
234
|
+
# Automatically serialized formats:
|
235
|
+
# * JSON (application/json)
|
236
|
+
# * YAML (text/yaml)
|
237
|
+
# * Marshal (application/x-ruby-marshal)
|
238
|
+
# When given an IO-like object (e.g. File), no serialization will
|
239
|
+
# be done.
|
240
|
+
# @param [Object] payload the data to serialize
|
241
|
+
def serialize(payload)
|
242
|
+
Serializers.serialize(@content_type, payload)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Deserializes the internal object data from a Riak response. Differs based on the content-type.
|
246
|
+
# This method is called internally when loading the object.
|
247
|
+
# Automatically deserialized formats:
|
248
|
+
# * JSON (application/json)
|
249
|
+
# * YAML (text/yaml)
|
250
|
+
# * Marshal (application/x-ruby-marshal)
|
251
|
+
# @param [String] body the serialized response body
|
252
|
+
def deserialize(body)
|
253
|
+
Serializers.deserialize(@content_type, body)
|
254
|
+
end
|
255
|
+
|
256
|
+
# @return [String] A representation suitable for IRB and debugging output
|
257
|
+
def inspect
|
258
|
+
body = if @data || Serializers[content_type]
|
259
|
+
data.inspect
|
260
|
+
else
|
261
|
+
@raw_data && "(#{@raw_data.size} bytes)"
|
262
|
+
end
|
263
|
+
"#<#{self.class.name} {#{bucket.name}#{"," + @key if @key}} [#{@content_type}]:#{body}>"
|
264
|
+
end
|
265
|
+
|
266
|
+
# Walks links from this object to other objects in Riak.
|
267
|
+
# @param [Array<Hash,WalkSpec>] link specifications for the query
|
268
|
+
def walk(*params)
|
269
|
+
specs = WalkSpec.normalize(*params)
|
270
|
+
@bucket.client.link_walk(self, specs)
|
271
|
+
end
|
272
|
+
|
273
|
+
# Converts the object to a link suitable for linking other objects
|
274
|
+
# to it
|
275
|
+
# @param [String] tag the tag to apply to the link
|
276
|
+
def to_link(tag)
|
277
|
+
Link.new(@bucket.name, @key, tag)
|
278
|
+
end
|
279
|
+
|
280
|
+
alias :vector_clock :vclock
|
281
|
+
alias :vector_clock= :vclock=
|
282
|
+
|
283
|
+
protected
|
284
|
+
def load_map_reduce_value(hash)
|
285
|
+
metadata = hash['metadata']
|
286
|
+
extract_if_present(metadata, 'X-Riak-VTag', :etag)
|
287
|
+
extract_if_present(metadata, 'content-type', :content_type)
|
288
|
+
extract_if_present(metadata, 'X-Riak-Last-Modified', :last_modified) { |v| Time.httpdate( v ) }
|
289
|
+
extract_if_present(metadata, 'index', :indexes) do |entries|
|
290
|
+
Hash[ entries.map {|k,v| [k, Set.new(Array(v))] } ]
|
291
|
+
end
|
292
|
+
extract_if_present(metadata, 'Links', :links) do |links|
|
293
|
+
Set.new( links.map { |l| Link.new(*l) } )
|
294
|
+
end
|
295
|
+
extract_if_present(metadata, 'X-Riak-Meta', :meta) do |meta|
|
296
|
+
Hash[
|
297
|
+
meta.map do |k,v|
|
298
|
+
[k.sub(%r{^x-riak-meta-}i, ''), [v]]
|
299
|
+
end
|
300
|
+
]
|
301
|
+
end
|
302
|
+
extract_if_present(hash, 'data', :data) { |v| deserialize(v) }
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
def extract_if_present(hash, key, attribute=nil)
|
307
|
+
if hash[key].present?
|
308
|
+
attribute ||= key
|
309
|
+
value = block_given? ? yield(hash[key]) : hash[key]
|
310
|
+
send("#{attribute}=", value)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def new_index_hash
|
315
|
+
Hash.new {|h,k| h[k] = Set.new }
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|