riak-client 2.1.0 → 2.2.0.pre1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -1
  3. data/Guardfile +15 -9
  4. data/README.markdown +118 -9
  5. data/lib/riak/bucket.rb +16 -1
  6. data/lib/riak/bucket_properties.rb +67 -0
  7. data/lib/riak/bucket_type.rb +48 -0
  8. data/lib/riak/bucket_typed/bucket.rb +113 -0
  9. data/lib/riak/client/beefcake/bucket_properties_operator.rb +178 -0
  10. data/lib/riak/client/beefcake/message_overlay.rb +42 -20
  11. data/lib/riak/client/beefcake/object_methods.rb +1 -1
  12. data/lib/riak/client/beefcake/socket.rb +6 -6
  13. data/lib/riak/client/beefcake_protobuffs_backend.rb +44 -60
  14. data/lib/riak/client/protobuffs_backend.rb +8 -8
  15. data/lib/riak/client.rb +7 -2
  16. data/lib/riak/crdt/base.rb +29 -1
  17. data/lib/riak/crdt/counter.rb +7 -3
  18. data/lib/riak/crdt/inner_flag.rb +1 -1
  19. data/lib/riak/crdt/map.rb +7 -3
  20. data/lib/riak/crdt/set.rb +7 -4
  21. data/lib/riak/errors/failed_request.rb +2 -0
  22. data/lib/riak/errors/search_error.rb +29 -0
  23. data/lib/riak/locale/en.yml +7 -0
  24. data/lib/riak/map_reduce.rb +70 -6
  25. data/lib/riak/robject.rb +19 -3
  26. data/lib/riak/search/index.rb +73 -0
  27. data/lib/riak/search/query.rb +133 -0
  28. data/lib/riak/search/result_collection.rb +91 -0
  29. data/lib/riak/search/result_document.rb +59 -0
  30. data/lib/riak/search/schema.rb +65 -0
  31. data/lib/riak/search.rb +10 -0
  32. data/lib/riak/secondary_index.rb +6 -0
  33. data/lib/riak/version.rb +1 -1
  34. data/spec/fixtures/yz_schema_template.xml +18 -0
  35. data/spec/integration/riak/bucket_types_spec.rb +218 -39
  36. data/spec/integration/riak/conflict_resolution_spec.rb +96 -0
  37. data/spec/integration/riak/crdt_spec.rb +30 -2
  38. data/spec/integration/riak/properties_spec.rb +69 -0
  39. data/spec/integration/riak/search_spec.rb +104 -0
  40. data/spec/integration/riak/secondary_index_spec.rb +18 -0
  41. data/spec/riak/beefcake_protobuffs_backend/bucket_properties_operator_spec.rb +247 -0
  42. data/spec/riak/bucket_properties_spec.rb +114 -0
  43. data/spec/riak/bucket_type_spec.rb +34 -0
  44. data/spec/riak/bucket_typed/bucket.rb +43 -0
  45. data/spec/riak/client_spec.rb +16 -8
  46. data/spec/riak/crdt/counter_spec.rb +2 -1
  47. data/spec/riak/crdt/map_spec.rb +2 -1
  48. data/spec/riak/crdt/set_spec.rb +2 -1
  49. data/spec/riak/map_reduce_spec.rb +169 -124
  50. data/spec/riak/search/index_spec.rb +62 -0
  51. data/spec/riak/search/query_spec.rb +88 -0
  52. data/spec/riak/search/result_collection_spec.rb +87 -0
  53. data/spec/riak/search/result_document_spec.rb +63 -0
  54. data/spec/riak/search/schema_spec.rb +63 -0
  55. data/spec/spec_helper.rb +2 -1
  56. data/spec/support/search_config.rb +81 -0
  57. data/spec/support/test_client.yml +14 -7
  58. metadata +44 -5
data/lib/riak/robject.rb CHANGED
@@ -96,6 +96,13 @@ module Riak
96
96
  # @see Bucket#get
97
97
  def initialize(bucket, key=nil)
98
98
  @bucket, @key = bucket, key
