cassandra 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/CHANGELOG +2 -0
- data/Manifest +4 -4
- data/README +11 -11
- data/Rakefile +58 -3
- data/cassandra.gemspec +10 -7
- data/conf/storage-conf.xml +28 -15
- data/lib/cassandra.rb +3 -3
- data/lib/cassandra/array.rb +1 -1
- data/lib/cassandra/cassandra.rb +199 -216
- data/lib/cassandra/{helper.rb → columns.rb} +16 -15
- data/lib/cassandra/comparable.rb +1 -1
- data/lib/cassandra/constants.rb +4 -0
- data/lib/cassandra/long.rb +16 -14
- data/lib/cassandra/ordered_hash.rb +1 -1
- data/lib/cassandra/protocol.rb +86 -0
- data/lib/cassandra/safe_client.rb +1 -1
- data/lib/cassandra/time.rb +8 -6
- data/lib/cassandra/uuid.rb +93 -28
- data/test/{cassandra_client_test.rb → cassandra_test.rb} +54 -54
- data/test/comparable_types_test.rb +5 -6
- data/test/test_helper.rb +14 -0
- data/vendor/gen-rb/cassandra.rb +109 -417
- data/vendor/gen-rb/cassandra_types.rb +35 -15
- metadata +22 -13
- metadata.gz.sig +0 -0
- data/lib/cassandra/serialization.rb +0 -57
- data/quickstart.sh +0 -12
data.tar.gz.sig
CHANGED
Binary file
|
data/CHANGELOG
CHANGED
data/Manifest
CHANGED
@@ -4,23 +4,23 @@ conf/log4j.properties
|
|
4
4
|
conf/storage-conf.xml
|
5
5
|
lib/cassandra/array.rb
|
6
6
|
lib/cassandra/cassandra.rb
|
7
|
+
lib/cassandra/columns.rb
|
7
8
|
lib/cassandra/comparable.rb
|
8
9
|
lib/cassandra/constants.rb
|
9
|
-
lib/cassandra/helper.rb
|
10
10
|
lib/cassandra/long.rb
|
11
11
|
lib/cassandra/ordered_hash.rb
|
12
|
+
lib/cassandra/protocol.rb
|
12
13
|
lib/cassandra/safe_client.rb
|
13
|
-
lib/cassandra/serialization.rb
|
14
14
|
lib/cassandra/time.rb
|
15
15
|
lib/cassandra/uuid.rb
|
16
16
|
lib/cassandra.rb
|
17
17
|
LICENSE
|
18
18
|
Manifest
|
19
|
-
quickstart.sh
|
20
19
|
Rakefile
|
21
20
|
README
|
22
|
-
test/
|
21
|
+
test/cassandra_test.rb
|
23
22
|
test/comparable_types_test.rb
|
23
|
+
test/test_helper.rb
|
24
24
|
vendor/gen-rb/cassandra.rb
|
25
25
|
vendor/gen-rb/cassandra_constants.rb
|
26
26
|
vendor/gen-rb/cassandra_types.rb
|
data/README
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
cassandra
|
3
3
|
|
4
|
-
A client for the Cassandra distributed database.
|
4
|
+
A Ruby client for the Cassandra distributed database.
|
5
5
|
|
6
6
|
== License
|
7
7
|
|
@@ -12,26 +12,26 @@ The public certificate for this gem is here[http://rubyforge.org/frs/download.ph
|
|
12
12
|
== Features
|
13
13
|
|
14
14
|
* clean encapsulation of the Thrift API
|
15
|
-
*
|
15
|
+
* compatible UUID and Long classes, for GUID generation
|
16
16
|
* Ruby 1.9 compatibility
|
17
17
|
|
18
|
-
This is an alpha release and
|
18
|
+
This is an alpha release and may not yet support the full set of Cassandra features.
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
The Github source repository is {here}[http://github.com/fauna/cassandra/]; patches and contributions are very welcome.
|
20
|
+
The Github source repository is {here}[http://github.com/fauna/cassandra/]. Patches and contributions are very welcome.
|
23
21
|
|
24
22
|
== Installation
|
25
23
|
|
26
|
-
You need Ruby 1.8 or 1.9,
|
27
|
-
|
28
|
-
If you don't want to install Thrift from {source}[http://incubator.apache.org/thrift/download/], you can use {this pre-packaged gem}[http://blog.evanweaver.com/files/cassandra/thrift-0.1.0.gem]. Once you have Thrift, just run:
|
24
|
+
You need Ruby 1.8 or 1.9. If you have those, just run:
|
29
25
|
|
30
26
|
sudo gem install cassandra
|
31
27
|
|
32
28
|
== Usage
|
33
29
|
|
34
|
-
|
30
|
+
Cassandra is a rapidly moving target. In order to get a working server, change to the checkout or gem directory, and run:
|
31
|
+
|
32
|
+
rake cassandra
|
33
|
+
|
34
|
+
Now, start IRb and require the library:
|
35
35
|
|
36
36
|
require 'cassandra'
|
37
37
|
|
@@ -45,7 +45,7 @@ Insert into a column family. You can insert a Cassandra::OrderedHash, or a regul
|
|
45
45
|
|
46
46
|
Insert into a super column family:
|
47
47
|
|
48
|
-
client.insert(:UserRelationships, "5", {"user_timeline" => {
|
48
|
+
client.insert(:UserRelationships, "5", {"user_timeline" => {UUID.new => "1"}})
|
49
49
|
|
50
50
|
Query a super column:
|
51
51
|
|
data/Rakefile
CHANGED
@@ -3,11 +3,66 @@ require 'echoe'
|
|
3
3
|
Echoe.new("cassandra") do |p|
|
4
4
|
p.author = "Evan Weaver"
|
5
5
|
p.project = "fauna"
|
6
|
-
p.summary = "A Ruby client for
|
6
|
+
p.summary = "A Ruby client for the Cassandra distributed database."
|
7
7
|
p.rubygems_version = ">= 0.8"
|
8
|
-
p.dependencies = ['json']
|
8
|
+
p.dependencies = ['json', 'thrift']
|
9
9
|
p.ignore_pattern = /^(data|vendor\/cassandra|cassandra|vendor\/thrift)/
|
10
|
-
p.rdoc_pattern = /^(lib|bin|tasks|ext)
|
10
|
+
p.rdoc_pattern = /^(lib|bin|tasks|ext)|^README|^CHANGELOG|^TODO|^LICENSE|^COPYING$/
|
11
11
|
p.url = "http://blog.evanweaver.com/files/doc/fauna/cassandra/"
|
12
12
|
p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
|
13
13
|
end
|
14
|
+
|
15
|
+
desc "Start Cassandra"
|
16
|
+
task :cassandra => [:checkout, :patch, :build] do
|
17
|
+
env = "CASSANDRA_INCLUDE=#{Dir.pwd}/conf/cassandra.in.sh" unless ENV["CASSANDRA_INCLUDE"]
|
18
|
+
exec("env #{env} cassandra/bin/cassandra -f")
|
19
|
+
end
|
20
|
+
|
21
|
+
REVISION = "15354b4906fd654d58fe50fd01ebf95b69434ba9"
|
22
|
+
PATCHES = [
|
23
|
+
"http://issues.apache.org/jira/secure/attachment/12416014/0001-CASSANDRA-356-rename-clean-up-collectColumns-methods.txt",
|
24
|
+
"http://issues.apache.org/jira/secure/attachment/12416073/0002-v3.patch",
|
25
|
+
"http://issues.apache.org/jira/secure/attachment/12416074/357-v2.patch",
|
26
|
+
"http://issues.apache.org/jira/secure/attachment/12416086/357-3.patch"]
|
27
|
+
|
28
|
+
task :checkout do
|
29
|
+
# Like a git submodule, but all in one obvious place
|
30
|
+
unless File.exist?("cassandra")
|
31
|
+
system("git clone git://git.apache.org/cassandra.git")
|
32
|
+
ENV["RESET"] = "true"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
task :patch do
|
37
|
+
if ENV["RESET"]
|
38
|
+
Dir.chdir("cassandra") do
|
39
|
+
system("ant clean && git fetch && git reset #{REVISION} --hard")
|
40
|
+
# Delete untracked files, so that the patchs can apply again
|
41
|
+
Array(`git status`[/Untracked files:(.*)$/m, 1].to_s.split("\n")[3..-1]).each do |file|
|
42
|
+
File.unlink(file.sub(/^.\s+/, "")) rescue nil
|
43
|
+
end
|
44
|
+
# Patch, with a handy commit for each one
|
45
|
+
PATCHES.each do |url|
|
46
|
+
raise "#{url} failed" unless system("curl #{url} | patch -p1")
|
47
|
+
system("git commit -a -m 'Applied patch: #{url.inspect}'")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
task :build do
|
54
|
+
Dir.chdir("cassandra") { system("ant") } unless File.exist?("cassandra/build")
|
55
|
+
end
|
56
|
+
|
57
|
+
task :clean do
|
58
|
+
Dir.chdir("cassandra") { system("ant clean") }
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "Regenerate thrift bindings for Cassandra"
|
62
|
+
task :thrift do
|
63
|
+
system(
|
64
|
+
"cd vendor &&
|
65
|
+
rm -rf gen-rb &&
|
66
|
+
thrift -gen rb ../cassandra/interface/cassandra.thrift")
|
67
|
+
end
|
68
|
+
|
data/cassandra.gemspec
CHANGED
@@ -2,24 +2,24 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{cassandra}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.5"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0.8") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Evan Weaver"]
|
9
9
|
s.cert_chain = ["/Users/eweaver/p/configuration/gem_certificates/evan_weaver-original-public_cert.pem"]
|
10
|
-
s.date = %q{2009-
|
11
|
-
s.description = %q{A Ruby client for
|
10
|
+
s.date = %q{2009-08-18}
|
11
|
+
s.description = %q{A Ruby client for the Cassandra distributed database.}
|
12
12
|
s.email = %q{}
|
13
|
-
s.extra_rdoc_files = ["CHANGELOG", "lib/cassandra/array.rb", "lib/cassandra/cassandra.rb", "lib/cassandra/
|
14
|
-
s.files = ["CHANGELOG", "conf/cassandra.in.sh", "conf/log4j.properties", "conf/storage-conf.xml", "lib/cassandra/array.rb", "lib/cassandra/cassandra.rb", "lib/cassandra/
|
13
|
+
s.extra_rdoc_files = ["CHANGELOG", "lib/cassandra/array.rb", "lib/cassandra/cassandra.rb", "lib/cassandra/columns.rb", "lib/cassandra/comparable.rb", "lib/cassandra/constants.rb", "lib/cassandra/long.rb", "lib/cassandra/ordered_hash.rb", "lib/cassandra/protocol.rb", "lib/cassandra/safe_client.rb", "lib/cassandra/time.rb", "lib/cassandra/uuid.rb", "lib/cassandra.rb", "LICENSE", "README"]
|
14
|
+
s.files = ["CHANGELOG", "conf/cassandra.in.sh", "conf/log4j.properties", "conf/storage-conf.xml", "lib/cassandra/array.rb", "lib/cassandra/cassandra.rb", "lib/cassandra/columns.rb", "lib/cassandra/comparable.rb", "lib/cassandra/constants.rb", "lib/cassandra/long.rb", "lib/cassandra/ordered_hash.rb", "lib/cassandra/protocol.rb", "lib/cassandra/safe_client.rb", "lib/cassandra/time.rb", "lib/cassandra/uuid.rb", "lib/cassandra.rb", "LICENSE", "Manifest", "Rakefile", "README", "test/cassandra_test.rb", "test/comparable_types_test.rb", "test/test_helper.rb", "vendor/gen-rb/cassandra.rb", "vendor/gen-rb/cassandra_constants.rb", "vendor/gen-rb/cassandra_types.rb", "cassandra.gemspec"]
|
15
15
|
s.homepage = %q{http://blog.evanweaver.com/files/doc/fauna/cassandra/}
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Cassandra", "--main", "README"]
|
17
17
|
s.require_paths = ["lib"]
|
18
18
|
s.rubyforge_project = %q{fauna}
|
19
19
|
s.rubygems_version = %q{1.3.4}
|
20
20
|
s.signing_key = %q{/Users/eweaver/p/configuration/gem_certificates/evan_weaver-original-private_key.pem}
|
21
|
-
s.summary = %q{A Ruby client for
|
22
|
-
s.test_files = ["test/
|
21
|
+
s.summary = %q{A Ruby client for the Cassandra distributed database.}
|
22
|
+
s.test_files = ["test/cassandra_test.rb", "test/comparable_types_test.rb", "test/test_helper.rb"]
|
23
23
|
|
24
24
|
if s.respond_to? :specification_version then
|
25
25
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -27,10 +27,13 @@ Gem::Specification.new do |s|
|
|
27
27
|
|
28
28
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
29
29
|
s.add_runtime_dependency(%q<json>, [">= 0"])
|
30
|
+
s.add_runtime_dependency(%q<thrift>, [">= 0"])
|
30
31
|
else
|
31
32
|
s.add_dependency(%q<json>, [">= 0"])
|
33
|
+
s.add_dependency(%q<thrift>, [">= 0"])
|
32
34
|
end
|
33
35
|
else
|
34
36
|
s.add_dependency(%q<json>, [">= 0"])
|
37
|
+
s.add_dependency(%q<thrift>, [">= 0"])
|
35
38
|
end
|
36
39
|
end
|
data/conf/storage-conf.xml
CHANGED
@@ -28,24 +28,24 @@
|
|
28
28
|
|
29
29
|
There is an implicit table named 'system' for Cassandra internals.
|
30
30
|
-->
|
31
|
-
<
|
32
|
-
<
|
31
|
+
<Keyspaces>
|
32
|
+
<Keyspace Name="Twitter">
|
33
33
|
<KeysCachedFraction>0.01</KeysCachedFraction>
|
34
34
|
<ColumnFamily CompareWith="UTF8Type" Name="Users" />
|
35
35
|
<ColumnFamily CompareWith="UTF8Type" Name="UserAudits" />
|
36
|
-
<ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="
|
36
|
+
<ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="UserRelationships" />
|
37
37
|
<ColumnFamily CompareWith="UTF8Type" Name="Usernames" />
|
38
38
|
<ColumnFamily CompareWith="UTF8Type" Name="Statuses" />
|
39
39
|
<ColumnFamily CompareWith="UTF8Type" Name="StatusAudits" />
|
40
|
-
<ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="
|
41
|
-
</
|
40
|
+
<ColumnFamily CompareWith="UTF8Type" CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" Name="StatusRelationships" />
|
41
|
+
</Keyspace>
|
42
42
|
|
43
|
-
<
|
43
|
+
<Keyspace Name="Multiblog">
|
44
44
|
<KeysCachedFraction>0.01</KeysCachedFraction>
|
45
45
|
<ColumnFamily CompareWith="UTF8Type" Name="Blogs"/>
|
46
46
|
<ColumnFamily CompareWith="UTF8Type" Name="Comments"/>
|
47
|
-
</
|
48
|
-
</
|
47
|
+
</Keyspace>
|
48
|
+
</Keyspaces>
|
49
49
|
|
50
50
|
<!-- Partitioner: any IPartitioner may be used, including your own
|
51
51
|
as long as it is on the classpath. Out of the box,
|
@@ -71,11 +71,24 @@
|
|
71
71
|
clusters with a small number of nodes. -->
|
72
72
|
<InitialToken></InitialToken>
|
73
73
|
|
74
|
-
|
75
|
-
|
74
|
+
|
75
|
+
<!-- EndPointSnitch: Setting this to the class that implements IEndPointSnitch
|
76
|
+
which will see if two endpoints are in the same data center or on the same rack.
|
77
|
+
Out of the box, Cassandra provides
|
78
|
+
org.apache.cassandra.locator.EndPointSnitch
|
76
79
|
-->
|
77
|
-
<
|
78
|
-
|
80
|
+
<EndPointSnitch>org.apache.cassandra.locator.EndPointSnitch</EndPointSnitch>
|
81
|
+
|
82
|
+
<!-- Strategy: Setting this to the class that implements IReplicaPlacementStrategy
|
83
|
+
will change the way the node picker works.
|
84
|
+
Out of the box, Cassandra provides
|
85
|
+
org.apache.cassandra.locator.RackUnawareStrategy
|
86
|
+
org.apache.cassandra.locator.RackAwareStrategy
|
87
|
+
(place one replica in a different datacenter, and the
|
88
|
+
others on different racks in the same one.)
|
89
|
+
-->
|
90
|
+
<ReplicaPlacementStrategy>org.apache.cassandra.locator.RackUnawareStrategy</ReplicaPlacementStrategy>
|
91
|
+
|
79
92
|
<!-- Number of replicas of the data-->
|
80
93
|
<ReplicationFactor>1</ReplicationFactor>
|
81
94
|
|
@@ -90,14 +103,15 @@
|
|
90
103
|
<BootstrapFileDirectory>data/bootstrap</BootstrapFileDirectory>
|
91
104
|
<StagingFileDirectory>data/staging</StagingFileDirectory>
|
92
105
|
|
93
|
-
|
94
|
-
<!-- Add names of hosts that are deemed contact points. Cassandra nodes use
|
106
|
+
<!-- Addresses of hosts that are deemed contact points. Cassandra nodes use
|
95
107
|
this list of hosts to find each other and learn the topology of the ring.
|
108
|
+
You must change this if you are running multiple nodes!
|
96
109
|
-->
|
97
110
|
<Seeds>
|
98
111
|
<Seed>127.0.0.1</Seed>
|
99
112
|
</Seeds>
|
100
113
|
|
114
|
+
|
101
115
|
<!-- Miscellaneous -->
|
102
116
|
|
103
117
|
<!-- time to wait for a reply from other nodes before failing the command -->
|
@@ -193,5 +207,4 @@
|
|
193
207
|
face of hardware failures. The default value is ten days.
|
194
208
|
-->
|
195
209
|
<GCGraceSeconds>864000</GCGraceSeconds>
|
196
|
-
|
197
210
|
</Storage>
|
data/lib/cassandra.rb
CHANGED
@@ -15,8 +15,8 @@ require 'cassandra/comparable'
|
|
15
15
|
require 'cassandra/uuid'
|
16
16
|
require 'cassandra/long'
|
17
17
|
require 'cassandra/safe_client'
|
18
|
-
require 'cassandra/serialization'
|
19
18
|
require 'cassandra/ordered_hash'
|
20
|
-
require 'cassandra/
|
21
|
-
require 'cassandra/
|
19
|
+
require 'cassandra/columns'
|
20
|
+
require 'cassandra/protocol'
|
22
21
|
require 'cassandra/cassandra'
|
22
|
+
require 'cassandra/constants'
|
data/lib/cassandra/array.rb
CHANGED
data/lib/cassandra/cassandra.rb
CHANGED
@@ -1,22 +1,62 @@
|
|
1
1
|
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
=begin rdoc
|
3
|
+
Create a new Cassandra client instance. Accepts a keyspace name, and optional host and port.
|
4
|
+
|
5
|
+
client = Cassandra.new('twitter', '127.0.0.1', 9160)
|
5
6
|
|
6
|
-
|
7
|
+
You can then make calls to the server via the <tt>client</tt> instance.
|
8
|
+
|
9
|
+
client.insert(:UserRelationships, "5", {"user_timeline" => {UUID.new => "1"}})
|
10
|
+
client.get(:UserRelationships, "5", "user_timeline")
|
7
11
|
|
12
|
+
For read methods, valid option parameters are:
|
13
|
+
|
14
|
+
<tt>:count</tt>:: How many results to return. Defaults to 100.
|
15
|
+
<tt>:start</tt>:: Column name token at which to start iterating, inclusive. Defaults to nil, which means the first column in the collation order.
|
16
|
+
<tt>:finish</tt>:: Column name token at which to stop iterating, inclusive. Defaults to nil, which means no boundary.
|
17
|
+
<tt>:reversed</tt>:: Swap the direction of the collation order.
|
18
|
+
<tt>:consistency</tt>:: The consistency level of the request. Defaults to <tt>Cassandra::Consistency::ONE</tt> (one node must respond). Other valid options are <tt>Cassandra::Consistency::ZERO</tt>, <tt>Cassandra::Consistency::QUORUM</tt>, and <tt>Cassandra::Consistency::ALL</tt>.
|
19
|
+
|
20
|
+
Note that some read options have no relevance in some contexts.
|
21
|
+
|
22
|
+
For write methods, valid option parameters are:
|
23
|
+
|
24
|
+
<tt>:timestamp </tt>:: The transaction timestamp. Defaults to the current time in milliseconds. This is used for conflict resolution by the server; you normally never need to change it.
|
25
|
+
<tt>:consistency</tt>:: See above.
|
26
|
+
|
27
|
+
=end rdoc
|
28
|
+
|
29
|
+
class Cassandra
|
30
|
+
include Columns
|
31
|
+
include Protocol
|
32
|
+
|
33
|
+
class AccessError < StandardError #:nodoc:
|
34
|
+
end
|
35
|
+
|
8
36
|
module Consistency
|
9
37
|
include CassandraThrift::ConsistencyLevel
|
10
|
-
NONE = ZERO
|
11
|
-
WEAK = ONE
|
12
|
-
STRONG = QUORUM
|
13
|
-
PERFECT = ALL
|
14
38
|
end
|
15
|
-
|
39
|
+
|
40
|
+
MAX_INT = 2**31 - 1
|
41
|
+
|
42
|
+
WRITE_DEFAULTS = {
|
43
|
+
:count => MAX_INT,
|
44
|
+
:timestamp => nil,
|
45
|
+
:consistency => Consistency::ONE
|
46
|
+
}.freeze
|
47
|
+
|
48
|
+
READ_DEFAULTS = {
|
49
|
+
:count => 100,
|
50
|
+
:start => nil,
|
51
|
+
:finish => nil,
|
52
|
+
:reversed => false,
|
53
|
+
:consistency => Consistency::ONE
|
54
|
+
}.freeze
|
55
|
+
|
16
56
|
attr_reader :keyspace, :host, :port, :serializer, :transport, :client, :schema
|
17
57
|
|
18
58
|
# Instantiate a new Cassandra and open the connection.
|
19
|
-
def initialize(keyspace, host = '127.0.0.1', port = 9160
|
59
|
+
def initialize(keyspace, host = '127.0.0.1', port = 9160)
|
20
60
|
@is_super = {}
|
21
61
|
@column_name_class = {}
|
22
62
|
@sub_column_name_class = {}
|
@@ -24,242 +64,194 @@ class Cassandra
|
|
24
64
|
@keyspace = keyspace
|
25
65
|
@host = host
|
26
66
|
@port = port
|
27
|
-
@serializer = serializer
|
28
67
|
|
29
68
|
@transport = Thrift::BufferedTransport.new(Thrift::Socket.new(@host, @port))
|
30
|
-
@transport.open
|
69
|
+
@transport.open
|
31
70
|
@client = CassandraThrift::Cassandra::SafeClient.new(
|
32
|
-
CassandraThrift::Cassandra::Client.new(Thrift::BinaryProtocol.new(@transport)),
|
71
|
+
CassandraThrift::Cassandra::Client.new(Thrift::BinaryProtocol.new(@transport)),
|
33
72
|
@transport)
|
34
73
|
|
35
|
-
keyspaces = @client.get_string_list_property("
|
74
|
+
keyspaces = @client.get_string_list_property("keyspaces")
|
36
75
|
unless keyspaces.include?(@keyspace)
|
37
76
|
raise AccessError, "Keyspace #{@keyspace.inspect} not found. Available: #{keyspaces.inspect}"
|
38
77
|
end
|
39
|
-
|
40
|
-
@schema = @client.
|
78
|
+
|
79
|
+
@schema = @client.describe_keyspace(@keyspace)
|
41
80
|
end
|
42
|
-
|
81
|
+
|
43
82
|
def inspect
|
44
83
|
"#<Cassandra:#{object_id}, @keyspace=#{keyspace.inspect}, @schema={#{
|
45
84
|
schema.map {|name, hash| ":#{name} => #{hash['type'].inspect}"}.join(', ')
|
46
|
-
}}, @host=#{host.inspect}, @port=#{port}
|
85
|
+
}}, @host=#{host.inspect}, @port=#{port}>"
|
47
86
|
end
|
48
87
|
|
49
|
-
|
50
|
-
|
51
|
-
# Insert a row for a key. Pass a flat hash for a regular column family, and
|
52
|
-
# a nested hash for a super column family.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
88
|
+
### Write
|
89
|
+
|
90
|
+
# Insert a row for a key. Pass a flat hash for a regular column family, and
|
91
|
+
# a nested hash for a super column family. Supports the <tt>:consistency</tt>
|
92
|
+
# and <tt>:timestamp</tt> options.
|
93
|
+
def insert(column_family, key, hash, options = {})
|
94
|
+
column_family, _, _, options = params(column_family, [options], WRITE_DEFAULTS)
|
95
|
+
|
96
|
+
mutation = if is_super(column_family)
|
97
|
+
CassandraThrift::BatchMutationSuper.new(
|
98
|
+
:key => key,
|
99
|
+
:cfmap => {column_family =>
|
100
|
+
hash_to_super_columns(column_family, hash, options[:timestamp] || Time.stamp)})
|
57
101
|
else
|
58
|
-
CassandraThrift::BatchMutation.new(
|
102
|
+
CassandraThrift::BatchMutation.new(
|
103
|
+
:key => key,
|
104
|
+
:cfmap => {column_family =>
|
105
|
+
hash_to_columns(column_family, hash, options[:timestamp] || Time.stamp)})
|
59
106
|
end
|
60
|
-
|
61
|
-
|
107
|
+
|
108
|
+
args = [mutation, options[:consistency]]
|
109
|
+
@batch ? @batch << args : _insert(*args)
|
62
110
|
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def _insert(mutation, consistency = Consistency::WEAK)
|
67
|
-
case mutation
|
68
|
-
when CassandraThrift::BatchMutationSuper then @client.batch_insert_super_column(@keyspace, mutation, consistency)
|
69
|
-
when CassandraThrift::BatchMutation then @client.batch_insert(@keyspace, mutation, consistency)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
public
|
74
|
-
|
111
|
+
|
75
112
|
## Delete
|
76
|
-
|
113
|
+
|
77
114
|
# Remove the element at the column_family:key:[column]:[sub_column]
|
78
|
-
# path you request.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
column = column.to_s if column
|
84
|
-
sub_column = sub_column.to_s if sub_column
|
85
|
-
args = [column_family, key, column, sub_column, consistency, timestamp]
|
115
|
+
# path you request. Supports the <tt>:consistency</tt> and <tt>:timestamp</tt>
|
116
|
+
# options.
|
117
|
+
def remove(column_family, key, *columns_and_options)
|
118
|
+
column_family, column, sub_column, options = params(column_family, columns_and_options, WRITE_DEFAULTS)
|
119
|
+
args = [column_family, key, column, sub_column, options[:consistency], options[:timestamp] || Time.stamp]
|
86
120
|
@batch ? @batch << args : _remove(*args)
|
87
121
|
end
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
CassandraThrift::ColumnPathOrParent.new(:column_family => column_family, :column => column)
|
96
|
-
end
|
97
|
-
@client.remove(@keyspace, key, column_path_or_parent, timestamp, consistency)
|
122
|
+
|
123
|
+
# Remove all rows in the column family you request. Supports options
|
124
|
+
# <tt>:consistency</tt> and <tt>:timestamp</tt>.
|
125
|
+
# FIXME May not currently delete all records without multiple calls. Waiting
|
126
|
+
# for ranged remove support in Cassandra.
|
127
|
+
def clear_column_family!(column_family, options = {})
|
128
|
+
get_range(column_family).each { |key| remove(column_family, key, options) }
|
98
129
|
end
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
remove(column_family, key)
|
107
|
-
end
|
130
|
+
|
131
|
+
# Remove all rows in the keyspace. Supports options <tt>:consistency</tt> and
|
132
|
+
# <tt>:timestamp</tt>.
|
133
|
+
# FIXME May not currently delete all records without multiple calls. Waiting
|
134
|
+
# for ranged remove support in Cassandra.
|
135
|
+
def clear_keyspace!(options = {})
|
136
|
+
@schema.keys.each { |column_family| clear_column_family!(column_family, options) }
|
108
137
|
end
|
109
138
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
139
|
+
### Read
|
140
|
+
|
141
|
+
# Count the elements at the column_family:key:[super_column] path you
|
142
|
+
# request. Supports options <tt>:count</tt>, <tt>:start</tt>, <tt>:finish</tt>,
|
143
|
+
# <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
144
|
+
def count_columns(column_family, key, *columns_and_options)
|
145
|
+
column_family, super_column, _, options = params(column_family, columns_and_options, READ_DEFAULTS)
|
146
|
+
_count_columns(column_family, key, super_column, options[:consistency])
|
116
147
|
end
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
def count_columns(column_family, key, super_column = nil, consistency = Consistency::WEAK)
|
123
|
-
column_family = column_family.to_s
|
124
|
-
assert_column_name_classes(column_family, super_column)
|
125
|
-
|
126
|
-
super_column = super_column.to_s if super_column
|
127
|
-
@client.get_column_count(@keyspace, key,
|
128
|
-
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => super_column),
|
129
|
-
consistency
|
130
|
-
)
|
148
|
+
|
149
|
+
# Multi-key version of Cassandra#count_columns. Supports options <tt>:count</tt>,
|
150
|
+
# <tt>:start</tt>, <tt>:finish</tt>, <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
151
|
+
def multi_count_columns(column_family, keys, *options)
|
152
|
+
OrderedHash[*keys.map { |key| [key, count_columns(column_family, key, *options)] }._flatten_once]
|
131
153
|
end
|
132
|
-
|
133
|
-
# Multi-key version of Cassandra#count_columns.
|
134
|
-
def multi_count_columns(column_family, keys, super_column = nil, consistency = Consistency::WEAK)
|
135
|
-
OrderedHash[*keys.map do |key|
|
136
|
-
[key, count_columns(column_family, key, super_column)]
|
137
|
-
end._flatten_once]
|
138
|
-
end
|
139
|
-
|
154
|
+
|
140
155
|
# Return a list of single values for the elements at the
|
141
|
-
# column_family:key:column[s]:[sub_columns] path you request.
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
result = if is_super(column_family)
|
147
|
-
if sub_columns
|
148
|
-
columns_to_hash(column_family, @client.get_slice_by_names(@keyspace, key,
|
149
|
-
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => columns),
|
150
|
-
sub_columns, consistency))
|
151
|
-
else
|
152
|
-
columns_to_hash(column_family, @client.get_slice_super_by_names(@keyspace, key, column_family, columns, consistency))
|
153
|
-
end
|
154
|
-
else
|
155
|
-
columns_to_hash(column_family, @client.get_slice_by_names(@keyspace, key,
|
156
|
-
CassandraThrift::ColumnParent.new(:column_family => column_family), columns, consistency))
|
157
|
-
end
|
158
|
-
sub_columns || columns.map { |name| result[name] }
|
156
|
+
# column_family:key:column[s]:[sub_columns] path you request. Supports the
|
157
|
+
# <tt>:consistency</tt> option.
|
158
|
+
def get_columns(column_family, key, *columns_and_options)
|
159
|
+
column_family, columns, sub_columns, options = params(column_family, columns_and_options, READ_DEFAULTS)
|
160
|
+
_get_columns(column_family, key, columns, sub_columns, options[:consistency])
|
159
161
|
end
|
160
162
|
|
161
|
-
# Multi-key version of Cassandra#get_columns.
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
end._flatten_once]
|
163
|
+
# Multi-key version of Cassandra#get_columns. Supports the <tt>:consistency</tt>
|
164
|
+
# option.
|
165
|
+
def multi_get_columns(column_family, keys, *options)
|
166
|
+
OrderedHash[*keys.map { |key| [key, get_columns(column_family, key, *options)] }._flatten_once]
|
166
167
|
end
|
167
|
-
|
168
|
-
# Return a hash (actually, a Cassandra::OrderedHash) or a single value
|
168
|
+
|
169
|
+
# Return a hash (actually, a Cassandra::OrderedHash) or a single value
|
169
170
|
# representing the element at the column_family:key:[column]:[sub_column]
|
170
|
-
# path you request.
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
_get(column_family, key, column, sub_column,
|
171
|
+
# path you request. Supports options <tt>:count</tt>, <tt>:start</tt>,
|
172
|
+
# <tt>:finish</tt>, <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
173
|
+
def get(column_family, key, *columns_and_options)
|
174
|
+
column_family, column, sub_column, options = params(column_family, columns_and_options, READ_DEFAULTS)
|
175
|
+
_get(column_family, key, column, sub_column, options[:count], options[:start], options[:finish], options[:reversed], options[:consistency])
|
175
176
|
rescue CassandraThrift::NotFoundException
|
176
177
|
is_super(column_family) && !sub_column ? OrderedHash.new : nil
|
177
178
|
end
|
178
179
|
|
179
|
-
# Multi-key version of Cassandra#get.
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
end._flatten_once]
|
180
|
+
# Multi-key version of Cassandra#get. Supports options <tt>:count</tt>,
|
181
|
+
# <tt>:start</tt>, <tt>:finish</tt>, <tt>:reversed</tt>, and <tt>:consistency</tt>.
|
182
|
+
def multi_get(column_family, keys, *options)
|
183
|
+
OrderedHash[*keys.map { |key| [key, get(column_family, key, *options)] }._flatten_once]
|
184
184
|
end
|
185
|
-
|
186
|
-
# Return true if the column_family:key:[column]:[sub_column] path you
|
187
|
-
# request exists.
|
188
|
-
def exists?(column_family, key,
|
189
|
-
column_family = column_family
|
190
|
-
|
191
|
-
_get(column_family, key, column, sub_column, 1, ''..'', false, consistency)
|
185
|
+
|
186
|
+
# Return true if the column_family:key:[column]:[sub_column] path you
|
187
|
+
# request exists. Supports the <tt>:consistency</tt> option.
|
188
|
+
def exists?(column_family, key, *columns_and_options)
|
189
|
+
column_family, column, sub_column, options = params(column_family, columns_and_options, READ_DEFAULTS)
|
190
|
+
_get(column_family, key, column, sub_column, 1, nil, nil, nil, options[:consistency])
|
192
191
|
true
|
193
192
|
rescue CassandraThrift::NotFoundException
|
194
193
|
end
|
195
|
-
|
196
|
-
private
|
197
|
-
|
198
|
-
def _get(column_family, key, column = nil, sub_column = nil, limit = 100, column_range = ''..'', reversed = false, consistency = Consistency::WEAK)
|
199
|
-
column = column.to_s if column
|
200
|
-
sub_column = sub_column.to_s if sub_column
|
201
|
-
# FIXME Comparable types in a are not checked
|
202
|
-
column_range = (column_range.begin.to_s)..(column_range.end.to_s)
|
203
|
-
|
204
|
-
# You have got to be kidding
|
205
|
-
if is_super(column_family)
|
206
|
-
if sub_column
|
207
|
-
# Limit and column_range parameters have no effect
|
208
|
-
load(@client.get_column(@keyspace, key,
|
209
|
-
CassandraThrift::ColumnPath.new(:column_family => column_family, :super_column => column, :column => sub_column),
|
210
|
-
consistency).value)
|
211
|
-
elsif column
|
212
|
-
sub_columns_to_hash(column_family,
|
213
|
-
@client.get_slice(@keyspace, key,
|
214
|
-
CassandraThrift::ColumnParent.new(:column_family => column_family, :super_column => column),
|
215
|
-
column_range.begin, column_range.end, !reversed, limit, consistency))
|
216
|
-
else
|
217
|
-
columns_to_hash(column_family,
|
218
|
-
@client.get_slice_super(@keyspace, key, column_family, column_range.begin, column_range.end, !reversed, limit, consistency))
|
219
|
-
end
|
220
|
-
else
|
221
|
-
if column
|
222
|
-
# Limit and column_range parameters have no effect
|
223
|
-
load(@client.get_column(@keyspace, key,
|
224
|
-
CassandraThrift::ColumnPath.new(:column_family => column_family, :column => column),
|
225
|
-
consistency).value)
|
226
|
-
else
|
227
|
-
columns_to_hash(column_family,
|
228
|
-
@client.get_slice(@keyspace, key,
|
229
|
-
CassandraThrift::ColumnParent.new(:column_family => column_family),
|
230
|
-
column_range.begin, column_range.end, !reversed, limit, consistency))
|
231
|
-
end
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
public
|
236
|
-
|
194
|
+
|
237
195
|
# Return a list of keys in the column_family you request. Requires the
|
238
|
-
# table to be partitioned with OrderPreservingHash.
|
239
|
-
|
240
|
-
|
241
|
-
|
196
|
+
# table to be partitioned with OrderPreservingHash. Supports the
|
197
|
+
# <tt>:count</tt>, <tt>:start</tt>, <tt>:finish</tt>, and <tt>:consistency</tt>
|
198
|
+
# options.
|
199
|
+
def get_range(column_family, options = {})
|
200
|
+
column_family, _, _, options = params(column_family, [options], READ_DEFAULTS)
|
201
|
+
_get_range(column_family, options[:start], options[:finish], options[:count], options[:consistency])
|
242
202
|
end
|
243
|
-
|
244
|
-
# Count all rows in the column_family you request. Requires the table
|
245
|
-
# to be partitioned with OrderPreservingHash.
|
246
|
-
|
247
|
-
|
203
|
+
|
204
|
+
# Count all rows in the column_family you request. Requires the table
|
205
|
+
# to be partitioned with OrderPreservingHash. Supports the <tt>:start</tt>,
|
206
|
+
# <tt>:finish</tt>, and <tt>:consistency</tt> options.
|
207
|
+
# FIXME will count only MAX_INT records
|
208
|
+
def count_range(column_family, options = {})
|
209
|
+
get_range(column_family, options.merge(:count => MAX_INT)).size
|
248
210
|
end
|
249
|
-
|
211
|
+
|
212
|
+
# Open a batch operation and yield. Inserts and deletes will be queued until
|
213
|
+
# the block closes, and then sent atomically to the server.
|
214
|
+
# FIXME Make deletes truly atomic.
|
250
215
|
def batch
|
251
216
|
@batch = []
|
252
|
-
yield
|
253
|
-
compact_mutations
|
254
|
-
dispatch_mutations
|
217
|
+
yield
|
218
|
+
compact_mutations
|
219
|
+
dispatch_mutations
|
255
220
|
@batch = nil
|
256
221
|
end
|
257
222
|
|
258
223
|
private
|
224
|
+
|
225
|
+
# Extract and validate options.
|
226
|
+
def params(column_family, args, options)
|
227
|
+
if args.last.is_a?(Hash)
|
228
|
+
if (extras = args.last.keys - options.keys).any?
|
229
|
+
this = "#{self.class}##{caller[0].split('`').last[0..-2]}"
|
230
|
+
raise ArgumentError, "Invalid options #{extras.inspect[1..-2]} for #{this}"
|
231
|
+
end
|
232
|
+
options = options.merge(args.pop)
|
233
|
+
end
|
259
234
|
|
260
|
-
|
235
|
+
column_family, column, sub_column = column_family.to_s, args[0], args[1]
|
236
|
+
assert_column_name_classes(column_family, column, sub_column)
|
237
|
+
[column_family, map_to_s(column), map_to_s(sub_column), options]
|
238
|
+
end
|
239
|
+
|
240
|
+
# Convert stuff to strings.
|
241
|
+
def map_to_s(el)
|
242
|
+
case el
|
243
|
+
when NilClass # nil
|
244
|
+
when Array then el.map { |i| map_to_s(i) }
|
245
|
+
when Comparable, String, Symbol then el.to_s
|
246
|
+
else
|
247
|
+
raise Comparable::TypeError, "Can't map #{el.inspect}"
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Roll up queued mutations as much as possible, to improve atomicity.
|
252
|
+
def compact_mutations
|
261
253
|
compact_batch = []
|
262
|
-
mutations = {}
|
254
|
+
mutations = {}
|
263
255
|
|
264
256
|
@batch << nil # Close it
|
265
257
|
@batch.each do |m|
|
@@ -269,7 +261,7 @@ class Cassandra
|
|
269
261
|
compact_batch.concat(mutations.values.map {|x| x.values}.flatten)
|
270
262
|
mutations = {}
|
271
263
|
# Insert delete operation
|
272
|
-
compact_batch << m
|
264
|
+
compact_batch << m
|
273
265
|
else # BatchMutation, BatchMutationSuper
|
274
266
|
# Do a nested hash merge
|
275
267
|
if mutation_class = mutations[m.class]
|
@@ -287,29 +279,20 @@ class Cassandra
|
|
287
279
|
end
|
288
280
|
end
|
289
281
|
end
|
290
|
-
|
282
|
+
|
291
283
|
@batch = compact_batch
|
292
284
|
end
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
when CassandraThrift::BatchMutationSuper, CassandraThrift::BatchMutation
|
285
|
+
|
286
|
+
# Send all the queued mutations to the server.
|
287
|
+
def dispatch_mutations
|
288
|
+
@batch.compact!
|
289
|
+
@batch.each do |args|
|
290
|
+
case args.first
|
291
|
+
when CassandraThrift::BatchMutationSuper, CassandraThrift::BatchMutation
|
300
292
|
_insert(*args)
|
293
|
+
else
|
294
|
+
_remove(*args)
|
301
295
|
end
|
302
296
|
end
|
303
297
|
end
|
304
|
-
|
305
|
-
def dump(object)
|
306
|
-
# Special-case nil as the empty byte array
|
307
|
-
return "" if object == nil
|
308
|
-
@serializer.dump(object)
|
309
|
-
end
|
310
|
-
|
311
|
-
def load(object)
|
312
|
-
return nil if object == ""
|
313
|
-
@serializer.load(object)
|
314
|
-
end
|
315
298
|
end
|