cassandra 0.4 → 0.5
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.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
|