99
+
100
+ # fix a require-loop
101
+ require 'riak/bucket_typed/bucket'
102
+
103
+ if @bucket.is_a? BucketTyped::Bucket
104
+ @type = @bucket.type.name
105
+ end
99
106
  @siblings = [ RContent.new(self) ]
100
107
  yield self if block_given?
101
108
  end
@@ -128,7 +135,7 @@ module Riak
128
135
  raise Conflict, self if conflict?
129
136
  raise ArgumentError, t("content_type_undefined") unless content_type.present?
130
137
  raise ArgumentError, t("zero_length_key") if key == ''
131
- @bucket.client.store_object(self, options)
138
+ @bucket.client.store_object(self, default(options))
132
139
  self
133
140
  end
134
141
 
@@ -143,7 +150,7 @@ module Riak
143
150
  force = options.delete(:force)
144
151
  return self unless @key && (@vclock || force)
145
152
  self.etag = self.last_modified = nil if force
146
- bucket.client.reload_object(self, options)
153
+ bucket.client.reload_object(self, default(options))
147
154
  end
148
155
 
149
156
  alias :fetch :reload
@@ -154,7 +161,7 @@ module Riak
154
161
  def delete(options={})
155
162
  return if key.blank?
156
163
  options[:vclock] = vclock if vclock
157
- @bucket.delete(key, options)
164
+ @bucket.delete(key, default(options))
158
165
  freeze
159
166
  end
160
167
 
@@ -189,5 +196,14 @@ module Riak
189
196
  def to_link(tag)
190
197
  Link.new(@bucket.name, @key, tag)
191
198
  end
199
+
200
+ private
201
+
202
+ def default(options)
203
+ return options unless options.is_a? Hash
204
+ return options unless @type
205
+
206
+ {type: @type}.merge options
207
+ end
192
208
  end
193
209
  end
