cassandra_model 0.9.21 → 1.0.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +2 -2
- data/README.md +1 -1
- data/lib/cassandra_model.rb +6 -1
- data/lib/cassandra_model/batch_reactor.rb +1 -1
- data/lib/cassandra_model/composite_record.rb +11 -1
- data/lib/cassandra_model/composite_record_static.rb +35 -13
- data/lib/cassandra_model/counter_record.rb +14 -8
- data/lib/cassandra_model/data_modelling.rb +13 -2
- data/lib/cassandra_model/data_set.rb +13 -2
- data/lib/cassandra_model/meta_table.rb +9 -3
- data/lib/cassandra_model/mock_connection.rb +1 -9
- data/lib/cassandra_model/query_builder.rb +63 -19
- data/lib/cassandra_model/raw_connection.rb +25 -18
- data/lib/cassandra_model/record.rb +21 -3
- data/lib/cassandra_model/result_chunker.rb +29 -0
- data/lib/cassandra_model/result_limiter.rb +31 -0
- data/lib/cassandra_model/result_paginator.rb +31 -12
- data/lib/cassandra_model/result_reducer.rb +49 -0
- data/lib/cassandra_model/scopes.rb +14 -0
- data/lib/cassandra_model/session_compatibility.rb +20 -0
- data/lib/cassandra_model/table_definition.rb +74 -31
- data/lib/cassandra_model/table_descriptor.rb +3 -1
- metadata +37 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8caf227ae8b22d30047431ce0293f39a965c07aa
|
4
|
+
data.tar.gz: b03cf72cb34c9d9fa8f9e18ee4930f8e7574639a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e1d04f2b752b5509709bebfe0fe5e2c3ec79a57876ad4a246858e1ff2a3dc14aa27d342db54b90bf2e00a0e20c726b636511f2dd09b960c8ffbf7fc1fa76168a
|
7
|
+
data.tar.gz: 06b8f1d4f77529b16b0a530c0c981a2025313b20280c0627a71ef8c00ee6acc31ca0692ed89e8d99e746fe670c393ab67c8c427035b48fa8d1ae7037c60ad1c6
|
data/LICENSE.txt
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright 2014-
|
1
|
+
Copyright 2014-2016 Thomas RM Rogers
|
2
2
|
|
3
3
|
Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
you may not use this file except in compliance with the License.
|
@@ -10,4 +10,4 @@ Unless required by applicable law or agreed to in writing, software
|
|
10
10
|
distributed under the License is distributed on an "AS IS" BASIS,
|
11
11
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
12
|
See the License for the specific language governing permissions and
|
13
|
-
limitations under the License.
|
13
|
+
limitations under the License.
|
data/README.md
CHANGED
@@ -155,7 +155,7 @@ There are a number of features in Cassandra Model that may have missing or incom
|
|
155
155
|
|
156
156
|
## Copyright
|
157
157
|
|
158
|
-
Copyright 2014-
|
158
|
+
Copyright 2014-2016 Thomas Rogers.
|
159
159
|
|
160
160
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
161
161
|
|
data/lib/cassandra_model.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright 2014-
|
2
|
+
# Copyright 2014-2016 Thomas RM Rogers
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -23,6 +23,7 @@ require 'batch_reactor'
|
|
23
23
|
require 'active_support/all'
|
24
24
|
require 'active_support/core_ext/class/attribute_accessors'
|
25
25
|
|
26
|
+
require 'cassandra_model/session_compatibility'
|
26
27
|
require 'cassandra_model/concurrency_helper'
|
27
28
|
require 'cassandra_model/logging'
|
28
29
|
require 'cassandra_model/global_callbacks'
|
@@ -39,9 +40,13 @@ require 'cassandra_model/table_definition'
|
|
39
40
|
require 'cassandra_model/table_debug'
|
40
41
|
require 'cassandra_model/table_redux'
|
41
42
|
require 'cassandra_model/result_paginator'
|
43
|
+
require 'cassandra_model/result_limiter'
|
44
|
+
require 'cassandra_model/result_chunker'
|
45
|
+
require 'cassandra_model/result_reducer'
|
42
46
|
require 'cassandra_model/query_result'
|
43
47
|
require 'cassandra_model/query_builder'
|
44
48
|
require 'cassandra_model/displayable_attributes'
|
49
|
+
require 'cassandra_model/scopes'
|
45
50
|
require 'cassandra_model/record_debug'
|
46
51
|
require 'cassandra_model/record'
|
47
52
|
require 'cassandra_model/counter_record'
|
@@ -1,7 +1,17 @@
|
|
1
1
|
module CassandraModel
|
2
|
+
module CompositeCounterRecord
|
3
|
+
def increment_async!(counts)
|
4
|
+
futures = composite_rows.map { |record| record.internal_increment_async!(counts) }
|
5
|
+
|
6
|
+
futures << internal_increment_async!(counts)
|
7
|
+
Cassandra::Future.all(futures).then { self }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
2
11
|
module CompositeRecord
|
3
12
|
def self.included(klass)
|
4
13
|
klass.extend CompositeRecordStatic
|
14
|
+
klass.send(:include, CompositeCounterRecord) if klass < CounterRecord
|
5
15
|
end
|
6
16
|
|
7
17
|
def save_async(options = {})
|
@@ -44,7 +54,7 @@ module CassandraModel
|
|
44
54
|
|
45
55
|
def internal_attributes
|
46
56
|
internal_columns.inject({}) do |memo, column|
|
47
|
-
memo.merge(column => attribute(column))
|
57
|
+
memo.merge!(column => attribute(column))
|
48
58
|
end
|
49
59
|
end
|
50
60
|
end
|
@@ -1,30 +1,33 @@
|
|
1
1
|
module CassandraModel
|
2
|
+
#noinspection ALL
|
2
3
|
module CompositeRecordStatic
|
3
|
-
MUTEX = Mutex.new
|
4
|
-
|
5
4
|
extend Forwardable
|
6
5
|
|
7
6
|
def_delegator :table_config, :composite_defaults=
|
7
|
+
|
8
|
+
def self.extended(base)
|
9
|
+
base.instance_variable_set(:@mutex, Mutex.new)
|
10
|
+
end
|
8
11
|
|
9
12
|
def partition_key
|
10
|
-
table_data.composite_partition_key ||= internal_partition_key.map { |column|
|
13
|
+
table_data.composite_partition_key ||= internal_partition_key.map { |column| trim_column!(column, /^rk_/, composite_pk_map) || column }
|
11
14
|
end
|
12
15
|
|
13
16
|
def clustering_columns
|
14
|
-
table_data.composite_clustering_columns ||= internal_clustering_columns.map { |column|
|
17
|
+
table_data.composite_clustering_columns ||= internal_clustering_columns.map { |column| trim_column!(column, /^ck_/, composite_ck_map) || column }
|
15
18
|
end
|
16
19
|
|
17
20
|
def primary_key
|
18
21
|
table_data.composite_primary_key ||= (internal_partition_key + internal_clustering_columns).map do |column|
|
19
|
-
|
20
|
-
|
22
|
+
trim_column!(column, /^rk_/, composite_pk_map) ||
|
23
|
+
trim_column!(column, /^ck_/, composite_ck_map) ||
|
21
24
|
column
|
22
25
|
end.uniq
|
23
26
|
end
|
24
27
|
|
25
28
|
def columns
|
26
29
|
unless table_data.composite_columns
|
27
|
-
|
30
|
+
@mutex.synchronize do
|
28
31
|
return table_data.composite_columns if table_data.composite_columns
|
29
32
|
|
30
33
|
table_data.composite_pk_map = {}
|
@@ -37,6 +40,20 @@ module CassandraModel
|
|
37
40
|
|
38
41
|
alias :ensure_attributes_accessible! :columns
|
39
42
|
|
43
|
+
def denormalized_column_map(input_columns)
|
44
|
+
internal_columns.inject({}) do |memo, column|
|
45
|
+
result_column = input_columns.find { |input_column| input_column == column }
|
46
|
+
unless result_column
|
47
|
+
trimmed_column = trimmed_column(column, /^rk_/) ||
|
48
|
+
trimmed_column(column, /^ck_/) ||
|
49
|
+
column
|
50
|
+
result_column = input_columns.find { |input_column| input_column == trimmed_column }
|
51
|
+
end
|
52
|
+
memo[column] = result_column if result_column
|
53
|
+
memo
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
40
57
|
def composite_pk_map
|
41
58
|
ensure_attributes_accessible! unless table_data.composite_columns
|
42
59
|
table_data.composite_pk_map
|
@@ -122,22 +139,27 @@ module CassandraModel
|
|
122
139
|
|
123
140
|
def composite_columns
|
124
141
|
internal_columns.map do |column|
|
125
|
-
|
126
|
-
|
142
|
+
trim_column!(column, /^rk_/, table_data.composite_pk_map) ||
|
143
|
+
trim_column!(column, /^ck_/, table_data.composite_ck_map) ||
|
127
144
|
column
|
128
145
|
end.uniq
|
129
146
|
end
|
130
147
|
|
131
|
-
def
|
132
|
-
|
133
|
-
if
|
134
|
-
|
148
|
+
def trim_column!(column, column_trim, map)
|
149
|
+
trimmed_column = trimmed_column(column, column_trim)
|
150
|
+
if trimmed_column
|
151
|
+
trimmed_column.tap do |result_column|
|
135
152
|
map[result_column] = column
|
136
153
|
map[column] = result_column
|
137
154
|
end
|
138
155
|
end
|
139
156
|
end
|
140
157
|
|
158
|
+
def trimmed_column(column, column_trim)
|
159
|
+
column_str = column.to_s
|
160
|
+
column_str.gsub(column_trim, '').to_sym if column_str =~ column_trim
|
161
|
+
end
|
162
|
+
|
141
163
|
def select_clause(select)
|
142
164
|
select = select_columns(select) if select
|
143
165
|
super(select)
|
@@ -2,6 +2,20 @@ module CassandraModel
|
|
2
2
|
class CounterRecord < Record
|
3
3
|
|
4
4
|
def increment_async!(options)
|
5
|
+
internal_increment_async!(options)
|
6
|
+
end
|
7
|
+
|
8
|
+
def increment!(options)
|
9
|
+
increment_async!(options).get
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def internal_save_async(options = {})
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def internal_increment_async!(options)
|
5
19
|
counter_clause = counter_clause(options)
|
6
20
|
row_key = internal_primary_key.values
|
7
21
|
statement = increment_statement(counter_clause)
|
@@ -22,14 +36,6 @@ module CassandraModel
|
|
22
36
|
end.then { self }
|
23
37
|
end
|
24
38
|
|
25
|
-
def increment!(options)
|
26
|
-
increment_async!(options).get
|
27
|
-
end
|
28
|
-
|
29
|
-
def save_async
|
30
|
-
raise NotImplementedError
|
31
|
-
end
|
32
|
-
|
33
39
|
private
|
34
40
|
|
35
41
|
def internal_primary_key
|
@@ -5,6 +5,10 @@ module CassandraModel
|
|
5
5
|
base.send(:include, CompositeRecord)
|
6
6
|
end
|
7
7
|
|
8
|
+
def table_properties=(value)
|
9
|
+
@table_properties = value
|
10
|
+
end
|
11
|
+
|
8
12
|
def model_data
|
9
13
|
inquirer = DataInquirer.new
|
10
14
|
data_set = DataSet.new
|
@@ -17,6 +21,13 @@ module CassandraModel
|
|
17
21
|
end
|
18
22
|
|
19
23
|
generate_composite_defaults_from_inquirer(inquirer)
|
24
|
+
columns
|
25
|
+
end
|
26
|
+
|
27
|
+
def serialized_column(column, serializer)
|
28
|
+
serialized_column = :"#{column}_data"
|
29
|
+
deferred_column column, on_load: ->(attributes) { serializer.load(attributes[serialized_column]) if attributes[serialized_column] },
|
30
|
+
on_save: ->(attributes, value) { attributes[serialized_column] = (serializer.dump(value) if value) }
|
20
31
|
end
|
21
32
|
|
22
33
|
private
|
@@ -37,9 +48,9 @@ module CassandraModel
|
|
37
48
|
end
|
38
49
|
|
39
50
|
def meta_table(table_name, inquirer, data_set)
|
40
|
-
table_definition = CassandraModel::TableDefinition.from_data_model(table_name, inquirer, data_set)
|
51
|
+
table_definition = CassandraModel::TableDefinition.from_data_model(table_name, inquirer, data_set, @table_properties)
|
41
52
|
CassandraModel::MetaTable.new(table_config.connection_name, table_definition)
|
42
53
|
end
|
43
54
|
|
44
55
|
end
|
45
|
-
end
|
56
|
+
end
|
@@ -2,12 +2,13 @@ module CassandraModel
|
|
2
2
|
class DataSet
|
3
3
|
include TypeGuessing
|
4
4
|
|
5
|
-
attr_reader :columns, :
|
5
|
+
attr_reader :columns, :data_rotation, :clustering_order
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
@columns = Hash.new { |hash, key| hash[key] = :text }
|
9
9
|
@data_rotation = {}
|
10
10
|
@clustering_columns = []
|
11
|
+
@clustering_order = {}
|
11
12
|
end
|
12
13
|
|
13
14
|
def knows_about(*columns)
|
@@ -34,7 +35,7 @@ module CassandraModel
|
|
34
35
|
|
35
36
|
def is_defined_by(*columns)
|
36
37
|
knows_about(*columns)
|
37
|
-
|
38
|
+
clustering_columns.concat(columns)
|
38
39
|
end
|
39
40
|
|
40
41
|
def change_type_of(column)
|
@@ -42,6 +43,16 @@ module CassandraModel
|
|
42
43
|
ColumnType.new(column, self)
|
43
44
|
end
|
44
45
|
|
46
|
+
def sorts(sorting)
|
47
|
+
unknown_column = sorting.keys.find { |column, _| !columns.include?(column) }
|
48
|
+
raise "Cannot sort unknown column #{unknown_column}" if unknown_column
|
49
|
+
clustering_order.merge!(sorting)
|
50
|
+
end
|
51
|
+
|
52
|
+
def clustering_columns
|
53
|
+
@clustering_columns ||= []
|
54
|
+
end
|
55
|
+
|
45
56
|
private
|
46
57
|
|
47
58
|
def count_column(column)
|
@@ -11,7 +11,7 @@ module CassandraModel
|
|
11
11
|
|
12
12
|
def name
|
13
13
|
@name ||= begin
|
14
|
-
|
14
|
+
table
|
15
15
|
name_in_cassandra
|
16
16
|
end
|
17
17
|
end
|
@@ -28,7 +28,7 @@ module CassandraModel
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def table
|
31
|
-
|
31
|
+
keyspace.table(name_in_cassandra) || create_table
|
32
32
|
end
|
33
33
|
|
34
34
|
def keyspace
|
@@ -42,7 +42,13 @@ module CassandraModel
|
|
42
42
|
sleep 0.100
|
43
43
|
break if keyspace.table(name_in_cassandra)
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
|
+
keyspace.table(name_in_cassandra).tap do |table|
|
47
|
+
unless table
|
48
|
+
descriptor.delete
|
49
|
+
raise "Could not verify the creation of table #{name_in_cassandra}"
|
50
|
+
end
|
51
|
+
end
|
46
52
|
end
|
47
53
|
|
48
54
|
def create_cassandra_table(descriptor)
|
@@ -1,15 +1,7 @@
|
|
1
1
|
module CassandraModel
|
2
2
|
class RawConnection
|
3
3
|
def cluster
|
4
|
-
@cluster ||=
|
5
|
-
Cassandra::Mocks::Cluster.new.tap do |cluster|
|
6
|
-
cluster.add_keyspace(config[:keyspace])
|
7
|
-
end
|
8
|
-
end
|
9
|
-
end
|
10
|
-
|
11
|
-
def session
|
12
|
-
@session ||= Cassandra::Mocks::Session.new(config[:keyspace], cluster)
|
4
|
+
@cluster ||= Cassandra::Mocks::Cluster.new
|
13
5
|
end
|
14
6
|
end
|
15
7
|
end
|
@@ -3,12 +3,13 @@ module CassandraModel
|
|
3
3
|
include Enumerable
|
4
4
|
extend Forwardable
|
5
5
|
|
6
|
-
|
6
|
+
EMPTY_OPTION = [].freeze
|
7
7
|
|
8
|
-
def initialize(record_klass)
|
8
|
+
def initialize(record_klass, params = {}, options = {}, extra_options = {})
|
9
9
|
@record_klass = record_klass
|
10
|
-
@params =
|
11
|
-
@options =
|
10
|
+
@params = params
|
11
|
+
@options = options
|
12
|
+
@extra_options = extra_options
|
12
13
|
end
|
13
14
|
|
14
15
|
def async
|
@@ -59,8 +60,7 @@ module CassandraModel
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def check_exists
|
62
|
-
@options.merge
|
63
|
-
self
|
63
|
+
new_instance(@params, @options.merge(check_exists: true), @extra_options)
|
64
64
|
end
|
65
65
|
|
66
66
|
def pluck(*columns)
|
@@ -73,12 +73,34 @@ module CassandraModel
|
|
73
73
|
end
|
74
74
|
|
75
75
|
def each_slice(slice_size = nil, &block)
|
76
|
+
raise NotImplementedError if @extra_options[:cluster]
|
76
77
|
paginate(slice_size).async.each_slice(&block)
|
77
78
|
end
|
78
79
|
|
80
|
+
def each(&block)
|
81
|
+
if @extra_options[:cluster]
|
82
|
+
enum = ResultChunker.new(async, @extra_options[:cluster])
|
83
|
+
enum = if @extra_options[:cluster_limit]
|
84
|
+
ResultLimiter.new(enum, @extra_options[:cluster_limit])
|
85
|
+
else
|
86
|
+
enum
|
87
|
+
end
|
88
|
+
block_given? ? enum.each(&block) : enum
|
89
|
+
else
|
90
|
+
async.each(&block)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def cluster(*columns)
|
95
|
+
new_instance(@params, @options, @extra_options.merge(cluster: columns))
|
96
|
+
end
|
97
|
+
|
98
|
+
def cluster_except(*columns)
|
99
|
+
cluster(*(@record_klass.primary_key - columns))
|
100
|
+
end
|
101
|
+
|
79
102
|
def where(params)
|
80
|
-
@params.merge
|
81
|
-
self
|
103
|
+
new_instance(@params.merge(params.symbolize_keys), @options, @extra_options)
|
82
104
|
end
|
83
105
|
|
84
106
|
def select(*columns)
|
@@ -90,33 +112,55 @@ module CassandraModel
|
|
90
112
|
end
|
91
113
|
|
92
114
|
def limit(limit)
|
93
|
-
@
|
94
|
-
|
115
|
+
if @extra_options[:cluster]
|
116
|
+
new_instance(@params, @options, @extra_options.merge(cluster_limit: limit))
|
117
|
+
else
|
118
|
+
new_instance(@params, @options.merge(limit: limit), @extra_options)
|
119
|
+
end
|
95
120
|
end
|
96
121
|
|
97
122
|
def trace(trace)
|
98
|
-
@options
|
99
|
-
self
|
123
|
+
new_instance(@params, @options.merge(trace: trace), @extra_options)
|
100
124
|
end
|
101
125
|
|
102
126
|
def paginate(page_size)
|
103
|
-
@options
|
104
|
-
|
127
|
+
new_instance(@params, @options.merge(page_size: page_size), @extra_options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def ==(rhs)
|
131
|
+
rhs.is_a?(QueryBuilder) &&
|
132
|
+
rhs.record_klass == record_klass &&
|
133
|
+
rhs.params == params &&
|
134
|
+
rhs.options == options &&
|
135
|
+
rhs.extra_options == extra_options
|
136
|
+
end
|
137
|
+
|
138
|
+
def method_missing(method, *args)
|
139
|
+
scope = record_klass.scopes[method]
|
140
|
+
scope ? instance_exec(*args, &scope) : super
|
105
141
|
end
|
106
142
|
|
143
|
+
protected
|
144
|
+
|
145
|
+
attr_reader :record_klass, :params, :options, :extra_options
|
146
|
+
|
107
147
|
private
|
108
148
|
|
149
|
+
def new_instance(params, options, extra_options)
|
150
|
+
self.class.new(record_klass, params, options, extra_options)
|
151
|
+
end
|
152
|
+
|
109
153
|
def append_option(columns, option)
|
110
|
-
@options[option]
|
154
|
+
new_option = (@options[option] || EMPTY_OPTION).dup
|
111
155
|
if columns.first.is_a?(Hash)
|
112
156
|
columns = columns.first.map do |column, direction|
|
113
|
-
{column => direction}
|
157
|
+
{column.to_sym => direction}
|
114
158
|
end
|
115
|
-
|
159
|
+
new_option.concat(columns)
|
116
160
|
else
|
117
|
-
|
161
|
+
new_option.concat(columns.map(&:to_sym))
|
118
162
|
end
|
119
|
-
|
163
|
+
new_instance(@params, @options.merge(option => new_option), @extra_options)
|
120
164
|
end
|
121
165
|
|
122
166
|
def pluck_values(columns, result)
|
@@ -1,10 +1,7 @@
|
|
1
1
|
module CassandraModel
|
2
|
+
#noinspection RubyTooManyInstanceVariablesInspection
|
2
3
|
class RawConnection
|
3
|
-
|
4
|
-
SESSION_MUTEX = Mutex.new
|
5
|
-
CONFIG_MUTEX = Mutex.new
|
6
|
-
STATEMENT_MUTEX = Mutex.new
|
7
|
-
REACTOR_MUTEX = Mutex.new
|
4
|
+
extend Forwardable
|
8
5
|
|
9
6
|
DEFAULT_CONFIGURATION = {
|
10
7
|
hosts: %w(localhost),
|
@@ -21,34 +18,46 @@ module CassandraModel
|
|
21
18
|
|
22
19
|
include ConcurrencyHelper
|
23
20
|
|
21
|
+
def_delegator :@executor, :value, :executor
|
22
|
+
def_delegator :@futures_factory, :value, :futures_factory
|
23
|
+
|
24
24
|
def initialize(config_name = nil)
|
25
25
|
@config_name = config_name
|
26
|
-
@statement_cache =
|
26
|
+
@statement_cache = Concurrent::Map.new
|
27
|
+
|
28
|
+
@cluster_mutex = Mutex.new
|
29
|
+
@session_mutex = Mutex.new
|
30
|
+
@config_mutex = Mutex.new
|
31
|
+
@reactor_mutex = Mutex.new
|
32
|
+
|
33
|
+
@executor = Concurrent::Delay.new { Concurrent::CachedThreadPool.new }
|
34
|
+
@futures_factory = Concurrent::Delay.new { Cassandra::Future::Factory.new(executor) }
|
27
35
|
end
|
28
36
|
|
29
37
|
def config=(value)
|
30
|
-
|
38
|
+
@config_mutex.synchronize { @config = DEFAULT_CONFIGURATION.merge(value) }
|
31
39
|
end
|
32
40
|
|
33
41
|
def config
|
34
|
-
safe_getset_variable(
|
42
|
+
safe_getset_variable(@config_mutex, :@config) { load_config }
|
35
43
|
end
|
36
44
|
|
37
45
|
def cluster
|
38
|
-
safe_getset_variable(
|
46
|
+
safe_getset_variable(@cluster_mutex, :@cluster) do
|
39
47
|
connection_configuration = config.slice(:hosts,
|
40
48
|
:compression,
|
41
49
|
:consistency,
|
42
50
|
:connection_timeout, :timeout,
|
43
51
|
:username, :password,
|
44
52
|
:address_resolution)
|
45
|
-
connection_configuration
|
53
|
+
connection_configuration[:logger] = Logging.logger
|
54
|
+
connection_configuration[:futures_factory] = futures_factory
|
46
55
|
Cassandra.cluster(connection_configuration)
|
47
56
|
end
|
48
57
|
end
|
49
58
|
|
50
59
|
def session
|
51
|
-
safe_getset_variable(
|
60
|
+
safe_getset_variable(@session_mutex, :@session) { cluster.connect(config[:keyspace]) }
|
52
61
|
end
|
53
62
|
|
54
63
|
def keyspace
|
@@ -68,14 +77,12 @@ module CassandraModel
|
|
68
77
|
end
|
69
78
|
|
70
79
|
def statement(query)
|
71
|
-
statement_cache
|
72
|
-
STATEMENT_MUTEX.synchronize { statement_cache[query] ||= session.prepare(query) }
|
73
|
-
end
|
80
|
+
statement_cache.fetch_or_store(query) { session.prepare(query) }
|
74
81
|
end
|
75
82
|
|
76
83
|
def shutdown
|
77
84
|
@shutdown = true
|
78
|
-
|
85
|
+
@reactor_mutex.synchronize do
|
79
86
|
@unlogged_reactor.stop.get if @unlogged_reactor
|
80
87
|
@unlogged_reactor = nil
|
81
88
|
|
@@ -85,11 +92,11 @@ module CassandraModel
|
|
85
92
|
@counter_reactor.stop.get if @counter_reactor
|
86
93
|
@counter_reactor = nil
|
87
94
|
end
|
88
|
-
|
95
|
+
@session_mutex.synchronize do
|
89
96
|
@session.close if @session
|
90
97
|
@session = nil
|
91
98
|
end
|
92
|
-
|
99
|
+
@cluster_mutex.synchronize do
|
93
100
|
@cluster.close if @cluster
|
94
101
|
@cluster = nil
|
95
102
|
end
|
@@ -122,7 +129,7 @@ module CassandraModel
|
|
122
129
|
end
|
123
130
|
|
124
131
|
def reactor(name, type)
|
125
|
-
safe_getset_variable(
|
132
|
+
safe_getset_variable(@reactor_mutex, name) do
|
126
133
|
BatchReactor.new(cluster, session, type, config[:batch_reactor] || {}).tap do |reactor|
|
127
134
|
reactor.start.get
|
128
135
|
end
|
@@ -3,6 +3,7 @@ require_relative 'meta_columns'
|
|
3
3
|
|
4
4
|
module CassandraModel
|
5
5
|
class Record
|
6
|
+
extend Scopes
|
6
7
|
extend QueryHelper
|
7
8
|
include MetaColumns
|
8
9
|
include DisplayableAttributes
|
@@ -34,6 +35,7 @@ module CassandraModel
|
|
34
35
|
:connection_name,
|
35
36
|
|
36
37
|
:write_consistency,
|
38
|
+
:serial_consistency,
|
37
39
|
:read_consistency,
|
38
40
|
|
39
41
|
:before_save_callbacks,
|
@@ -109,7 +111,9 @@ module CassandraModel
|
|
109
111
|
alias :to_s :inspect
|
110
112
|
|
111
113
|
def ==(rhs)
|
112
|
-
rhs.respond_to?(:attributes) &&
|
114
|
+
rhs.respond_to?(:attributes) && columns.all? do |column|
|
115
|
+
attributes[column] == rhs.attributes[column]
|
116
|
+
end
|
113
117
|
end
|
114
118
|
|
115
119
|
private
|
@@ -228,6 +232,7 @@ module CassandraModel
|
|
228
232
|
def write_query_options(options = {})
|
229
233
|
{}.tap do |new_option|
|
230
234
|
new_option[:consistency] = write_consistency if write_consistency
|
235
|
+
new_option[:serial_consistency] = serial_consistency if serial_consistency
|
231
236
|
new_option[:trace] = true if options[:trace]
|
232
237
|
end
|
233
238
|
end
|
@@ -236,6 +241,10 @@ module CassandraModel
|
|
236
241
|
self.class.write_consistency
|
237
242
|
end
|
238
243
|
|
244
|
+
def serial_consistency
|
245
|
+
self.class.serial_consistency
|
246
|
+
end
|
247
|
+
|
239
248
|
def column_values
|
240
249
|
attributes = internal_attributes
|
241
250
|
internal_columns.map { |column| attributes[column] }
|
@@ -355,7 +364,7 @@ module CassandraModel
|
|
355
364
|
def_delegator :table, :primary_key, :internal_primary_key
|
356
365
|
def_delegator :table, :name, :table_name
|
357
366
|
def_delegator :table, :columns, :internal_columns
|
358
|
-
def_delegators :table_config, :write_consistency, :read_consistency, :write_consistency=, :read_consistency=
|
367
|
+
def_delegators :table_config, :write_consistency, :serial_consistency, :read_consistency, :write_consistency=, :serial_consistency=, :read_consistency=
|
359
368
|
|
360
369
|
alias :partition_key :internal_partition_key
|
361
370
|
alias :clustering_columns :internal_clustering_columns
|
@@ -394,12 +403,21 @@ module CassandraModel
|
|
394
403
|
end
|
395
404
|
end
|
396
405
|
|
406
|
+
def denormalized_column_map(input_columns)
|
407
|
+
(columns & input_columns).inject({}) { |memo, column| memo.merge!(column => column) }
|
408
|
+
end
|
409
|
+
|
410
|
+
def composite_defaults
|
411
|
+
[]
|
412
|
+
end
|
413
|
+
|
397
414
|
def query_for_save(options = {})
|
398
415
|
existence_clause = options[:check_exists] && ' IF NOT EXISTS'
|
416
|
+
ttl_clause = options[:ttl] && " USING TTL #{options[:ttl]}"
|
399
417
|
column_names = internal_columns.join(', ')
|
400
418
|
column_sanitizers = (%w(?) * internal_columns.size).join(', ')
|
401
419
|
save_query = "INSERT INTO #{table_name} (#{column_names}) VALUES (#{column_sanitizers})"
|
402
|
-
"#{save_query}#{existence_clause}"
|
420
|
+
"#{save_query}#{existence_clause}#{ttl_clause}"
|
403
421
|
end
|
404
422
|
|
405
423
|
def query_for_delete
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
class ResultChunker
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(enum, cluster)
|
6
|
+
@enum = enum
|
7
|
+
@cluster = cluster
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
enum.chunk do |value|
|
12
|
+
value.attributes.values_at(*cluster)
|
13
|
+
end.each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias :get :to_a
|
17
|
+
|
18
|
+
def ==(rhs)
|
19
|
+
rhs.is_a?(ResultChunker) &&
|
20
|
+
enum == rhs.enum &&
|
21
|
+
cluster == rhs.cluster
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
attr_reader :enum, :cluster
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
class ResultLimiter
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(enum, limit)
|
6
|
+
@enum = enum
|
7
|
+
@limit = limit
|
8
|
+
end
|
9
|
+
|
10
|
+
def each
|
11
|
+
return to_enum(:each) unless block_given?
|
12
|
+
|
13
|
+
@enum.each.with_index do |value, index|
|
14
|
+
break if index >= @limit
|
15
|
+
yield value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
alias :get :to_a
|
20
|
+
|
21
|
+
def ==(rhs)
|
22
|
+
rhs.is_a?(ResultLimiter) &&
|
23
|
+
enum == rhs.enum &&
|
24
|
+
limit == rhs.limit
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
attr_reader :enum, :limit
|
30
|
+
end
|
31
|
+
end
|
@@ -13,25 +13,44 @@ module CassandraModel
|
|
13
13
|
each_slice { |slice| slice.each(&block) }
|
14
14
|
end
|
15
15
|
|
16
|
-
def each_slice
|
16
|
+
def each_slice(&block)
|
17
17
|
return to_enum(:each_slice) unless block_given?
|
18
18
|
|
19
19
|
current_page = @page
|
20
|
-
|
21
|
-
|
22
|
-
modified_results = page_results.map { |result| @callback.call(result, page_results.execution_info) }
|
23
|
-
break if page_results.empty?
|
24
|
-
if page_results.last_page?
|
25
|
-
yield modified_results
|
26
|
-
break
|
27
|
-
else
|
28
|
-
current_page = page_results.next_page_async
|
29
|
-
yield modified_results
|
30
|
-
end
|
20
|
+
while current_page
|
21
|
+
current_page = iterate_page(current_page, &block)
|
31
22
|
end
|
32
23
|
end
|
33
24
|
|
34
25
|
alias :get :to_a
|
35
26
|
|
27
|
+
private
|
28
|
+
|
29
|
+
def iterate_page(current_page, &block)
|
30
|
+
page_results = current_page.get
|
31
|
+
unless page_results.empty?
|
32
|
+
next_page(page_results, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def next_page(page_results, &block)
|
37
|
+
if page_results.last_page?
|
38
|
+
modify_and_yield_page_results(page_results, &block)
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
current_page = page_results.next_page_async
|
42
|
+
modify_and_yield_page_results(page_results, &block)
|
43
|
+
current_page
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def modify_and_yield_page_results(page_results)
|
48
|
+
yield modified_page_results(page_results)
|
49
|
+
end
|
50
|
+
|
51
|
+
def modified_page_results(page_results)
|
52
|
+
page_results.map { |result| @callback[result, page_results.execution_info] }
|
53
|
+
end
|
54
|
+
|
36
55
|
end
|
37
56
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module CassandraModel
|
2
|
+
class ResultReducer
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(enum, filter_keys)
|
6
|
+
@enum = enum
|
7
|
+
@filter_keys = filter_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
def each(&block)
|
11
|
+
return to_enum(:each) unless block_given?
|
12
|
+
|
13
|
+
@enum.each do |_, rows|
|
14
|
+
if @filter_keys.one?
|
15
|
+
yield rows.first
|
16
|
+
elsif @filter_keys.any?
|
17
|
+
filter_results(rows, &block)
|
18
|
+
else
|
19
|
+
rows.each(&block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def filter_results(rows)
|
27
|
+
prev_filter = []
|
28
|
+
|
29
|
+
rows.each.with_index do |row, index|
|
30
|
+
break if index >= @filter_keys.length
|
31
|
+
next_filter = row_filter(row, index)
|
32
|
+
break unless next_filter == prev_filter
|
33
|
+
prev_filter = next_filter << row_filter_key(index, row)
|
34
|
+
yield row
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def row_filter(row, filter_length)
|
39
|
+
filter_length.times.map do |index|
|
40
|
+
row_filter_key(index, row)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def row_filter_key(index, row)
|
45
|
+
row.attributes[@filter_keys[index]]
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# only install this hack if our driver is incompatible with the old interface
|
2
|
+
if Gem::Version.new(Gem.loaded_specs['cassandra-driver'].version) >= Gem::Version.new('2')
|
3
|
+
module Cassandra
|
4
|
+
class Session
|
5
|
+
|
6
|
+
alias :__execute_async :execute_async
|
7
|
+
|
8
|
+
def execute_async(statement, *args)
|
9
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
10
|
+
options = options.merge(arguments: args) unless options[:arguments]
|
11
|
+
__execute_async(statement, options)
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(statement, *args)
|
15
|
+
execute_async(statement, *args).get
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -2,28 +2,72 @@ module CassandraModel
|
|
2
2
|
class TableDefinition
|
3
3
|
attr_reader :name
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def from_data_model(table_name, inquirer, data_set, properties)
|
8
|
+
partition_key = inquirer_partition_key(inquirer)
|
9
|
+
if inquirer.shard_column
|
10
|
+
if inquirer.shard_column.is_a?(Hash)
|
11
|
+
column_name, type = inquirer.shard_column.first
|
12
|
+
partition_key.merge!(:"rk_#{column_name}" => type)
|
13
|
+
else
|
14
|
+
partition_key.merge!(:"rk_#{inquirer.shard_column}" => :int)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
clustering_columns = table_set_clustering_columns(data_set)
|
18
|
+
remaining_columns = table_set_remaining_columns(data_set)
|
19
|
+
|
20
|
+
updated_properties = table_properties_from_data_set(data_set, properties)
|
21
|
+
|
22
|
+
new(name: table_name, partition_key: partition_key,
|
23
|
+
clustering_columns: clustering_columns,
|
24
|
+
remaining_columns: remaining_columns,
|
25
|
+
properties: updated_properties)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def table_properties_from_data_set(data_set, properties)
|
31
|
+
if data_set.clustering_order.present?
|
32
|
+
updated_clustering_order = data_set.clustering_order.inject({}) do |memo, (column, order)|
|
33
|
+
memo.merge!(:"ck_#{column}" => order)
|
34
|
+
end
|
35
|
+
properties.merge(clustering_order: updated_clustering_order)
|
11
36
|
else
|
12
|
-
|
37
|
+
properties
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def table_set_remaining_columns(data_set)
|
42
|
+
data_set.columns.except(*data_set.clustering_columns)
|
43
|
+
end
|
44
|
+
|
45
|
+
def table_set_clustering_columns(data_set)
|
46
|
+
data_set.clustering_columns.inject({}) do |memo, column|
|
47
|
+
memo.merge!(:"ck_#{column}" => data_set.columns[column])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def inquirer_partition_key(inquirer)
|
52
|
+
inquirer.partition_key.inject({}) do |memo, (key, value)|
|
53
|
+
memo.merge!(:"rk_#{key}" => value)
|
13
54
|
end
|
14
55
|
end
|
15
|
-
|
16
|
-
remaining_columns = table_set_remaining_columns(data_set)
|
17
|
-
new(name: table_name, partition_key: partition_key,
|
18
|
-
clustering_columns: clustering_columns,
|
19
|
-
remaining_columns: remaining_columns)
|
56
|
+
|
20
57
|
end
|
21
58
|
|
59
|
+
attr_reader :table_id, :name_in_cassandra
|
60
|
+
|
22
61
|
def initialize(options)
|
23
62
|
@partition_key = options[:partition_key].keys
|
24
63
|
@clustering_columns = options[:clustering_columns].keys
|
25
64
|
@name = options[:name]
|
26
65
|
@columns = options[:partition_key].merge(options[:clustering_columns].merge(options[:remaining_columns]))
|
66
|
+
@table_id = generate_table_id
|
67
|
+
@name_in_cassandra = "#{name}_#{table_id}"
|
68
|
+
@properties = (options[:properties] || {}).select do |name, _|
|
69
|
+
[:compaction, :clustering_order].include?(name)
|
70
|
+
end
|
27
71
|
end
|
28
72
|
|
29
73
|
def to_cql(options = {})
|
@@ -31,15 +75,18 @@ module CassandraModel
|
|
31
75
|
exists = if options[:check_exists]
|
32
76
|
'IF NOT EXISTS '
|
33
77
|
end
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
78
|
+
properties = if @properties.present?
|
79
|
+
property_values = @properties.map do |property, definition|
|
80
|
+
case property
|
81
|
+
when :compaction
|
82
|
+
"COMPACTION = #{to_property_string(definition)}"
|
83
|
+
when :clustering_order
|
84
|
+
"CLUSTERING ORDER BY #{to_clustering_order_string(definition)}"
|
85
|
+
end
|
86
|
+
end * ' AND '
|
87
|
+
" WITH #{property_values}"
|
88
|
+
end
|
89
|
+
"CREATE TABLE #{exists}#{table_name} (#{columns}, PRIMARY KEY #{primary_key})#{properties}"
|
43
90
|
end
|
44
91
|
|
45
92
|
def ==(rhs)
|
@@ -48,20 +95,16 @@ module CassandraModel
|
|
48
95
|
|
49
96
|
private
|
50
97
|
|
51
|
-
def
|
52
|
-
|
98
|
+
def to_property_string(property)
|
99
|
+
"{#{property.map { |key, value| "'#{key}': '#{value}'" } * ', '}}"
|
53
100
|
end
|
54
101
|
|
55
|
-
def
|
56
|
-
|
57
|
-
memo.merge!(:"ck_#{column}" => data_set.columns[column])
|
58
|
-
end
|
102
|
+
def to_clustering_order_string(clustering_order)
|
103
|
+
"(#{clustering_order.map { |column, order| "#{column} #{order.upcase}" } * ', '})"
|
59
104
|
end
|
60
105
|
|
61
|
-
def
|
62
|
-
|
63
|
-
memo.merge!(:"rk_#{key}" => value)
|
64
|
-
end
|
106
|
+
def generate_table_id
|
107
|
+
Digest::MD5.hexdigest(columns)
|
65
108
|
end
|
66
109
|
|
67
110
|
def columns
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module CassandraModel
|
2
2
|
class TableDescriptor < Record
|
3
3
|
|
4
|
+
self.serial_consistency = :serial
|
5
|
+
|
4
6
|
class << self
|
5
7
|
def create_async(table_definition)
|
6
8
|
super(table_descriptor(table_definition), check_exists: true)
|
@@ -46,4 +48,4 @@ module CassandraModel
|
|
46
48
|
end
|
47
49
|
|
48
50
|
end
|
49
|
-
end
|
51
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cassandra_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas RM Rogers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: cassandra-driver
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.1'
|
20
|
+
- - <=
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.1
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
|
-
- -
|
27
|
+
- - '>='
|
25
28
|
- !ruby/object:Gem::Version
|
26
29
|
version: '1.1'
|
30
|
+
- - <=
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.1
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: activesupport
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,18 +60,38 @@ dependencies:
|
|
54
60
|
version: 0.0.1
|
55
61
|
- !ruby/object:Gem::Dependency
|
56
62
|
name: thomas_utils
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 0.1.19
|
68
|
+
- - <=
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0.3'
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.1.19
|
78
|
+
- - <=
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0.3'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: concurrent-ruby
|
57
83
|
requirement: !ruby/object:Gem::Requirement
|
58
84
|
requirements:
|
59
85
|
- - ~>
|
60
86
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
87
|
+
version: 1.0.0
|
62
88
|
type: :runtime
|
63
89
|
prerelease: false
|
64
90
|
version_requirements: !ruby/object:Gem::Requirement
|
65
91
|
requirements:
|
66
92
|
- - ~>
|
67
93
|
- !ruby/object:Gem::Version
|
68
|
-
version: 0.
|
94
|
+
version: 1.0.0
|
69
95
|
description: |-
|
70
96
|
Cassandra data modelling framework for Ruby that makes
|
71
97
|
data modelling for Cassandra tables easy, fast, and stable
|
@@ -99,8 +125,13 @@ files:
|
|
99
125
|
- lib/cassandra_model/raw_connection.rb
|
100
126
|
- lib/cassandra_model/record.rb
|
101
127
|
- lib/cassandra_model/record_debug.rb
|
128
|
+
- lib/cassandra_model/result_chunker.rb
|
129
|
+
- lib/cassandra_model/result_limiter.rb
|
102
130
|
- lib/cassandra_model/result_paginator.rb
|
131
|
+
- lib/cassandra_model/result_reducer.rb
|
103
132
|
- lib/cassandra_model/rotating_table.rb
|
133
|
+
- lib/cassandra_model/scopes.rb
|
134
|
+
- lib/cassandra_model/session_compatibility.rb
|
104
135
|
- lib/cassandra_model/single_token_batch.rb
|
105
136
|
- lib/cassandra_model/single_token_counter_batch.rb
|
106
137
|
- lib/cassandra_model/single_token_logged_batch.rb
|