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