@@ -0,0 +1,73 @@
1
+ require 'riak/search'
2
+ require 'riak/errors/search_error'
3
+
4
+ module Riak::Search
5
+ # A {Riak::Search::Index} is how Solr finds documents in Riak Search 2. A
6
+ # bucket or bucket type property must be configured to use the index in order
7
+ # for new and updated documents to be indexed and searchable.
8
+ class Index
9
+ # @return [String] the name of the index
10
+ attr_reader :name
11
+
12
+ # Initializes an index object, that may or may not exist.
13
+ #
14
+ # @param [Riak::Client] client the client connected to the Riak cluster
15
+ # you wish to operate on
16
+ # @param [String] name the name of the index
17
+ def initialize(client, name)
18
+ @client = client
19
+ @name = name
20
+ end
21
+
22
+ # @return [Boolean] does this index exist on Riak?
23
+ def exists?
24
+ !!index_data
25
+ end
26
+
27
+ # @return [Integer] N-value/replication parameter of this index
28
+ def n_val
29
+ index_data[:n_val]
30
+ end
31
+
32
+ # @return [String] schema name of this index
33
+ def schema
34
+ index_data[:schema]
35
+ end
36
+
37
+ # Attempt to create this index
38
+ #
39
+ # @raise [Riak::SearchError::IndexExistsError] if an index with the given
40
+ # name already exists
41
+ def create!(schema = nil, n_val = nil)
42
+ raise Riak::SearchError::IndexExistsError.new name if exists?
43
+
44
+ @client.backend do |b|
45
+ b.create_search_index name, schema, n_val
46
+ end
47
+
48
+ @index_data = nil
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+ def index_data
55
+ return @index_data if defined?(@index_data) && @index_data
56
+
57
+ id = nil
58
+
59
+ begin
60
+ id = @client.backend do |b|
61
+ b.get_search_index name
62
+ end.index
63
+ rescue Riak::ProtobuffsFailedRequest => e
64
+ return nil if e.not_found?
65
+ raise e
66
+ end
67
+
68
+ id = id.first if id.is_a? Array
69
+
70
+ return @index_data = id
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,133 @@
1
+ require 'riak/search'
2
+
3
+ module Riak::Search
4
+ # A {Riak::Search::Query} wraps a Solr query for Riak Search 2.
5
+ class Query
6
+
7
+ # @!attribute rows
8
+ # @return [Numeric] the number of rows to return from the query
9
+ attr_accessor :rows
10
+
11
+ # @!attribute start
12
+ # @return [Numeric] the offset into the total result set to get results for
13
+ attr_accessor :start
14
+
15
+ # @!attribute sort
16
+ # @return [String] how Solr should sort the result set
17
+ attr_accessor :sort
18
+
19
+ # @!attribute filter
20
+ # @return [String] have Solr filter the results prior to returning them
21
+ attr_accessor :filter
22
+
23
+ # @!attribute df
24
+ # @return [Array<String>] default fields for Solr to search
25
+ attr_accessor :df
26
+
27
+ # @!attribute op
28
+ # @return [String] Solr search operator
29
+ attr_accessor :op
30
+
31
+ # @!attribute fl
32
+ # @return [Array<String>] fields for Solr to return
33
+ attr_accessor :fl
34
+
35
+ # @!attribute [r] term
36
+ # @return [String] the term to query
37
+ attr_reader :term
38
+
39
+ # Initializes a query object.
40
+ #
41
+ # @param [Riak::Client] client the client connected to the Riak cluster
42
+ # @param [String,Riak::Search::Index] index the index to query, either a
43
+ # {Riak::Search::Index} instance or a {String}
44
+ # @param [String] term the query term
45
+ # @param [Hash] options a hash of options to quickly set attributes
46
+ def initialize(client, index, term, options={ })
47
+ @client = client
48
+ validate_index index
49
+ @term = term
50
+ @options = options.symbolize_keys
51
+
52
+ set_defaults
53
+ consume_options
54
+ end
55
+
56
+ # Get results from the query. Performs the query when called the first time.
57
+ #
58
+ # @return [Riak::Search::ResultCollection] collection of results
59
+ def results
60
+ return @results if defined? @results
61
+
62
+ @results = ResultCollection.new @client, raw_results
63
+ end
64
+
65
+ private
66
+
67
+ def index_name
68
+ return @index if @index.is_a? String
69
+ return @index.name
70
+ end
71
+
72
+ def validate_index(index)
73
+ if index.is_a? String
74
+ index = Riak::Search::Index.new @client, index
75
+ end
76
+
77
+ unless index.is_a? Riak::Search::Index
78
+ raise Riak::SearchError::IndexArgumentError.new index
79
+ end
80
+
81
+ unless index.exists?
82
+ raise Riak::SearchError::IndexNonExistError.new index.name
83
+ end
84
+
85
+ @index = index
86
+ end
87
+
88
+ def set_defaults
89
+ @rows = nil
90
+ @start = nil
91
+
92
+ @sort = nil
93
+ @filter = nil
94
+
95
+ @df = %w{text}
96
+ @op = nil
97
+ @fl = %w{_yz_rb _yz_rk _yz_rt score}
98
+
99
+ @presort = nil
100
+ end
101
+
102
+ def consume_options
103
+ @rows = @options[:rows] if @options[:rows]
104
+ @start = @options[:start] if @options[:start]
105
+
106
+ @sort = @options[:sort] if @options[:sort]
107
+ @filter = @options[:filter] if @options[:filter]
108
+
109
+ @df = @options[:df] if @options[:df]
110
+ @op = @options[:op] if @options[:op]
111
+ @fl = @options[:fl] if @options[:fl]
112
+ end
113
+
114
+ def prepare_options
115
+ configured_options = {
116
+ rows: @rows,
117
+ start: @start,
118
+ sort: @sort,
119
+ filter: @filter,
120
+ df: @df.join(' '),
121
+ op: @op,
122
+ fl: @fl
123
+ }
124
+ @options.merge configured_options
125
+ end
126
+
127
+ def raw_results
128
+ @client.backend do |be|
129
+ be.search index_name, @term, prepare_options
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,91 @@
1
+ require 'riak/search'
2
+
3
+ module Riak::Search
4
+
5
+ # A collection of Riak Search 2 ("Yokozuna") results. Provides direct access
6
+ # to the {Riak::RObject} instances found, and access through the #docs method
7
+ # to the results as returned by Solr.
8
+ class ResultCollection
9
+ # @return [Riak::Client]
10
+ attr_reader :client
11
+
12
+ # @return [Hash] the de-serialzed hash returned from Solr
13
+ attr_reader :raw
14
+
15
+ # @return [Numeric] the maximum score found by Solr
16
+ attr_reader :max_score
17
+
18
+ # @return [Integer] the number of documents in this collection
19
+ attr_reader :length
20
+
21
+ # @return [Integer] the total number of documents matched, including ones
22
+ # not returned due to row-limiting
23
+ attr_reader :num_found
24
+
25
+ # Initialize a {ResultCollection} with the client queried and the raw
26
+ # JSON returned from the search API.
27
+ #
28
+ # This is automatically called by {Riak::Search::Query}#results
29
+ #
30
+ # @api private
31
+ def initialize(client, raw_results)
32
+ @client = client
33
+ @raw = raw_results
34
+ @max_score = raw['max_score']
35
+ @num_found = raw['num_found']
36
+ @length = raw['docs'].length
37
+ end
38
+
39
+ # Access the individual documents from the search results. The document
40
+ # metadata are each wrapped in a {Riak::Search::ResultDocument}.
41
+ #
42
+ # @return [Array<Riak::Search::ResultDocument>] individual documents
43
+ def docs
44
+ @docs ||= raw['docs'].map do |result|
45
+ ResultDocument.new client, result
46
+ end
47
+ end
48
+
49
+ # @return [Boolean] does this collection contain any documents?
50
+ def empty?
51
+ length == 0
52
+ end
53
+
54
+ # @param [Integer] index the index of the [Riak::RObject] to load and return
55
+ # @return [Riak::RObject,NilClass] the found object, or nil if the index
56
+ # is out of range
57
+ def [](index)
58
+ doc = docs[index]
59
+ return nil if doc.nil?
60
+
61
+ doc.robject
62
+ end
63
+
64
+ # @return [Riak::RObject,NilClass] the first found object, or nil if the
65
+ # index is out of range
66
+ def first
67
+ self[0]
68
+ end
69
+
70
+ # {Enumerable}-compatible iterator method. If a block is given, yields with
71
+ # each {Riak::RObject} in the collection. If no block is given, returns an
72
+ # {Enumerator} over each {Riak::RObject in the collection.
73
+ # @yieldparam robject [Riak::RObject]
74
+ # @return [Enumerator<Riak::RObject>]
75
+ def each_robject
76
+ enum = docs.each_with_index
77
+
78
+ if block_given?
79
+ enum.each do |doc, idx|
80
+ yield self[idx]
81
+ end
82
+ else
83
+ Enumerator.new do |yielder|
84
+ enum.each do |doc, idx|
85
+ yielder << self[idx]
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,59 @@
1
+ module Riak::Search
2
+
3
+ # A single document from a Riak Search 2 response. Materializes the document
4
+ # fields into {Riak::BucketType}, {Riak::Bucket}, and {Riak::RObject}
5
+ # instances on demand.
6
+ class ResultDocument
7
+ # @return [Riak::Client]
8
+ attr_reader :client
9
+
10
+ # @return [Hash] the de-serialized hash returned from Solr
11
+ attr_reader :raw
12
+
13
+ # Iniitalize a {ResultDocument} with the client queried and the relevant
14
+ # part of the JSON returned from the search API.
15
+ #
16
+ # This is automatically called by {Riak::Search::ResultCollection}#docs
17
+ #
18
+ # @api private
19
+ def initialize(client, raw)
20
+ @client = client
21
+ @raw = raw
22
+ end
23
+
24
+ # @return [String] the key of the result
25
+ def key
26
+ @key ||= raw['_yz_rk']
27
+ end
28
+
29
+ # @return [Riak::BucketType] the bucket type containing the result
30
+ def bucket_type
31
+ @bucket_type ||= client.bucket_type raw['_yz_rt']
32
+ end
33
+
34
+ # @return [Riak::Bucket] the bucket containing the result
35
+ def bucket
36
+ @bucket ||= bucket_type.bucket raw['_yz_rb']
37
+ end
38
+
39
+ # @return [Numeric] the score of the match
40
+ def score
41
+ @score ||= Float(raw['score'])
42
+ end
43
+
44
+ # Provides access to other parts of the result document without
45
+ # materializing them. Useful when querying non-default fields.
46
+ #
47
+ # @return [String,Numeric] other search result document field
48
+ def [](field_name)
49
+ raw[field_name.to_s]
50
+ end
51
+
52
+ # Loads the {Riak::RObject} referred to by the result document.
53
+ #
54
+ # @return [Riak::RObject]
55
+ def robject
56
+ @robject ||= bucket.get key
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,65 @@
1
+ require 'riak/search'
2
+ require 'riak/errors/search_error'
3
+
4
+ module Riak::Search
5
+ # A schema is a Riak Search 2 concept that describes how to index documents.
6
+ # They're implemented as a standard Solr XML schema.
7
+ class Schema
8
+ # @return [String] the name of the schema
9
+ attr_reader :name
10
+
11
+ # Initializes a schema object, that may or may not exist.
12
+ #
13
+ # @param [Riak::Client] client the client connected to the Riak cluster
14
+ # you wish to operate on
15
+ # @param [String] name the name of the schema
16
+ def initialize(client, name)
17
+ @client = client
18
+ @name = name
19
+ end
20
+
21
+ # @return [Boolean] does this schema exist on Riak?
22
+ def exists?
23
+ !!schema_data
24
+ end
25
+
26
+ # @return [String] the XML content of this schema
27
+ def content
28
+ schema_data.content
29
+ end
30
+
31
+ # @param [String] content the XML content of this schema
32
+ # @raise [Riak::SearchError::SchemaExistsError] if a schema with the given
33
+ # name already exists
34
+ def create!(content)
35
+ raise Riak::SearchError::SchemaExistsError.new name if exists?
36
+
37
+ @client.backend do |b|
38
+ b.create_search_schema name, content
39
+ end
40
+
41
+ @schema_data = nil
42
+
43
+ true
44
+ end
45
+
46
+ private
47
+ def schema_data
48
+ return @schema_data if defined?(@schema_data) && @schema_data
49
+
50
+ sd = nil
51
+
52
+ begin
53
+ sd = @client.backend do |b|
54
+ b.get_search_schema name
55
+ end
56
+ rescue Riak::ProtobuffsFailedRequest => e
57
+ return nil if e.not_found?
58
+ raise e
59
+ end
60
+
61
+ return @schema_data = sd
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,10 @@
1
+ module Riak
2
+ module Search
3
+ end
4
+ end
5
+
6
+ require 'riak/search/index'
7
+ require 'riak/search/query'
8
+ require 'riak/search/result_collection'
9
+ require 'riak/search/result_document'
10
+ require 'riak/search/schema'
@@ -1,4 +1,6 @@
1
1
  require 'riak/index_collection'
