better-riak-client 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|