cequel 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -187,23 +187,13 @@ module Cequel
187
187
  execute_with_consistency(statement, bind_vars, default_consistency)
188
188
  end
189
189
 
190
- #
191
- # Execute a CQL query in this keyspace with the given consistency
192
- #
193
- # @param statement [String] CQL string
194
- # @param bind_vars [Array] array of values for bind variables
195
- # @param consistency [Symbol] consistency at which to execute query
196
- # @return [Enumerable] the results of the query
197
- #
198
- # @since 1.1.0
199
- #
200
- def execute_with_consistency(statement, bind_vars, consistency)
201
- retries = max_retries
190
+ def execute_with_options(statement, bind_vars, options={})
191
+ options[:consistency] ||= default_consistency
202
192
 
193
+ retries = max_retries
203
194
  log('CQL', statement, *bind_vars) do
204
195
  begin
205
- client.execute(sanitize(statement, bind_vars),
206
- consistency: consistency || default_consistency)
196
+ client.execute(sanitize(statement, bind_vars), options)
207
197
  rescue Cassandra::Errors::NoHostsAvailable,
208
198
  Ione::Io::ConnectionError => e
209
199
  clear_active_connections!
@@ -213,6 +203,20 @@ module Cequel
213
203
  retry
214
204
  end
215
205
  end
206
+
207
+ end
208
+ #
209
+ # Execute a CQL query in this keyspace with the given consistency
210
+ #
211
+ # @param statement [String] CQL string
212
+ # @param bind_vars [Array] array of values for bind variables
213
+ # @param consistency [Symbol] consistency at which to execute query
214
+ # @return [Enumerable] the results of the query
215
+ #
216
+ # @since 1.1.0
217
+ #
218
+ def execute_with_consistency(statement, bind_vars, consistency)
219
+ execute_with_options(statement, bind_vars, {consistency: consistency || default_consistency})
216
220
  end
217
221
 
218
222
  #
@@ -51,9 +51,16 @@ module Cequel
51
51
  private
52
52
 
53
53
  def format_for_log(label, timing, statement, bind_vars)
54
+ bind_vars = bind_vars.map{|it| String === it ? limit_length(it) : it }
54
55
  format('%s (%s) %s', label, timing, sanitize(statement, bind_vars))
55
56
  end
56
57
 
58
+ def limit_length(str)
59
+ return str if str.length < 100
60
+
61
+ str[0..25] + "..." + str[-25..-1]
62
+ end
63
+
57
64
  def_delegator 'Cequel::Metal::Keyspace', :sanitize
58
65
  private :sanitize
59
66
  end
@@ -40,6 +40,8 @@ module Cequel
40
40
  add_bounds
41
41
  add_order
42
42
  set_consistency
43
+ set_page_size
44
+ set_paging_state
43
45
  data_set
44
46
  end
45
47
 
@@ -51,7 +53,7 @@ module Cequel
51
53
  :scoped_key_names, :scoped_key_values,
52
54
  :scoped_indexed_column, :lower_bound,
53
55
  :upper_bound, :reversed?, :order_by_column,
54
- :query_consistency, :ascends_by?
56
+ :query_consistency, :query_page_size, :query_paging_state, :ascends_by?
55
57
 
56
58
  private
57
59
 
@@ -97,6 +99,18 @@ module Cequel
97
99
  end
98
100
  end
99
101
 
102
+ def set_page_size
103
+ if query_page_size
104
+ self.data_set = data_set.page_size(query_page_size)
105
+ end
106
+ end
107
+
108
+ def set_paging_state
109
+ if query_paging_state
110
+ self.data_set = data_set.paging_state(query_paging_state)
111
+ end
112
+ end
113
+
100
114
  def sort_direction
101
115
  ascends_by?(order_by_column) ? :asc : :desc
102
116
  end
@@ -343,6 +343,8 @@ module Cequel
343
343
  unless self.class.reflect_on_column(name)
344
344
  fail UnknownAttributeError, "unknown attribute: #{name}"
345
345
  end
346
+
347
+ send("#{name}_will_change!") unless value === read_attribute(name)
346
348
  @attributes[name] = value
347
349
  end
348
350
 
@@ -464,6 +464,30 @@ module Cequel
464
464
  scoped(query_consistency: consistency)
465
465
  end
466
466
 
467
+ #
468
+ # Set the page_size at which to read records into the record set.
469
+ #
470
+ # @param page_size [Integer] page_size for reads
471
+ # @return [RecordSet] record set tuned to given page_size
472
+ #
473
+ def page_size(page_size)
474
+ scoped(query_page_size: page_size)
475
+ end
476
+
477
+ #
478
+ # Set the paging_state at which to read records into the record set.
479
+ #
480
+ # @param paging_state [String] paging_state for reads
481
+ # @return [RecordSet] record set tuned to given paging_state
482
+ #
483
+ def paging_state(paging_state)
484
+ scoped(query_paging_state: paging_state)
485
+ end
486
+
487
+ def next_paging_state
488
+ data_set.next_paging_state
489
+ end
490
+
467
491
  #
