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.
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