virsandra 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +22 -0
- data/README.md +93 -0
- data/Rakefile +18 -0
- data/lib/virsandra.rb +89 -0
- data/lib/virsandra/configuration.rb +61 -0
- data/lib/virsandra/connection.rb +39 -0
- data/lib/virsandra/cql_value.rb +38 -0
- data/lib/virsandra/errors.rb +3 -0
- data/lib/virsandra/model.rb +86 -0
- data/lib/virsandra/model_query.rb +52 -0
- data/lib/virsandra/queries/add_query.rb +25 -0
- data/lib/virsandra/queries/alter_query.rb +34 -0
- data/lib/virsandra/queries/delete_query.rb +25 -0
- data/lib/virsandra/queries/insert_query.rb +34 -0
- data/lib/virsandra/queries/limit_query.rb +17 -0
- data/lib/virsandra/queries/order_query.rb +36 -0
- data/lib/virsandra/queries/select_query.rb +72 -0
- data/lib/virsandra/queries/table_query.rb +13 -0
- data/lib/virsandra/queries/values_query.rb +41 -0
- data/lib/virsandra/queries/where_query.rb +69 -0
- data/lib/virsandra/query.rb +87 -0
- data/lib/virsandra/version.rb +3 -0
- data/spec/feature_helper.rb +62 -0
- data/spec/integration/virsandra_spec.rb +13 -0
- data/spec/lib/virsandra/configuration_spec.rb +66 -0
- data/spec/lib/virsandra/connection_spec.rb +47 -0
- data/spec/lib/virsandra/cql_value_spec.rb +25 -0
- data/spec/lib/virsandra/model_query_spec.rb +58 -0
- data/spec/lib/virsandra/model_spec.rb +173 -0
- data/spec/lib/virsandra/queries/add_query_spec.rb +26 -0
- data/spec/lib/virsandra/queries/alter_query_spec.rb +35 -0
- data/spec/lib/virsandra/queries/delete_query_spec.rb +34 -0
- data/spec/lib/virsandra/queries/insert_query_spec.rb +36 -0
- data/spec/lib/virsandra/queries/limit_query_spec.rb +20 -0
- data/spec/lib/virsandra/queries/order_query_spec.rb +33 -0
- data/spec/lib/virsandra/queries/select_query_spec.rb +108 -0
- data/spec/lib/virsandra/queries/table_query_spec.rb +13 -0
- data/spec/lib/virsandra/queries/values_query_spec.rb +41 -0
- data/spec/lib/virsandra/queries/where_query_spec.rb +76 -0
- data/spec/lib/virsandra/query_spec.rb +117 -0
- data/spec/lib/virsandra_spec.rb +108 -0
- data/spec/spec_helper.rb +19 -0
- metadata +207 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
TEST_KEYSPACE = "virtest"
|
4
|
+
|
5
|
+
module IntegrationTestHelper
|
6
|
+
def create_keyspace
|
7
|
+
Virsandra.keyspace = 'system'
|
8
|
+
Virsandra.execute("CREATE KEYSPACE #{TEST_KEYSPACE} WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}")
|
9
|
+
Virsandra.reset!
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_companies_table
|
13
|
+
cql = <<-CQL
|
14
|
+
CREATE TABLE companies (
|
15
|
+
id uuid,
|
16
|
+
name text,
|
17
|
+
score int,
|
18
|
+
founder text,
|
19
|
+
founded int,
|
20
|
+
PRIMARY KEY (id, score))
|
21
|
+
CQL
|
22
|
+
Virsandra.keyspace = TEST_KEYSPACE
|
23
|
+
Virsandra.execute(cql)
|
24
|
+
end
|
25
|
+
|
26
|
+
def drop_keyspace
|
27
|
+
Virsandra.reset!
|
28
|
+
Virsandra.keyspace = 'system'
|
29
|
+
Virsandra.execute("DROP KEYSPACE #{TEST_KEYSPACE}")
|
30
|
+
end
|
31
|
+
|
32
|
+
def build_up
|
33
|
+
begin
|
34
|
+
create_keyspace
|
35
|
+
create_companies_table
|
36
|
+
rescue Cql::QueryError
|
37
|
+
drop_keyspace
|
38
|
+
|
39
|
+
if defined?(retried)
|
40
|
+
raise $!
|
41
|
+
else
|
42
|
+
retried = true
|
43
|
+
retry
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
RSpec.configure do |config|
|
52
|
+
|
53
|
+
config.include(IntegrationTestHelper)
|
54
|
+
|
55
|
+
config.before do
|
56
|
+
if example.metadata[:integration]
|
57
|
+
build_up
|
58
|
+
Virsandra.reset!
|
59
|
+
Virsandra.keyspace = TEST_KEYSPACE
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'feature_helper'
|
2
|
+
|
3
|
+
describe "Virsandra", integration: true do
|
4
|
+
|
5
|
+
it "return connection to server" do
|
6
|
+
Virsandra.connection.should_not be_nil
|
7
|
+
end
|
8
|
+
|
9
|
+
it "allows to disconnect" do
|
10
|
+
Virsandra.connection.should_receive(:disconnect!).and_call_original
|
11
|
+
Virsandra.disconnect!
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virsandra::Configuration do
|
4
|
+
subject(:config){ described_class.new(given_options) }
|
5
|
+
let(:given_options){ {} }
|
6
|
+
|
7
|
+
its(:servers){ should eq("127.0.0.1") }
|
8
|
+
its(:consistency){ should eq(:quorum) }
|
9
|
+
its(:keyspace){ should be_nil }
|
10
|
+
|
11
|
+
it "uses given values over default ones" do
|
12
|
+
described_class.new(consistency: :one).consistency.should eq(:one)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "validate!" do
|
16
|
+
context "no keyspace" do
|
17
|
+
it "should raise an error" do
|
18
|
+
expect{ config.validate! }.to raise_error(Virsandra::ConfigurationError)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context "no servers" do
|
23
|
+
let(:given_options){ {servers: nil} }
|
24
|
+
|
25
|
+
it "should raise an error" do
|
26
|
+
expect{ config.validate! }.to raise_error(Virsandra::ConfigurationError)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#reset!" do
|
32
|
+
let(:given_options){ {keyspace: :my_keyspace} }
|
33
|
+
|
34
|
+
it "reset options to default ones" do
|
35
|
+
config.reset!
|
36
|
+
config.keyspace.should == :my_keyspace
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "to_hash" do
|
41
|
+
it "returns all options as a hash" do
|
42
|
+
config.to_hash.should eq({
|
43
|
+
consistency: :quorum,
|
44
|
+
keyspace: nil,
|
45
|
+
servers: "127.0.0.1"
|
46
|
+
})
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "changed?" do
|
51
|
+
it "should be changed when option attribute value changes" do
|
52
|
+
config.changed?.should be_false
|
53
|
+
config.keyspace = :other
|
54
|
+
config.changed?.should be_true
|
55
|
+
end
|
56
|
+
|
57
|
+
it "stops being changed when changes are accepted" do
|
58
|
+
config.changed?.should be_false
|
59
|
+
config.keyspace = :other
|
60
|
+
config.changed?.should be_true
|
61
|
+
config.accept_changes
|
62
|
+
config.changed?.should be_false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virsandra::Connection do
|
4
|
+
let(:config){ Virsandra::Configuration.new(keyspace: :my_keyspace) }
|
5
|
+
let(:handle){ double("handle", use: nil) }
|
6
|
+
subject(:connection){ described_class.new(config) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Cql::Client.stub(:connect).and_return(handle)
|
10
|
+
end
|
11
|
+
|
12
|
+
its(:config){ should eq(config) }
|
13
|
+
|
14
|
+
context "invalid configuration" do
|
15
|
+
it "raises an exception if settings are invalid" do
|
16
|
+
config.keyspace = nil
|
17
|
+
expect { connection }.to raise_error(Virsandra::ConfigurationError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "obtains a db connection" do
|
22
|
+
connection.handle.should eq(handle)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "delegates to the cassandra handle" do
|
26
|
+
connection.handle.should_receive(:keyspace)
|
27
|
+
connection.keyspace
|
28
|
+
end
|
29
|
+
|
30
|
+
it "checks if a cassandra connection will respond to a method" do
|
31
|
+
handle.stub(:respond_to? => true)
|
32
|
+
connection.should respond_to :execute
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should disconnect" do
|
36
|
+
handle.should_receive(:close)
|
37
|
+
connection.disconnect!
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#execute" do
|
41
|
+
it "should use configuration consistency when none is given" do
|
42
|
+
config.consistency = :one
|
43
|
+
handle.should_receive(:execute).with("query", :one)
|
44
|
+
connection.execute("query")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virsandra::CQLValue do
|
4
|
+
|
5
|
+
subject { Virsandra::CQLValue }
|
6
|
+
|
7
|
+
it "returns plain numbers" do
|
8
|
+
subject.convert(10).should == "10"
|
9
|
+
subject.convert(10.0).should == "10.0"
|
10
|
+
subject.convert(BigDecimal.new("10.0")).should == "0.1E2"
|
11
|
+
end
|
12
|
+
|
13
|
+
it "quotes strings" do
|
14
|
+
subject.convert("Hello").should == "'Hello'"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "escapes strings" do
|
18
|
+
subject.convert("It's all good.").should == "'It''s all good.'"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "converts UUID to guid" do
|
22
|
+
uuid = SimpleUUID::UUID.new
|
23
|
+
subject.convert(uuid).should == uuid.to_guid
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virsandra::ModelQuery do
|
4
|
+
|
5
|
+
let(:model) {
|
6
|
+
stub(:model,
|
7
|
+
attributes: {id: 123, name: 'Funky Circle'},
|
8
|
+
table: :companies,
|
9
|
+
key: {id: 123})
|
10
|
+
}
|
11
|
+
|
12
|
+
let(:model_query) { Virsandra::ModelQuery.new(model) }
|
13
|
+
|
14
|
+
it "stores the model on initialization" do
|
15
|
+
model_query
|
16
|
+
model_query.instance_variable_get(:@model).should == model
|
17
|
+
end
|
18
|
+
|
19
|
+
it "saves the model" do
|
20
|
+
query = stub(into: '', values: '', fetch: '')
|
21
|
+
|
22
|
+
Virsandra::Query.should_receive(:insert).and_return(query)
|
23
|
+
|
24
|
+
query.should_receive(:into).with(:companies).and_return(query)
|
25
|
+
query.should_receive(:values).with(id: 123, name: "Funky Circle").and_return(query)
|
26
|
+
query.should_receive(:fetch)
|
27
|
+
model_query.save
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can retrieve the hash row from cassandra using the model's key" do
|
31
|
+
query = stub(from: '', where: '', fetch: '')
|
32
|
+
|
33
|
+
Virsandra::Query.should_receive(:select).and_return(query)
|
34
|
+
model.should_receive(:valid?).and_return(true)
|
35
|
+
query.should_receive(:from).with(:companies).and_return(query)
|
36
|
+
query.should_receive(:where).with(id: 123).and_return(query)
|
37
|
+
query.should_receive(:fetch)
|
38
|
+
|
39
|
+
model_query.find_by_key
|
40
|
+
end
|
41
|
+
|
42
|
+
it "doesn't fetch a row if the model is invalid" do
|
43
|
+
model.should_receive(:valid?).and_return(false)
|
44
|
+
model_query.find_by_key.should == {}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "deletes the model" do
|
48
|
+
query = stub(from: '', where: '', fetch: '')
|
49
|
+
|
50
|
+
Virsandra::Query.should_receive(:delete).and_return(query)
|
51
|
+
query.should_receive(:from).with(:companies).and_return(query)
|
52
|
+
query.should_receive(:where).with(id: 123).and_return(query)
|
53
|
+
query.should_receive(:fetch)
|
54
|
+
|
55
|
+
model_query.delete
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'feature_helper'
|
2
|
+
|
3
|
+
class Company
|
4
|
+
include Virsandra::Model
|
5
|
+
|
6
|
+
attribute :id, SimpleUUID::UUID, :default => proc { SimpleUUID::UUID.new }
|
7
|
+
attribute :name, String
|
8
|
+
attribute :score, Fixnum
|
9
|
+
attribute :founded, Fixnum
|
10
|
+
attribute :founder, String
|
11
|
+
|
12
|
+
table :companies
|
13
|
+
key :id, :score
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
describe Virsandra::Model, integration: true do
|
18
|
+
|
19
|
+
let(:id) { SimpleUUID::UUID.new }
|
20
|
+
|
21
|
+
let(:company) { Company.new(id: id, name: "Testco", score: 78)}
|
22
|
+
|
23
|
+
it "has attributes" do
|
24
|
+
company.attributes.should be_a Hash
|
25
|
+
end
|
26
|
+
|
27
|
+
it "selects keys" do
|
28
|
+
company.key.should == {id: id, score: 78}
|
29
|
+
end
|
30
|
+
|
31
|
+
it "reads the configured table" do
|
32
|
+
company.table.should == :companies
|
33
|
+
end
|
34
|
+
|
35
|
+
it "is valid with a key" do
|
36
|
+
company.should be_valid
|
37
|
+
end
|
38
|
+
|
39
|
+
it "is invalid when a key element is nil" do
|
40
|
+
company.id = nil
|
41
|
+
company.should_not be_valid
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "with no assigned id" do
|
45
|
+
it "creates unique uuid's for each instance" do
|
46
|
+
company_one = Company.new(name: 'x')
|
47
|
+
company_two = Company.new(name: 'x')
|
48
|
+
company_one.should_not == company_two
|
49
|
+
company_one[:id].should_not == company_two[:id]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "is equivelent with the same model same attributes" do
|
54
|
+
Company.new(id: id, name: 'x').should == Company.new(id: id, name: 'x')
|
55
|
+
end
|
56
|
+
|
57
|
+
it "is not equivelent with different attributes" do
|
58
|
+
Company.new(id: id, name: 'x').should_not == Company.new(id: id, name: 'y')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "is not equivelent to a different model" do
|
62
|
+
Company.new(id: id, name: 'x').should_not == stub(attributes: {id: id, name: 'x'})
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "finding an existing record" do
|
66
|
+
before do
|
67
|
+
Virsandra.execute("INSERT INTO companies (id, score, name, founded) VALUES (#{id.to_guid}, 101, 'Funky', 1990)")
|
68
|
+
end
|
69
|
+
|
70
|
+
it "can find a record from a key" do
|
71
|
+
company = Company.find(id: id, score: 101)
|
72
|
+
company.attributes.should == {id: id, score: 101, name: "Funky", founded: 1990, founder: nil}
|
73
|
+
end
|
74
|
+
|
75
|
+
it "raises an error with an incomplete key" do
|
76
|
+
expect { Company.find(score: 11) }.to raise_error(ArgumentError)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "raises an error with an over-specified key" do
|
80
|
+
expect { Company.find(score: 11, id: id, name: 'Whatever') }.to raise_error(ArgumentError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "populates missing columns, keeping specified values" do
|
84
|
+
attrs = {id: id, name: "Google", score: 101, founder: "Larry Brin"}
|
85
|
+
|
86
|
+
company = Company.load(attrs)
|
87
|
+
company.attributes.should == attrs.merge(founded: 1990)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "saving a model" do
|
92
|
+
before do
|
93
|
+
Virsandra.execute("USE virtest")
|
94
|
+
Virsandra.execute("TRUNCATE companies")
|
95
|
+
end
|
96
|
+
|
97
|
+
it "can be saved" do
|
98
|
+
company = Company.new(id: id, score: 101, name: "Job Place")
|
99
|
+
company.save
|
100
|
+
Company.find(company.key).should == company
|
101
|
+
end
|
102
|
+
|
103
|
+
it "doesn't save when invalid" do
|
104
|
+
company = Company.new(name: "Keyless Inc.")
|
105
|
+
Virsandra::ModelQuery.should_not_receive(:new)
|
106
|
+
company.save
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "deleting a model" do
|
111
|
+
before do
|
112
|
+
Virsandra.execute("USE virtest")
|
113
|
+
Virsandra.execute("TRUNCATE companies")
|
114
|
+
end
|
115
|
+
|
116
|
+
it "can be deleted" do
|
117
|
+
Virsandra.execute("INSERT INTO companies (id, score) VALUES (#{id.to_guid}, 101)")
|
118
|
+
company = Company.find(id: id, score: 101)
|
119
|
+
company.delete
|
120
|
+
Company.where(id: id, score: 101).to_a.should be_empty
|
121
|
+
end
|
122
|
+
|
123
|
+
it "only deletes the current model" do
|
124
|
+
Virsandra.execute("INSERT INTO companies (id, score) VALUES (#{id.to_guid}, 101)")
|
125
|
+
Virsandra.execute("INSERT INTO companies (id, score) VALUES (#{id.to_guid}, 102)")
|
126
|
+
company = Company.find(id: id, score: 101)
|
127
|
+
company.delete
|
128
|
+
Company.where(id: id, score: 102).to_a.should_not be_empty
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
end
|
135
|
+
|
136
|
+
describe "working with existing records" do
|
137
|
+
before do
|
138
|
+
Virsandra.execute("USE virtest")
|
139
|
+
Virsandra.execute("TRUNCATE companies")
|
140
|
+
|
141
|
+
5.times { |n| Company.new(id: id, score: n + 1, name: "Test").save }
|
142
|
+
5.times { |n| Company.new(id: SimpleUUID::UUID.new, score: n + 1, name: "Test").save }
|
143
|
+
end
|
144
|
+
|
145
|
+
it "checks all search terms are attributes" do
|
146
|
+
expect { Company.where(id: 1, missing: 'key') }.to raise_error(ArgumentError)
|
147
|
+
expect { Company.where(id: id, score: 10) }.not_to raise_error(ArgumentError)
|
148
|
+
end
|
149
|
+
|
150
|
+
it "returns an enumerable" do
|
151
|
+
Company.where(id: id).should be_an Enumerator
|
152
|
+
end
|
153
|
+
|
154
|
+
it "creates an model instance for each entry " do
|
155
|
+
Company.where(id: id).map(&:score).should == [1,2,3,4,5]
|
156
|
+
end
|
157
|
+
|
158
|
+
it "can get all records" do
|
159
|
+
Company.all.to_a.length.should == 10
|
160
|
+
end
|
161
|
+
|
162
|
+
it "lazily instanciates models" do
|
163
|
+
Company.should_receive(:new).twice
|
164
|
+
Company.all.take(2)
|
165
|
+
end
|
166
|
+
|
167
|
+
it "can be empty" do
|
168
|
+
Company.where(id: id, score: 1009).to_a.should be_empty
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|