virsandra 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/LICENSE.txt +22 -0
  2. data/README.md +93 -0
  3. data/Rakefile +18 -0
  4. data/lib/virsandra.rb +89 -0
  5. data/lib/virsandra/configuration.rb +61 -0
  6. data/lib/virsandra/connection.rb +39 -0
  7. data/lib/virsandra/cql_value.rb +38 -0
  8. data/lib/virsandra/errors.rb +3 -0
  9. data/lib/virsandra/model.rb +86 -0
  10. data/lib/virsandra/model_query.rb +52 -0
  11. data/lib/virsandra/queries/add_query.rb +25 -0
  12. data/lib/virsandra/queries/alter_query.rb +34 -0
  13. data/lib/virsandra/queries/delete_query.rb +25 -0
  14. data/lib/virsandra/queries/insert_query.rb +34 -0
  15. data/lib/virsandra/queries/limit_query.rb +17 -0
  16. data/lib/virsandra/queries/order_query.rb +36 -0
  17. data/lib/virsandra/queries/select_query.rb +72 -0
  18. data/lib/virsandra/queries/table_query.rb +13 -0
  19. data/lib/virsandra/queries/values_query.rb +41 -0
  20. data/lib/virsandra/queries/where_query.rb +69 -0
  21. data/lib/virsandra/query.rb +87 -0
  22. data/lib/virsandra/version.rb +3 -0
  23. data/spec/feature_helper.rb +62 -0
  24. data/spec/integration/virsandra_spec.rb +13 -0
  25. data/spec/lib/virsandra/configuration_spec.rb +66 -0
  26. data/spec/lib/virsandra/connection_spec.rb +47 -0
  27. data/spec/lib/virsandra/cql_value_spec.rb +25 -0
  28. data/spec/lib/virsandra/model_query_spec.rb +58 -0
  29. data/spec/lib/virsandra/model_spec.rb +173 -0
  30. data/spec/lib/virsandra/queries/add_query_spec.rb +26 -0
  31. data/spec/lib/virsandra/queries/alter_query_spec.rb +35 -0
  32. data/spec/lib/virsandra/queries/delete_query_spec.rb +34 -0
  33. data/spec/lib/virsandra/queries/insert_query_spec.rb +36 -0
  34. data/spec/lib/virsandra/queries/limit_query_spec.rb +20 -0
  35. data/spec/lib/virsandra/queries/order_query_spec.rb +33 -0
  36. data/spec/lib/virsandra/queries/select_query_spec.rb +108 -0
  37. data/spec/lib/virsandra/queries/table_query_spec.rb +13 -0
  38. data/spec/lib/virsandra/queries/values_query_spec.rb +41 -0
  39. data/spec/lib/virsandra/queries/where_query_spec.rb +76 -0
  40. data/spec/lib/virsandra/query_spec.rb +117 -0
  41. data/spec/lib/virsandra_spec.rb +108 -0
  42. data/spec/spec_helper.rb +19 -0
  43. metadata +207 -0
@@ -0,0 +1,3 @@
1
+ module Virsandra
2
+ VERSION = "0.5.0"
3
+ end
@@ -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