eurydice 1.0.0-java

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/.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