cequel 1.0.0.pre.6 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 720dcb8eb0924dd3f972efba9e93c2d96ae046ab
4
- data.tar.gz: 36b08070aa03e28e6f0748e1c6a375e2ca57822f
3
+ metadata.gz: 6d15e60b9c3a9725632fe70d140449a03dc1e033
4
+ data.tar.gz: cc8dd115c0639388ee13b77437b3a7ae6addb2ef
5
5
  SHA512:
6
- metadata.gz: c51e2ab0b17869ca3f6720e1c74b78c6f116ac705cce8aac49925750e4204012a7701ea1ed6ce4b51702a9f0a88ed2aad3d39609e5b57080ca9d2be1b0913fa6
7
- data.tar.gz: 1dae89c0e31809ee91f54a137ca22396d8c591b5ebdf85efe9091ef53143674a71d9873b3b84b562c2deeb5eacad8ffd8dfe25d4fcb4706489656c5307e7ec81
6
+ metadata.gz: 7b710ee3238b02cc47f0b57a9804ee4f729c1cd807e809b7134888f44b066f59330714aaf7460bafae1d702d2eba47f7dff3ac3b8e2f18b30f03bb3e259498c0
7
+ data.tar.gz: 870947477a6da3d0f72aae1d6f4154293c41da41b1bf7419c4906737ed86d70ebc9598901a2eec2a121a0412955b7c00e4b5cd03df3dd9228597653b64347bd9
@@ -20,7 +20,8 @@ module Cequel
20
20
  #
21
21
  def initialize(keyspace, options = {})
22
22
  @keyspace = keyspace
23
- @auto_apply = options[:auto_apply]
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("BEGIN BATCH\n")
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[:host] || configuration[:hosts]
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
- return yield if get_batch
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
- def_delegator 'self.class', :connection
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
- connection[table_name].where(key_attributes)
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
- end
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) { |row| row.each(&block) }
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
@@ -11,7 +11,9 @@ module Cequel
11
11
  extend Forwardable
12
12
 
13
13
  def_delegators :current_scope,
14
- *(RecordSet.public_instance_methods(false) - Object.instance_methods)
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
@@ -1,3 +1,3 @@
1
1
  module Cequel
2
- VERSION = '1.0.0.pre.6'
2
+ VERSION = '1.0.0.rc1'
3
3
  end
@@ -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.pre.6
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-09 00:00:00.000000000 Z
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.0.3
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