468
492
  # @overload first
469
493
  # @return [Record] the first record in this record set
@@ -667,9 +691,11 @@ module Cequel
667
691
  attr_reader :attributes
668
692
  hattr_reader :attributes, :select_columns, :scoped_key_values,
669
693
  :row_limit, :lower_bound, :upper_bound,
670
- :scoped_indexed_column, :query_consistency
694
+ :scoped_indexed_column, :query_consistency,
695
+ :query_page_size, :query_paging_state
671
696
  protected :select_columns, :scoped_key_values, :row_limit, :lower_bound,
672
- :upper_bound, :scoped_indexed_column, :query_consistency
697
+ :upper_bound, :scoped_indexed_column, :query_consistency,
698
+ :query_page_size, :query_paging_state
673
699
  hattr_inquirer :attributes, :reversed
674
700
  protected :reversed?
675
701
 
@@ -75,7 +75,7 @@ module Cequel
75
75
  def read_partition_keys
76
76
  validators = table_data['key_validator']
77
77
  types = parse_composite_types(validators) || [validators]
78
- columns = partition_columns.sort { |c| c['component_index'] }
78
+ columns = partition_columns.sort_by { |c| c['component_index'] }
79
79
  .map { |c| c['column_name'] }
80
80
 
81
81
  columns.zip(types) do |name, type|
@@ -1,5 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Cequel
3
3
  # The current version of the library
4
- VERSION = '1.7.0'
4
+ VERSION = '1.8.0'
5
5
  end
@@ -654,6 +654,30 @@ describe Cequel::Metal::DataSet do
654
654
  end
655
655
  end
656
656
 
657
+ describe '#page_size' do
658
+ let(:data_set) { cequel[:posts].page_size(1) }
659
+
660
+ it 'should issue SELECT with scoped page size' do
661
+ expect_query_with_options(/SELECT/, :page_size => 1) { data_set.to_a }
662
+ end
663
+
664
+ it 'should issue COUNT with scoped page size' do
665
+ expect_query_with_options(/SELECT.*COUNT/, :page_size => 1) { data_set.count }
666
+ end
667
+ end
668
+
669
+ describe '#paging_state' do
670
+ let(:data_set) { cequel[:posts].paging_state(nil) }
671
+
672
+ it 'should issue SELECT with scoped paging state' do
673
+ expect_query_with_options(/SELECT/, :paging_state => nil) { data_set.to_a }
674
+ end
675
+
676
+ it 'should issue COUNT with scoped paging state' do
677
+ expect_query_with_options(/SELECT.*COUNT/, :paging_state => nil) { data_set.count }
678
+ end
679
+ end
680
+
657
681
  describe 'result enumeration' do
658
682
  let(:row) { row_keys.merge(:title => 'Big Data') }
659
683
 
@@ -59,12 +59,4 @@ describe Cequel::Record::Dirty do
59
59
  end
60
60
  end
61
61
 
62
- context 'unloaded model' do
63
- let(:post) { Post['cequel'] }
64
-
65
- it 'should not track changes' do
66
- post.title = 'Cequel'
67
- expect(post.changes).to be_empty
68
- end
69
- end
70
62
  end
@@ -128,8 +128,8 @@ describe Cequel::Record::Persistence do
128
128
  it 'should save with specified TTL' do
129
129
  blog.name = 'Cequel 1.4'
130
130
  blog.save(ttl: 10)
131
- expect(cequel[Blog.table_name].select_ttl(:name).first.ttl(:name)).
132
- to be_within(0.1).of(9.9)
131
+ expect(cequel[Blog.table_name].select_ttl(:name).first.ttl(:name))
132
+ .to be_between(9,10).inclusive
133
133
  end
134
134
 
135
135
  it 'should save with specified timestamp' do
@@ -53,6 +53,11 @@ describe Cequel::Record::Properties do
53
53
  title).to eq('Big Data')
54
54
  end
55
55
 
56
+ it 'should mark the attribute as dirty when setting attributes' do
57
+ expect(Post.new { |post| post.title = 'Big Data' }.changed).to eq(['title'])
58
+ expect(Post.new { |post| post.title = 'Big Data' }.changes).to eq({'title' => [nil,'Big Data']})
59
+ end
60
+
56
61
  it 'should get attributes with indifferent access' do
