cequel 1.0.0.pre.6 → 1.0.0.rc1
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.
- checksums.yaml +4 -4
- data/lib/cequel/metal/batch.rb +15 -2
- data/lib/cequel/metal/keyspace.rb +15 -3
- data/lib/cequel/record.rb +1 -0
- data/lib/cequel/record/bulk_writes.rb +27 -0
- data/lib/cequel/record/configuration_generator.rb +12 -0
- data/lib/cequel/record/lazy_record_collection.rb +9 -0
- data/lib/cequel/record/persistence.rb +6 -2
- data/lib/cequel/record/railtie.rb +13 -18
- data/lib/cequel/record/record_set.rb +16 -1
- data/lib/cequel/record/scoped.rb +3 -1
- data/lib/cequel/record/tasks.rb +37 -0
- data/lib/cequel/schema/keyspace.rb +21 -0
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/record/record_set_spec.rb +67 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6d15e60b9c3a9725632fe70d140449a03dc1e033
|
4
|
+
data.tar.gz: cc8dd115c0639388ee13b77437b3a7ae6addb2ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b710ee3238b02cc47f0b57a9804ee4f729c1cd807e809b7134888f44b066f59330714aaf7460bafae1d702d2eba47f7dff3ac3b8e2f18b30f03bb3e259498c0
|
7
|
+
data.tar.gz: 870947477a6da3d0f72aae1d6f4154293c41da41b1bf7419c4906737ed86d70ebc9598901a2eec2a121a0412955b7c00e4b5cd03df3dd9228597653b64347bd9
|
data/lib/cequel/metal/batch.rb
CHANGED
@@ -20,7 +20,8 @@ module Cequel
|
|
20
20
|
#
|
21
21
|
def initialize(keyspace, options = {})
|
22
22
|
@keyspace = keyspace
|
23
|
-
@auto_apply = options
|
23
|
+
@auto_apply = options.fetch(:auto_apply, false)
|
24
|
+
@unlogged = options.fetch(:unlogged, false)
|
24
25
|
reset
|
25
26
|
end
|
26
27
|
|
@@ -44,12 +45,20 @@ module Cequel
|
|
44
45
|
def apply
|
45
46
|
return if @statement_count.zero?
|
46
47
|
if @statement_count > 1
|
47
|
-
@statement.prepend(
|
48
|
+
@statement.prepend(begin_statement)
|
48
49
|
@statement.append("APPLY BATCH\n")
|
49
50
|
end
|
50
51
|
@keyspace.execute(*@statement.args)
|
51
52
|
end
|
52
53
|
|
54
|
+
def unlogged?
|
55
|
+
@unlogged
|
56
|
+
end
|
57
|
+
|
58
|
+
def logged?
|
59
|
+
!unlogged?
|
60
|
+
end
|
61
|
+
|
53
62
|
private
|
54
63
|
|
55
64
|
def reset
|
@@ -57,6 +66,10 @@ module Cequel
|
|
57
66
|
@statement_count = 0
|
58
67
|
end
|
59
68
|
|
69
|
+
def begin_statement
|
70
|
+
"BEGIN #{"UNLOGGED " if unlogged?}BATCH\n"
|
71
|
+
end
|
72
|
+
|
60
73
|
end
|
61
74
|
|
62
75
|
end
|
@@ -6,6 +6,7 @@ module Cequel
|
|
6
6
|
# Handle to a Cassandra keyspace.
|
7
7
|
#
|
8
8
|
class Keyspace
|
9
|
+
attr_reader :configuration
|
9
10
|
|
10
11
|
#
|
11
12
|
# @api private
|
@@ -25,7 +26,7 @@ module Cequel
|
|
25
26
|
|
26
27
|
def configure(configuration = {})
|
27
28
|
@configuration = configuration
|
28
|
-
@hosts = configuration
|
29
|
+
@hosts = configuration.fetch(:host, configuration.fetch(:hosts, '127.0.0.1:9160'))
|
29
30
|
@thrift_options = configuration[:thrift].try(:symbolize_keys) || {}
|
30
31
|
@keyspace = configuration[:keyspace]
|
31
32
|
# reset the connections
|
@@ -144,9 +145,20 @@ module Cequel
|
|
144
145
|
# end
|
145
146
|
#
|
146
147
|
def batch(options = {})
|
147
|
-
|
148
|
+
new_batch = Batch.new(self, options)
|
149
|
+
|
150
|
+
if get_batch
|
151
|
+
if get_batch.unlogged? && new_batch.logged?
|
152
|
+
raise ArgumentError,
|
153
|
+
"Already in a logged batch; can't start an unlogged batch."
|
154
|
+
elsif get_batch.logged? && new_batch.unlogged?
|
155
|
+
raise ArgumentError,
|
156
|
+
"Already in an unlogged batch; can't start a logged batch."
|
157
|
+
end
|
158
|
+
return yield
|
159
|
+
end
|
160
|
+
|
148
161
|
begin
|
149
|
-
new_batch = Batch.new(self, options)
|
150
162
|
set_batch(new_batch)
|
151
163
|
yield.tap { new_batch.apply }
|
152
164
|
ensure
|
data/lib/cequel/record.rb
CHANGED
@@ -6,6 +6,7 @@ require 'cequel/record/schema'
|
|
6
6
|
require 'cequel/record/properties'
|
7
7
|
require 'cequel/record/collection'
|
8
8
|
require 'cequel/record/persistence'
|
9
|
+
require 'cequel/record/bulk_writes'
|
9
10
|
require 'cequel/record/record_set'
|
10
11
|
require 'cequel/record/bound'
|
11
12
|
require 'cequel/record/lazy_record_collection'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Cequel
|
2
|
+
module Record
|
3
|
+
module BulkWrites
|
4
|
+
def update_all(attributes)
|
5
|
+
each_data_set { |data_set| data_set.update(attributes) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def delete_all
|
9
|
+
each_data_set { |data_set| data_set.delete }
|
10
|
+
end
|
11
|
+
|
12
|
+
def destroy_all
|
13
|
+
each { |record| record.destroy }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def each_data_set
|
19
|
+
key_attributes_for_each_row.each_slice(100) do |batch|
|
20
|
+
connection.batch(:unlogged => true) do
|
21
|
+
batch.each { |key_attributes| yield table.where(key_attributes) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Cequel
|
2
|
+
module Record
|
3
|
+
class ConfigurationGenerator < Rails::Generators::Base
|
4
|
+
namespace 'cequel:configuration'
|
5
|
+
source_root File.expand_path('../../../../templates/', __FILE__)
|
6
|
+
|
7
|
+
def create_configuration
|
8
|
+
template "config/cequel.yml"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -4,6 +4,11 @@ module Cequel
|
|
4
4
|
|
5
5
|
class LazyRecordCollection < DelegateClass(Array)
|
6
6
|
|
7
|
+
extend Forwardable
|
8
|
+
include BulkWrites
|
9
|
+
|
10
|
+
def_delegators :record_set, :table, :connection
|
11
|
+
|
7
12
|
def initialize(record_set)
|
8
13
|
raise ArgumentError if record_set.nil?
|
9
14
|
|
@@ -37,6 +42,10 @@ module Cequel
|
|
37
42
|
private
|
38
43
|
attr_reader :record_set
|
39
44
|
|
45
|
+
def key_attributes_for_each_row
|
46
|
+
map { |record| record.key_attributes }
|
47
|
+
end
|
48
|
+
|
40
49
|
end
|
41
50
|
|
42
51
|
end
|
@@ -16,13 +16,17 @@ module Cequel
|
|
16
16
|
new(attributes, &block).tap { |record| record.save }
|
17
17
|
end
|
18
18
|
|
19
|
+
def table
|
20
|
+
connection[table_name]
|
21
|
+
end
|
22
|
+
|
19
23
|
def hydrate(row)
|
20
24
|
new_empty(row).__send__(:hydrated!)
|
21
25
|
end
|
22
26
|
|
23
27
|
end
|
24
28
|
|
25
|
-
|
29
|
+
def_delegators 'self.class', :connection, :table
|
26
30
|
|
27
31
|
def key_attributes
|
28
32
|
@attributes.slice(*self.class.key_column_names)
|
@@ -182,7 +186,7 @@ module Cequel
|
|
182
186
|
end
|
183
187
|
|
184
188
|
def metal_scope
|
185
|
-
|
189
|
+
table.where(key_attributes)
|
186
190
|
end
|
187
191
|
|
188
192
|
def attributes_for_create
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Record
|
4
|
-
|
5
3
|
class Railtie < Rails::Railtie
|
6
|
-
|
7
4
|
config.cequel = Record
|
8
5
|
|
6
|
+
def self.app_name
|
7
|
+
Rails.application.railtie_name.sub(/_application$/, '')
|
8
|
+
end
|
9
|
+
|
9
10
|
initializer "cequel.configure_rails" do
|
10
|
-
app_name = Rails.application.railtie_name.sub(/_application$/, '')
|
11
11
|
config_path = Rails.root.join('config/cequel.yml').to_s
|
12
12
|
|
13
13
|
if File.exist?(config_path)
|
@@ -16,25 +16,20 @@ module Cequel
|
|
16
16
|
else
|
17
17
|
config = {host: '127.0.0.1:9160'}
|
18
18
|
end
|
19
|
-
config.reverse_merge!(keyspace: "#{app_name}_#{Rails.env}")
|
19
|
+
config.reverse_merge!(keyspace: "#{Railtie.app_name}_#{Rails.env}")
|
20
20
|
connection = Cequel.connect(config)
|
21
21
|
|
22
|
-
begin
|
23
|
-
connection = Cequel.connect(config)
|
24
|
-
rescue CassandraCQL::Error::InvalidRequestException
|
25
|
-
connection = Cequel.connect(config.except(:keyspace))
|
26
|
-
#XXX This should be read from the configuration
|
27
|
-
connection.execute(<<-CQL)
|
28
|
-
CREATE KEYSPACE #{keyspace}
|
29
|
-
WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}
|
30
|
-
CQL
|
31
|
-
retry
|
32
|
-
end
|
33
22
|
connection.logger = Rails.logger
|
34
23
|
Record.connection = connection
|
35
24
|
end
|
36
|
-
end
|
37
25
|
|
38
|
-
|
26
|
+
rake_tasks do
|
27
|
+
require "cequel/record/tasks"
|
28
|
+
end
|
39
29
|
|
30
|
+
generators do
|
31
|
+
require 'cequel/record/configuration_generator.rb'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
40
35
|
end
|
@@ -7,6 +7,7 @@ module Cequel
|
|
7
7
|
extend Forwardable
|
8
8
|
extend Cequel::Util::HashAccessors
|
9
9
|
include Enumerable
|
10
|
+
include BulkWrites
|
10
11
|
|
11
12
|
def self.default_attributes
|
12
13
|
{:scoped_key_values => [], :select_columns => []}
|
@@ -134,9 +135,16 @@ module Cequel
|
|
134
135
|
find_each_row(options) { |row| yield target_class.hydrate(row) }
|
135
136
|
end
|
136
137
|
|
138
|
+
def find_in_batches(options = {})
|
139
|
+
return enum_for(:find_in_batches, options) unless block_given?
|
140
|
+
find_rows_in_batches(options) do |rows|
|
141
|
+
yield rows.map { |row| target_class.hydrate(row) }
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
137
145
|
def find_each_row(options = {}, &block)
|
138
146
|
return enum_for(:find_each_row, options) unless block
|
139
|
-
find_rows_in_batches(options) { |
|
147
|
+
find_rows_in_batches(options) { |rows| rows.each(&block) }
|
140
148
|
end
|
141
149
|
|
142
150
|
def find_rows_in_batches(options = {}, &block)
|
@@ -333,6 +341,13 @@ module Cequel
|
|
333
341
|
RecordSet.new(target_class, attributes_copy)
|
334
342
|
end
|
335
343
|
|
344
|
+
def key_attributes_for_each_row
|
345
|
+
return enum_for(:key_attributes_for_each_row) unless block_given?
|
346
|
+
select(*key_column_names).find_each do |record|
|
347
|
+
yield record.key_attributes
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
336
351
|
end
|
337
352
|
|
338
353
|
end
|
data/lib/cequel/record/scoped.rb
CHANGED
@@ -11,7 +11,9 @@ module Cequel
|
|
11
11
|
extend Forwardable
|
12
12
|
|
13
13
|
def_delegators :current_scope,
|
14
|
-
*(RecordSet.public_instance_methods(false)
|
14
|
+
*(RecordSet.public_instance_methods(false) +
|
15
|
+
BulkWrites.public_instance_methods -
|
16
|
+
Object.instance_methods)
|
15
17
|
|
16
18
|
def current_scope
|
17
19
|
delegating_scope || RecordSet.new(self)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
namespace :cequel do
|
2
|
+
namespace :keyspace do
|
3
|
+
desc 'Initialize Cassandra keyspace'
|
4
|
+
task :create => :environment do
|
5
|
+
Cequel::Record.connection.schema.create!
|
6
|
+
puts "Created keyspace #{Cequel::Record.connection.name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
desc 'Drop Cassandra keyspace'
|
10
|
+
task :drop => :environment do
|
11
|
+
Cequel::Record.connection.schema.drop!
|
12
|
+
puts "Dropped keyspace #{Cequel::Record.connection.name}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Synchronize all models defined in `app/models' with Cassandra database schema"
|
17
|
+
task :migrate => :environment do
|
18
|
+
watch_stack = ActiveSupport::Dependencies::WatchStack.new
|
19
|
+
|
20
|
+
Dir.glob(Rails.root.join('app', 'models', '**', '*.rb')).each do |file|
|
21
|
+
watch_stack.watch_namespaces([Object])
|
22
|
+
|
23
|
+
require_dependency(file)
|
24
|
+
|
25
|
+
watch_stack.new_constants.each do |class_name|
|
26
|
+
clazz = class_name.constantize
|
27
|
+
if clazz.ancestors.include?(Cequel::Record)
|
28
|
+
clazz.synchronize_schema
|
29
|
+
puts "Synchronized schema for #{class_name}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Create keyspace and tables for all defined models"
|
36
|
+
task :init => %w(keyspace:create migrate)
|
37
|
+
end
|
@@ -8,6 +8,27 @@ module Cequel
|
|
8
8
|
@keyspace = keyspace
|
9
9
|
end
|
10
10
|
|
11
|
+
def create!(options = {})
|
12
|
+
bare_connection =
|
13
|
+
Metal::Keyspace.new(keyspace.configuration.except(:keyspace))
|
14
|
+
|
15
|
+
options = options.symbolize_keys
|
16
|
+
options[:class] ||= 'SimpleStrategy'
|
17
|
+
options[:replication_factor] ||= 1 if options[:class] == 'SimpleStrategy'
|
18
|
+
options_strs = options.map do |name, value|
|
19
|
+
"'#{name}': #{CassandraCQL::Statement.quote(value)}"
|
20
|
+
end
|
21
|
+
|
22
|
+
bare_connection.execute(<<-CQL)
|
23
|
+
CREATE KEYSPACE #{keyspace.name}
|
24
|
+
WITH REPLICATION = {#{options_strs.join(', ')}}
|
25
|
+
CQL
|
26
|
+
end
|
27
|
+
|
28
|
+
def drop!
|
29
|
+
keyspace.execute("DROP KEYSPACE #{keyspace.name}")
|
30
|
+
end
|
31
|
+
|
11
32
|
def read_table(name)
|
12
33
|
TableReader.read(keyspace, name)
|
13
34
|
end
|
data/lib/cequel/version.rb
CHANGED
@@ -102,7 +102,7 @@ describe Cequel::Record::RecordSet do
|
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
105
|
-
let(:posts) { [cassandra_posts, postgres_posts] }
|
105
|
+
let(:posts) { [*cassandra_posts, *postgres_posts] }
|
106
106
|
|
107
107
|
let(:comments) do
|
108
108
|
5.times.map do |i|
|
@@ -592,4 +592,70 @@ describe Cequel::Record::RecordSet do
|
|
592
592
|
end
|
593
593
|
end
|
594
594
|
|
595
|
+
describe '#update_all' do
|
596
|
+
let(:records) { posts }
|
597
|
+
|
598
|
+
it 'should be able to update with no scoping' do
|
599
|
+
Post.update_all(title: 'Same Title')
|
600
|
+
Post.all.map(&:title).should == Array.new(posts.length) { 'Same Title' }
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'should update posts with scoping' do
|
604
|
+
Post['cassandra'].update_all(title: 'Same Title')
|
605
|
+
Post['cassandra'].map(&:title).
|
606
|
+
should == Array.new(cassandra_posts.length) { 'Same Title' }
|
607
|
+
Post['postgres'].map(&:title).should == postgres_posts.map(&:title)
|
608
|
+
end
|
609
|
+
|
610
|
+
it 'should update fully specified collection' do
|
611
|
+
Post['cassandra']['cequel0', 'cequel1', 'cequel2'].
|
612
|
+
update_all(title: 'Same Title')
|
613
|
+
Post['cassandra']['cequel0', 'cequel1', 'cequel2'].map(&:title).
|
614
|
+
should == Array.new(3) { 'Same Title' }
|
615
|
+
Post['cassandra']['cequel3', 'cequel4'].map(&:title).
|
616
|
+
should == cassandra_posts.drop(3).map(&:title)
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
describe '#delete_all' do
|
621
|
+
let(:records) { posts }
|
622
|
+
|
623
|
+
it 'should be able to delete with no scoping' do
|
624
|
+
Post.delete_all
|
625
|
+
Post.count.should be_zero
|
626
|
+
end
|
627
|
+
|
628
|
+
it 'should be able to delete with scoping' do
|
629
|
+
Post['postgres'].delete_all
|
630
|
+
Post['postgres'].count.should be_zero
|
631
|
+
Post['cassandra'].count.should == cassandra_posts.length
|
632
|
+
end
|
633
|
+
|
634
|
+
it 'should be able to delete fully specified collection' do
|
635
|
+
Post['postgres']['sequel0', 'sequel1'].delete_all
|
636
|
+
Post['postgres'].map(&:permalink).
|
637
|
+
should == postgres_posts.drop(2).map(&:permalink)
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
describe '#destroy_all' do
|
642
|
+
let(:records) { posts }
|
643
|
+
|
644
|
+
it 'should be able to delete with no scoping' do
|
645
|
+
Post.destroy_all
|
646
|
+
Post.count.should be_zero
|
647
|
+
end
|
648
|
+
|
649
|
+
it 'should be able to delete with scoping' do
|
650
|
+
Post['postgres'].destroy_all
|
651
|
+
Post['postgres'].count.should be_zero
|
652
|
+
Post['cassandra'].count.should == cassandra_posts.length
|
653
|
+
end
|
654
|
+
|
655
|
+
it 'should be able to delete fully specified collection' do
|
656
|
+
Post['postgres']['sequel0', 'sequel1'].destroy_all
|
657
|
+
Post['postgres'].map(&:permalink).
|
658
|
+
should == postgres_posts.drop(2).map(&:permalink)
|
659
|
+
end
|
660
|
+
end
|
595
661
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mat Brown
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2013-11-
|
15
|
+
date: 2013-11-29 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activesupport
|
@@ -155,8 +155,10 @@ files:
|
|
155
155
|
- lib/cequel/record/associations.rb
|
156
156
|
- lib/cequel/record/belongs_to_association.rb
|
157
157
|
- lib/cequel/record/bound.rb
|
158
|
+
- lib/cequel/record/bulk_writes.rb
|
158
159
|
- lib/cequel/record/callbacks.rb
|
159
160
|
- lib/cequel/record/collection.rb
|
161
|
+
- lib/cequel/record/configuration_generator.rb
|
160
162
|
- lib/cequel/record/dirty.rb
|
161
163
|
- lib/cequel/record/errors.rb
|
162
164
|
- lib/cequel/record/has_many_association.rb
|
@@ -169,6 +171,7 @@ files:
|
|
169
171
|
- lib/cequel/record/schema.rb
|
170
172
|
- lib/cequel/record/scoped.rb
|
171
173
|
- lib/cequel/record/secondary_indexes.rb
|
174
|
+
- lib/cequel/record/tasks.rb
|
172
175
|
- lib/cequel/record/validations.rb
|
173
176
|
- lib/cequel/record.rb
|
174
177
|
- lib/cequel/schema/column.rb
|
@@ -235,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
235
238
|
requirements:
|
236
239
|
- Cassandra 1.0+
|
237
240
|
rubyforge_project:
|
238
|
-
rubygems_version: 2.
|
241
|
+
rubygems_version: 2.1.11
|
239
242
|
signing_key:
|
240
243
|
specification_version: 4
|
241
244
|
summary: Full-featured, ActiveModel-compliant ORM for Cassandra using CQL3
|