better-riak-client 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/LICENSE +16 -0
  2. data/README.markdown +198 -0
  3. data/RELEASE_NOTES.md +211 -0
  4. data/better-riak-client.gemspec +61 -0
  5. data/erl_src/riak_kv_test014_backend.beam +0 -0
  6. data/erl_src/riak_kv_test014_backend.erl +189 -0
  7. data/erl_src/riak_kv_test_backend.beam +0 -0
  8. data/erl_src/riak_kv_test_backend.erl +697 -0
  9. data/erl_src/riak_search_test_backend.beam +0 -0
  10. data/erl_src/riak_search_test_backend.erl +175 -0
  11. data/lib/riak/bucket.rb +221 -0
  12. data/lib/riak/client/beefcake/messages.rb +213 -0
  13. data/lib/riak/client/beefcake/object_methods.rb +111 -0
  14. data/lib/riak/client/beefcake_protobuffs_backend.rb +226 -0
  15. data/lib/riak/client/decaying.rb +36 -0
  16. data/lib/riak/client/excon_backend.rb +162 -0
  17. data/lib/riak/client/feature_detection.rb +88 -0
  18. data/lib/riak/client/http_backend/configuration.rb +211 -0
  19. data/lib/riak/client/http_backend/key_streamer.rb +43 -0
  20. data/lib/riak/client/http_backend/object_methods.rb +106 -0
  21. data/lib/riak/client/http_backend/request_headers.rb +34 -0
  22. data/lib/riak/client/http_backend/transport_methods.rb +201 -0
  23. data/lib/riak/client/http_backend.rb +340 -0
  24. data/lib/riak/client/net_http_backend.rb +82 -0
  25. data/lib/riak/client/node.rb +115 -0
  26. data/lib/riak/client/protobuffs_backend.rb +173 -0
  27. data/lib/riak/client/search.rb +91 -0
  28. data/lib/riak/client.rb +540 -0
  29. data/lib/riak/cluster.rb +151 -0
  30. data/lib/riak/core_ext/blank.rb +53 -0
  31. data/lib/riak/core_ext/deep_dup.rb +13 -0
  32. data/lib/riak/core_ext/extract_options.rb +7 -0
  33. data/lib/riak/core_ext/json.rb +15 -0
  34. data/lib/riak/core_ext/slice.rb +18 -0
  35. data/lib/riak/core_ext/stringify_keys.rb +10 -0
  36. data/lib/riak/core_ext/symbolize_keys.rb +10 -0
  37. data/lib/riak/core_ext/to_param.rb +31 -0
  38. data/lib/riak/core_ext.rb +7 -0
  39. data/lib/riak/encoding.rb +6 -0
  40. data/lib/riak/failed_request.rb +81 -0
  41. data/lib/riak/i18n.rb +5 -0
  42. data/lib/riak/json.rb +52 -0
  43. data/lib/riak/link.rb +94 -0
  44. data/lib/riak/locale/en.yml +53 -0
  45. data/lib/riak/locale/fr.yml +52 -0
  46. data/lib/riak/map_reduce/filter_builder.rb +103 -0
  47. data/lib/riak/map_reduce/phase.rb +98 -0
  48. data/lib/riak/map_reduce.rb +225 -0
  49. data/lib/riak/map_reduce_error.rb +7 -0
  50. data/lib/riak/node/configuration.rb +293 -0
  51. data/lib/riak/node/console.rb +133 -0
  52. data/lib/riak/node/control.rb +207 -0
  53. data/lib/riak/node/defaults.rb +83 -0
  54. data/lib/riak/node/generation.rb +106 -0
  55. data/lib/riak/node/log.rb +34 -0
  56. data/lib/riak/node/version.rb +43 -0
  57. data/lib/riak/node.rb +38 -0
  58. data/lib/riak/robject.rb +318 -0
  59. data/lib/riak/search.rb +3 -0
  60. data/lib/riak/serializers.rb +74 -0
  61. data/lib/riak/stamp.rb +77 -0
  62. data/lib/riak/test_server.rb +89 -0
  63. data/lib/riak/util/escape.rb +76 -0
  64. data/lib/riak/util/headers.rb +53 -0
  65. data/lib/riak/util/multipart/stream_parser.rb +62 -0
  66. data/lib/riak/util/multipart.rb +52 -0
  67. data/lib/riak/util/tcp_socket_extensions.rb +58 -0
  68. data/lib/riak/util/translation.rb +19 -0
  69. data/lib/riak/version.rb +3 -0
  70. data/lib/riak/walk_spec.rb +105 -0
  71. data/lib/riak.rb +21 -0
  72. 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