57
62
  post = Post.new.tap { |post| post.attributes = {:downcased_title => 'big data' }}
58
63
  expect(post.attributes[:title]).to eq 'Big Data'
@@ -811,6 +811,41 @@ describe Cequel::Record::RecordSet do
811
811
  end
812
812
  end
813
813
 
814
+ describe '#page_size' do
815
+ it 'should return the number of records specified by page_size' do
816
+ expect(Post.page_size(2).to_a.length).to be(2)
817
+ end
818
+ end
819
+
820
+ describe '#next_paging_state' do
821
+ it 'should return the paging state of the result' do
822
+ expect(Post.page_size(1).next_paging_state).not_to eq(nil)
823
+ end
824
+ end
825
+
826
+ describe '#paging_state' do
827
+ let(:page_one) { Post.page_size(1) }
828
+ let(:page_two) { Post.page_size(1).paging_state(page_one.next_paging_state) }
829
+ let(:page_size) { 3 }
830
+
831
+ it 'should page through all records' do
832
+ all_pages = []
833
+ next_paging_state = nil
834
+
835
+ loop do
836
+ a_page = Post.page_size(page_size).paging_state(next_paging_state)
837
+ next_paging_state = a_page.next_paging_state
838
+
839
+ # There may be less than page size records on final page
840
+ expect(a_page.to_a.length).to be <= page_size
841
+ all_pages.concat(a_page.to_a)
842
+ break if next_paging_state.nil?
843
+ end
844
+
845
+ expect(all_pages).to eq(posts.to_a)
846
+ end
847
+ end
848
+
814
849
  describe '#count' do
815
850
  let(:records) { blogs }
816
851
 
@@ -73,6 +73,48 @@ describe Cequel::Record::Schema do
73
73
  end
74
74
  end
75
75
 
76
+ context 'CQL3 table with non-dictionary-ordered partition columns' do
77
+ let(:table_name) { 'accesses_' + SecureRandom.hex(4) }
78
+
79
+ let(:model) do
80
+ model_table_name = table_name
81
+ Class.new do
82
+ include Cequel::Record
83
+ self.table_name = model_table_name
84
+
85
+ key :serial, :text, partition: true
86
+ key :username, :text, partition: true
87
+ key :date, :text, partition: true
88
+ key :access_time, :timeuuid
89
+ column :url, :text
90
+ end
91
+ end
92
+
93
+ let(:model_modified) do
94
+ model_table_name = table_name
95
+ Class.new do
96
+ include Cequel::Record
97
+ self.table_name = model_table_name
98
+
99
+ key :serial, :text, partition: true
100
+ key :username, :text, partition: true
101
+ key :date, :text, partition: true
102
+ key :access_time, :timeuuid
103
+ column :url, :text
104
+ column :user_agent, :text
105
+ end
106
+ end
107
+
108
+ before { model.synchronize_schema }
109
+ after { cequel.schema.drop_table(table_name) }
110
+
111
+ it 'should be able to synchronize schema again' do
112
+ expect {
113
+ model_modified.synchronize_schema
114
+ }.not_to raise_error
115
+ end
116
+ end
117
+
76
118
  context 'wide-row legacy table' do
77
119
  let(:table_name) { 'legacy_posts_' + SecureRandom.hex(4) }
78
120
 
@@ -2,16 +2,17 @@
2
2
  require File.expand_path('../../spec_helper', __FILE__)
3
3
 
4
4
  describe Cequel::Schema::TableReader do
5
+ let(:table_name) { :"posts_#{SecureRandom.hex(4)}" }
5
6
 
6
7
  after do
7
- cequel.schema.drop_table(:posts)
8
+ cequel.schema.drop_table(table_name)
8
9
  end
9
10
 
10
- let(:table) { cequel.schema.read_table(:posts) }
11
+ let(:table) { cequel.schema.read_table(table_name) }
11
12
 
12
13
  describe 'reading simple key' do
13
14
  before do
14
- cequel.execute("CREATE TABLE posts (permalink text PRIMARY KEY)")
15
+ cequel.execute("CREATE TABLE #{table_name} (permalink text PRIMARY KEY)")
15
16
  end
16
17
 
17
18
  it 'should read name correctly' do
@@ -30,7 +31,7 @@ describe Cequel::Schema::TableReader do
30
31
  describe 'reading single non-partition key' do
31
32
  before do
32
33
  cequel.execute <<-CQL
