extendi-cassandra_object 1.0.17 → 1.1.1

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
  SHA256:
3
- metadata.gz: 2eb80f8a2bc136c5333d17e73df717ed4d2d33f8e28fa31ff30fdf7c8648e645
4
- data.tar.gz: 57c5cb2c0939b8eca99790ac0b7f80a643e04617f31762138aea076a6e868bea
3
+ metadata.gz: c5523ad43210b62402fbfb637042415f14a7fe174fa3f3646556b1b1e3701df9
4
+ data.tar.gz: a9ed0b2512e264395a1340c2fbccbaf9259d893105c738c9724d2ee0eba2a944
5
5
  SHA512:
6
- metadata.gz: ccdf0d3c0599fd1373d9dc214f5ebd3409acd43b80f27ace5951d5d31fad059bee0f8a78f719b2075fbe472e2639c1d4a7f510ad4570b49fa0e3db6f3fa2be34
7
- data.tar.gz: c61f6366852d7bbff248c80dee0ae620c0b4b087ae4905b45982e255563f8c560fbfbee8d217cbf23292b9ef0c2395a392cb9470f154bb7da17820e32a703c23
6
+ metadata.gz: f038a28315870cef3b0bc1366e3baa46d4098b7a7c3a4733ec02a2a71281861f87a111e9dfbf1d5c9a0c167b8e1e72198662dded4c7a7b90acbd154b5131e305
7
+ data.tar.gz: 58ab0a6d6347d97533334ee017ef8d7f9aaf9890e770d1b2abf3629f7e1b80f0a372241985c7c38e47dc4cebd8a2efa100a5c98384ff6ca17dea6927f976ea76
data/.gitignore CHANGED
@@ -4,3 +4,7 @@ Gemfile.lock
4
4
  .byebug_history
5
5
  .ruby-version
6
6
  TAGS
7
+ .editorconfig
8
+ .rubocop.yml
9
+ .prettierrc.js
10
+ .eslintrc.js
@@ -1,20 +1,21 @@
1
+ dist: trusty
2
+
1
3
  language: ruby
2
4
  rvm:
3
- - 2.4.0
4
- - 2.4.1
5
- - 2.4.2
6
5
  - 2.5.1
6
+ - 2.6.5
7
+ - 2.7.0
7
8
  env:
8
- - CASSANDRA_VERSION=2.1.2
9
+ - SCYLLA_VERSION=3.0.2
9
10
  - CASSANDRA_VERSION=3.0.10
10
11
  - CASSANDRA_VERSION=3.9
11
- - CASSANDRA_VERSION=2.1.2 ACTIVEMODEL_VERSION='< 5'
12
- - CASSANDRA_VERSION=3.0.10 ACTIVEMODEL_VERSION='< 5'
13
- - CASSANDRA_VERSION=3.9 ACTIVEMODEL_VERSION='< 5'
14
12
 
15
13
  jdk:
16
14
  - oraclejdk8
17
15
 
16
+ services:
17
+ - docker
18
+
18
19
  before_install:
19
20
  - sudo apt-get install libjna-java
20
21
  - sudo apt-get install python-support
@@ -23,7 +24,18 @@ before_install:
23
24
  - sudo pip install ccm
24
25
 
25
26
  install:
