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
data/lib/riak/link.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
require 'riak/util/translation'
|
3
|
+
require 'riak/util/escape'
|
4
|
+
require 'riak/walk_spec'
|
5
|
+
|
6
|
+
module Riak
|
7
|
+
# Represents a link from one object to another in Riak
|
8
|
+
class Link
|
9
|
+
include Util::Translation
|
10
|
+
include Util::Escape
|
11
|
+
|
12
|
+
# @return [String] the relationship tag (or "rel") of the other resource to this one
|
13
|
+
attr_accessor :tag
|
14
|
+
alias_method :rel, :tag
|
15
|
+
alias_method :rel=, :tag=
|
16
|
+
|
17
|
+
# @return [String] the bucket of the related resource
|
18
|
+
attr_accessor :bucket
|
19
|
+
|
20
|
+
# @return [String] the key of the related resource
|
21
|
+
attr_accessor :key
|
22
|
+
|
23
|
+
%w{bucket key}.each do |m|
|
24
|
+
define_method("#{m}=") { |value|
|
25
|
+
@url = nil
|
26
|
+
instance_variable_set("@#{m}", value)
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [String] header_string the string value of the Link: HTTP header from a Riak response
|
31
|
+
# @return [Array<Link>] an array of Riak::Link structs parsed from the header
|
32
|
+
def self.parse(header_string)
|
33
|
+
header_string.scan(%r{<([^>]+)>\s*;\s*(?:rel|riaktag)=\"([^\"]+)\"}).map do |match|
|
34
|
+
new(match[0], match[1])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# @overload initialize(url, tag)
|
39
|
+
# @param [String] url the url of the related resource
|
40
|
+
# @param [String] tag the tag for the related resource
|
41
|
+
# @overload initialize(bucket, key, tag)
|
42
|
+
# @param [String] bucket the bucket of the related resource
|
43
|
+
# @param [String] key the key of the related resource
|
44
|
+
# @param [String] tag the tag for the related resource
|
45
|
+
def initialize(*args)
|
46
|
+
raise ArgumentError unless (2..3).include?(args.size)
|
47
|
+
if args.size == 2
|
48
|
+
self.url, @tag = args
|
49
|
+
else
|
50
|
+
@bucket, @key, @tag = args
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [String] the URL (relative or absolute) of the related resource
|
55
|
+
def url(new_scheme=false)
|
56
|
+
return @url unless @bucket
|
57
|
+
|
58
|
+
if new_scheme
|
59
|
+
"/buckets/#{escape(bucket)}" + (key.blank? ? "" : "/keys/#{escape(key)}")
|
60
|
+
else
|
61
|
+
"/riak/#{escape(bucket)}" + (key.blank? ? "" : "/#{escape(key)}")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def url=(value)
|
66
|
+
@url = value
|
67
|
+
@bucket = unescape($1) if value =~ %r{^/buckets/([^/]+)/?} || value =~ %r{^/[^/]+/([^/]+)/?}
|
68
|
+
@key = unescape($1) if value =~ %r{^/buckets/[^/]+/keys/([^/]+)/?} || value =~ %r{^/[^/]+/[^/]+/([^/]+)/?}
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect; to_s; end
|
72
|
+
|
73
|
+
def to_s(new_scheme=false)
|
74
|
+
%Q[<#{url(new_scheme)}>; riaktag="#{tag}"]
|
75
|
+
end
|
76
|
+
|
77
|
+
def hash
|
78
|
+
self.to_s.hash
|
79
|
+
end
|
80
|
+
|
81
|
+
def eql?(other)
|
82
|
+
self == other
|
83
|
+
end
|
84
|
+
|
85
|
+
def ==(other)
|
86
|
+
other.is_a?(Link) && url == other.url && tag == other.tag
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_walk_spec
|
90
|
+
raise t("bucket_link_conversion") if tag == "up" || key.nil?
|
91
|
+
WalkSpec.new(:bucket => bucket, :tag => tag)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
en:
|
2
|
+
riak:
|
3
|
+
backwards_clock: "System clock moved backwards, ID generation will fail for %{delay} more milliseconds."
|
4
|
+
bucket_link_conversion: "Can't convert a bucket link to a walk spec"
|
5
|
+
client_type: "invalid argument %{client} is not a Riak::Client"
|
6
|
+
conflict_resolver_invalid: "The given resolver (%{resolver}) did not respond to :call"
|
7
|
+
content_type_undefined: "content_type is not defined!"
|
8
|
+
deprecated:
|
9
|
+
port: "DEPRECATION: Riak::Client#port has been deprecated, use #http_port or #pb_port for the appropriate protocol.\n%{backtrace}"
|
10
|
+
search: "DEPRECATION: Riak Search features are included in the main client, you no longer need to require 'riak/search'.\n%{backtrace}"
|
11
|
+
empty_map_reduce_query: "Specify one or more query phases to your MapReduce."
|
12
|
+
failed_request: "Client request failed."
|
13
|
+
filter_needs_block: "Filter %{filter} expects a block."
|
14
|
+
filter_arity_mismatch: "Filter %{filter} expects %{expected} arguments but %{received} were given."
|
15
|
+
full_bucket_mapred: "Full-bucket MapReduce, including key filters, invokes list-keys which is an expensive operation that should not be used in production.\n %{backtrace}"
|
16
|
+
hash_type: "invalid argument %{hash} is not a Hash"
|
17
|
+
http_configuration: "The %{backend} HTTP backend cannot be used. Please check its requirements."
|
18
|
+
http_failed_request: "Expected %{expected} from Riak but received %{code}. %{body}"
|
19
|
+
hostname_invalid: "host must be a valid hostname"
|
20
|
+
protocol_invalid: "'%{invalid}' is not a valid protocol, valid values are %{valid}"
|
21
|
+
invalid_basic_auth: "basic auth must be set using 'user:pass'"
|
22
|
+
invalid_client_id: "Invalid client ID, must be a string or between 0 and %{max_id}"
|
23
|
+
invalid_io_object: "Invalid IO-like object assigned to RObject#data. It should be assigned to raw_data instead."
|
24
|
+
invalid_function_value: "invalid value for function: %{value}"
|
25
|
+
invalid_options: "Invalid configuration options given."
|
26
|
+
invalid_phase_type: "type must be :map, :reduce, or :link"
|
27
|
+
invalid_ssl_verify_mode: "%{invalid} is not a valid :verify_mode option for SSL. Valid options are 'peer' and 'none'."
|
28
|
+
invalid_index_query: "%{value} is not a valid index query term, only Strings, Integers, and Ranges of those are allowed."
|
29
|
+
indexes_unsupported: "Riak server does not support secondary indexes."
|
30
|
+
loading_bucket: "while loading bucket '%{name}'"
|
31
|
+
list_buckets: "Riak::Client#buckets is an expensive operation that should not be used in production.\n %{backtrace}"
|
32
|
+
list_keys: "Riak::Bucket#keys is an expensive operation that should not be used in production.\n %{backtrace}"
|
33
|
+
luwak_unsupported: "Riak server does not support Luwak. Enable it in app.config before using."
|
34
|
+
missing_block: "A block must be given."
|
35
|
+
missing_host_and_port: "You must specify a host and port, or use the defaults of 127.0.0.1:8098"
|
36
|
+
module_function_pair_required: "function must have two elements when an array"
|
37
|
+
not_found: "The requested object was not found."
|
38
|
+
no_pipes: "Could not find or open pipes for Riak console in %{path}."
|
39
|
+
port_invalid: "port must be an integer between 0 and 65535"
|
40
|
+
protobuffs_failed_request: "Expected success from Riak but received %{code}. %{body}"
|
41
|
+
protobuffs_configuration: "The %{backend} Protobuffs backend cannot be used. Please check its requirements."
|
42
|
+
request_body_type: "Request body must be a String or respond to :read."
|
43
|
+
search_unsupported: "Riak server does not support search."
|
44
|
+
search_docs_require_id: "Search index documents must include the 'id' field."
|
45
|
+
search_remove_requires_id_or_query: "Search index documents to be removed must have 'id' or 'query' keys."
|
46
|
+
serializer_not_implemented: "No serializer has been registered for content type %{content_type}"
|
47
|
+
source_and_root_required: "Riak::Node configuration must include :source and :root keys."
|
48
|
+
stale_write_prevented: "Stale write prevented by client."
|
49
|
+
stored_function_invalid: "function must have :bucket and :key when a hash"
|
50
|
+
string_type: "invalid_argument %{string} is not a String"
|
51
|
+
too_few_arguments: "too few arguments: %{params}"
|
52
|
+
walk_spec_invalid_unless_link: "WalkSpec is only valid for a function when the type is :link"
|
53
|
+
wrong_argument_count_walk_spec: "wrong number of arguments (one Hash or bucket,tag,keep required)"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
fr:
|
2
|
+
riak:
|
3
|
+
backwards_clock: "L'horloge système a reculé. La génération des ID échouera pour les %{delay} millisecondes à venir."
|
4
|
+
bucket_link_conversion: "Ne peut convertir un lien de bucket vers un walk spec"
|
5
|
+
client_type: "argument invalide : %{client} n'est pas un Riak::Client"
|
6
|
+
conflict_resolver_invalid: "Le résolveur (%{resolver}) n'a pas répondu à :call"
|
7
|
+
content_type_undefined: "content_type n'est pas défini !"
|
8
|
+
deprecated:
|
9
|
+
port: "DEPRECATION: Riak::Client#port ne doit plus être utilisé (deprecated), utilisez #http_port ou #pb_port pour spécifier le protocol.\n%{backtrace}"
|
10
|
+
search: "DEPRECATION: Les fonctionnalités Riak Search sont désormais inclues dans le client principal. Vous n'avez plus besoin d'inclure 'riak/search'.\n%{backtrace}"
|
11
|
+
empty_map_reduce_query: "Spécifiez une ou plusieurs requêtes pour MapReduce."
|
12
|
+
failed_request: "La requête client a échouée."
|
13
|
+
filter_needs_block: "Le filtre %{filter} nécessite un bloc."
|
14
|
+
filter_arity_mismatch: "Le filtre %{filter} nécessite %{expected} arguments. Seulement %{received} ont été fournis."
|
15
|
+
full_bucket_mapred: "Les MapReduce sur un bucket complet, y compris les filtres de clé, invoque 'list-keys' qui est une opération coûteuse et ne doit pas être utilisée en production.\n %{backtrace}"
|
16
|
+
hash_type: "argument invalide : %{hash} n'est pas un Hash"
|
17
|
+
http_configuration: "Le moteur HTTP %{backend} ne peut être utilisé. Merci de vérifier ses dépendances et/ou sa configuration."
|
18
|
+
http_failed_request: "%{expected} attendu depuis Riak mais %{code} reçu. %{body}"
|
19
|
+
hostname_invalid: "host doit être un nom d'hôte valide"
|
20
|
+
protocol_invalid: "'%{invalid}' n'est pas un protocole valide. Les valeurs possibles sont %{valid}"
|
21
|
+
invalid_basic_auth: "L'authentification basique doit être utilisée comme ceci : 'user:pass'"
|
22
|
+
invalid_client_id: "ID client invalide, doit être une chaîne ou un entier entre 0 et %{max_id}"
|
23
|
+
invalid_io_object: "Object de type IO invalide assigné à RObject#data. Il devrait plutôt être assigné à raw_data"
|
24
|
+
invalid_function_value: "Valeur invalide pour la fonction : %{value}"
|
25
|
+
invalid_options: "Options de configuration invalides."
|
26
|
+
invalid_phase_type: "type doit être :map, :reduce, ou :link"
|
27
|
+
invalid_ssl_verify_mode: "%{invalid} n'est pas une option :verify_mode valide pour SSL. Les options valides sont 'peer' and 'none'."
|
28
|
+
invalid_index_query: "%{value} n'est pas un term de requête valide. Seules les String, Integer et Range de ces types sont autorisés"
|
29
|
+
indexes_unsupported: "Le serveur Riak ne supporte pas les index secondaires."
|
30
|
+
loading_bucket: "pendant le chargement du bucket '%{name}'"
|
31
|
+
list_buckets: "Riak::Client#buckets est une opération coûteuse et ne doit pas être utilisée en production.\n %{backtrace}"
|
32
|
+
list_keys: "Riak::Bucket#keys est une opération coûteuse et ne doit pas être utilisée en production.\n %{backtrace}"
|
33
|
+
luwak_unsupported: "Le serveur Riak ne supporte pas Luwak. Activez-le dans app.config avant de l'utiliser"
|
34
|
+
missing_block: "Un bloc doit être fourni."
|
35
|
+
missing_host_and_port: "Vous devez spécifier un hôte et un port, utiliser la valeur par défaut : 127.0.0.1:8098"
|
36
|
+
module_function_pair_required: "la fonction doit avoir deux élément lorsqu'elle est définie par un tableau"
|
37
|
+
not_found: "L'objet demandé n'a pas été trouvé."
|
38
|
+
no_pipes: "Ne peut trouver ou ne peut ouvrir un pipe pour la console Riak dans %{path}."
|
39
|
+
port_invalid: "le port doit être un entier entre 0 et 65535"
|
40
|
+
protobuffs_failed_request: "Riak a retourné le code d'erreur %{code} au lieu d'une réponse réussie. %{body}"
|
41
|
+
request_body_type: "Le corps de la requête doit être une chaine ou répondre à :read."
|
42
|
+
search_unsupported: "Le serveur Riak ne supporte pas la recherche."
|
43
|
+
search_docs_require_id: "Search index documents must include the 'id' field."
|
44
|
+
search_remove_requires_id_or_query: "Les documents d'index de recherche doivent avoir des clés 'id' ou 'query'"
|
45
|
+
serializer_not_implemented: "Aucun sérialiseur n'a été défini pour le type de contenu %{content_type}"
|
46
|
+
source_and_root_required: "La configuration de Riak::Node configuration doit inclure les clés :source and :root."
|
47
|
+
stale_write_prevented: "Le client a empêché une écriture périmée."
|
48
|
+
stored_function_invalid: "Une fonction définie par un hash doit avoir :bucket et :key"
|
49
|
+
string_type: "invalid_argument %{string} n'est pas une String"
|
50
|
+
too_few_arguments: "pas assez d'arguments : %{params}"
|
51
|
+
walk_spec_invalid_unless_link: "Le WalkSpec n'est valide pour une fonction que lorsque le type est :link"
|
52
|
+
wrong_argument_count_walk_spec: "nombre d'argument invalide (un Hash ou bucket,tag,keep requis)"
|
@@ -0,0 +1,103 @@
|
|
1
|
+
|
2
|
+
require 'riak/util/translation'
|
3
|
+
|
4
|
+
module Riak
|
5
|
+
class MapReduce
|
6
|
+
# Builds key-filter lists for MapReduce inputs in a DSL-like fashion.
|
7
|
+
class FilterBuilder
|
8
|
+
include Util::Translation
|
9
|
+
|
10
|
+
# Known filters available in riak_kv_mapred_filters, mapped to
|
11
|
+
# their arities. These are turned into instance methods.
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# FilterBuilder.new do
|
15
|
+
# string_to_int
|
16
|
+
# less_than 50
|
17
|
+
# end
|
18
|
+
FILTERS = {
|
19
|
+
:int_to_string => 0,
|
20
|
+
:string_to_int => 0,
|
21
|
+
:float_to_string => 0,
|
22
|
+
:string_to_float => 0,
|
23
|
+
:to_upper => 0,
|
24
|
+
:to_lower => 0,
|
25
|
+
:tokenize => 2,
|
26
|
+
:urldecode => 0,
|
27
|
+
:greater_than => 1,
|
28
|
+
:less_than => 1,
|
29
|
+
:greater_than_eq => 1,
|
30
|
+
:less_than_eq => 1,
|
31
|
+
:between => [2,3],
|
32
|
+
:matches => 1,
|
33
|
+
:neq => 1,
|
34
|
+
:eq => 1,
|
35
|
+
:set_member => -1,
|
36
|
+
:similar_to => 2,
|
37
|
+
:starts_with => 1,
|
38
|
+
:ends_with => 1
|
39
|
+
}
|
40
|
+
|
41
|
+
# Available logical operations for joining filter chains. These
|
42
|
+
# are turned into instance methods with leading underscores,
|
43
|
+
# with aliases to uppercase versions.
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# FilterBuilder.new do
|
47
|
+
# string_to_int
|
48
|
+
# AND do
|
49
|
+
# seq { greater_than_eq 50 }
|
50
|
+
# seq { neq 100 }
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
LOGICAL_OPERATIONS = %w{and or not}
|
54
|
+
|
55
|
+
FILTERS.each do |f,arity|
|
56
|
+
arities = [arity].flatten
|
57
|
+
|
58
|
+
define_method(f) { |*args|
|
59
|
+
unless arities.include?(-1) or arities.include?(args.size)
|
60
|
+
raise ArgumentError.new t("filter_arity_mismatch",
|
61
|
+
:filter => f,
|
62
|
+
:expected => arities,
|
63
|
+
:received => args.size
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
@filters << [f, *args]
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
LOGICAL_OPERATIONS.each do |op|
|
72
|
+
# NB: string eval is needed here because in ruby 1.8, blocks can't yield to
|
73
|
+
# other blocks
|
74
|
+
class_eval <<-CODE
|
75
|
+
def _#{op}(&block)
|
76
|
+
raise ArgumentError.new(t('filter_needs_block', :filter => '#{op}')) unless block_given?
|
77
|
+
@filters << [:#{op}, self.class.new(&block).to_a]
|
78
|
+
end
|
79
|
+
alias :#{op.to_s.upcase} :_#{op}
|
80
|
+
CODE
|
81
|
+
end
|
82
|
+
|
83
|
+
# Creates a new FilterBuilder. Pass a block that will be
|
84
|
+
# instance_eval'ed to construct the sequence of filters.
|
85
|
+
def initialize(&block)
|
86
|
+
@filters = []
|
87
|
+
instance_eval(&block) if block_given?
|
88
|
+
end
|
89
|
+
|
90
|
+
# Wraps multi-step filters for use inside logical
|
91
|
+
# operations. Does not correspond to an actual filter.
|
92
|
+
def sequence(&block)
|
93
|
+
@filters << self.class.new(&block).to_a
|
94
|
+
end
|
95
|
+
alias :seq :sequence
|
96
|
+
|
97
|
+
# @return A list of filters for handing to the MapReduce inputs.
|
98
|
+
def to_a
|
99
|
+
@filters
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
require 'riak/json'
|
3
|
+
require 'riak/util/translation'
|
4
|
+
require 'riak/walk_spec'
|
5
|
+
|
6
|
+
module Riak
|
7
|
+
class MapReduce
|
8
|
+
# Represents an individual phase in a map-reduce pipeline. Generally you'll want to call
|
9
|
+
# methods of MapReduce instead of using this directly.
|
10
|
+
class Phase
|
11
|
+
include Util::Translation
|
12
|
+
# @return [Symbol] the type of phase - :map, :reduce, or :link
|
13
|
+
attr_accessor :type
|
14
|
+
|
15
|
+
# @return [String, Array<String, String>, Hash, WalkSpec] For :map and :reduce types, the Javascript function to run (as a string or hash with bucket/key), or the module + function in Erlang to run. For a :link type, a {Riak::WalkSpec} or an equivalent hash.
|
16
|
+
attr_accessor :function
|
17
|
+
|
18
|
+
# @return [String] the language of the phase's function - "javascript" or "erlang". Meaningless for :link type phases.
|
19
|
+
attr_accessor :language
|
20
|
+
|
21
|
+
# @return [Boolean] whether results of this phase will be returned
|
22
|
+
attr_accessor :keep
|
23
|
+
|
24
|
+
# @return [Array] any extra static arguments to pass to the phase
|
25
|
+
attr_accessor :arg
|
26
|
+
|
27
|
+
# Creates a phase in the map-reduce pipeline
|
28
|
+
# @param [Hash] options options for the phase
|
29
|
+
# @option options [Symbol] :type one of :map, :reduce, :link
|
30
|
+
# @option options [String] :language ("javascript") "erlang" or "javascript"
|
31
|
+
# @option options [String, Array, Hash] :function In the case of Javascript, a literal function in a string, or a hash with :bucket and :key. In the case of Erlang, an Array of [module, function]. For a :link phase, a hash including any of :bucket, :tag or a WalkSpec.
|
32
|
+
# @option options [Boolean] :keep (false) whether to return the results of this phase
|
33
|
+
# @option options [Array] :arg (nil) any extra static arguments to pass to the phase
|
34
|
+
def initialize(options={})
|
35
|
+
self.type = options[:type]
|
36
|
+
self.language = options[:language] || "javascript"
|
37
|
+
self.function = options[:function]
|
38
|
+
self.keep = options[:keep] || false
|
39
|
+
self.arg = options[:arg]
|
40
|
+
end
|
41
|
+
|
42
|
+
def type=(value)
|
43
|
+
raise ArgumentError, t("invalid_phase_type") unless value.to_s =~ /^(map|reduce|link)$/i
|
44
|
+
@type = value.to_s.downcase.to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
def function=(value)
|
48
|
+
case value
|
49
|
+
when Array
|
50
|
+
raise ArgumentError, t("module_function_pair_required") unless value.size == 2
|
51
|
+
@language = "erlang"
|
52
|
+
when Hash
|
53
|
+
raise ArgumentError, t("stored_function_invalid") unless type == :link || value.has_key?(:bucket) && value.has_key?(:key)
|
54
|
+
@language = "javascript"
|
55
|
+
when String
|
56
|
+
@language = "javascript"
|
57
|
+
when WalkSpec
|
58
|
+
raise ArgumentError, t("walk_spec_invalid_unless_link") unless type == :link
|
59
|
+
else
|
60
|
+
raise ArgumentError, t("invalid_function_value", :value => value.inspect)
|
61
|
+
end
|
62
|
+
@function = value
|
63
|
+
end
|
64
|
+
|
65
|
+
# Converts the phase to JSON for use while invoking a job.
|
66
|
+
# @return [String] a JSON representation of the phase
|
67
|
+
def to_json(*a)
|
68
|
+
as_json.to_json(*a)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Converts the phase to its JSON-compatible representation for job invocation.
|
72
|
+
# @return [Hash] a Hash-equivalent of the phase
|
73
|
+
def as_json(options=nil)
|
74
|
+
obj = case type
|
75
|
+
when :map, :reduce
|
76
|
+
defaults = {"language" => language, "keep" => keep}
|
77
|
+
case function
|
78
|
+
when Hash
|
79
|
+
defaults.merge(function)
|
80
|
+
when String
|
81
|
+
if function =~ /\s*function/
|
82
|
+
defaults.merge("source" => function)
|
83
|
+
else
|
84
|
+
defaults.merge("name" => function)
|
85
|
+
end
|
86
|
+
when Array
|
87
|
+
defaults.merge("module" => function[0], "function" => function[1])
|
88
|
+
end
|
89
|
+
when :link
|
90
|
+
spec = WalkSpec.normalize(function).first
|
91
|
+
{"bucket" => spec.bucket, "tag" => spec.tag, "keep" => spec.keep || keep}
|
92
|
+
end
|
93
|
+
obj["arg"] = arg if arg
|
94
|
+
{ type => obj }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,225 @@
|
|
1
|
+
require 'riak/util/translation'
|
2
|
+
require 'riak/util/escape'
|
3
|
+
require 'riak/json'
|
4
|
+
require 'riak/client'
|
5
|
+
require 'riak/bucket'
|
6
|
+
require 'riak/robject'
|
7
|
+
require 'riak/walk_spec'
|
8
|
+
require 'riak/failed_request'
|
9
|
+
require 'riak/map_reduce_error'
|
10
|
+
require 'riak/map_reduce/phase'
|
11
|
+
require 'riak/map_reduce/filter_builder'
|
12
|
+
|
13
|
+
module Riak
|
14
|
+
# Class for invoking map-reduce jobs using the HTTP interface.
|
15
|
+
class MapReduce
|
16
|
+
include Util::Translation
|
17
|
+
include Util::Escape
|
18
|
+
|
19
|
+
# @return [Array<[bucket,key]>,String,Hash<:bucket,:filters>] The
|
20
|
+
# bucket/keys for input to the job, or the bucket (all
|
21
|
+
# keys), or a hash containing the bucket and key-filters.
|
22
|
+
# @see #add
|
23
|
+
attr_accessor :inputs
|
24
|
+
|
25
|
+
# @return [Array<Phase>] The map and reduce phases that will be executed
|
26
|
+
# @see #map
|
27
|
+
# @see #reduce
|
28
|
+
# @see #link
|
29
|
+
attr_accessor :query
|
30
|
+
|
31
|
+
# Creates a new map-reduce job.
|
32
|
+
# @param [Client] client the Riak::Client interface
|
33
|
+
# @yield [self] helpful for initializing the job
|
34
|
+
def initialize(client)
|
35
|
+
@client, @inputs, @query = client, [], []
|
36
|
+
yield self if block_given?
|
37
|
+
end
|
38
|
+
|
39
|
+
# Add or replace inputs for the job.
|
40
|
+
# @overload add(bucket)
|
41
|
+
# Run the job across all keys in the bucket. This will replace any other inputs previously added.
|
42
|
+
# @param [String, Bucket] bucket the bucket to run the job on
|
43
|
+
# @overload add(bucket,key)
|
44
|
+
# Add a bucket/key pair to the job.
|
45
|
+
# @param [String,Bucket] bucket the bucket of the object
|
46
|
+
# @param [String] key the key of the object
|
47
|
+
# @overload add(object)
|
48
|
+
# Add an object to the job (by its bucket/key)
|
49
|
+
# @param [RObject] object the object to add to the inputs
|
50
|
+
# @overload add(bucket, key, keydata)
|
51
|
+
# @param [String,Bucket] bucket the bucket of the object
|
52
|
+
# @param [String] key the key of the object
|
53
|
+
# @param [String] keydata extra data to pass along with the object to the job
|
54
|
+
# @overload add(bucket, filters)
|
55
|
+
# Run the job across all keys in the bucket, with the given
|
56
|
+
# key-filters. This will replace any other inputs previously
|
57
|
+
# added. (Requires Riak 0.14)
|
58
|
+
# @param [String,Bucket] bucket the bucket to filter keys from
|
59
|
+
# @param [Array<Array>] filters a list of key-filters to apply
|
60
|
+
# to the key list
|
61
|
+
# @return [MapReduce] self
|
62
|
+
def add(*params)
|
63
|
+
params = params.dup
|
64
|
+
params = params.first if Array === params.first
|
65
|
+
case params.size
|
66
|
+
when 1
|
67
|
+
p = params.first
|
68
|
+
case p
|
69
|
+
when Bucket
|
70
|
+
warn(t('full_bucket_mapred', :backtrace => caller.join("\n "))) unless Riak.disable_list_keys_warnings
|
71
|
+
@inputs = maybe_escape(p.name)
|
72
|
+
when RObject
|
73
|
+
@inputs << [maybe_escape(p.bucket.name), maybe_escape(p.key)]
|
74
|
+
when String
|
75
|
+
warn(t('full_bucket_mapred', :backtrace => caller.join("\n "))) unless Riak.disable_list_keys_warnings
|
76
|
+
@inputs = maybe_escape(p)
|
77
|
+
end
|
78
|
+
when 2..3
|
79
|
+
bucket = params.shift
|
80
|
+
bucket = bucket.name if Bucket === bucket
|
81
|
+
if Array === params.first
|
82
|
+
warn(t('full_bucket_mapred', :backtrace => caller.join("\n "))) unless Riak.disable_list_keys_warnings
|
83
|
+
@inputs = {:bucket => maybe_escape(bucket), :key_filters => params.first }
|
84
|
+
else
|
85
|
+
key = params.shift
|
86
|
+
@inputs << params.unshift(maybe_escape(key)).unshift(maybe_escape(bucket))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
self
|
90
|
+
end
|
91
|
+
alias :<< :add
|
92
|
+
alias :include :add
|
93
|
+
|
94
|
+
# Adds a bucket and key-filters built by the given
|
95
|
+
# block. Equivalent to #add with a list of filters.
|
96
|
+
# @param [String] bucket the bucket to apply key-filters to
|
97
|
+
# @yield [] builder block - instance_eval'ed into a FilterBuilder
|
98
|
+
# @return [MapReduce] self
|
99
|
+
# @see MapReduce#add
|
100
|
+
def filter(bucket, &block)
|
101
|
+
add(bucket, FilterBuilder.new(&block).to_a)
|
102
|
+
end
|
103
|
+
|
104
|
+
# (Riak Search) Use a search query to start a map/reduce job.
|
105
|
+
# @param [String, Bucket] bucket the bucket/index to search
|
106
|
+
# @param [String] query the query to run
|
107
|
+
# @return [MapReduce] self
|
108
|
+
def search(bucket, query)
|
109
|
+
bucket = bucket.name if bucket.respond_to?(:name)
|
110
|
+
@inputs = {:module => "riak_search", :function => "mapred_search", :arg => [bucket, query]}
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
# (Secondary Indexes) Use a secondary index query to start a
|
115
|
+
# map/reduce job.
|
116
|
+
# @param [String, Bucket] bucket the bucket whose index to query
|
117
|
+
# @param [String] index the index to query
|
118
|
+
# @param [String, Integer, Range] query the value of the index, or
|
119
|
+
# a range of values (of Strings or Integers)
|
120
|
+
# @return [MapReduce] self
|
121
|
+
def index(bucket, index, query)
|
122
|
+
bucket = bucket.name if bucket.respond_to?(:name)
|
123
|
+
case query
|
124
|
+
when String, Fixnum
|
125
|
+
@inputs = {:bucket => maybe_escape(bucket), :index => index, :key => query}
|
126
|
+
when Range
|
127
|
+
raise ArgumentError, t('invalid_index_query', :value => query.inspect) unless String === query.begin || Integer === query.begin
|
128
|
+
@inputs = {:bucket => maybe_escape(bucket), :index => index, :start => query.begin, :end => query.end}
|
129
|
+
else
|
130
|
+
raise ArgumentError, t('invalid_index_query', :value => query.inspect)
|
131
|
+
end
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
135
|
+
# Add a map phase to the job.
|
136
|
+
# @overload map(function)
|
137
|
+
# @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module,function] pair
|
138
|
+
# @overload map(function?, options)
|
139
|
+
# @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module, function] pair
|
140
|
+
# @param [Hash] options extra options for the phase (see {Phase#initialize})
|
141
|
+
# @return [MapReduce] self
|
142
|
+
# @see Phase#initialize
|
143
|
+
def map(*params)
|
144
|
+
options = params.extract_options!
|
145
|
+
@query << Phase.new({:type => :map, :function => params.shift}.merge(options))
|
146
|
+
self
|
147
|
+
end
|
148
|
+
|
149
|
+
# Add a reduce phase to the job.
|
150
|
+
# @overload reduce(function)
|
151
|
+
# @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module,function] pair
|
152
|
+
# @overload reduce(function?, options)
|
153
|
+
# @param [String, Array] function a Javascript function that represents the phase, or an Erlang [module, function] pair
|
154
|
+
# @param [Hash] options extra options for the phase (see {Phase#initialize})
|
155
|
+
# @return [MapReduce] self
|
156
|
+
# @see Phase#initialize
|
157
|
+
def reduce(*params)
|
158
|
+
options = params.extract_options!
|
159
|
+
@query << Phase.new({:type => :reduce, :function => params.shift}.merge(options))
|
160
|
+
self
|
161
|
+
end
|
162
|
+
|
163
|
+
# Add a link phase to the job. Link phases follow links attached to objects automatically (a special case of map).
|
164
|
+
# @overload link(walk_spec, options={})
|
165
|
+
# @param [WalkSpec] walk_spec a WalkSpec that represents the types of links to follow
|
166
|
+
# @param [Hash] options extra options for the phase (see {Phase#initialize})
|
167
|
+
# @overload link(bucket, tag, keep, options={})
|
168
|
+
# @param [String, nil] bucket the bucket to limit links to
|
169
|
+
# @param [String, nil] tag the tag to limit links to
|
170
|
+
# @param [Boolean] keep whether to keep results of this phase (overrides the phase options)
|
171
|
+
# @param [Hash] options extra options for the phase (see {Phase#initialize})
|
172
|
+
# @overload link(options)
|
173
|
+
# @param [Hash] options options for both the walk spec and link phase
|
174
|
+
# @see WalkSpec#initialize
|
175
|
+
# @return [MapReduce] self
|
176
|
+
# @see Phase#initialize
|
177
|
+
def link(*params)
|
178
|
+
options = params.extract_options!
|
179
|
+
walk_spec_options = options.slice!(:type, :function, :language, :arg) unless params.first
|
180
|
+
walk_spec = WalkSpec.normalize(params.shift || walk_spec_options).first
|
181
|
+
@query << Phase.new({:type => :link, :function => walk_spec}.merge(options))
|
182
|
+
self
|
183
|
+
end
|
184
|
+
|
185
|
+
# Sets the timeout for the map-reduce job.
|
186
|
+
# @param [Fixnum] value the job timeout, in milliseconds
|
187
|
+
def timeout(value)
|
188
|
+
@timeout = value
|
189
|
+
return self
|
190
|
+
end
|
191
|
+
alias :timeout= :timeout
|
192
|
+
|
193
|
+
# Convert the job to JSON for submission over the HTTP interface.
|
194
|
+
# @return [String] the JSON representation
|
195
|
+
def to_json(*a)
|
196
|
+
hash = {"inputs" => inputs, "query" => query.map(&:as_json)}
|
197
|
+
hash['timeout'] = @timeout.to_i if @timeout
|
198
|
+
hash.to_json(*a)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Executes this map-reduce job.
|
202
|
+
# @overload run
|
203
|
+
# Return the entire collection of results.
|
204
|
+
# @return [Array<Array>] similar to link-walking, each element is
|
205
|
+
# an array of results from a phase where "keep" is true. If there
|
206
|
+
# is only one "keep" phase, only the results from that phase will
|
207
|
+
# be returned.
|
208
|
+
# @overload run
|
209
|
+
# Stream the results through the given block without accumulating.
|
210
|
+
# @yield [phase, data] A block to stream results through
|
211
|
+
# @yieldparam [Fixnum] phase the phase from which the results were
|
212
|
+
# generated
|
213
|
+
# @yieldparam [Array] data a list of results from the phase
|
214
|
+
# @return [nil] nothing
|
215
|
+
def run(&block)
|
216
|
+
@client.mapred(self, &block)
|
217
|
+
rescue FailedRequest => fr
|
218
|
+
if fr.server_error? && fr.is_json?
|
219
|
+
raise MapReduceError.new(fr.body)
|
220
|
+
else
|
221
|
+
raise fr
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|