extendi-cassandra_object 1.0.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.
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