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