extendi-cassandra_object 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.travis.yml +23 -0
  4. data/CHANGELOG +0 -0
  5. data/Gemfile +17 -0
  6. data/LICENSE +13 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +177 -0
  9. data/Rakefile +12 -0
  10. data/extendi-cassandra_object.gemspec +26 -0
  11. data/lib/cassandra_object.rb +73 -0
  12. data/lib/cassandra_object/adapters/abstract_adapter.rb +61 -0
  13. data/lib/cassandra_object/adapters/cassandra_adapter.rb +269 -0
  14. data/lib/cassandra_object/adapters/cassandra_schemaless_adapter.rb +306 -0
  15. data/lib/cassandra_object/attribute_methods.rb +96 -0
  16. data/lib/cassandra_object/attribute_methods/definition.rb +22 -0
  17. data/lib/cassandra_object/attribute_methods/dirty.rb +36 -0
  18. data/lib/cassandra_object/attribute_methods/primary_key.rb +25 -0
  19. data/lib/cassandra_object/attribute_methods/typecasting.rb +59 -0
  20. data/lib/cassandra_object/base.rb +33 -0
  21. data/lib/cassandra_object/base_schema.rb +11 -0
  22. data/lib/cassandra_object/base_schemaless.rb +11 -0
  23. data/lib/cassandra_object/base_schemaless_dynamic.rb +11 -0
  24. data/lib/cassandra_object/belongs_to.rb +63 -0
  25. data/lib/cassandra_object/belongs_to/association.rb +49 -0
  26. data/lib/cassandra_object/belongs_to/builder.rb +40 -0
  27. data/lib/cassandra_object/belongs_to/reflection.rb +30 -0
  28. data/lib/cassandra_object/callbacks.rb +29 -0
  29. data/lib/cassandra_object/core.rb +63 -0
  30. data/lib/cassandra_object/errors.rb +10 -0
  31. data/lib/cassandra_object/identity.rb +26 -0
  32. data/lib/cassandra_object/inspect.rb +25 -0
  33. data/lib/cassandra_object/log_subscriber.rb +44 -0
  34. data/lib/cassandra_object/model.rb +60 -0
  35. data/lib/cassandra_object/persistence.rb +203 -0
  36. data/lib/cassandra_object/railtie.rb +33 -0
  37. data/lib/cassandra_object/railties/controller_runtime.rb +45 -0
  38. data/lib/cassandra_object/schema.rb +83 -0
  39. data/lib/cassandra_object/schemaless.rb +83 -0
  40. data/lib/cassandra_object/scope.rb +86 -0
  41. data/lib/cassandra_object/scope/finder_methods.rb +54 -0
  42. data/lib/cassandra_object/scope/query_methods.rb +69 -0
  43. data/lib/cassandra_object/scoping.rb +27 -0
  44. data/lib/cassandra_object/serialization.rb +6 -0
  45. data/lib/cassandra_object/tasks/ks.rake +54 -0
  46. data/lib/cassandra_object/timestamps.rb +19 -0
  47. data/lib/cassandra_object/type.rb +16 -0
  48. data/lib/cassandra_object/types.rb +8 -0
  49. data/lib/cassandra_object/types/array_type.rb +16 -0
  50. data/lib/cassandra_object/types/base_type.rb +26 -0
  51. data/lib/cassandra_object/types/boolean_type.rb +20 -0
  52. data/lib/cassandra_object/types/date_type.rb +22 -0
  53. data/lib/cassandra_object/types/float_type.rb +16 -0
  54. data/lib/cassandra_object/types/integer_type.rb +20 -0
  55. data/lib/cassandra_object/types/json_type.rb +13 -0
  56. data/lib/cassandra_object/types/string_type.rb +19 -0
  57. data/lib/cassandra_object/types/time_type.rb +16 -0
  58. data/lib/cassandra_object/types/type_helper.rb +39 -0
  59. data/lib/cassandra_object/validations.rb +44 -0
  60. data/test/support/cassandra.rb +63 -0
  61. data/test/support/issue.rb +12 -0
  62. data/test/support/issue_dynamic.rb +12 -0
  63. data/test/support/issue_schema.rb +17 -0
  64. data/test/support/issue_schema_child.rb +17 -0
  65. data/test/support/issue_schema_father.rb +13 -0
  66. data/test/test_helper.rb +41 -0
  67. data/test/unit/active_model_test.rb +18 -0
  68. data/test/unit/adapters/adapter_test.rb +6 -0
  69. data/test/unit/attribute_methods/definition_test.rb +13 -0
  70. data/test/unit/attribute_methods/dirty_test.rb +72 -0
  71. data/test/unit/attribute_methods/primary_key_test.rb +26 -0
  72. data/test/unit/attribute_methods/typecasting_test.rb +119 -0
  73. data/test/unit/attribute_methods_test.rb +51 -0
  74. data/test/unit/base_test.rb +20 -0
  75. data/test/unit/belongs_to/reflection_test.rb +12 -0
  76. data/test/unit/belongs_to_test.rb +63 -0
  77. data/test/unit/callbacks_test.rb +46 -0
  78. data/test/unit/connection_test.rb +6 -0
  79. data/test/unit/connections/connections_test.rb +55 -0
  80. data/test/unit/core_test.rb +55 -0
  81. data/test/unit/identity_test.rb +26 -0
  82. data/test/unit/inspect_test.rb +26 -0
  83. data/test/unit/log_subscriber_test.rb +25 -0
  84. data/test/unit/persistence_schema_test.rb +156 -0
  85. data/test/unit/persistence_test.rb +266 -0
  86. data/test/unit/railties/controller_runtime_test.rb +48 -0
  87. data/test/unit/schema/tasks_test.rb +32 -0
  88. data/test/unit/schema_test.rb +115 -0
  89. data/test/unit/schemaless_test.rb +100 -0
  90. data/test/unit/scope/finder_methods_test.rb +117 -0
  91. data/test/unit/scope/query_methods_test.rb +32 -0
  92. data/test/unit/scoping_test.rb +7 -0
  93. data/test/unit/timestamps_test.rb +27 -0
  94. data/test/unit/types/array_type_test.rb +17 -0
  95. data/test/unit/types/base_type_test.rb +19 -0
  96. data/test/unit/types/boolean_type_test.rb +24 -0
  97. data/test/unit/types/date_type_test.rb +15 -0
  98. data/test/unit/types/float_type_test.rb +17 -0
  99. data/test/unit/types/integer_type_test.rb +19 -0
  100. data/test/unit/types/json_type_test.rb +23 -0
  101. data/test/unit/types/string_type_test.rb +25 -0
  102. data/test/unit/types/time_type_test.rb +14 -0
  103. data/test/unit/validations_test.rb +27 -0
  104. metadata +202 -0
