cassandra_model 0.9.21 → 1.0.16
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.
- 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
|