2
+ require 'riak/bucket_typed/bucket'
3
+
2
4
  module Riak
3
5
  class SecondaryIndex
4
6
  include Util::Translation
@@ -16,6 +18,10 @@ module Riak
16
18
  @query = query
17
19
  @options = options
18
20
 
21
+ if @bucket.is_a? Riak::BucketTyped::Bucket
22
+ @options = { type: @bucket.type.name }.merge @options
23
+ end
24
+
19
25
  validate_options
20
26
  end
21
27
 
data/lib/riak/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Riak
2
- VERSION = "2.1.0"
2
+ VERSION = "2.2.0.pre1"
3
3
  end
@@ -0,0 +1,18 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <schema name="SCHEMA_NAME" version="1.5">
3
+ <fields>
4
+ <field name="_yz_id" type="_yz_str" indexed="true" stored="true" multiValued="false" required="true" />
5
+ <field name="_yz_ed" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
6
+ <field name="_yz_pn" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
7
+ <field name="_yz_fpn" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
8
+ <field name="_yz_vtag" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
9
+ <field name="_yz_rk" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
10
+ <field name="_yz_rb" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
11
+ <field name="_yz_rt" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
12
+ <field name="_yz_err" type="_yz_str" indexed="true" stored="true" multiValued="false"/>
13
+ </fields>
14
+ <uniqueKey>_yz_id</uniqueKey>
15
+ <types>
16
+ <fieldType name="_yz_str" class="solr.StrField" sortMissingLast="true" />
17
+ </types>
18
+ </schema>