@@ -0,0 +1,269 @@
1
+ gem 'cassandra-driver'
2
+ require 'cassandra'
3
+ require 'logger'
4
+
5
+ module CassandraObject
6
+ module Adapters
7
+ class CassandraAdapter < AbstractAdapter
8
+ class QueryBuilder
9
+
10
+ def initialize(adapter, scope)
11
+ @adapter = adapter
12
+ @scope = scope
13
+ end
14
+
15
+ def select_string
16
+ if @scope.select_values.any?
17
+ (['KEY'] | @scope.select_values) * ','
18
+ else
19
+ '*'
20
+ end
21
+ end
22
+
23
+ def to_query_async
24
+ # empty ids
25
+ if @scope.id_values.empty?
26
+ str = [
27
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
28
+ where_string_async(nil)
29
+ ]
30
+ str << "ALLOW FILTERING" if @scope.klass.allow_filtering
31
+ return [] << str.delete_if(&:blank?) * ' '
32
+ end
33
+ @scope.id_values.map { |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
+ }
41
+ end
42
+
43
+ def where_string_async(id)
44
+ wheres = @scope.where_values.dup
45
+ wheres << "#{@scope._key} = '#{id}'" if !id.nil?
46
+ "WHERE #{wheres * ' AND '}" if wheres.any?
47
+ end
48
+
49
+ end
50
+
51
+ def cassandra_cluster_options
52
+ cluster_options = config.slice(*[
53
+ :auth_provider,
54
+ :client_cert,
55
+ :compression,
56
+ :compressor,
57
+ :connect_timeout,
58
+ :connections_per_local_node,
59
+ :connections_per_remote_node,
60
+ :consistency,
61
+ :credentials,
62
+ :futures_factory,
63
+ :hosts,
64
+ :load_balancing_policy,
65
+ :logger,
66
+ :page_size,
67
+ :passphrase,
68
+ :password,
69
+ :port,
70
+ :private_key,
71
+ :protocol_version,
72
+ :reconnection_policy,
73
+ :retry_policy,
74
+ :schema_refresh_delay,
75
+ :schema_refresh_timeout,
76
+ :server_cert,
77
+ :ssl,
78
+ :timeout,
79
+ :trace,
80
+ :username
81
+ ])
82
+
83
+ {
84
+ load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
85
+ reconnection_policy: 'Cassandra::Reconnection::Policies::%s',
86
+ retry_policy: 'Cassandra::Retry::Policies::%s'
87
+ }.each do |policy_key, class_template|
88
+ if cluster_options[policy_key]
89
+ cluster_options[policy_key] = (class_template % [policy_key.classify]).constantize
90
+ end
91
+ end
92
+
93
+ # Setting defaults
94
+ cluster_options.merge!({
95
+ consistency: cluster_options[:consistency] || :quorum,
96
+ protocol_version: cluster_options[:protocol_version] || 3,
97
+ page_size: cluster_options[:page_size] || 10000
98
+ })
99
+ return cluster_options
100
+ end
101
+
102
+
103
+ def connection
104
+ @connection ||= begin
105
+ cluster = Cassandra.cluster cassandra_cluster_options
106
+ cluster.connect config[:keyspace]
107
+ end
108
+ end
109
+
110
+ def execute(statement, arguments = [])
111
+ ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: statement) do
112
+ type_hints = []
113
+ arguments.map { |a| type_hints << CassandraObject::Types::TypeHelper.guess_type(a) } if !arguments.nil?
114
+ connection.execute statement, arguments: arguments, type_hints: type_hints, consistency: consistency, page_size: config[:page_size]
115
+ end
116
+ end
117
+
118
+ def execute_async(queries, arguments = [])
119
+ futures = queries.map { |q|
120
+ ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: q) do
121
+ connection.execute_async q, arguments: arguments, consistency: consistency, page_size: config[:page_size]
122
+ end
123
+ }
124
+ futures.map do |future|
125
+ rows = future.get
126
+ rows
127
+ end
128
+ end
129
+
130
+ def select(scope)
131
+ queries = QueryBuilder.new(self, scope).to_query_async
132
+ # todo paginate
133
+ cql_rows = execute_async(queries).map{|item| item.rows.map{|x| x}}.flatten!
134
+ cql_rows.each do |cql_row|
135
+ attributes = cql_row.to_hash
136
+ key = attributes.delete(scope._key)
137
+ yield(key, attributes) unless attributes.empty?
138
+ end
139
+ end
140
+
141
+ def insert(table, id, attributes, ttl = nil)
142
+ write(table, id, attributes, ttl)
143
+ end
144
+
145
+ def update(table, id, attributes, ttl = nil)
146
+ write_update(table, id, attributes)
147
+ end
148
+
149
+ def write(table, id, attributes, ttl = nil)
150
+ statement = "INSERT INTO #{table} (#{(attributes.keys).join(',')}) VALUES (#{(['?'] * attributes.size).join(',')})"
151
+ statement += " USING TTL #{ttl.to_s}" if ttl.present?
152
+ arguments = attributes.values
153
+ execute(statement, arguments)
154
+ end
155
+
156
+ def write_update(table, id, attributes)
157
+ queries =[]
158
+ # id here is the name of the key of the model
159
+ id_value = attributes[id]
160
+ if (not_nil_attributes = attributes.reject { |key, value| value.nil? }).any?
161
+ statement = "INSERT INTO #{table} (#{(not_nil_attributes.keys).join(',')}) VALUES (#{(['?'] * not_nil_attributes.size).join(',')})"
162
+ queries << {query: statement, arguments: not_nil_attributes.values}
163
+ end
164
+ if (nil_attributes = attributes.select { |key, value| value.nil? }).any?
165
+ queries << {query: "DELETE #{nil_attributes.keys.join(',')} FROM #{table} WHERE #{id} = ?", arguments: [id_value.to_s]}
166
+ end
167
+ execute_batchable(queries)
168
+ end
169
+
170
+ def delete(table, key, ids)
171
+ ids = [ids] if !ids.is_a?(Array)
172
+ statement = "DELETE FROM #{table} WHERE #{key} IN (#{ids.map { |id| "'#{id}'" }.join(',')})"
173
+ execute(statement, nil)
174
+ end
175
+
176
+ def execute_batch(statements)
177
+ raise 'No can do' if statements.empty?
178
+ batch = connection.batch do |b|
179
+ statements.each do |statement|
180
+ b.add(statement[:query], arguments: statement[:arguments])
181
+ end
182
+ end
183
+ connection.execute(batch, page_size: config[:page_size])
184
+ end
185
+
186
+ # SCHEMA
187
+ def create_table(table_name, params = {})
188
+ stmt = "CREATE TABLE #{table_name} "
189
+ if params.any? && !params[:attributes].present?
190
+ raise 'No attributes for the table'
191
+ elsif !params[:attributes].include? 'PRIMARY KEY'
192
+ raise 'No PRIMARY KEY defined'
193
+ end
194
+
195
+ stmt += "(#{params[:attributes]})"
196
+ # WITH COMPACT STORAGE
197
+ schema_execute statement_create_with_options(stmt, params[:options]), config[:keyspace]
198
+ end
199
+
200
+ def drop_table(table_name, confirm = false)
201
+ count = (schema_execute "SELECT count(*) FROM #{table_name}", config[:keyspace]).rows.first['count']
202
+ if confirm || count == 0
203
+ schema_execute "DROP TABLE #{table_name}", config[:keyspace]
204
+ else
205
+ raise "The table #{table_name} is not empty! If you want to drop it add the option confirm = true"
206
+ end
207
+ end
208
+
209
+ def schema_execute(cql, keyspace)
210
+ schema_db = Cassandra.cluster cassandra_cluster_options
211
+ connection = schema_db.connect keyspace
212
+ connection.execute cql, consistency: consistency
213
+ end
214
+
215
+ def cassandra_version
216
+ @cassandra_version ||= execute('select release_version from system.local').rows.first['release_version'].to_f
217
+ end
218
+
219
+ # /SCHEMA
220
+
221
+ def consistency
222
+ defined?(@consistency) ? @consistency : nil
223
+ end
224
+
225
+ def consistency=(val)
226
+ @consistency = val
227
+ end
228
+
229
+ def statement_create_with_options(stmt, options = '')
230
+ if !options.nil?
231
+ statement_with_options stmt, options
232
+ else
233
+ # standard
234
+ if cassandra_version < 3
235
+ "#{stmt} WITH bloom_filter_fp_chance = 0.001
236
+ AND caching = '{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}'
237
+ AND comment = ''
238
+ AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
239
+ AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
240
+ AND dclocal_read_repair_chance = 0.0
241
+ AND default_time_to_live = 0
242
+ AND gc_grace_seconds = 864000
243
+ AND max_index_interval = 2048
244
+ AND memtable_flush_period_in_ms = 0
245
+ AND min_index_interval = 128
246
+ AND read_repair_chance = 1.0
247
+ AND speculative_retry = 'NONE';"
248
+ else
249
+ "#{stmt} WITH read_repair_chance = 0.0
250
+ AND dclocal_read_repair_chance = 0.1
251
+ AND gc_grace_seconds = 864000
252
+ AND bloom_filter_fp_chance = 0.01
253
+ AND caching = { 'keys' : 'ALL', 'rows_per_partition' : 'NONE' }
254
+ AND comment = ''
255
+ AND compaction = { 'class' : 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold' : 32, 'min_threshold' : 4 }
256
+ AND compression = { 'chunk_length_in_kb' : 64, 'class' : 'org.apache.cassandra.io.compress.LZ4Compressor' }
257
+ AND default_time_to_live = 0
258
+ AND speculative_retry = '99PERCENTILE'
259
+ AND min_index_interval = 128
260
+ AND max_index_interval = 2048
261
+ AND crc_check_chance = 1.0;
262
+ "
263
+ end
264
+ end
265
+ end
266
+
267
+ end
268
+ end
269
+ end
@@ -0,0 +1,306 @@
1
+ gem 'cassandra-driver'
2
+ require 'cassandra'
3
+ require 'logger'
4
+
5
+ module CassandraObject
6
+ module Adapters
7
+ class CassandraSchemalessAdapter < AbstractAdapter
8
+ class QueryBuilder
9
+
10
+ def initialize(adapter, scope)
11
+ @adapter = adapter
12
+ @scope = scope
13
+ end
14
+
15
+ def select_string
16
+ selected_values = @scope.select_values.select { |sv| sv == :column1 || sv == :values }
17
+ if selected_values.any?
18
+ (['KEY'] | selected_values) * ','
19
+ else
20
+ '*'
21
+ end
22
+ end
23
+
24
+ def to_query_async
25
+ # empty ids
26
+ return nil if !@scope.id_values.present? && !@scope.where_values.present? && !@scope.is_all && !@scope.limit_value.present?
27
+
28
+ if @scope.id_values.empty?
29
+ str = [
30
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
31
+ where_string_async(nil)
32
+ ]
33
+ str << "ALLOW FILTERING" if @scope.klass.allow_filtering
34
+ return [] << str.delete_if(&:blank?) * ' '
35
+ end
36
+ @scope.id_values.map { |id|
37
+ str = [
38
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
39
+ where_string_async(id)
40
+ ]
41
+ str << "ALLOW FILTERING" if @scope.klass.allow_filtering
42
+ str.delete_if(&:blank?) * ' '
43
+ }
44
+ end
45
+
46
+ def where_string_async(id)
47
+ conditions = []
48
+ conditions << "#{@adapter.primary_key_column} = '#{id}'" if !id.nil?
49
+ conditions += @scope.select_values.select { |sv| sv != :column1 }.map { |sv| 'column1 = ?' }
50
+ conditions += @scope.where_values.select.each_with_index { |_, i| i.even? }
51
+ return conditions.any? ? "WHERE #{conditions.join(' AND ')}" : nil
52
+ end
53
+
54
+ end
55
+
56
+ def primary_key_column
57
+ 'key'
58
+ end
59
+
60
+ def cassandra_cluster_options
61
+ cluster_options = config.slice(*[
62
+ :auth_provider,
63
+ :client_cert,
64
+ :compression,
65
+ :compressor,
66
+ :connect_timeout,
67
+ :connections_per_local_node,
68
+ :connections_per_remote_node,
69
+ :consistency,
70
+ :credentials,
71
+ :futures_factory,
72
+ :hosts,
73
+ :load_balancing_policy,
74
+ :logger,
75
+ :page_size,
76
+ :passphrase,
77
+ :password,
78
+ :port,
79
+ :private_key,
80
+ :protocol_version,
81
+ :reconnection_policy,
82
+ :retry_policy,
83
+ :schema_refresh_delay,
84
+ :schema_refresh_timeout,
85
+ :server_cert,
86
+ :ssl,
87
+ :timeout,
88
+ :trace,
89
+ :username
90
+ ])
91
+
92
+
93
+ {
94
+ load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
95
+ reconnection_policy: 'Cassandra::Reconnection::Policies::%s',
96
+ retry_policy: 'Cassandra::Retry::Policies::%s'
97
+ }.each do |policy_key, class_template|
98
+ if cluster_options[policy_key]
99
+ cluster_options[policy_key] = (class_template % [policy_key.classify]).constantize
100
+ end
101
+ end
102
+
103
+ # Setting defaults
104
+ cluster_options.merge!({
105
+ max_schema_agreement_wait: 1,
106
+ consistency: cluster_options[:consistency]||:quorum,
107
+ protocol_version: cluster_options[:protocol_version]||3,
108
+ page_size: cluster_options[:page_size] || 10000
109
+ })
110
+ return cluster_options
111
+ end
112
+
113
+
114
+ def connection
115
+ @connection ||= begin
116
+ cluster = Cassandra.cluster cassandra_cluster_options
117
+ cluster.connect config[:keyspace]
118
+ end
119
+ end
120
+
121
+ def execute(statement, arguments = [])
122
+ ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: statement) do
123
+ connection.execute statement, arguments: arguments, consistency: consistency, page_size: config[:page_size]
124
+ end
125
+ end
126
+
127
+ def execute_async(queries, arguments = [], per_page = nil, next_cursor = nil)
128
+ per_page ||= config[:page_size]
129
+ futures = queries.map { |q|
130
+ ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: q) do
131
+ connection.execute_async q, arguments: arguments, consistency: consistency, page_size: per_page, paging_state: next_cursor
132
+ end
133
+ }
134
+ futures.map do |future|
135
+ rows = future.get
136
+ rows
137
+ end
138
+ end
139
+
140
+ def pre_select(scope, per_page = nil, next_cursor = nil)
141
+ query = "SELECT DISTINCT #{primary_key_column} FROM #{scope.klass.column_family}"
142
+ ids = []
143
+ new_next_cursor = nil
144
+ execute_async([query], nil, per_page, next_cursor).map do |item|
145
+ item.rows.map { |x| ids << x[primary_key_column] }
146
+ new_next_cursor = item.paging_state if !item.last_page?
147
+ end
148
+ return {ids: ids, new_next_cursor: new_next_cursor}
149
+ end
150
+
151
+ def select(scope)
152
+ queries = QueryBuilder.new(self, scope).to_query_async
153
+ queries.compact! if queries.present?
154
+ raise CassandraObject::RecordNotFound if !queries.present?
155
+
156
+ arguments = scope.select_values.select { |sv| sv != :column1 }.map(&:to_s)
157
+ arguments += scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.empty? }.map(&:to_s)
158
+ records = execute_async(queries, arguments).map do |item|
159
+ # pagination
160
+ elems = []
161
+ loop do
162
+ item.rows.map { |x| elems << x }
163
+ break if item.last_page?
164
+ item = item.next_page
165
+ end
166
+ elems
167
+ end
168
+ records.flatten!
169
+ end
170
+
171
+ def insert(table, id, attributes, ttl = nil)
172
+ write(table, id, attributes, ttl)
173
+ end
174
+
175
+ def update(table, id, attributes, ttl = nil)
176
+ write(table, id, attributes, ttl)
177
+ end
178
+
179
+ def write(table, id, attributes, ttl)
180
+ queries = []
181
+ # puts attributes
182
+ attributes.each do |column, value|
183
+ if !value.nil?
184
+ query = "INSERT INTO #{table} (#{primary_key_column},column1,value) VALUES (?,?,?)"
185
+ query += " USING TTL #{ttl.to_s}" if !ttl.nil?
186
+ args = [id.to_s, column.to_s, value.to_s]
187
+
188
+ queries << {query: query, arguments: args}
189
+ else
190
+ queries << {query: "DELETE FROM #{table} WHERE #{primary_key_column} = ? AND column1= ?", arguments: [id.to_s, column.to_s]}
191
+ end
192
+ end
193
+ execute_batchable(queries)
194
+ end
195
+
196
+
197
+ def delete(table, ids)
198
+ ids = [ids] if !ids.is_a?(Array)
199
+ arguments = nil
200
+ arguments = ids if ids.size == 1
201
+ statement = "DELETE FROM #{table} WHERE #{create_ids_where_clause(ids)}" #.gsub('?', ids.map { |id| "'#{id}'" }.join(','))
202
+ execute(statement, arguments)
203
+ end
204
+
205
+ def execute_batch(statements)
206
+ raise 'No can do' if statements.empty?
207
+ batch = connection.batch do |b|
208
+ statements.each do |statement|
209
+ b.add(statement[:query], arguments: statement[:arguments])
210
+ end
211
+ end
212
+ connection.execute(batch, page_size: config[:page_size])
213
+ end
214
+
215
+ # SCHEMA
216
+ def create_table(table_name, params = {})
217
+ stmt = "CREATE TABLE #{table_name} (" +
218
+ 'key text,' +
219
+ 'column1 text,' +
220
+ 'value text,' +
221
+ 'PRIMARY KEY (key, column1)' +
222
+ ')'
223
+ # WITH COMPACT STORAGE
224
+ schema_execute statement_with_options(stmt, params[:options]), config[:keyspace]
225
+ end
226
+
227
+ def drop_table(table_name, confirm = false)
228
+ count = (schema_execute "SELECT count(*) FROM #{table_name}", config[:keyspace]).rows.first['count']
229
+ if confirm || count == 0
230
+ schema_execute "DROP TABLE #{table_name}", config[:keyspace]
231
+ else
232
+ raise "The table #{table_name} is not empty! If you want to drop it add the option confirm = true"
233
+ end
234
+ end
235
+
236
+ def schema_execute(cql, keyspace)
237
+ schema_db = Cassandra.cluster cassandra_cluster_options
238
+ connection = schema_db.connect keyspace
239
+ connection.execute cql, consistency: consistency
240
+ end
241
+
242
+ def cassandra_version
243
+ @cassandra_version ||= execute('select release_version from system.local').rows.first['release_version'].to_f
244
+ end
245
+
246
+ # /SCHEMA
247
+
248
+ def consistency
249
+ defined?(@consistency) ? @consistency : nil
250
+ end
251
+
252
+ def consistency=(val)
253
+ @consistency = val
254
+ end
255
+
256
+ def statement_create_with_options(stmt, options)
257
+ if !options.nil?
258
+ statement_with_options stmt, options
259
+ else
260
+ # standard
261
+ if cassandra_version < 3
262
+ "#{stmt} WITH COMPACT STORAGE
263
+ AND bloom_filter_fp_chance = 0.001
264
+ AND CLUSTERING ORDER BY (column1 ASC)
265
+ AND caching = '{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}'
266
+ AND comment = ''
267
+ AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
268
+ AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
269
+ AND dclocal_read_repair_chance = 0.0
270
+ AND default_time_to_live = 0
271
+ AND gc_grace_seconds = 864000
272
+ AND max_index_interval = 2048
273
+ AND memtable_flush_period_in_ms = 0
274
+ AND min_index_interval = 128
275
+ AND read_repair_chance = 1.0
276
+ AND speculative_retry = 'NONE';"
277
+ else
278
+ "#{stmt} WITH read_repair_chance = 0.0
279
+ AND dclocal_read_repair_chance = 0.1
280
+ AND gc_grace_seconds = 864000
281
+ AND bloom_filter_fp_chance = 0.01
282
+ AND caching = { 'keys' : 'ALL', 'rows_per_partition' : 'NONE' }
283
+ AND comment = ''
284
+ AND compaction = { 'class' : 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold' : 32, 'min_threshold' : 4 }
285
+ AND compression = { 'chunk_length_in_kb' : 64, 'class' : 'org.apache.cassandra.io.compress.LZ4Compressor' }
286
+ AND default_time_to_live = 0
287
+ AND speculative_retry = '99PERCENTILE'
288
+ AND min_index_interval = 128
289
+ AND max_index_interval = 2048
290
+ AND crc_check_chance = 1.0;
291
+ "
292
+
293
+ end
294
+ end
295
+ end
296
+
297
+ def create_ids_where_clause(ids)
298
+ return ids if ids.empty?
299
+ ids = ids.first if ids.is_a?(Array) && ids.one?
300
+ sql = ids.is_a?(Array) ? "#{primary_key_column} IN (#{ids.map { |id| "'#{id}'" }.join(',')})" : "#{primary_key_column} = ?"
301
+ return sql
302
+ end
303
+
304
+ end
305
+ end
306
+ end