eurydice 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ /ext
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format doc
3
+
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create use jruby-1.6.2@eurydice
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'jruby-openssl'
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ eurydice (1.0.0-java)
5
+ pelops-jars (= 1.2)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ bouncy-castle-java (1.5.0146.1)
11
+ cassandra-jars (0.8.0-java)
12
+ diff-lcs (1.1.2)
13
+ jruby-openssl (0.7.4)
14
+ bouncy-castle-java
15
+ pelops-jars (1.2-java)
16
+ cassandra-jars (~> 0.8.0)
17
+ rake (0.9.2)
18
+ rspec (2.6.0)
19
+ rspec-core (~> 2.6.0)
20
+ rspec-expectations (~> 2.6.0)
21
+ rspec-mocks (~> 2.6.0)
22
+ rspec-core (2.6.4)
23
+ rspec-expectations (2.6.0)
24
+ diff-lcs (~> 1.1.2)
25
+ rspec-mocks (2.6.0)
26
+
27
+ PLATFORMS
28
+ java
29
+
30
+ DEPENDENCIES
31
+ eurydice!
32
+ jruby-openssl
33
+ rake
34
+ rspec
data/README.mdown ADDED
@@ -0,0 +1,25 @@
1
+ # Eurydice
2
+
3
+ Eurydice is a Cassandra client library for JRuby built on top of [Pelops](https://github.com/s7/scale7-pelops).
4
+
5
+ See the `examples` directory and the specs for usage.
6
+
7
+ ## Installation & requirements
8
+
9
+ Tested with JRuby 1.6.2 in 1.9 mode and Cassandra 0.8.1.
10
+
11
+ gem install eurydice
12
+
13
+ This will also install two dependencies: `pelops-jars` and `cassandra-jars` which contain the Pelops and Cassandra JAR files.
14
+
15
+ ## Contributors
16
+
17
+ Theo Hultberg, [@iconara](http://twitter.com/iconara)
18
+
19
+ ## License
20
+
21
+ Eurydice is licensed under the [MIT License](http://www.opensource.org/licenses/mit-license.php), the same as Pelops.
22
+
23
+ ## Eurydice?
24
+
25
+ Eurydice was the daughter of Pelops (but she's not _that_ [Eurydice](http://en.wikipedia.org/wiki/Eurydice)).
data/eurydice.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ $: << File.expand_path('../lib', __FILE__)
4
+
5
+ require 'eurydice/version'
6
+
7
+
8
+ Gem::Specification.new do |s|
9
+ s.name = 'eurydice'
10
+ s.version = Eurydice::VERSION
11
+ s.platform = 'java'
12
+ s.authors = ['Theo Hultberg']
13
+ s.email = ['theo@burtcorp.com']
14
+ s.homepage = 'http://github.com/iconara/eurydice'
15
+ s.summary = %q{Ruby wrapper for the Pelops library}
16
+ s.description = %q{}
17
+
18
+ s.rubyforge_project = 'eurydice'
19
+
20
+ s.add_dependency 'pelops-jars', '= 1.2'
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.require_paths = %w(lib)
24
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'common'
4
+
5
+
6
+ # Connect to the default host (localhost) and port (9160), these can be
7
+ # overridden by passing then :host and :port options.
8
+ cluster = Eurydice.connect
9
+
10
+ # Get a reference to a keyspace, it will be created if it does not exist
11
+ # (pass the option :create => false to not automatically create the keyspace).
12
+ keyspace = cluster.keyspace('my_keyspace')
13
+
14
+ # Clean up by dropping the keyspace
15
+ keyspace.drop!
16
+
17
+ # Finally disconnect everything
18
+ Eurydice.disconnect!
@@ -0,0 +1,19 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'common'
4
+
5
+
6
+ cluster = Eurydice.connect
7
+
8
+ # Get a reference to a keyspace, but don't automatically create it, instead
9
+ # we will create it explicitly, and with a few options.
10
+ keyspace = cluster.keyspace('my_keyspace', :create => false)
11
+
12
+ # Create the keyspace with some options, the possible options can be found
13
+ # here: http://www.datastax.com/docs/0.8/configuration/storage_configuration
14
+ keyspace.create!(
15
+ :strategy_class => 'org.apache.cassandra.locator.NetworkTopologyStrategy',
16
+ :strategy_options => {:replication_factor => 3}
17
+ )
18
+
19
+ keyspace.drop!
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'common'
4
+
5
+
6
+ cluster = Eurydice.connect
7
+ keyspace = cluster.keyspace('my_keyspace')
8
+
9
+ # Get a reference to a column family, but don't automatically create it,
10
+ # instead we will create it explicitly, and with a few options.
11
+ column_family = keyspace.column_family('my_family', :create => false)
12
+
13
+ # Create the column family with some options, the possible options can be found
14
+ # here: http://www.datastax.com/docs/0.8/configuration/storage_configuration
15
+ column_family.create!(
16
+ :key_validation_class => :ascii, # the type of the row keys
17
+ :comparator_type => :ascii, # the type of the column keys
18
+ :default_validation_class => :utf8, # the type of the column values
19
+ :column_metadata => {
20
+ 'name' => {
21
+ :validation_class => :utf8, # you can declare the types of columns
22
+ :index_name => 'name_index', # and set up indexing
23
+ :index_type => :keys
24
+ }
25
+ }
26
+ )
27
+
28
+ keyspace.drop!
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'common'
4
+
5
+
6
+ cluster = Eurydice.connect
7
+ keyspace = cluster.keyspace('my_keyspace')
8
+ column_family = keyspace.column_family('employees')
9
+
10
+ # Insert a few rows representing employees
11
+ column_family.insert('employee:1', {'name' => 'Sam', 'role' => 'Developer'})
12
+ column_family.insert('employee:2', {'name' => 'Phil', 'role' => 'Accountant'})
13
+ column_family.insert('employee:3', {'name' => 'Steve', 'role' => 'Developer'})
14
+ column_family.insert('employee:4', {'name' => 'Julie', 'role' => 'CEO'})
15
+
16
+ # #insert is actually an alias for #update, in some cases it feels more
17
+ # natural to say "insert" than "update", but in the end the operations are the
18
+ # same -- adding a column to a row (and adding a column that is already there
19
+ # replaces the old value).
20
+ column_family.update('employee:3', {'email' => 'steve@acme.com'})
21
+ column_family.update('employee:3', {'role' => 'tester'})
22
+
23
+ # If you want to insert numbers you must be explicit, unfortunately. Use the
24
+ # :validations option to pass a hash of property types. Currently the only one
25
+ # besides the default is :long (the default is to make the value a string and
26
+ # then creating a byte array from the string, this works with the :bytes,
27
+ # :ascii and :utf8 validations [read "column value types"] if the string has
28
+ # the right encoding).
29
+ column_family.update('employee:2', {'age' => 44}, :validations => {'age' => :long})
30
+
31
+ # You can specify :consistency_level as :one, :quorum, :all or :any (default is :one)
32
+ column_family.update('employee:4', {'email' => 'boss@acme.com'}, :consistency_level => :quorum)
33
+
34
+ # :cl is an alias for :consistency_level
35
+ column_family.update('employee:1', {'email' => 'sam@acme.com'}, :cl => :one)
36
+
37
+ keyspace.drop!
@@ -0,0 +1,86 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'common'
4
+
5
+
6
+ cluster = Eurydice.connect
7
+ keyspace = cluster.keyspace('my_keyspace')
8
+ column_family = keyspace.column_family('employees', :create => false)
9
+ column_family.create!(:column_metadata => {'name' => {:validation_class => :utf8, :index_name => 'name_index', :index_type => :keys}})
10
+ column_family.insert('employee:1', {'name' => 'Sam', 'role' => 'Developer'})
11
+ column_family.insert('employee:2', {'name' => 'Phil', 'role' => 'Accountant'})
12
+ column_family.insert('employee:3', {'name' => 'Steve', 'role' => 'Developer'})
13
+ column_family.insert('employee:4', {'name' => 'Julie', 'role' => 'CEO'})
14
+ column_family.update('employee:3', {'email' => 'steve@acme.com'})
15
+ column_family.update('employee:3', {'role' => 'tester'})
16
+ column_family.update('employee:2', {'age' => 44}, :validations => {'age' => :long})
17
+ column_family.update('employee:4', {'email' => 'boss@acme.com'}, :consistency_level => :quorum)
18
+ column_family.update('employee:1', {'email' => 'sam@acme.com'}, :cl => :one)
19
+
20
+ # Load a single row
21
+ employee1 = column_family.get('employee:1')
22
+ puts "employee:1 => #{employee1['name']}, #{employee1['role']}"
23
+
24
+ # Load multiple rows
25
+ employees = column_family.get(%w(employee:2 employee:3 employee:4))
26
+ employees.each do |row_key, employee|
27
+ puts "#{row_key} => #{employee['name']}, #{employee['role']}"
28
+ end
29
+
30
+ puts '---'
31
+
32
+ # Load only the specified columns
33
+ employee1 = column_family.get('employee:1', :columns => %w(name))
34
+ puts "employee:1 => #{employee1['name']}"
35
+
36
+ employees = column_family.get(%w(employee:2 employee:3 employee:4), :columns => %w(name))
37
+ employees.each do |row_key, employee|
38
+ puts "#{row_key} => #{employee['name']}"
39
+ end
40
+
41
+ puts '---'
42
+
43
+ # Load only the value from a single column
44
+ employee1_name = column_family.get_column('employee:1', 'name')
45
+ puts "employee:1 => #{employee1_name}"
46
+
47
+ puts '---'
48
+
49
+ # If you've stored a number you have to specify :validations when loading, too
50
+ employee2 = column_family.get('employee:2', :validations => {'age' => :long})
51
+ puts "employee:2 => #{employee2['name']}, #{employee2['age']}"
52
+
53
+ puts '---'
54
+
55
+ # You can check if a row exists
56
+ puts "Is there a employee:5? #{column_family.key?('employee:0') ? 'yes' : 'no'}"
57
+
58
+ # #row_exists? is an alias for #key?
59
+ puts "Is there a employee:1? #{column_family.row_exists?('employee:1') ? 'yes' : 'no'}"
60
+
61
+ puts '---'
62
+
63
+ # You can specify :consistency_level as :one, :quorum, :all or :any
64
+ employee1 = column_family.get('employee:1', :consistency_level => :quorum)
65
+ puts "employee:1 => #{employee1['email']}"
66
+
67
+ puts '---'
68
+
69
+ # If you have a row with lots of columns, you can iterate over them (in order)
70
+ # with #each_column. Under the hood they will be loaded in batches
71
+ column_family.update('employee:5', Hash[(0...1000).map { |i| ["property#{i}", "value#{i}"] }])
72
+ count = 0
73
+ column_family.each_column('employee:5') do |column_name, column_value|
74
+ count += 1
75
+ end
76
+ puts "There were #{count} columns"
77
+
78
+ puts '---'
79
+
80
+ # Cassandra has basic secondary indexes, which can be used when querying
81
+ column_family.insert('employees:6', {'name' => 'Sam'})
82
+ column_family.insert('employees:7', {'name' => 'Sam'})
83
+ employees_named_sam = column_family.get_indexed('name', :==, 'Sam')
84
+ puts "There are #{employees_named_sam.size} employees named 'Sam'"
85
+
86
+ keyspace.drop!
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'common'
4
+
5
+
6
+ cluster = Eurydice.connect
7
+
8
+ # List the keyspaces
9
+ puts 'Keyspaces:'
10
+ cluster.keyspaces.each_with_index do |keyspace_name, i|
11
+ puts "#{i + 1}: #{keyspace_name}"
12
+ end
13
+
14
+ puts '---'
15
+
16
+ # List the nodes in the cluster
17
+ puts 'Nodes:'
18
+ cluster.nodes.each_with_index do |node_address, i|
19
+ puts "#{i + 1}: #{node_address}"
20
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ $: << File.expand_path('../../lib', __FILE__)
4
+
5
+ require 'eurydice'
data/lib/cassandra.rb ADDED
@@ -0,0 +1,158 @@
1
+ # encoding: utf-8
2
+
3
+ module Thrift
4
+ import 'org.apache.thrift.transport.TTransportException'
5
+ end
6
+
7
+ module Cassandra
8
+ import 'org.apache.cassandra.thrift.ConsistencyLevel'
9
+ import 'org.apache.cassandra.thrift.IndexType'
10
+ import 'org.apache.cassandra.thrift.Column'
11
+ import 'org.apache.cassandra.thrift.KsDef'
12
+ import 'org.apache.cassandra.thrift.CfDef'
13
+ import 'org.apache.cassandra.thrift.ColumnDef'
14
+ import 'org.apache.cassandra.thrift.InvalidRequestException'
15
+ import 'org.apache.cassandra.thrift.SlicePredicate'
16
+ import 'org.apache.cassandra.thrift.SliceRange'
17
+ import 'org.apache.cassandra.thrift.IndexOperator'
18
+
19
+ CONSISTENCY_LEVELS = {
20
+ :one => Cassandra::ConsistencyLevel::ONE,
21
+ :quorum => Cassandra::ConsistencyLevel::QUORUM,
22
+ :all => Cassandra::ConsistencyLevel::ALL,
23
+ :any => Cassandra::ConsistencyLevel::ANY
24
+ }.freeze
25
+
26
+ MARSHAL_TYPES = {
27
+ :bytes => 'org.apache.cassandra.db.marshal.BytesType'.freeze,
28
+ :ascii => 'org.apache.cassandra.db.marshal.AsciiType'.freeze,
29
+ :utf8 => 'org.apache.cassandra.db.marshal.UTF8Type'.freeze,
30
+ :long => 'org.apache.cassandra.db.marshal.LongType'.freeze,
31
+ :lexical_uuid => 'org.apache.cassandra.db.marshal.LexicalUUIDType'.freeze,
32
+ :time_uuid => 'org.apache.cassandra.db.marshal.TimeUUIDType'.freeze
33
+ }.freeze
34
+
35
+ INDEX_OPERATORS = {
36
+ :== => IndexOperator::EQ,
37
+ :eq => IndexOperator::EQ,
38
+ :> => IndexOperator::GT,
39
+ :gt => IndexOperator::GT,
40
+ :>= => IndexOperator::GTE,
41
+ :gte => IndexOperator::GTE,
42
+ :< => IndexOperator::LT,
43
+ :lt => IndexOperator::LT,
44
+ :<= => IndexOperator::LTE,
45
+ :lte => IndexOperator::LTE
46
+ }.freeze
47
+
48
+ class KsDef
49
+ def self.from_h(h)
50
+ ks_def = h.reduce(self.new) do |ks_def, (field_name, field_value)|
51
+ case field_name.to_sym
52
+ when :strategy_options
53
+ field_value = Hash[field_value.map { |k, v| [k.to_s, v.to_s] }]
54
+ when :column_families
55
+ field_name = 'cf_defs'
56
+ field_value = field_value.map { |cf_name, cf_def_h| CfDef.from_h(cf_def_h.merge(:name => cf_name, :keyspace => h[:name])) }
57
+ end
58
+ field = self::_Fields.find_by_name(field_name.to_s)
59
+ raise ArgumentError, %(No field named "#{field_name}") unless field
60
+ ks_def.set_field_value(field, field_value)
61
+ ks_def
62
+ end
63
+ ks_def.cf_defs = java.util.Collections.emptyList unless ks_def.cf_defs
64
+ ks_def
65
+ end
66
+
67
+ def to_h
68
+ self.class.metaDataMap.reduce({}) do |acc, (field, field_meta_data)|
69
+ field_name = field.field_name.to_sym
70
+ field_value = get_field_value(field)
71
+ case field_name.to_sym
72
+ when :cf_defs
73
+ cf_hs = field_value.map { |cf_def| cf_def.to_h }
74
+ acc[:column_families] = Hash[cf_hs.map { |cf_h| [cf_h[:name], cf_h] }]
75
+ when :strategy_options
76
+ acc[field_name] = Hash[field_value.map { |pair| [pair.first.to_sym, pair.last] }] # JRuby 1.6.2 Java Map doesn't splat when yielding
77
+ else
78
+ acc[field_name] = field_value
79
+ end
80
+ acc
81
+ end
82
+ end
83
+ end
84
+
85
+ class CfDef
86
+ def self.from_h(h)
87
+ h.reduce(self.new) do |cf_def, (field_name, field_value)|
88
+ case field_name.to_sym
89
+ when :column_type
90
+ field_value = field_value.to_s.capitalize
91
+ when :key_validation_class, :default_validation_class, :comparator_type, :subcomparator_type
92
+ field_value = Cassandra::MARSHAL_TYPES.fetch(field_value, field_value)
93
+ when :column_metadata
94
+ field_value = field_value.map do |column_name, column_def_h|
95
+ Cassandra::ColumnDef.from_h(column_def_h.merge(:name => column_name))
96
+ end
97
+ end
98
+ field = self::_Fields.find_by_name(field_name.to_s)
99
+ raise ArgumentError, %(No field named "#{field_name}") unless field
100
+ cf_def.set_field_value(field, field_value)
101
+ cf_def
102
+ end
103
+ end
104
+
105
+ def to_h
106
+ self.class.metaDataMap.reduce({:column_metadata => {}}) do |acc, (field, field_meta_data)|
107
+ field_name = field.field_name.to_sym
108
+ case field_name
109
+ when :column_metadata
110
+ column_hs = get_field_value(field).map { |col_def| col_def.to_h }
111
+ acc[field_name] = Hash[column_hs.map { |col_h| [col_h[:name], col_h] }]
112
+ when :column_type
113
+ acc[field_name] = get_field_value(field).downcase.to_sym
114
+ else
115
+ acc[field_name] = get_field_value(field)
116
+ end
117
+ acc
118
+ end
119
+ end
120
+ end
121
+
122
+ class ColumnDef
123
+ def self.from_h(h)
124
+ h.reduce(self.new) do |col_def, (field_name, field_value)|
125
+ case field_name.to_sym
126
+ when :name
127
+ field_value = Eurydice::Pelops::ByteHelpers.to_nio_bytes(field_value)
128
+ when :index_type
129
+ field_value = Cassandra::IndexType.valueOf(field_value.to_s.upcase)
130
+ when :validation_class
131
+ field_value = MARSHAL_TYPES.fetch(field_value, field_value)
132
+ end
133
+ field = self::_Fields.find_by_name(field_name.to_s)
134
+ raise ArgumentError, %(No field named "#{field_name}") unless field
135
+ col_def.set_field_value(field, field_value)
136
+ col_def
137
+ end
138
+ end
139
+
140
+ def to_h
141
+ self.class.metaDataMap.reduce({}) do |acc, (field, field_meta_data)|
142
+ field_name = field.field_name.to_sym
143
+ acc[field_name] = begin
144
+ case field_name
145
+ when :name
146
+ Eurydice::Pelops::ByteHelpers.nio_bytes_to_s(get_field_value(field))
147
+ when :index_type
148
+ value = get_field_value(field)
149
+ value.toString.downcase.to_sym if value
150
+ else
151
+ get_field_value(field)
152
+ end
153
+ end
154
+ acc
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module Eurydice
4
+ module Pelops
5
+ class Cluster
6
+ def initialize(cluster, driver=::Pelops::Pelops)
7
+ @cluster = cluster
8
+ @driver = driver
9
+ end
10
+
11
+ def connected?
12
+ @driver.create_cluster_manager(@cluster).cassandra_version
13
+ true
14
+ rescue Exception => e
15
+ false
16
+ end
17
+
18
+ def keyspace(keyspace_name, options={})
19
+ pool_name = options.fetch(:pool_name, "eurydice_#{keyspace_name}_pool")
20
+ create = options.fetch(:create, true)
21
+ @driver.add_pool(pool_name, @cluster, keyspace_name)
22
+ keyspace = Keyspace.new(keyspace_name, @cluster, pool_name, @driver)
23
+ keyspace.create! if create && !keyspace.exists?
24
+ keyspace
25
+ end
26
+
27
+ def keyspaces
28
+ keyspace_manager.keyspace_names.map { |ks_def| ks_def.name }
29
+ end
30
+
31
+ def nodes
32
+ @cluster.nodes.map { |n| n.address }
33
+ end
34
+
35
+ private
36
+
37
+ def keyspace_manager
38
+ @keyspace_manager ||= @driver.create_keyspace_manager(@cluster)
39
+ end
40
+ end
41
+ end
42
+ end