26
- - ccm create -n 1 -v $CASSANDRA_VERSION -i 127.0.0. -s -b test-cluster
27
- - ccm start
28
- - if [ -n "$ACTIVEMODEL_VERSION" ];then bundle add activemodel --version "$ACTIVEMODEL_VERSION"; fi
29
- - bundle install
27
+ - |
28
+ if [ -n "$CASSANDRA_VERSION" ];then
29
+ ccm create -n 1 -v $CASSANDRA_VERSION -i 127.0.0. -s -b test-cluster;
30
+ ccm start;
31
+ fi
32
+ if [ -n "$SCYLLA_VERSION" ];then
33
+ SCYLLA_IMAGE=scylladb/scylla:$SCYLLA_VERSION
34
+ docker pull $SCYLLA_IMAGE
35
+ docker run --name cassandra_test -d -p "9042:9042" -p "9160:9160" $SCYLLA_IMAGE
36
+ function check_scylla(){ docker exec -it cassandra_test nodetool status | grep UN; }
37
+ until check_scylla; do
38
+ echo "waiting..."
39
+ done
40
+ fi
41
+ bundle install
data/CHANGELOG CHANGED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0] - 2020-06-18
4
+ ### Changed
5
+ - load_balancing_policy now accept only Cassandra::LoadBalancing::Policies objects
6
+
7
+ ## [1.1.1] - 2020-06-18
8
+ ### Changed
9
+ - add write_consistency on cassandra object config
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Cassandra Object
2
2
  [![Build Status](https://secure.travis-ci.org/giovannelli/cassandra_object.png)](http://travis-ci.org/giovannelli/cassandra_object) [![Code Climate](https://codeclimate.com/github/giovannelli/cassandra_object/badges/gpa.svg)](https://codeclimate.com/github/giovannelli/cassandra_object)
3
3
 
4
- Cassandra Object uses ActiveModel to mimic much of the behavior in ActiveRecord.
4
+ Cassandra Object uses ActiveModel to mimic much of the behavior in ActiveRecord.
5
5
  Use cql3 provided by ruby-driver gem and uses the old thrift structure with the possible option at [this link](https://docs.datastax.com/en/cql/3.1/cql/cql_reference/create_table_r.html?hl=create%2Ctable):
6
6
 
7
7
  ```shell
@@ -11,22 +11,20 @@ CREATE TABLE keyspace.table (
11
11
  column1 text,
12
12
  value blob,
13
13
  PRIMARY KEY (key, column1)
14
- ) WITH
15
- COMPACT STORAGE
16
- AND CLUSTERING ORDER BY (column1 ASC)
17
- AND bloom_filter_fp_chance = 0.001
18
- AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}'
14
+ ) WITH bloom_filter_fp_chance = 0.01
15
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
19
16
  AND comment = ''
20
- AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
21
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
22
- AND dclocal_read_repair_chance = 0.0
17
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
18
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
19
+ AND crc_check_chance = 1.0
20
+ AND dclocal_read_repair_chance = 0.1
23
21
  AND default_time_to_live = 0
24
22
  AND gc_grace_seconds = 864000
25
23
  AND max_index_interval = 2048
26
24
  AND memtable_flush_period_in_ms = 0
27
25
  AND min_index_interval = 128
28
- AND read_repair_chance = 1.0
29
- AND speculative_retry = 'NONE';
26
+ AND read_repair_chance = 0.0
27
+ AND speculative_retry = '99.0PERCENTILE';
30
28
  ```
31
29
 
32
30
  You can also use the a custom schema structure with the possible options at [this link](https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cqlCreateTable.html#tabProp):
@@ -39,20 +37,20 @@ CREATE TABLE keyspace.table (
39
37
  field2 varchar,
40
38
  field3 float,
41
39
  PRIMARY KEY (key)
42
- ) WITH
43
- bloom_filter_fp_chance = 0.001
44
- AND caching = {'keys':'ALL', 'rows_per_partition':'NONE'}'
40
+ ) WITH bloom_filter_fp_chance = 0.01
41
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
45
42
  AND comment = ''
46
- AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
47
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
48
- AND dclocal_read_repair_chance = 0.0
43
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
44
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
45
+ AND crc_check_chance = 1.0
46
+ AND dclocal_read_repair_chance = 0.1
49
47
  AND default_time_to_live = 0
50
48
  AND gc_grace_seconds = 864000
51
49
  AND max_index_interval = 2048
52
50
  AND memtable_flush_period_in_ms = 0
53
51
  AND min_index_interval = 128
54
- AND read_repair_chance = 1.0
55
- AND speculative_retry = 'NONE';
52
+ AND read_repair_chance = 0.0
53
+ AND speculative_retry = '99.0PERCENTILE';
56
54
  ```
57
55
 
58
56
  ## Installation
@@ -120,16 +118,16 @@ You can define a custom configuration for the cassandra connection, allowing you
120
118
  ```ruby
121
119
  class Widget < CassandraObject::BaseSchema
122
120
  string :name
123
-
121
+
124
122
  def self.custom_config
125
- #return custom cassandra configuration
123
+ #return custom cassandra configuration
126
124
  { }
127
125
  end
128
126
  end
129
127
  ```
130
-
128
+
131
129
  ## Using with Cassandra
132
-
130
+
133
131
  Add a config/cassandra.yml:
134
132
 
135
133
  ```yaml
@@ -140,6 +138,7 @@ development:
140
138
  connect_timeout: 0.1,
141
139
  request_timeout: 0.1,
142
140
  consistency: :any/:one/:two/:three/:quorum/:all/:local_quorum/:each_quorum/:serial/:local_serial/:local_one,
141
+ write_consistency: :any/:one/:two/:three/:quorum/:all/:local_quorum/:each_quorum/:serial/:local_serial/:local_one,
143
142
  protocol_version: 3,
144
143
  page_size: 10000,
145
144
  trace: true/false
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'extendi-cassandra_object'
5
- s.version = '1.0.17'
5
+ s.version = '1.1.1'
6
6
  s.description = 'Cassandra ActiveModel'
7
7
  s.summary = 'Cassandra ActiveModel'
8
8
  s.authors = ['Duccio Giovannelli', 'gotime']
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test}/*`.split("\n")
18
18
  s.require_paths = ['lib']
19
19
 
20
- s.add_runtime_dependency('activemodel', '<= 5.2')
20
+ s.add_runtime_dependency('activemodel', '>= 4.2.0', '< 7.0.0')
21
21
  s.add_runtime_dependency('cassandra-driver', '>= 3.2.3')
22
22
  s.add_runtime_dependency('lz4-ruby', '>= 0.3.3')
23
23
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'cassandra-driver'
2
4
  require 'cassandra'
3
5
  require 'logger'
@@ -6,7 +8,6 @@ module CassandraObject
6
8
  module Adapters
7
9
  class CassandraAdapter < AbstractAdapter
8
10
  class QueryBuilder
9
-
10
11
  def initialize(adapter, scope)
11
12
  @adapter = adapter
12
13
  @scope = scope
@@ -27,22 +28,27 @@ module CassandraObject
27
28
  "SELECT #{select_string} FROM #{@scope.klass.column_family}",
28
29
  where_string_async(nil)
29
30
  ]
30
- str << "ALLOW FILTERING" if @scope.klass.allow_filtering
31
+ str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
31
32
  return [] << str.delete_if(&:blank?) * ' '
32
33
  end
33
- @scope.id_values.map do |id|
34
- str = [
35
- "SELECT #{select_string} FROM #{@scope.klass.column_family}",
36
- where_string_async(id)
37
- ]
38
- str << "ALLOW FILTERING" if @scope.klass.allow_filtering
39
- str.delete_if(&:blank?) * ' '
40
- end
34
+
35
+ str = [
36
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
37
+ where_string_async(@scope.id_values)
38
+ ]
39
+ str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
40
+ [str.delete_if(&:blank?) * ' ']
41
41
  end
42
42
 
43
- def where_string_async(id)
43
+ def where_string_async(ids)
44
44
  wheres = @scope.where_values.dup.select.each_with_index { |_, i| i.even? }
45
- wheres << "#{@scope._key} = '#{id}'" if !id.nil?
45
+ if ids.present?
46
+ wheres << if ids.size > 1
47
+ "#{@scope._key} IN (#{ids.map { |id| "'#{id}'" }.join(',')})"
48
+ else
49
+ "#{@scope._key} = '#{ids.first}'"
50
+ end
51
+ end
46
52
  "WHERE #{wheres * ' AND '}" if wheres.any?
47
53
  end
48
54
  end
@@ -57,6 +63,7 @@ module CassandraObject
57
63
  :connections_per_local_node,
58
64
  :connections_per_remote_node,
59
65
  :consistency,
66
+ :write_consistency,
60
67
  :credentials,
61
68
  :futures_factory,
62
69
  :hosts,
@@ -82,14 +89,14 @@ module CassandraObject
82
89
  ])
83
90
 
84
91
  {
85
- load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
92
+ # load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
86
93
  reconnection_policy: 'Cassandra::Reconnection::Policies::%s',
87
94
  retry_policy: 'Cassandra::Retry::Policies::%s'
88
95
  }.each do |policy_key, class_template|
89
96
  params = cluster_options[policy_key]
90
97
  if params
91
98
  if params.is_a?(Hash)
92
- cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params]||[])
99
+ cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params] || [])
93
100
  else
94
101
  cluster_options[policy_key] = (class_template % [params.classify]).constantize.new
95
102
  end
@@ -101,7 +108,8 @@ module CassandraObject
101
108
  heartbeat_interval: cluster_options.keys.include?(:heartbeat_interval) ? cluster_options[:heartbeat_interval] : 30,
102
109
  idle_timeout: cluster_options[:idle_timeout] || 60,
103
110
  max_schema_agreement_wait: 1,
104
- consistency: cluster_options[:consistency] || :one,
111
+ consistency: cluster_options[:consistency] || :local_one,
112
+ write_consistency: cluster_options[:write_consistency] || cluster_options[:consistency] || :local_one,
105
113
  protocol_version: cluster_options[:protocol_version] || 3,
106
114
  page_size: cluster_options[:page_size] || 10000
107
115
  })
@@ -116,6 +124,8 @@ module CassandraObject
116
124
  end
117
125
 
118
126
  def execute(statement, arguments = [])
127
+ consistency = config[:write_consistency] || config[:consistency]
128
+ # puts "cassandra adapter: #{consistency}"
119
129
  ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: statement) do
120
130
  type_hints = []
121
131
  arguments.each { |a| type_hints << CassandraObject::Types::TypeHelper.guess_type(a) } unless arguments.nil?
@@ -124,6 +134,8 @@ module CassandraObject
124
134
  end
125
135
 
126
136
  def execute_async(queries, arguments = [])
137
+ consistency = config[:consistency]
138
+ # puts "execute_async adapter: #{consistency}"
127
139
  retries = 0
128
140
  futures = queries.map do |q|
129
141
  ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: q) do
@@ -146,8 +158,8 @@ module CassandraObject
146
158
  def select(scope)
147
159
  queries = QueryBuilder.new(self, scope).to_query_async
148
160
  # todo paginate
149
- arguments = scope.where_values.select.each_with_index{ |_, i| i.odd? }.reject{ |c| c.blank? }
150
- cql_rows = execute_async(queries, arguments).map{|item| item.rows.map{|x| x}}.flatten!
161
+ arguments = scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.blank? }
162
+ cql_rows = execute_async(queries, arguments).map { |item| item.rows.map { |x| x } }.flatten!
151
163
  cql_rows.each do |cql_row|
152
164
  attributes = cql_row.to_hash
153
165
  key = attributes.delete(scope._key)
@@ -165,31 +177,31 @@ module CassandraObject
165
177
 
166
178
  def write(table, id, attributes, ttl = nil)
167
179
  statement = "INSERT INTO #{table} (#{(attributes.keys).join(',')}) VALUES (#{(['?'] * attributes.size).join(',')})"
168
- statement += " USING TTL #{ttl.to_s}" if ttl.present?
180
+ statement += " USING TTL #{ttl}" if ttl.present?
169
181
  arguments = attributes.values
170
182
  execute(statement, arguments)
171
183
  end
172
184
 
173
185
  def write_update(table, id, attributes)
174
- queries =[]
186
+ queries = []
175
187
  # id here is the name of the key of the model
176
188
  id_value = attributes[id]
177
189
  if (not_nil_attributes = attributes.reject { |key, value| value.nil? }).any?
178
190
  statement = "INSERT INTO #{table} (#{(not_nil_attributes.keys).join(',')}) VALUES (#{(['?'] * not_nil_attributes.size).join(',')})"
179
- queries << {query: statement, arguments: not_nil_attributes.values}
191
+ queries << { query: statement, arguments: not_nil_attributes.values }
180
192
  end
181
193
  if (nil_attributes = attributes.select { |key, value| value.nil? }).any?
182
- queries << {query: "DELETE #{nil_attributes.keys.join(',')} FROM #{table} WHERE #{id} = ?", arguments: [id_value.to_s]}
194
+ queries << { query: "DELETE #{nil_attributes.keys.join(',')} FROM #{table} WHERE #{id} = ?", arguments: [id_value.to_s] }
183
195
  end
184
196
  execute_batchable(queries)
185
197
  end
186
198
 
187
199
  def delete(scope, ids, attributes = {})
188
200
  ids = [ids] if !ids.is_a?(Array)
189
- statement = "DELETE FROM #{scope.column_family} WHERE #{scope._key} IN (#{ids.map{|id| '?'}.join(',')})"
201
+ statement = "DELETE FROM #{scope.column_family} WHERE #{scope._key} IN (#{ids.map { |id| '?' }.join(',')})"
190
202
  arguments = ids
191
203
  unless attributes.blank?
192
- statement += " AND #{attributes.keys.map{ |k| "#{k} = ?" }.join(' AND ')}"
204
+ statement += " AND #{attributes.keys.map { |k| "#{k} = ?" }.join(' AND ')}"
193
205
  arguments += attributes.values
194
206
  end
195
207
  execute(statement, arguments)
@@ -197,20 +209,23 @@ module CassandraObject
197
209
 
198
210
  def delete_single(obj)
199
211
  keys = obj.class._keys
200
- wheres = keys.map{ |k| "#{k} = ?" }.join(' AND ')
201
- arguments = keys.map{ |k| obj.attributes[k] }
212
+ wheres = keys.map { |k| "#{k} = ?" }.join(' AND ')
213
+ arguments = keys.map { |k| obj.attributes[k] }
202
214
  statement = "DELETE FROM #{obj.class.column_family} WHERE #{wheres}"
203
215
  execute(statement, arguments)
204
216
  end
205
217
 
206
218
  def execute_batch(statements)
207
- raise 'No can do' if statements.empty?
219
+ raise 'Statements is empty!' if statements.empty?
220
+ consistency = config[:write_consistency] || config[:consistency]
221
+ # puts "cassandra adapter execute batch #{consistency}"
222
+
208
223
  batch = connection.batch do |b|
209
224
  statements.each do |statement|
210
225
  b.add(statement[:query], arguments: statement[:arguments])
211
226
  end
212
227
  end
213
- connection.execute(batch, page_size: config[:page_size])
228
+ connection.execute(batch, consistency: consistency, page_size: config[:page_size])
214
229
  end
215
230
 
216
231
  # SCHEMA
@@ -239,7 +254,7 @@ module CassandraObject
239
254
  def schema_execute(cql, keyspace)
240
255
  schema_db = Cassandra.cluster cassandra_cluster_options
241
256
  connection = schema_db.connect keyspace
242
- connection.execute cql, consistency: consistency
257
+ connection.execute cql, consistency: config[:write_consistency] || config[:consistency]
243
258
  end
244
259
 
245
260
  def cassandra_version
@@ -248,14 +263,6 @@ module CassandraObject
248
263
 
249
264
  # /SCHEMA
250
265
 
251
- def consistency
252
- defined?(@consistency) ? @consistency : nil
253
- end
254
-
255
- def consistency=(val)
256
- @consistency = val
257
- end
258
-
259
266
  def statement_create_with_options(stmt, options = '')
260
267
  if !options.nil?
261
268
  statement_with_options stmt, options
@@ -266,7 +273,7 @@ module CassandraObject
266
273
  AND caching = '{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}'
267
274
  AND comment = ''
268
275
  AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
269
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
276
+ AND compression = {'chunk_length_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
270
277
  AND dclocal_read_repair_chance = 0.0
271
278
  AND default_time_to_live = 0
272
279
  AND gc_grace_seconds = 864000
@@ -276,24 +283,24 @@ module CassandraObject
276
283
  AND read_repair_chance = 1.0
277
284
  AND speculative_retry = 'NONE';"
278
285
  else
279
- "#{stmt} WITH read_repair_chance = 0.0
280
- AND dclocal_read_repair_chance = 0.1
281
- AND gc_grace_seconds = 864000
282
- AND bloom_filter_fp_chance = 0.01
283
- AND caching = { 'keys' : 'ALL', 'rows_per_partition' : 'NONE' }
286
+ "#{stmt} WITH bloom_filter_fp_chance = 0.01
287
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
284
288
  AND comment = ''
285
- AND compaction = { 'class' : 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold' : 32, 'min_threshold' : 4 }
286
- AND compression = { 'chunk_length_in_kb' : 64, 'class' : 'org.apache.cassandra.io.compress.LZ4Compressor' }
289
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
290
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
291
+ AND crc_check_chance = 1.0
292
+ AND dclocal_read_repair_chance = 0.1
287
293
  AND default_time_to_live = 0
288
- AND speculative_retry = '99PERCENTILE'
289
- AND min_index_interval = 128
294
+ AND gc_grace_seconds = 864000
290
295
  AND max_index_interval = 2048
291
- AND crc_check_chance = 1.0;
296
+ AND memtable_flush_period_in_ms = 0
297
+ AND min_index_interval = 128
298
+ AND read_repair_chance = 0.0
299
+ AND speculative_retry = '99.0PERCENTILE';
292
300
  "
293
301
  end
294
302
  end
295
303
  end
296
-
297
304
  end
298
305
  end
299
306
  end
@@ -26,34 +26,39 @@ module CassandraObject
26
26
 
27
27
  if @scope.id_values.empty?
28
28
  str = [
29
- "SELECT #{select_string} FROM #{@scope.klass.column_family}",
30
- where_string_async(nil)
29
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
30
+ where_string_async(nil)
31
31
  ]
32
32
  str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
33
33
  return [] << str.delete_if(&:blank?) * ' '
34
34
  end
35
- @scope.id_values.map do |id|
36
- str = [
37
- "SELECT #{select_string} FROM #{@scope.klass.column_family}",
38
- where_string_async(id)
39
- ]
40
- str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
41
- str.delete_if(&:blank?) * ' '
42
- end
35
+ str = [
36
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
37
+ where_string_async(@scope.id_values)
38
+ ]
39
+ str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
40
+ [str.delete_if(&:blank?) * ' ']
43
41
  end
44
42
 
45
- def where_string_async(id)
43
+ def where_string_async(ids)
46
44
  conditions = []
47
- conditions << "#{@adapter.primary_key_column} = '#{id}'" if !id.nil?
45
+
46
+ if ids.present?
47
+ conditions << if ids.size > 1
48
+ "#{@adapter.primary_key_column} IN (#{ids.map { |id| "'#{id}'" }.join(',')})"
49
+ else
50
+ "#{@adapter.primary_key_column} = '#{ids.first}'"
51
+ end
52
+ end
53
+
48
54
  select_values = @scope.select_values.select { |sv| sv != :column1 }
49
55
  if select_values.size > 0
50
56
  select_str = select_values.size > 1 ? "column1 IN (#{select_values.map { |sv| '?' }.join(',')})" : 'column1 = ?'
51
57
  conditions << select_str
52
58
  end
53
59
  conditions += @scope.where_values.select.each_with_index { |_, i| i.even? }
54
- return conditions.any? ? "WHERE #{conditions.join(' AND ')}" : nil
60
+ conditions.any? ? "WHERE #{conditions.join(' AND ')}" : nil
55
61
  end
56
-
57
62
  end
58
63
 
59
64
  def primary_key_column
@@ -70,6 +75,7 @@ module CassandraObject
70
75
  :connections_per_local_node,
71
76
  :connections_per_remote_node,
72
77
  :consistency,
78
+ :write_consistency,
73
79
  :credentials,
74
80
  :futures_factory,
75
81
  :hosts,
@@ -95,26 +101,27 @@ module CassandraObject
95
101
  ])
96
102
 
97
103
  {
98
- load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
104
+ # load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
99
105
  reconnection_policy: 'Cassandra::Reconnection::Policies::%s',
100
106
  retry_policy: 'Cassandra::Retry::Policies::%s'
101
107
  }.each do |policy_key, class_template|
102
108
  params = cluster_options[policy_key]
103
109
  if params
104
110
  if params.is_a?(Hash)
105
- cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params]||[])
111
+ cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params] || [])
106
112
  else
107
113
  cluster_options[policy_key] = (class_template % [params.classify]).constantize.new
108
114
  end
109
115
  end
110
116
  end
111
-
117
+
112
118
  # Setting defaults
113
119
  cluster_options.merge!({
114
120
  heartbeat_interval: cluster_options.keys.include?(:heartbeat_interval) ? cluster_options[:heartbeat_interval] : 30,
115
121
  idle_timeout: cluster_options[:idle_timeout] || 60,
116
122
  max_schema_agreement_wait: 1,
117
- consistency: cluster_options[:consistency] || :one,
123
+ consistency: cluster_options[:consistency] || :local_one,
124
+ write_consistency: cluster_options[:write_consistency] || cluster_options[:consistency] || :local_one,
118
125
  protocol_version: cluster_options[:protocol_version] || 3,
119
126
  page_size: cluster_options[:page_size] || 10000
120
127
  })
@@ -129,12 +136,17 @@ module CassandraObject
129
136
  end
130
137
 
131
138
  def execute(statement, arguments = [])
139
+ consistency = config[:write_consistency] || config[:consistency]
140
+ # puts "schemaless adapter: #{consistency}"
132
141
  ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: statement) do
133
142
  connection.execute statement, arguments: arguments, consistency: consistency, page_size: config[:page_size]
134
143
  end
135
144
  end
136
145
 
137
146
  def execute_async(queries, arguments = [], per_page = nil, next_cursor = nil)
147
+ consistency = config[:consistency]
148
+ # puts "schemaless adapter async: #{consistency}"
149
+
138
150
  retries = 0
139
151
  per_page ||= config[:page_size]
140
152
  futures = queries.map { |q|
@@ -164,7 +176,7 @@ module CassandraObject
164
176
  item.rows.each { |x| ids << x[primary_key_column] }
165
177
  new_next_cursor = item.paging_state unless item.last_page?
166
178
  end
167
- return {ids: ids, new_next_cursor: new_next_cursor}
179
+ { ids: ids, new_next_cursor: new_next_cursor }
168
180
  end
169
181
 
170
182
  def select(scope)
@@ -172,19 +184,19 @@ module CassandraObject
172
184
  queries.compact! if queries.present?
173
185
  raise CassandraObject::RecordNotFound if !queries.present?
174
186
 
175
- arguments = scope.select_values.select{ |sv| sv != :column1 }.map(&:to_s)
176
- arguments += scope.where_values.select.each_with_index{ |_, i| i.odd? }.reject{ |c| c.empty? }.map(&:to_s)
187
+ arguments = scope.select_values.select { |sv| sv != :column1 }.map(&:to_s)
188
+ arguments += scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.empty? }.map(&:to_s)
177
189
  records = execute_async(queries, arguments).map do |item|
178
190
  # pagination
179
191
  elems = []
180
192
  loop do
181
- item.rows.each{ |x| elems << x }
193
+ item.rows.each { |x| elems << x }
182
194
  break if item.last_page?
183
195
  item = item.next_page
184
196
  end
185
197
  elems
186
198
  end
187
- {results: records.flatten!}
199
+ { results: records.flatten! }
188
200
  end
189
201
 
190
202
  def select_paginated(scope)
@@ -192,15 +204,15 @@ module CassandraObject
192
204
  queries.compact! if queries.present?
193
205
  raise CassandraObject::RecordNotFound if !queries.present?
194
206
 
195
- arguments = scope.select_values.select{ |sv| sv != :column1 }.map(&:to_s)
196
- arguments += scope.where_values.select.each_with_index{ |_, i| i.odd? }.reject{ |c| c.empty? }.map(&:to_s)
207
+ arguments = scope.select_values.select { |sv| sv != :column1 }.map(&:to_s)
208
+ arguments += scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.empty? }.map(&:to_s)
197
209
  new_next_cursor = nil
198
210
  records = []
199
211
  execute_async(queries, arguments, scope.limit_value, scope.next_cursor).each do |item|
200
212
  new_next_cursor = item.paging_state unless item.last_page?
201
- item.rows.each{ |x| records << x }
213
+ item.rows.each { |x| records << x }
202
214
  end
203
- {results: records, new_next_cursor: new_next_cursor}
215
+ { results: records, new_next_cursor: new_next_cursor }
204
216
  end
205
217
 
206
218
  def insert(table, id, attributes, ttl = nil)
@@ -213,16 +225,15 @@ module CassandraObject
213
225
 
214
226
  def write(table, id, attributes, ttl)
215
227
  queries = []
216
- # puts attributes
217
228
  attributes.each do |column, value|
218
229
  if !value.nil?
219
230
  query = "INSERT INTO #{table} (#{primary_key_column},column1,value) VALUES (?,?,?)"
220
- query += " USING TTL #{ttl.to_s}" if !ttl.nil?
231
+ query += " USING TTL #{ttl}" if !ttl.nil?
221
232
  args = [id.to_s, column.to_s, value.to_s]
222
233
 
223
- queries << {query: query, arguments: args}
234
+ queries << { query: query, arguments: args }
224
235
  else
225
- queries << {query: "DELETE FROM #{table} WHERE #{primary_key_column} = ? AND column1= ?", arguments: [id.to_s, column.to_s]}
236
+ queries << { query: "DELETE FROM #{table} WHERE #{primary_key_column} = ? AND column1= ?", arguments: [id.to_s, column.to_s] }
226
237
  end
227
238
  end
228
239
  execute_batchable(queries)
@@ -232,18 +243,20 @@ module CassandraObject
232
243
  ids = [ids] if !ids.is_a?(Array)
233
244
  arguments = nil
234
245
  arguments = ids if ids.size == 1
235
- statement = "DELETE FROM #{table} WHERE #{create_ids_where_clause(ids)}" #.gsub('?', ids.map { |id| "'#{id}'" }.join(','))
246
+ statement = "DELETE FROM #{table} WHERE #{create_ids_where_clause(ids)}" # .gsub('?', ids.map { |id| "'#{id}'" }.join(','))
236
247
  execute(statement, arguments)
237
248
  end
238
249
 
239
250
  def execute_batch(statements)
240
- raise 'No can do' if statements.empty?
251
+ consistency = config[:write_consistency] || config[:consistency]
252
+ # puts "schemaless execute batch #{consistency}"
253
+ raise 'Statements is empty!' if statements.empty?
241
254
  batch = connection.batch do |b|
242
255
  statements.each do |statement|
243
256
  b.add(statement[:query], arguments: statement[:arguments])
244
257
  end
245
258
  end
246
- connection.execute(batch, page_size: config[:page_size])
259
+ connection.execute(batch, consistency: consistency, page_size: config[:page_size])
247
260
  end
248
261
 
249
262
  # SCHEMA
@@ -270,7 +283,7 @@ module CassandraObject
270
283
  def schema_execute(cql, keyspace)
271
284
  schema_db = Cassandra.cluster cassandra_cluster_options
272
285
  connection = schema_db.connect keyspace
273
- connection.execute cql, consistency: consistency
286
+ connection.execute cql, consistency: config[:write_consistency] || config[:consistency]
274
287
  end
275
288
 
276
289
  def cassandra_version
@@ -279,14 +292,6 @@ module CassandraObject
279
292
 
280
293
  # /SCHEMA
281
294
 
282
- def consistency
283
- defined?(@consistency) ? @consistency : nil
284
- end
285
-
286
- def consistency=(val)
287
- @consistency = val
288
- end
289
-
290
295
  def statement_create_with_options(stmt, options)
291
296
  if !options.nil?
292
297
  statement_with_options stmt, options
@@ -299,7 +304,7 @@ module CassandraObject
299
304
  AND caching = '{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}'
300
305
  AND comment = ''
301
306
  AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
302
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
307
+ AND compression = {'chunk_length_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
303
308
  AND dclocal_read_repair_chance = 0.0
304
309
  AND default_time_to_live = 0
305
310
  AND gc_grace_seconds = 864000
@@ -309,19 +314,20 @@ module CassandraObject
309
314
  AND read_repair_chance = 1.0
310
315
  AND speculative_retry = 'NONE';"
311
316
  else
312
- "#{stmt} WITH read_repair_chance = 0.0
313
- AND dclocal_read_repair_chance = 0.1
314
- AND gc_grace_seconds = 864000
315
- AND bloom_filter_fp_chance = 0.01
316
- AND caching = { 'keys' : 'ALL', 'rows_per_partition' : 'NONE' }
317
+ "#{stmt} WITH bloom_filter_fp_chance = 0.01
318
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
317
319
  AND comment = ''
318
- AND compaction = { 'class' : 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold' : 32, 'min_threshold' : 4 }
319
- AND compression = { 'chunk_length_in_kb' : 64, 'class' : 'org.apache.cassandra.io.compress.LZ4Compressor' }
320
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
321
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
322
+ AND crc_check_chance = 1.0
323
+ AND dclocal_read_repair_chance = 0.1
320
324
  AND default_time_to_live = 0
321
- AND speculative_retry = '99PERCENTILE'
322
- AND min_index_interval = 128
325
+ AND gc_grace_seconds = 864000
323
326
  AND max_index_interval = 2048
324
- AND crc_check_chance = 1.0;
327
+ AND memtable_flush_period_in_ms = 0
328
+ AND min_index_interval = 128
329
+ AND read_repair_chance = 0.0
330
+ AND speculative_retry = '99.0PERCENTILE';
325
331
  "
326
332
 
327
333
  end
@@ -332,9 +338,8 @@ module CassandraObject
332
338
  return ids if ids.empty?
333
339
  ids = ids.first if ids.is_a?(Array) && ids.one?
334
340
  sql = ids.is_a?(Array) ? "#{primary_key_column} IN (#{ids.map { |id| "'#{id}'" }.join(',')})" : "#{primary_key_column} = ?"
335
- return sql
341
+ sql
336
342
  end
337
-
338
343
  end
339
344
  end
340
345
  end
@@ -5,9 +5,9 @@ module CassandraObject
5
5
 
6
6
  included do
7
7
  if ActiveModel::VERSION::STRING < '3.2'
8
- attribute_method_suffix("", "=")
8
+ attribute_method_suffix('', '=')
9
9
  else
10
- attribute_method_suffix("=")
10
+ attribute_method_suffix('=')
11
11
  end
12
12
 
13
13
  # (Alias for the protected read_attribute method).
@@ -4,10 +4,10 @@ module CassandraObject
4
4
 
5
5
  included do
6
6
  class_attribute :batch_statements
7
+ attr_accessor :store_updated_at
7
8
  end
8
9
 
9
10
  module ClassMethods
10
-
11
11
  def ttl=(value)
12
12
  @ttl = value
13
13
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CassandraObject
2
4
  class Scope
3
5
  module FinderMethods
@@ -16,50 +18,60 @@ module CassandraObject
16
18
  end
17
19
 
18
20
  def find_in_batches(id, next_cursor = nil)
19
- obj = self.clone
21
+ obj = clone
20
22
  obj.is_all = true
21
23
  obj.next_cursor = next_cursor
22
24
  obj.where_ids(id).execute_paged
23
25
  end
24
26
 
25
27
  def find_all_in_batches(next_cursor = nil)
26
- obj = self.clone
28
+ obj = clone
27
29
  obj.is_all = true
28
30
  obj.next_cursor = next_cursor
29
31
  obj.execute
30
32
  end
31
33
 
32
34
  def first
33
- return limit(1).find_all_in_batches[:results].first if self.schema_type == :dynamic_attributes || self.schema_type == :schemaless
35
+ return limit(1).find_all_in_batches[:results].first if schema_type == :dynamic_attributes || schema_type == :schemaless
34
36
  limit(1).execute.first
35
37
  end
36
38
 
37
39
  private
38
40
 
39
- def find_one(id)
40
- if id.blank?
41
- not_found(id)
42
- elsif self.schema_type == :dynamic_attributes
43
- record = where_ids(id).execute
44
- not_found(id) if record.empty?
45
- record
46
- elsif record = where_ids(id)[0]
47
- record
48
- else
49
- not_found(id)
41
+ def find_one(id)
42
+ if id.blank?
43
+ not_found(id)
44
+ elsif schema_type == :dynamic_attributes
45
+ record = where_ids(id).execute
46
+ not_found(id) if record.empty?
47
+ record
48
+ elsif record = where_ids(id)[0]
49
+ record
50
+ else
51
+ not_found(id)
52
+ end
50
53
  end
51
- end
52
54
 
53
- def find_some(ids)
54
- ids = ids.flatten
55
- return [] if ids.empty?
56
- ids = ids.compact.map(&:to_s).uniq
57
- where_ids(ids).execute
58
- end
55
+ def find_some(pids)
56
+ ids = pids.flatten.compact.uniq.map(&:to_s)
57
+ return [] if ids.empty?
59
58
 
60
- def not_found(id)
61
- raise CassandraObject::RecordNotFound, "Couldn't find #{self.name} with key #{id.inspect}"
62
- end
59
+ qr = where_ids(ids).execute
60
+ is_dymnamic = qr.is_a?(Hash)
61
+
62
+ results = qr.sort_by do |r|
63
+ id = r.keys.first if r.is_a?(Hash)
64
+ id = r[0] if r.is_a?(Array)
65
+ id = r.id if id.nil?
66
+ ids.index(id)
67
+ end
68
+
69
+ is_dymnamic ? Hash[results] : results
70
+ end
71
+
72
+ def not_found(id)
73
+ raise CassandraObject::RecordNotFound, "Couldn't find #{name} with key #{id.inspect}"
74
+ end
63
75
  end
64
76
  end
65
77
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CassandraObject
2
4
  module Scoping
3
5
  extend ActiveSupport::Concern
@@ -15,7 +15,11 @@ module CassandraObject
15
15
 
16
16
  before_update if: :changed? do
17
17
  if self.class.timestamps
18
- self.updated_at = Time.current
18
+ if store_updated_at.present?
19
+ self.updated_at = store_updated_at
20
+ else
21
+ self.updated_at = Time.current
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Bundler.require :cassandra
2
4
 
3
5
  CassandraObject::Base.config = {
@@ -13,7 +15,7 @@ CassandraObject::Base.config = {
13
15
  connections_per_local_node: 4,
14
16
  schema_refresh_delay: 0.1,
15
17
  schema_refresh_timeout: 0.1,
16
- load_balancing_policy: 'RoundRobin',
18
+ load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin.new,
17
19
  reconnection_policy: { policy: 'Constant', params: [5] },
18
20
  retry_policy: 'Default',
19
21
  # connections_per_remote_node: nil,
@@ -30,15 +32,12 @@ end
30
32
  sleep 1
31
33
  CassandraObject::Schema.create_keyspace 'cassandra_object_test'
32
34
  CassandraObject::Schemaless.create_column_family 'Issues'
33
- CassandraObject::Schema.create_column_family 'IssueSchemas', {attributes: 'id text, title text, description text, field float, intero int, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {}}
34
- CassandraObject::Schema.create_column_family 'IssueSchemaCks', {attributes: 'id text, type text, date timestamp, value float, PRIMARY KEY (id, type, date)', options: {}}
35
+ CassandraObject::Schema.create_column_family 'IssueSchemas', { attributes: 'id text, title text, description text, field float, intero int, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {} }
36
+ CassandraObject::Schema.create_column_family 'IssueSchemaCks', { attributes: 'id text, type text, date timestamp, value float, PRIMARY KEY (id, type, date)', options: {} }
35
37
  CassandraObject::Schemaless.create_column_family 'IssueDynamics'
36
38
  CassandraObject::Schemaless.create_column_family 'IssuesCustomConfig'
37
- CassandraObject::Schema.create_column_family 'IssueSchemaFathers', {attributes: 'id text, title text, field float, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {}}
38
- CassandraObject::Schema.create_column_family 'IssueSchemaChildren', {attributes: 'id text, title text, description text, field float, created_at timestamp, updated_at timestamp, issue_schema_father_id text, PRIMARY KEY (id)', options: {}}
39
- CassandraObject::BaseSchemaless.adapter.consistency = :quorum
40
- CassandraObject::BaseSchemalessDynamic.adapter.consistency = :quorum
41
- CassandraObject::BaseSchema.adapter.consistency = :quorum
39
+ CassandraObject::Schema.create_column_family 'IssueSchemaFathers', { attributes: 'id text, title text, field float, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {} }
40
+ CassandraObject::Schema.create_column_family 'IssueSchemaChildren', { attributes: 'id text, title text, description text, field float, created_at timestamp, updated_at timestamp, issue_schema_father_id text, PRIMARY KEY (id)', options: {} }
42
41
 
43
42
  CassandraObject::Base.class_eval do
44
43
  class_attribute :created_records
@@ -60,7 +59,6 @@ end
60
59
 
61
60
  module ActiveSupport
62
61
  class TestCase
63
-
64
62
  self.test_order = :random
65
63
 
66
64
  def after_setup
@@ -16,8 +16,6 @@ class CassandraObject::ConnectionsTest < CassandraObject::TestCase
16
16
  threads = []
17
17
 
18
18
  (0..10).collect do |i|
19
-
20
- # puts "spawn thread #{i}"
21
19
  thr = Thread.new do
22
20
  begin
23
21
  IssueSchema.find(ids)
@@ -4,7 +4,6 @@
4
4
  require 'test_helper'
5
5
 
6
6
  class CassandraObject::PersistenceSchemaCkTest < CassandraObject::TestCase
7
-
8
7
  test 'composite key' do
9
8
  time1 = Time.now
10
9
  time2 = time1 + 1.second
@@ -25,19 +24,17 @@ class CassandraObject::PersistenceSchemaCkTest < CassandraObject::TestCase
25
24
  item = res[1]
26
25
  assert_equal '1', item.id
27
26
  assert_equal time2.to_i, item.date.to_i
28
-
29
27
  end
30
28
 
31
29
  test 'delete' do
32
30
  IssueSchemaCk.create(id: '1', type: 'first', date: Time.now, value: 1.to_f)
33
31
  IssueSchemaCk.create(id: '1', type: 'second', date: Time.now, value: 1.to_f)
34
-
35
32
  IssueSchemaCk.delete('1')
36
33
  assert_equal 0, IssueSchemaCk.find_by_id([1]).size
37
34
  end
38
35
 
39
36
  test 'delete with attributes' do
40
- time = Time.now
37
+ time = Time.now - 10.days
41
38
  IssueSchemaCk.create(id: '1', type: 'first', date: time, value: 1.to_f)
42
39
  IssueSchemaCk.create(id: '1', type: 'first', date: Time.now, value: 1.to_f)
43
40
  IssueSchemaCk.create(id: '2', type: 'first', date: time, value: 1.to_f)
@@ -65,5 +62,4 @@ class CassandraObject::PersistenceSchemaCkTest < CassandraObject::TestCase
65
62
  IssueSchemaCk.find_by_id(['1']).first.destroy
66
63
  assert_equal 1, IssueSchemaCk.find_by_id([1]).size
67
64
  end
68
-
69
65
  end
@@ -26,6 +26,38 @@ class CassandraObject::FinderMethodsTest < CassandraObject::TestCase
26
26
  assert_equal [first_issue, second_issue].to_set, Issue.find([first_issue.id, second_issue.id]).to_set
27
27
  end
28
28
 
29
+ test 'IssueDynamic: find with ids sorted' do
30
+ ids = (0..999).to_a.map(&:to_s)
31
+ ids.each do |i|
32
+ IssueDynamic.create(key: i, title: "foo_title_#{i}")
33
+ end
34
+ ids_to_find = ids.sample(10)
35
+ assert_equal ids_to_find, IssueDynamic.find(ids_to_find).keys
36
+ IssueDynamic.delete_all
37
+ end
38
+
39
+ test 'Issue: find with ids sorted' do
40
+ ids = (0..999).to_a.map(&:to_s)
41
+ ids.each do |i|
42
+ Issue.create(id: i, title: "foo_title_#{i}")
43
+ end
44
+ ids_to_find = ids.sample(10)
45
+ assert_equal ids_to_find, Issue.find(ids_to_find).map(&:id)
46
+ Issue.delete_all
47
+ end
48
+
49
+ test 'IssueSchemaCk: find with ids sorted' do
50
+ ids = (0..999).to_a.map(&:to_s)
51
+ ids.each do |i|
52
+ IssueSchemaCk.create(id: i, type: 'first', date: Date.yesterday.to_time, value: 1.0)
53
+ IssueSchemaCk.create(id: i, type: 'first', date: Date.today.to_time, value: 2.0)
54
+ end
55
+ ids_to_find = ids.sample(10)
56
+ assert_equal ids_to_find.size * 2, IssueSchemaCk.find(ids_to_find).size
57
+ assert_equal ids_to_find, IssueSchemaCk.find(ids_to_find).map(&:id).uniq
58
+ IssueSchemaCk.delete_all
59
+ end
60
+
29
61
  test 'find_by_id' do
30
62
  Issue.create.tap do |issue|
31
63
  assert_equal issue, Issue.find_by_id(issue.id)
@@ -64,9 +96,7 @@ class CassandraObject::FinderMethodsTest < CassandraObject::TestCase
64
96
  IssueDynamic.delete(['1', '2'])
65
97
  end
66
98
 
67
-
68
99
  test 'find all in batches dynamic paged' do
69
-
70
100
  issues = []
71
101
  100.times.each do |i|
72
102
  issues << IssueDynamic.create(key: i, title: 'tit', dynamic_field1: 'one', dynamic_field2: 'two')
@@ -135,5 +165,4 @@ class CassandraObject::FinderMethodsTest < CassandraObject::TestCase
135
165
  # first_issue = IssueDynamic.create(key: '1', title: 'tit', dynamic_field1: 'one', dynamic_field2: 'two')
136
166
  # f = IssueDynamic.first
137
167
  # end
138
-
139
168
  end
@@ -37,5 +37,4 @@ class CassandraObject::Scope::QueryMethodsTest < CassandraObject::TestCase
37
37
  foo_issue_columns = Issue.columns.first
38
38
  assert_equal ['created_at', 'description', 'title', 'updated_at'], foo_issue_columns[foo_issue_columns.keys.first]
39
39
  end
40
-
41
40
  end
@@ -21,7 +21,30 @@ class CassandraObject::TimestampsTest < CassandraObject::TestCase
21
21
  test 'created_at sets only if nil' do
22
22
  time = 5.days.ago
23
23
  issue = Issue.create created_at: time
24
-
25
24
  assert_equal time, issue.created_at
26
25
  end
26
+
27
+ test 'set updated_at to now when not passed as an attribute' do
28
+ udate = 1.year.ago
29
+ issue = Issue.create(description: 'foo', updated_at: udate)
30
+ assert_equal udate, issue.updated_at
31
+ issue.update_attributes(description: 'test')
32
+ assert_not_equal udate, issue.updated_at
33
+ end
34
+
35
+ test 'set updated_at to passed value' do
36
+ issue = Issue.create(description: 'foo')
37
+ updated_at = issue.updated_at
38
+ new_updated_at = updated_at + 5.days
39
+ issue.update_attributes(description: 'bar', store_updated_at: new_updated_at)
40
+ assert_equal new_updated_at, issue.updated_at
41
+ end
42
+
43
+ test 'set updated_at to passed value even if is equal to the stored value' do
44
+ udate = 1.year.ago
45
+ issue = Issue.create(description: 'foo', updated_at: udate)
46
+ assert_equal udate, issue.updated_at
47
+ issue.update_attributes(description: 'bar', store_updated_at: issue.updated_at)
48
+ assert_equal udate, issue.updated_at
49
+ end
27
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extendi-cassandra_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.17
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Duccio Giovannelli
@@ -9,22 +9,28 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-11-27 00:00:00.000000000 Z
12
+ date: 2020-06-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "<="
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 4.2.0
21
+ - - "<"
19
22
  - !ruby/object:Gem::Version
20
- version: '5.2'
23
+ version: 7.0.0
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
25
- - - "<="
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: 4.2.0
31
+ - - "<"
26
32
  - !ruby/object:Gem::Version
27
- version: '5.2'
33
+ version: 7.0.0
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: cassandra-driver
30
36
  requirement: !ruby/object:Gem::Requirement
@@ -198,8 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
204
  - !ruby/object:Gem::Version
199
205
  version: 1.3.5
200
206
  requirements: []
201
- rubyforge_project:
202
- rubygems_version: 2.7.6
207
+ rubygems_version: 3.1.2
203
208
  signing_key:
204
209
  specification_version: 4
205
210
  summary: Cassandra ActiveModel