cequel 1.7.0 → 1.8.0

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