cloudsearchable 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +119 -0
- data/Rakefile +11 -0
- data/cloudsearchable.gemspec +50 -0
- data/lib/cloudsearchable.rb +206 -0
- data/lib/cloudsearchable/cloud_search.rb +41 -0
- data/lib/cloudsearchable/domain.rb +159 -0
- data/lib/cloudsearchable/field.rb +56 -0
- data/lib/cloudsearchable/query_chain.rb +218 -0
- data/lib/cloudsearchable/version.rb +3 -0
- data/spec/cloudsearchable/cloud_search_spec.rb +45 -0
- data/spec/cloudsearchable/cloudsearchable_spec.rb +71 -0
- data/spec/cloudsearchable/domain_spec.rb +158 -0
- data/spec/cloudsearchable/field_spec.rb +30 -0
- data/spec/cloudsearchable/query_chain_spec.rb +305 -0
- data/spec/spec_helper.rb +10 -0
- data/spec/test_classes/cloud_searchable_test_class.rb +42 -0
- metadata +153 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_classes/cloud_searchable_test_class'
|
3
|
+
|
4
|
+
describe Cloudsearchable do
|
5
|
+
let(:clazz){ CloudSearchableSampleClassFactory.call }
|
6
|
+
|
7
|
+
it 'can describe an index that returns ids for the class type' do
|
8
|
+
test_index = clazz.cloudsearch_index(:test_index)
|
9
|
+
test_index.should be_kind_of(Cloudsearchable::Domain)
|
10
|
+
test_index.fields.should have(4).items #3 explicit + 1 for the id of the object
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has a default index' do
|
14
|
+
clazz.cloudsearch_index.should be_kind_of(Cloudsearchable::Domain)
|
15
|
+
clazz.cloudsearch_index(:test_index).should_not be(clazz.cloudsearch_index)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'names domains consistent with CloudSearch limitations' do
|
19
|
+
clazz.cloudsearch_index(:test_index).name.should =~ /^[a-z][a-z0-9\-]+$/
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'an ordinary object' do
|
23
|
+
#An instance of the searchable class
|
24
|
+
let(:inst) do
|
25
|
+
inst = clazz.new
|
26
|
+
#arbitrary but plausible values for the core fields
|
27
|
+
inst.destroyed = false
|
28
|
+
inst.id = 42
|
29
|
+
inst.lock_version = 18
|
30
|
+
inst.customer = OpenStruct.new :id => 123
|
31
|
+
inst.name = "My Name"
|
32
|
+
inst
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'supplies the right values to the fields' do
|
36
|
+
test_index = clazz.cloudsearch_index(:test_index)
|
37
|
+
test_index.fields[:test_class_id].value_for(inst).should be(inst.id)
|
38
|
+
test_index.fields[:customer_id].value_for(inst).should be(inst.customer)
|
39
|
+
test_index.fields[:test_name].value_for(inst).should be(inst.name)
|
40
|
+
test_index.fields[:helpfulness].value_for(inst).should be(1234)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'reindexes when told to' do
|
44
|
+
clazz.cloudsearch_index( ).should_receive(:post_record).with(inst, inst.id, inst.lock_version)
|
45
|
+
clazz.cloudsearch_index(:test_index).should_receive(:post_record).with(inst, inst.id, inst.lock_version)
|
46
|
+
inst.update_indexes
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'generates a sensible addition sdf document' do
|
50
|
+
sdf = clazz.cloudsearch_index(:test_index).send :addition_sdf, inst, inst.id, inst.lock_version
|
51
|
+
sdf[:fields][:helpfulness].should be(1234)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'a destroyed object' do
|
56
|
+
#An instance of the searchable class
|
57
|
+
let(:inst) do
|
58
|
+
inst = clazz.new
|
59
|
+
#arbitrary but plausible values for the core fields
|
60
|
+
inst.destroyed = true
|
61
|
+
inst
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'reindexes when told to' do
|
65
|
+
clazz.cloudsearch_index( ).should_receive(:delete_record).with(inst.id, inst.lock_version)
|
66
|
+
clazz.cloudsearch_index(:test_index).should_receive(:delete_record).with(inst.id, inst.lock_version)
|
67
|
+
inst.update_indexes
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_classes/cloud_searchable_test_class'
|
3
|
+
|
4
|
+
describe Cloudsearchable::Domain do
|
5
|
+
before(:each) do
|
6
|
+
fake_client = double('client')
|
7
|
+
CloudSearch.stub(:client).and_return(fake_client)
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# First call to describe_domains returns it needs reindexing,
|
12
|
+
# Second call returns that it is processing,
|
13
|
+
# Third call returns that it is done processing
|
14
|
+
#
|
15
|
+
let(:needs_rebuild_domain) do
|
16
|
+
described_class.new('nrb-index').tap do |dom|
|
17
|
+
resp = describe_domain_response(dom.name)
|
18
|
+
CloudSearch.client.stub(:describe_domains).with(:domain_names => [dom.name]).
|
19
|
+
and_return(
|
20
|
+
describe_domain_response(dom.name, :required_index_documents => true),
|
21
|
+
describe_domain_response(dom.name, :processing => true),
|
22
|
+
describe_domain_response(dom.name)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# A domain name named 'my-index'
|
28
|
+
let(:domain) do
|
29
|
+
described_class.new('my-index').tap do |dom|
|
30
|
+
CloudSearch.client.stub(:describe_domains).and_return(describe_domain_response(dom.name))
|
31
|
+
CloudSearch.client.stub(:describe_domains).with(:domain_names => [dom.name]).and_return(describe_domain_response(dom.name))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:empty_domain) do
|
36
|
+
described_class.new('my-index').tap do |dom|
|
37
|
+
CloudSearch.client.stub(:describe_domains).and_return({})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'can be instantiated' do
|
42
|
+
index = domain
|
43
|
+
index.name.should end_with('my-index')
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can haz a literal field' do
|
47
|
+
index = domain
|
48
|
+
index.add_field(:literary, :literal) { nil }
|
49
|
+
index.fields[:literary].type.should eq(:literal)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can be initialized with a nested class' do
|
53
|
+
class OuterClassForCloudSearch
|
54
|
+
class InnerClass
|
55
|
+
include Cloudsearchable
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
OuterClassForCloudSearch::InnerClass.cloudsearch_prefix.should match(/^[A-Za-z0-9_-]+$/)
|
60
|
+
Object.instance_eval { remove_const :OuterClassForCloudSearch }
|
61
|
+
end
|
62
|
+
|
63
|
+
context "SDF documents" do
|
64
|
+
let(:object) { OpenStruct.new(:field_with_nil_value => nil, :field_with_present_value => 42) }
|
65
|
+
subject do
|
66
|
+
described_class.new('my-index').tap do |d|
|
67
|
+
d.add_field(:field_with_nil_value, :literal)
|
68
|
+
d.add_field(:field_with_present_value, :literal)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should generate present fields" do
|
73
|
+
subject.addition_sdf(object, "id", 1)[:fields][:field_with_present_value].should == 42
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not generate nil fields" do
|
77
|
+
subject.addition_sdf(object, "id", 1)[:fields][:field_with_nil_value].should be_nil
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'raises if the domain cannot be found' do
|
82
|
+
expect { empty_domain.send(:search_endpoint) }.to raise_error( Cloudsearchable::Domain::DomainNotFound,
|
83
|
+
/Cloudsearchable could not find the domain/)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# A test for the accidental clashing of domain caching
|
88
|
+
# on a class variable
|
89
|
+
#
|
90
|
+
it 'caches endpoints for multiple domains' do
|
91
|
+
domain.send(:search_endpoint).should_not eq(needs_rebuild_domain.send(:search_endpoint))
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'endpoint selected is based on the domain name' do
|
95
|
+
domain.send(:search_endpoint).should eq describe_domain_response(domain.name)[:domain_status_list][0][:search_service][:endpoint]
|
96
|
+
domain.send(:doc_endpoint).should eq describe_domain_response(domain.name)[:domain_status_list][0][:doc_service][:endpoint]
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'sleeps, waiting for reindexing' do
|
100
|
+
CloudSearch.client.should_receive(:index_documents).with(:domain_name => needs_rebuild_domain.name)
|
101
|
+
CloudSearch.client.should_receive(:describe_domains).exactly(3).times
|
102
|
+
needs_rebuild_domain.apply_changes(3).should == true
|
103
|
+
end
|
104
|
+
|
105
|
+
protected
|
106
|
+
|
107
|
+
#
|
108
|
+
# A mockup of the response to a describe_domain request
|
109
|
+
#
|
110
|
+
def describe_domain_response(domain_name, options = {})
|
111
|
+
{
|
112
|
+
:domain_status_list=> [
|
113
|
+
{
|
114
|
+
:search_partition_count=>1,
|
115
|
+
:search_service=>{
|
116
|
+
:arn=>"arn:aws:cs:us-east-1:510523556749:search/#{domain_name}",
|
117
|
+
:endpoint=>"search-#{domain_name}-7bq6utq4fdrwax5r6irje7xlra.us-east-1.cloudsearch.amazonaws.com"
|
118
|
+
},
|
119
|
+
:num_searchable_docs=>23,
|
120
|
+
:search_instance_type=>"search.m1.small",
|
121
|
+
:created=>true,
|
122
|
+
:domain_id=>"510523556749/#{domain_name}",
|
123
|
+
:processing=> options.fetch(:processing, false),
|
124
|
+
:search_instance_count=>1,
|
125
|
+
:domain_name=>"#{domain_name}",
|
126
|
+
:requires_index_documents=> options.fetch(:required_index_documents,false),
|
127
|
+
:deleted=>false,
|
128
|
+
:doc_service=>{
|
129
|
+
:arn=>"arn:aws:cs:us-east-1:510523556749:doc/#{domain_name}",
|
130
|
+
:endpoint=>"doc-#{domain_name}-7bq6utq4fdrwax5r6irje7xlra.us-east-1.cloudsearch.amazonaws.com"
|
131
|
+
}
|
132
|
+
},
|
133
|
+
{:search_partition_count=>1,
|
134
|
+
:search_service=>
|
135
|
+
{:arn=>
|
136
|
+
"arn:aws:cs:us-east-1:510523556749:search/dev-llarue-collection-items",
|
137
|
+
:endpoint=>
|
138
|
+
"search-dev-llarue-collection-items-hjopg2yzhcjdd4qxeglr2v5v7m.us-east-1.cloudsearch.amazonaws.com"},
|
139
|
+
:num_searchable_docs=>2,
|
140
|
+
:search_instance_type=>"search.m1.small",
|
141
|
+
:created=>true,
|
142
|
+
:domain_id=>"510523556749/dev-llarue-collection-items",
|
143
|
+
:processing=>false,
|
144
|
+
:search_instance_count=>1,
|
145
|
+
:domain_name=>"dev-llarue-collection-items",
|
146
|
+
:requires_index_documents=>false,
|
147
|
+
:deleted=>false,
|
148
|
+
:doc_service=>
|
149
|
+
{:arn=>"arn:aws:cs:us-east-1:510523556749:doc/dev-llarue-collection-items",
|
150
|
+
:endpoint=>
|
151
|
+
"doc-dev-llarue-collection-items-hjopg2yzhcjdd4qxeglr2v5v7m.us-east-1.cloudsearch.amazonaws.com"}}
|
152
|
+
],
|
153
|
+
:response_metadata=>{
|
154
|
+
:request_id=>"7d9487a7-1c9f-11e2-9f96-0958b8a97a74"
|
155
|
+
}
|
156
|
+
}
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe Cloudsearchable::Field do
|
5
|
+
it 'has a name' do
|
6
|
+
field = described_class.new 'fnord', :literal
|
7
|
+
field.name.should eq(:fnord)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'can find its value' do
|
11
|
+
test_value = nil
|
12
|
+
field = described_class.new('foo', :literal, :source => Proc.new { test_value })
|
13
|
+
test_value = 123
|
14
|
+
field.value_for(Object.new).should eq(test_value)
|
15
|
+
|
16
|
+
record = OpenStruct.new :a => test_value
|
17
|
+
field2 = described_class.new('bar', :literal, :source => :a)
|
18
|
+
field.value_for(record).should eq(test_value)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'generates a field definition' do
|
22
|
+
domain_name = 'narnia'
|
23
|
+
field = described_class.new('fnord', :literal, :search_enabled => true)
|
24
|
+
CloudSearch.client.should_receive(:define_index_field) do |call|
|
25
|
+
call[:domain_name].should eq(domain_name)
|
26
|
+
call[:index_field][:literal_options][:search_enabled].should be_true
|
27
|
+
end
|
28
|
+
field.define_in_domain domain_name
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'test_classes/cloud_searchable_test_class'
|
3
|
+
|
4
|
+
describe Cloudsearchable::Query do
|
5
|
+
let(:clazz){ CloudSearchableSampleClassFactory.call }
|
6
|
+
|
7
|
+
it 'can build a simple search query' do
|
8
|
+
clazz.search.where(:customer_id, :eq, 12345).query.to_q[:bq].should =~ /customer_id:'12345'/
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'chains' do
|
12
|
+
query = clazz.search.where(customer_id: 12345).where(helpfulness: 42).query.to_q[:bq]
|
13
|
+
query.should =~ /customer_id:'12345'/
|
14
|
+
query.should =~ /helpfulness:'42'/
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can build a query with "not equal to" condition' do
|
18
|
+
query = clazz.search.where(:customer_id, :!=, 1234).query.to_q[:bq].should =~ /\(not customer_id:'1234'\)/
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'can build a query from a hash' do
|
22
|
+
query = clazz.search.where(customer_id: 12345, helpfulness: 42).query.to_q[:bq]
|
23
|
+
query.should =~ /customer_id:'12345'/
|
24
|
+
query.should =~ /helpfulness:'42'/
|
25
|
+
end
|
26
|
+
|
27
|
+
it "doesn't build queries without a query term" do
|
28
|
+
expect do
|
29
|
+
query = clazz.search.limit(10).query.to_q
|
30
|
+
end.to raise_exception
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#where' do
|
34
|
+
describe 'uint data type' do
|
35
|
+
describe 'within_range' do
|
36
|
+
it 'supports range query with uint fields' do
|
37
|
+
clazz.search.where(:helpfulness, :within_range, "0..#{123}").query.to_q[:bq].should =~ /helpfulness:0..123/
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'supports range query with uint fields using a ruby range' do
|
41
|
+
clazz.search.where(:helpfulness, :within_range, 0..123).query.to_q[:bq].should =~ /helpfulness:0..123/
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'inequalities' do
|
46
|
+
it 'supports greater-than with uint fields' do
|
47
|
+
clazz.search.where(:helpfulness, :>, 123).query.to_q[:bq].should =~ /helpfulness:124../
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'supports greater-than-or-equal-to with uint fields' do
|
51
|
+
clazz.search.where(:helpfulness, :>=, 123).query.to_q[:bq].should =~ /helpfulness:123../
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'supports less-than with uint fields' do
|
55
|
+
clazz.search.where(:helpfulness, :<, 123).query.to_q[:bq].should =~ /helpfulness:..122/
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'supports less-than-or-equal-to with uint fields' do
|
59
|
+
clazz.search.where(:helpfulness, :<=, 123).query.to_q[:bq].should =~ /helpfulness:..123/
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does not permit inequality operators with non-integer fields' do
|
63
|
+
[:>, :>=, :<, :<=].each do |op|
|
64
|
+
['123', nil, 12.0].each do |value|
|
65
|
+
expect { clazz.search.where(:helpfulness, op, value).query.to_q[:bq] }.to raise_error
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
it 'supports querying for any of several values of a field' do
|
75
|
+
clazz.search.where(:category, :any, %w{big small}).query.to_q[:bq].should include("(or category:'big' category:'small')")
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'supports text method' do
|
79
|
+
query = clazz.search.text('test').query.to_q[:q]
|
80
|
+
query.should =~ /test/
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'supports chaining text and where clauses together' do
|
84
|
+
query = clazz.search.text('test').where(:featured, :==, 'f').query
|
85
|
+
query.to_q[:q].should =~ /test/
|
86
|
+
query.to_q[:bq].should =~ /featured:'f'/
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'supports ordering with a rank expression' do
|
90
|
+
clazz.search.where(customer_id: 12345).order('-helpfulness').query.to_q[:rank].should eq '-helpfulness'
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'supports limit' do
|
94
|
+
clazz.search.where(customer_id: 12345).limit(10).query.to_q[:size].should eq 10
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'has high default limit' do
|
98
|
+
clazz.search.where(customer_id: 12345).query.to_q[:size].should eq 100000
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'supports offset' do
|
102
|
+
clazz.search.where(customer_id: 12345).offset(100).query.to_q[:start].should eq 100
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'queries' do
|
106
|
+
before(:each) do
|
107
|
+
clazz.cloudsearch_index.should_receive(:execute_query).and_return(cloudsearch_response)
|
108
|
+
end
|
109
|
+
|
110
|
+
context 'query warning' do
|
111
|
+
before(:each) do
|
112
|
+
clazz.stub(:find).and_return([])
|
113
|
+
Cloudsearchable.logger.should_receive(:warn).with(/CS-InvalidFieldOrRankAliasInRankParameter/)
|
114
|
+
end
|
115
|
+
|
116
|
+
let(:query){clazz.search.where(customer_id: 12345).order("-adult")}
|
117
|
+
let(:cloudsearch_response) do
|
118
|
+
# generated by ranking a literal field that is search-enabled but not result-enabled
|
119
|
+
{
|
120
|
+
"rank" => "adult",
|
121
|
+
"match-expr" => "(label initialized_at:1363464074..1366056074)",
|
122
|
+
"hits" => {
|
123
|
+
"found" => 285,
|
124
|
+
"start" => 0,
|
125
|
+
"hit" => [
|
126
|
+
{"id" => "40bdd5072b6dbae6245fe4ee837d22e3","data" => {"collection_item_id" => ["PCxSz65GIcZTtc0UpRdT-i--w-1365550370"]}},
|
127
|
+
{"id" => "00af8f5f96aa1db7aff77be5651b3bb1","data" => {"collection_item_id" => ["PCxhksJTLRYnoGXvwZik82Fkw-1365020313"]}},
|
128
|
+
{"id" => "00b6ac84e3ae402e7698959bf692a53e","data" => {"collection_item_id" => ["PCxs-fIVZnBcTzZ4MtfDguS1A-1365020274"]}},
|
129
|
+
{"id" => "018fdee653bff74abd12ac30152a2837","data" => {"collection_item_id" => ["PCxmAGHFtAgyqUrgI3HgM_P6Q-1365548349"]}},
|
130
|
+
{"id" => "01d062d24c389906eea2d16b8193eb56","data" => {"collection_item_id" => ["PCxqjaTmwydKM82NqymbryNfg-1365470479"]}},
|
131
|
+
{"id" => "01e3ee5d848a30385a4e90eb851b094d","data" => {"collection_item_id" => ["PCxSz65GIcZTtc0UpRdT-i--w-1365550369"]}},
|
132
|
+
{"id" => "01fca44cc596adb295ca6ee9f9f36499","data" => {"collection_item_id" => ["PCx7XKbKwOVf1VvEWvTl5c1Eg-1365020176"]}},
|
133
|
+
{"id" => "02b85c9835b5045065ee389954a60c5f","data" => {"collection_item_id" => ["PCxp_xid_WeTfTmb5MySEfxhQ-1365115565"]}},
|
134
|
+
{"id" => "040c01be434552a1d9e99eef9db87bdd","data" => {"collection_item_id" => ["PCxLOYzA4bCt7-bP6wsZnl-ow-1365020297"]}},
|
135
|
+
{"id" => "048567c755e30d6d64d757508f1feaa0","data" => {"collection_item_id" => ["PCxJhhnpYkeSKrOxteQo5Jckw-1365115667"]}}
|
136
|
+
]
|
137
|
+
},
|
138
|
+
"info" => {
|
139
|
+
"rid" => "7df344e77e1076a903e1f2dc1effcf3dde0a89442fb459d00a6e60ac64b8bbfcab1fbc5b35c10949",
|
140
|
+
"time-ms" => 3,
|
141
|
+
"cpu-time-ms" => 0,
|
142
|
+
"messages" => [
|
143
|
+
{
|
144
|
+
"severity" => "warning",
|
145
|
+
"code" => "CS-InvalidFieldOrRankAliasInRankParameter",
|
146
|
+
"host" => "7df344e77e1076a9884a6c43665da57c",
|
147
|
+
"message" => "Unable to create score object for rank 'adult'"
|
148
|
+
}
|
149
|
+
]
|
150
|
+
}
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'causes WarningInQueryResult exception' do
|
155
|
+
lambda{ query.to_a }.should raise_error(Cloudsearchable::WarningInQueryResult)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'takes a :fatal_warnings option, and when set to false, does not raise' do
|
159
|
+
sample_query = Cloudsearchable::QueryChain.new(double, fatal_warnings: false)
|
160
|
+
sample_query.instance_variable_get(:@fatal_warnings).should eq false
|
161
|
+
|
162
|
+
q = query
|
163
|
+
q.query.instance_variable_set(:@fatal_warnings, false)
|
164
|
+
lambda{ q.to_a }.should_not raise_error(Cloudsearchable::WarningInQueryResult)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'valid query results' do
|
169
|
+
let(:customer_id){ '12345' }
|
170
|
+
let(:other_customer_id){ 'foo' }
|
171
|
+
|
172
|
+
let(:cloudsearch_response) do
|
173
|
+
{
|
174
|
+
"rank"=>"-text_relevance",
|
175
|
+
"match-expr"=>"(label customer_id:'12345')",
|
176
|
+
"hits"=>{
|
177
|
+
"found"=>11,
|
178
|
+
"start"=>0,
|
179
|
+
"info"=>{
|
180
|
+
"rid"=>"e2467862eecf73ec8dfcfe0cba1893abbe2e8803402f4da65b1195593c0f78ec3e8f1d29f6e40723",
|
181
|
+
"time-ms"=>2,
|
182
|
+
"cpu-time-ms"=>0
|
183
|
+
},
|
184
|
+
"hit"=>[
|
185
|
+
{"id"=>"0633e1c9793f5288c58b664356533e81", "data"=>{"test_class_id"=>["ANINSTANCEID"]}},
|
186
|
+
# {"id"=>"04931ebede796ae8b435f1fd5291e772", "data"=>{"test_class_id"=>["PCxTj26ZRmV_EnHigQWx0S06w"]}},
|
187
|
+
# {"id"=>"72159a172d3043bfcdadb5244862b9ee", "data"=>{"test_class_id"=>["PCxS_apFtZMrKuqyPhFNstzMQ"]}},
|
188
|
+
# {"id"=>"1eb815b075bc005e97dc5827e53b9615", "data"=>{"test_class_id"=>["PCxSksjDUBehPWhYYW2Dtj4KQ"]}},
|
189
|
+
# {"id"=>"3e4950b829456b13bf1460b25a7aca26", "data"=>{"test_class_id"=>["PCx1oiyh6vrHGSeLvis4USMfQ"]}},
|
190
|
+
# {"id"=>"00b441f55fff86d2d746227988da77a9", "data"=>{"test_class_id"=>["PCxpt-aW8topsnTGs-AIkzWCA"]}},
|
191
|
+
# {"id"=>"919ea27d21bbdc07ead4688a0d7ceca1", "data"=>{"test_class_id"=>["PCxFHGLbGJ2mzau_a6-gh5ORw"]}},
|
192
|
+
# {"id"=>"c663c2d9af342b0038fc808322143cfd", "data"=>{"test_class_id"=>["PCxyFwShwjWBp_WiXB0rFb2WA"]}},
|
193
|
+
# {"id"=>"de8f00af5636393e2553c4b4710d3393", "data"=>{"test_class_id"=>["PCxnqXfm8McflBgi4HsYoUXVw"]}},
|
194
|
+
# {"id"=>"e297cf21741a4c43697ea2586164a987", "data"=>{"test_class_id"=>["PCxrdk8gAEVbkuUCazu2-qLjQ"]}}
|
195
|
+
]
|
196
|
+
}
|
197
|
+
}
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'materializes' do
|
201
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return([customer_id])
|
202
|
+
query = clazz.search.where(customer_id: 12345)
|
203
|
+
query.to_a.should == [customer_id]
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'materializes db results only once' do
|
207
|
+
expected_results = [customer_id, other_customer_id]
|
208
|
+
clazz.should_receive(:find).once.and_return(expected_results)
|
209
|
+
|
210
|
+
query = clazz.search.where(customer_id: 12345)
|
211
|
+
query.materialize!
|
212
|
+
query.materialize!
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'should not materialize if only asking for found_count' do
|
216
|
+
clazz.should_not_receive(:find)
|
217
|
+
clazz.search.where(customer_id: 12345).found_count
|
218
|
+
end
|
219
|
+
|
220
|
+
it 'supports each for multiple results' do
|
221
|
+
expected_results = [customer_id, other_customer_id]
|
222
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return(expected_results)
|
223
|
+
|
224
|
+
results = clazz.search.where(customer_id: 12345).to_a
|
225
|
+
(0..results.length).each{ |i| results[i].should eq expected_results[i] }
|
226
|
+
end
|
227
|
+
|
228
|
+
it 'supports each for single results' do
|
229
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return(customer_id)
|
230
|
+
|
231
|
+
results = clazz.search.where(customer_id: 12345).to_a
|
232
|
+
results.each{ |r| r.should eq customer_id }
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'supports each for nil result' do
|
236
|
+
clazz.should_receive(:find).with(["ANINSTANCEID"]).and_return(nil)
|
237
|
+
|
238
|
+
results = clazz.search.where(customer_id: 12345).to_a
|
239
|
+
results.each{ |r| r.should_not be }
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'uses materialized method' do
|
243
|
+
clazz.should_receive(:another_find).with(["ANINSTANCEID"]).and_return(customer_id)
|
244
|
+
clazz.materialize_method :another_find
|
245
|
+
clazz.search.where(customer_id: 12345).to_a
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'returns the correct found count' do
|
249
|
+
clazz.search.where(customer_id: 12345).found_count.should == 11
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'invalid query results' do
|
254
|
+
let(:cloudsearch_response) do
|
255
|
+
{
|
256
|
+
"error"=>"info",
|
257
|
+
"rid"=>"6ddcaa561c05c4cc85ddb10cb46568af2ef64b0583910e32210f551c238586e40fc3abe629ca87b250796d395a628af6",
|
258
|
+
"time-ms"=>20,
|
259
|
+
"cpu-time-ms"=>0,
|
260
|
+
"messages"=>[
|
261
|
+
{
|
262
|
+
"severity"=>"fatal",
|
263
|
+
"code"=>"CS-UnknownFieldInMatchExpression",
|
264
|
+
"message"=>"Field 'asdf' is not defined in the metadata for this collection."
|
265
|
+
}
|
266
|
+
]
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'raises an exception when requesting found count with an error response' do
|
271
|
+
expect { clazz.search.where(customer_id: 12345).found_count }.to raise_error
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context 'empty results with non-empty data' do
|
276
|
+
let(:cloudsearch_response) do
|
277
|
+
{
|
278
|
+
# Empty-yet-present data may occur with a NOT query, such as "(not customer_id:'XYZ')".
|
279
|
+
# Refer to: https://aws.amazon.com/support/case?caseId=107084141&language=en
|
280
|
+
"rank" => "-text_relevance",
|
281
|
+
"match-expr" => "(not customer_id:'A3E4T85Q6WPY4F')",
|
282
|
+
"hits" => {
|
283
|
+
"found" => 2087,
|
284
|
+
"start" => 0,
|
285
|
+
"hit" => [
|
286
|
+
{"id" => "fb9fb53e32c4b3714cf39be4b855d34b", "data" => { "test_class_id" => []}},
|
287
|
+
]
|
288
|
+
},
|
289
|
+
"info" => {
|
290
|
+
"rid" => "621cf310b88f32076b1908e45b4930aafb872497bdbf3b5e64065619c0dcec96bbe513281093d6c7",
|
291
|
+
"time-ms" => 3,
|
292
|
+
"cpu-time-ms" => 0
|
293
|
+
}
|
294
|
+
}
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'does not raise an exception' do
|
298
|
+
clazz.should_receive(:find).with([]).and_return(nil)
|
299
|
+
clazz.search.where(:customer_id, :!=, 'ABCDE')
|
300
|
+
expect { clazz.search.where(:customer_id, :!=, 'ABCDE').to_a }.to_not raise_error
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|