33
- CREATE TABLE posts (
34
+ CREATE TABLE #{table_name} (
34
35
  blog_subdomain text,
35
36
  permalink ascii,
36
37
  PRIMARY KEY (blog_subdomain, permalink)
@@ -63,7 +64,7 @@ describe Cequel::Schema::TableReader do
63
64
  describe 'reading reverse-ordered non-partition key' do
64
65
  before do
65
66
  cequel.execute <<-CQL
66
- CREATE TABLE posts (
67
+ CREATE TABLE #{table_name} (
67
68
  blog_subdomain text,
68
69
  permalink ascii,
69
70
  PRIMARY KEY (blog_subdomain, permalink)
@@ -89,7 +90,7 @@ describe Cequel::Schema::TableReader do
89
90
  describe 'reading compound non-partition key' do
90
91
  before do
91
92
  cequel.execute <<-CQL
92
- CREATE TABLE posts (
93
+ CREATE TABLE #{table_name} (
93
94
  blog_subdomain text,
94
95
  permalink ascii,
95
96
  author_id uuid,
@@ -116,7 +117,7 @@ describe Cequel::Schema::TableReader do
116
117
  describe 'reading compound partition key' do
117
118
  before do
118
119
  cequel.execute <<-CQL
119
- CREATE TABLE posts (
120
+ CREATE TABLE #{table_name} (
120
121
  blog_subdomain text,
121
122
  permalink ascii,
122
123
  PRIMARY KEY ((blog_subdomain, permalink))
@@ -142,7 +143,7 @@ describe Cequel::Schema::TableReader do
142
143
  describe 'reading compound partition and non-partition keys' do
143
144
  before do
144
145
  cequel.execute <<-CQL
145
- CREATE TABLE posts (
146
+ CREATE TABLE #{table_name} (
146
147
  blog_subdomain text,
147
148
  permalink ascii,
148
149
  author_id uuid,
@@ -183,7 +184,7 @@ describe Cequel::Schema::TableReader do
183
184
 
184
185
  before do
185
186
  cequel.execute <<-CQL
186
- CREATE TABLE posts (
187
+ CREATE TABLE #{table_name} (
187
188
  blog_subdomain text,
188
189
  permalink ascii,
189
190
  title text,
@@ -194,7 +195,7 @@ describe Cequel::Schema::TableReader do
194
195
  PRIMARY KEY (blog_subdomain, permalink)
195
196
  )
196
197
  CQL
197
- cequel.execute('CREATE INDEX posts_author_id_idx ON posts (author_id)')
198
+ cequel.execute("CREATE INDEX posts_author_id_idx ON #{table_name} (author_id)")
198
199
  end
199
200
 
200
201
  it 'should read types of scalar data columns' do
@@ -255,7 +256,7 @@ describe Cequel::Schema::TableReader do
255
256
 
256
257
  before do
257
258
  cequel.execute <<-CQL
258
- CREATE TABLE posts (permalink text PRIMARY KEY)
259
+ CREATE TABLE #{table_name} (permalink text PRIMARY KEY)
259
260
  WITH bloom_filter_fp_chance = 0.02
260
261
  AND comment = 'Posts table'
261
262
  AND compaction = {
@@ -315,7 +316,7 @@ describe Cequel::Schema::TableReader do
315
316
  describe 'skinny-row compact storage' do
316
317
  before do
317
318
  cequel.execute <<-CQL
318
- CREATE TABLE posts (permalink text PRIMARY KEY, title text, body text)
319
+ CREATE TABLE #{table_name} (permalink text PRIMARY KEY, title text, body text)
319
320
  WITH COMPACT STORAGE
320
321
  CQL
321
322
  end
@@ -333,7 +334,7 @@ describe Cequel::Schema::TableReader do
333
334
  describe 'wide-row compact storage' do
334
335
  before do
335
336
  cequel.execute <<-CQL
336
- CREATE TABLE posts (
337
+ CREATE TABLE #{table_name} (
337
338
  blog_subdomain text,
338
339
  id uuid,
339
340
  data text,
@@ -356,7 +357,7 @@ describe Cequel::Schema::TableReader do
356
357
  describe 'skinny-row legacy table', thrift: true do
357
358
  before do
358
359
  legacy_connection.execute <<-CQL
359
- CREATE TABLE posts (permalink text PRIMARY KEY, title text, body text)
360
+ CREATE TABLE #{table_name} (permalink text PRIMARY KEY, title text, body text)
360
361
  CQL
361
362
  end
362
363
  subject { table }
@@ -375,7 +376,7 @@ describe Cequel::Schema::TableReader do
375
376
  describe 'wide-row legacy table', thrift: true do
376
377
  before do
377
378
  legacy_connection.execute(<<-CQL2)
378
- CREATE COLUMNFAMILY posts (blog_subdomain text PRIMARY KEY)
379
+ CREATE COLUMNFAMILY #{table_name} (blog_subdomain text PRIMARY KEY)
379
380
  WITH comparator=uuid AND default_validation=text
380
381
  CQL2
381
382
  end