cassandra_mocks 0.0.13 → 1.0.8
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/lib/cassandra_mocks.rb +3 -0
- data/lib/cassandra_mocks/cluster.rb +6 -3
- data/lib/cassandra_mocks/keyspace.rb +7 -3
- data/lib/cassandra_mocks/record.rb +6 -0
- data/lib/cassandra_mocks/row.rb +90 -0
- data/lib/cassandra_mocks/session.rb +22 -7
- data/lib/cassandra_mocks/statement.rb +12 -2
- data/lib/cassandra_mocks/statement/arithmetic.rb +2 -2
- data/lib/cassandra_mocks/table.rb +171 -57
- metadata +26 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 952c46e902eb98a2c45ff133302d2f867a9e3f06
|
|
4
|
+
data.tar.gz: 80aae79947b0bf4c7c7c6d3d7ecbf6724dc1549f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b5bc8c274cbd897c234e41c4ac4ad5bc77d6db745733146b63866ecb77af307e34b490d50e5c45cf39dc500875dc81443444bd4da331ef369207fc3cf9b323b
|
|
7
|
+
data.tar.gz: 5c9411783f168d2228d8c3717166e5179f9ff28188240ee8d347fdbdf7284d8b94a820f410eb386b4fbe710e6904e3396a9e52058ecb957c55b77f03e558636c
|
data/lib/cassandra_mocks.rb
CHANGED
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
#++
|
|
16
16
|
|
|
17
|
+
require 'concurrent'
|
|
17
18
|
require 'cassandra'
|
|
18
19
|
require 'active_support/all'
|
|
19
20
|
require 'active_support/core_ext/class/attribute_accessors'
|
|
20
21
|
|
|
21
22
|
require 'cassandra_mocks/result_page'
|
|
23
|
+
require 'cassandra_mocks/record'
|
|
24
|
+
require 'cassandra_mocks/row'
|
|
22
25
|
require 'cassandra_mocks/table'
|
|
23
26
|
require 'cassandra_mocks/statement/batch'
|
|
24
27
|
require 'cassandra_mocks/statement/arithmetic'
|
|
@@ -6,7 +6,7 @@ module Cassandra
|
|
|
6
6
|
def_delegator :@keyspaces, :[], :keyspace
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
|
-
@keyspaces =
|
|
9
|
+
@keyspaces = Concurrent::Map.new
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
def connect_async(keyspace = nil)
|
|
@@ -26,8 +26,11 @@ module Cassandra
|
|
|
26
26
|
connect_async(keyspace).get
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def add_keyspace(name)
|
|
30
|
-
|
|
29
|
+
def add_keyspace(name, ignore_existing)
|
|
30
|
+
if @keyspaces[name]
|
|
31
|
+
return if ignore_existing
|
|
32
|
+
raise Errors::AlreadyExistsError.new('Cannot create already existing keyspace', 'MockStatement', name, nil)
|
|
33
|
+
end
|
|
31
34
|
@keyspaces[name] = Keyspace.new(name)
|
|
32
35
|
end
|
|
33
36
|
|
|
@@ -5,11 +5,15 @@ module Cassandra
|
|
|
5
5
|
def initialize(name)
|
|
6
6
|
replication = Replication.new('mock', {})
|
|
7
7
|
super(name, false, replication, {})
|
|
8
|
+
@tables = Concurrent::Map.new
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
def add_table(table_name, primary_key, columns)
|
|
11
|
+
def add_table(table_name, primary_key, columns, ignore_existing)
|
|
11
12
|
raise Errors::InvalidError.new("Table name '#{table_name}' cannot be greater than 48 characters", 'MockStatement') if table_name.length > 48
|
|
12
|
-
|
|
13
|
+
if @tables[table_name]
|
|
14
|
+
return if ignore_existing
|
|
15
|
+
raise Errors::AlreadyExistsError.new('Cannot create already existing table', 'MockStatement', nil, table_name)
|
|
16
|
+
end
|
|
13
17
|
|
|
14
18
|
partition_key = primary_key.shift
|
|
15
19
|
partition_key_columns = partition_key_part(columns, partition_key)
|
|
@@ -29,7 +33,7 @@ module Cassandra
|
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
def fields(columns, partition_key, primary_key)
|
|
32
|
-
columns.except(partition_key + primary_key).map { |name, type| Cassandra::Column.new(name, type, :asc) }
|
|
36
|
+
columns.except(*(partition_key + primary_key)).map { |name, type| Cassandra::Column.new(name, type, :asc) }
|
|
33
37
|
end
|
|
34
38
|
|
|
35
39
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module Cassandra
|
|
2
|
+
module Mocks
|
|
3
|
+
class Row
|
|
4
|
+
include MonitorMixin
|
|
5
|
+
|
|
6
|
+
attr_reader :clusters, :partition_key
|
|
7
|
+
|
|
8
|
+
def initialize(partition_key)
|
|
9
|
+
@clusters = {}
|
|
10
|
+
@partition_key = partition_key
|
|
11
|
+
super()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def insert_record(clustering_columns, record_values, check_exists)
|
|
15
|
+
synchronize do
|
|
16
|
+
record_cluster = record_cluster(clustering_columns[0..-2])
|
|
17
|
+
update_record(check_exists, clustering_columns, record_cluster, record_values)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def find_records(clustering_columns)
|
|
22
|
+
cluster = find_cluster(clustering_columns)
|
|
23
|
+
if cluster
|
|
24
|
+
[cluster_values(cluster, [*clustering_columns])].flatten.map(&:values)
|
|
25
|
+
else
|
|
26
|
+
[]
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def delete_records(clustering_columns)
|
|
31
|
+
synchronize do
|
|
32
|
+
if clustering_columns.any?
|
|
33
|
+
cluster = find_cluster(clustering_columns[0..-2])
|
|
34
|
+
cluster.delete(clustering_columns.last) if cluster
|
|
35
|
+
else
|
|
36
|
+
@clusters.clear
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def cluster_values(cluster, values)
|
|
44
|
+
if cluster.is_a?(Record)
|
|
45
|
+
Record.new([*partition_key, *values, *cluster.values])
|
|
46
|
+
else
|
|
47
|
+
cluster.map do |clustering_key, child_cluster|
|
|
48
|
+
cluster_values(child_cluster, values + [clustering_key])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def find_cluster(clustering_columns)
|
|
54
|
+
clustering_columns.inject(@clusters) do |cluster, cluster_key|
|
|
55
|
+
cluster[cluster_key] if cluster
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def record_cluster(partial_clustering_columns)
|
|
60
|
+
partial_clustering_columns.inject(clusters) do |memo, cluster_key|
|
|
61
|
+
memo[cluster_key] ||= {}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def update_record(check_exists, clustering_columns, record_cluster, record_values)
|
|
66
|
+
previous_record = record_cluster[clustering_columns.last]
|
|
67
|
+
if previous_record
|
|
68
|
+
update_if_not_exists(check_exists, previous_record, record_values)
|
|
69
|
+
else
|
|
70
|
+
create_record(clustering_columns, record_cluster, record_values)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def create_record(clustering_columns, record_cluster, record_values)
|
|
75
|
+
record_cluster[clustering_columns.last] = Record.new(record_values)
|
|
76
|
+
true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def update_if_not_exists(check_exists, previous_record, record_values)
|
|
80
|
+
if check_exists
|
|
81
|
+
false
|
|
82
|
+
else
|
|
83
|
+
previous_record.values = record_values
|
|
84
|
+
true
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -25,6 +25,9 @@ module Cassandra
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def execute_async(cql, *args)
|
|
28
|
+
options = fetch_options(args)
|
|
29
|
+
args = options.fetch(:arguments) { args }
|
|
30
|
+
|
|
28
31
|
if cql.is_a?(Cassandra::Statements::Batch)
|
|
29
32
|
futures = cql.statements.map do |batched_statement|
|
|
30
33
|
execute_async(batched_statement.statement, *batched_statement.args)
|
|
@@ -35,17 +38,18 @@ module Cassandra
|
|
|
35
38
|
future = cql.is_a?(Statement) ? Cassandra::Future.value(cql.fill_params(args)) : prepare_async(cql)
|
|
36
39
|
future.then do |statement|
|
|
37
40
|
result = ResultPage.new
|
|
41
|
+
# noinspection RubyCaseWithoutElseBlockInspection
|
|
38
42
|
case statement.action
|
|
39
43
|
when :create_keyspace
|
|
40
|
-
cluster.add_keyspace(statement.args[:keyspace])
|
|
44
|
+
cluster.add_keyspace(statement.args[:keyspace], statement.args[:check_exists])
|
|
41
45
|
when :create_table
|
|
42
|
-
cluster.keyspace(keyspace).add_table(statement.args[:table], statement.args[:primary_key], statement.args[:columns])
|
|
46
|
+
cluster.keyspace(keyspace).add_table(statement.args[:table], statement.args[:primary_key], statement.args[:columns], statement.args[:check_exists])
|
|
43
47
|
when :insert
|
|
44
48
|
insert_query(result, statement)
|
|
45
49
|
when :update
|
|
46
50
|
update_query(statement)
|
|
47
51
|
when :truncate
|
|
48
|
-
cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table]).
|
|
52
|
+
cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table]).clear
|
|
49
53
|
when :drop_keyspace
|
|
50
54
|
cluster.drop_keyspace(statement.args[:keyspace])
|
|
51
55
|
when :drop_table
|
|
@@ -71,9 +75,19 @@ module Cassandra
|
|
|
71
75
|
|
|
72
76
|
private
|
|
73
77
|
|
|
78
|
+
def fetch_options(args)
|
|
79
|
+
if args.last.is_a?(Hash)
|
|
80
|
+
args.pop
|
|
81
|
+
else
|
|
82
|
+
{arguments: args}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
74
86
|
def insert_query(result, statement)
|
|
75
87
|
check_exists = !!statement.args[:check_exists]
|
|
76
|
-
|
|
88
|
+
table = cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table])
|
|
89
|
+
raise Cassandra::Errors::InvalidError.new('INSERT statement are not allowed on counter tables, use UPDATE instead', 'MockStatement') if table.counter_table?
|
|
90
|
+
inserted = table.insert(statement.args[:values], check_exists: check_exists)
|
|
77
91
|
result << {'[applied]' => inserted} if check_exists
|
|
78
92
|
end
|
|
79
93
|
|
|
@@ -93,15 +107,16 @@ module Cassandra
|
|
|
93
107
|
rows_to_update = table.select('*', restriction: statement.args[:filter])
|
|
94
108
|
rows_to_update = [statement.args[:filter].dup] if rows_to_update.empty?
|
|
95
109
|
rows_to_update.each do |row|
|
|
96
|
-
updated_row = updated_row(row, statement)
|
|
110
|
+
updated_row = updated_row(table, row, statement)
|
|
97
111
|
cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table]).insert(updated_row)
|
|
98
112
|
end
|
|
99
113
|
end
|
|
100
114
|
|
|
101
|
-
def updated_row(row, statement)
|
|
115
|
+
def updated_row(table, row, statement)
|
|
102
116
|
statement.args[:values].inject(row.dup) do |memo, (column, value)|
|
|
103
117
|
if value.is_a?(Statement::Arithmetic)
|
|
104
|
-
|
|
118
|
+
raise Cassandra::Errors::InvalidError.new("Invalid operation (#{column} = #{column} + ?) for non counter column #{column}", 'MockStatement') unless table.counter_table?
|
|
119
|
+
value.apply!(memo)
|
|
105
120
|
else
|
|
106
121
|
memo.merge!(column => value)
|
|
107
122
|
end
|
|
@@ -16,7 +16,7 @@ module Cassandra
|
|
|
16
16
|
parse_create_table
|
|
17
17
|
else
|
|
18
18
|
@action = :create_keyspace
|
|
19
|
-
|
|
19
|
+
parse_create_keyspace
|
|
20
20
|
end
|
|
21
21
|
elsif type_token.truncate?
|
|
22
22
|
parse_truncate_query
|
|
@@ -124,6 +124,16 @@ module Cassandra
|
|
|
124
124
|
end
|
|
125
125
|
end
|
|
126
126
|
|
|
127
|
+
def parse_create_keyspace
|
|
128
|
+
keyspace_name_token = next_token
|
|
129
|
+
check_exists = if keyspace_name_token.if?
|
|
130
|
+
next_token
|
|
131
|
+
next_token
|
|
132
|
+
keyspace_name_token = next_token
|
|
133
|
+
end
|
|
134
|
+
@args = {keyspace: keyspace_name_token.value, check_exists: !!check_exists}
|
|
135
|
+
end
|
|
136
|
+
|
|
127
137
|
def parse_create_table
|
|
128
138
|
table_name_token = next_token
|
|
129
139
|
check_exists = if table_name_token.if?
|
|
@@ -159,7 +169,7 @@ module Cassandra
|
|
|
159
169
|
primary_key = [partition_key, *primary_key_parts]
|
|
160
170
|
end
|
|
161
171
|
|
|
162
|
-
@args = {table: table_name, check_exists: !!check_exists, columns: additional_columns.merge({column_name => column_type}), primary_key: primary_key}
|
|
172
|
+
@args = {table: table_name, check_exists: !!check_exists, columns: additional_columns.merge!({column_name => column_type}), primary_key: primary_key}
|
|
163
173
|
end
|
|
164
174
|
|
|
165
175
|
def parse_truncate_query
|
|
@@ -3,8 +3,8 @@ module Cassandra
|
|
|
3
3
|
class Statement
|
|
4
4
|
class Arithmetic < Struct.new(:operation, :column, :amount)
|
|
5
5
|
|
|
6
|
-
def apply(row)
|
|
7
|
-
row.merge(column => (row[column] || 0).public_send(operator, amount))
|
|
6
|
+
def apply!(row)
|
|
7
|
+
row.merge!(column => (row[column] || 0).public_send(operator, amount))
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
private
|
|
@@ -1,42 +1,96 @@
|
|
|
1
1
|
module Cassandra
|
|
2
2
|
module Mocks
|
|
3
3
|
class Table < Cassandra::Table
|
|
4
|
+
extend Forwardable
|
|
5
|
+
|
|
6
|
+
def_delegator :@rows, :clear
|
|
7
|
+
|
|
4
8
|
def initialize(keyspace, name, partition_key, clustering_key, fields)
|
|
9
|
+
@rows = Concurrent::Map.new { |hash, key| hash[key] = Row.new(key) }
|
|
10
|
+
|
|
5
11
|
compaction = Cassandra::Table::Compaction.new('mock', {})
|
|
6
12
|
options = Cassandra::Table::Options.new({}, compaction, {}, false, 'mock')
|
|
7
13
|
column_map = column_map(partition_key, clustering_key, fields)
|
|
14
|
+
|
|
15
|
+
@counter_column = fields.find { |field| field.type.to_sym == :counter }
|
|
16
|
+
has_non_counters = !!fields.find { |field| field.type.to_sym != :counter }
|
|
17
|
+
|
|
18
|
+
if counter_column && has_non_counters
|
|
19
|
+
raise Cassandra::Errors::ConfigurationError.new("Cannot add counter column '#{counter_column.name}' to non-counter column family", 'MockStatement')
|
|
20
|
+
end
|
|
21
|
+
|
|
8
22
|
super(keyspace, name, partition_key, clustering_key, column_map, options, [])
|
|
23
|
+
@column_map = columns.inject({}) do |memo, column|
|
|
24
|
+
memo.merge!(column.name => column)
|
|
25
|
+
end
|
|
26
|
+
@partition_key_set = Set.new(partition_key_names)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def rows
|
|
30
|
+
results = []
|
|
31
|
+
@rows.each_pair do |_, row|
|
|
32
|
+
row_results = record_attributes(row.find_records([]))
|
|
33
|
+
results << row_results
|
|
34
|
+
end
|
|
35
|
+
results.flatten
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def counter_table?
|
|
39
|
+
!!counter_column
|
|
9
40
|
end
|
|
10
41
|
|
|
11
42
|
def insert(attributes, options = {})
|
|
12
43
|
validate_columns!(attributes)
|
|
13
44
|
validate_primary_key_presence!(attributes)
|
|
14
45
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
if prev_row_index
|
|
20
|
-
return false if options[:check_exists]
|
|
21
|
-
rows[prev_row_index] = attributes
|
|
22
|
-
else
|
|
23
|
-
rows << attributes
|
|
24
|
-
end
|
|
25
|
-
true
|
|
46
|
+
row = @rows[attribute_partition_key(attributes)]
|
|
47
|
+
row.insert_record(attribute_clustering_key(attributes), attribute_fields(attributes), options[:check_exists])
|
|
26
48
|
end
|
|
27
49
|
|
|
28
50
|
def select(*columns)
|
|
29
51
|
filter = select_filter(columns)
|
|
30
52
|
limit = filter.delete(:limit)
|
|
31
53
|
order = select_order(filter)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
54
|
+
|
|
55
|
+
order_keys_in_partition = order.keys.select { |column| partition_key_names.include?(column) }
|
|
56
|
+
if order_keys_in_partition.any?
|
|
57
|
+
raise Cassandra::Errors::InvalidError.new("Order by is currently only supported on the clustered columns of the PRIMARY KEY, got #{order_keys_in_partition * ', '}", 'MockStatement')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
missing_ordering_keys = order.keys.select { |column| !column_names.include?(column) }
|
|
61
|
+
if missing_ordering_keys.any?
|
|
62
|
+
raise Cassandra::Errors::InvalidError.new("Order by on unknown column(s) #{missing_ordering_keys * ', '}", 'MockStatement')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
out_of_order = order.keys.each.with_index.any? { |column, index| clustering_key_names[index] != column }
|
|
66
|
+
if out_of_order
|
|
67
|
+
raise Cassandra::Errors::InvalidError.new("Order by currently only support the ordering of columns following their declared order in the PRIMARY KEY (expected #{clustering_key_names * ', '} got #{order.keys * ', '})", 'MockStatement')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
inconsistent_order = (order.values.uniq.count > 1)
|
|
71
|
+
if inconsistent_order
|
|
72
|
+
raise Cassandra::Errors::InvalidError.new('Ordering direction must be consistent across all clustering columns', 'MockStatement')
|
|
37
73
|
end
|
|
38
74
|
|
|
39
|
-
|
|
75
|
+
filter = filter.fetch(:restriction) { {} }
|
|
76
|
+
validate_filter!(filter)
|
|
77
|
+
|
|
78
|
+
filtered_rows = if filter.any?
|
|
79
|
+
partition_key = attribute_partition_key(filter)
|
|
80
|
+
partition_range = (partition_key.pop if partition_key.last.is_a?(Array))
|
|
81
|
+
cluster_filter = filter.except(*partition_key_names)
|
|
82
|
+
cluster_key = attribute_partial_clustering_key(cluster_filter)
|
|
83
|
+
_, cluster_slice = cluster_filter.find do |_, value|
|
|
84
|
+
restriction_is_slice?(value)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
records = partially_filtered_rows(partition_range, partition_key, cluster_key)
|
|
88
|
+
rows = record_attributes(records)
|
|
89
|
+
filtered_rows(cluster_slice, rows)
|
|
90
|
+
else
|
|
91
|
+
self.rows
|
|
92
|
+
end
|
|
93
|
+
|
|
40
94
|
sorted_rows = filtered_rows.sort do |lhs, rhs|
|
|
41
95
|
compare_rows(0, lhs, rhs, order)
|
|
42
96
|
end
|
|
@@ -50,12 +104,41 @@ module Cassandra
|
|
|
50
104
|
end
|
|
51
105
|
|
|
52
106
|
def delete(filter)
|
|
53
|
-
|
|
54
|
-
@rows
|
|
107
|
+
validate_filter!(filter)
|
|
108
|
+
row = @rows[attribute_partition_key(filter)]
|
|
109
|
+
row.delete_records(attribute_partial_clustering_key(filter))
|
|
55
110
|
end
|
|
56
111
|
|
|
57
|
-
def
|
|
58
|
-
|
|
112
|
+
def attribute_partition_key(attributes)
|
|
113
|
+
partition_key_names.map { |column| attributes[column] }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def attribute_clustering_key(attributes)
|
|
117
|
+
clustering_key_names.map do |column|
|
|
118
|
+
value = attributes[column]
|
|
119
|
+
value unless restriction_is_slice?(value)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def attribute_partial_clustering_key(attributes)
|
|
124
|
+
attribute_clustering_key(attributes).compact
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def attribute_fields(attributes)
|
|
128
|
+
field_names.map { |column| attributes[column] }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def restriction_is_slice?(value)
|
|
132
|
+
value.is_a?(Statement::Comparitor) || value.is_a?(Array)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def validate_filter!(filter)
|
|
136
|
+
unless filter.empty?
|
|
137
|
+
validate_range_query!(filter)
|
|
138
|
+
validate_partition_key_filter!(filter)
|
|
139
|
+
validate_clustering_column_filter!(filter)
|
|
140
|
+
raise_if_fields_restricted!(filter)
|
|
141
|
+
end
|
|
59
142
|
end
|
|
60
143
|
|
|
61
144
|
# make #partition_key public
|
|
@@ -70,8 +153,11 @@ module Cassandra
|
|
|
70
153
|
|
|
71
154
|
private
|
|
72
155
|
|
|
156
|
+
attr_reader :counter_column
|
|
157
|
+
def_delegator :@column_map, :[], :find_column
|
|
158
|
+
|
|
73
159
|
def select_filter(columns)
|
|
74
|
-
columns.last.is_a?(Hash) ? columns.pop : {}
|
|
160
|
+
columns.last.is_a?(Hash) ? columns.pop.dup : {}
|
|
75
161
|
end
|
|
76
162
|
|
|
77
163
|
def select_order(filter)
|
|
@@ -145,42 +231,15 @@ module Cassandra
|
|
|
145
231
|
filter[column]
|
|
146
232
|
end
|
|
147
233
|
|
|
148
|
-
def
|
|
149
|
-
filter
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def apply_filter(filter)
|
|
153
|
-
rows.select do |row|
|
|
154
|
-
partial_row = filter_slices_row(filter, row)
|
|
155
|
-
filter.all? do |column, value|
|
|
156
|
-
if value.is_a?(Statement::Comparitor)
|
|
157
|
-
value.check_against(partial_row)
|
|
158
|
-
elsif value.is_a?(Array)
|
|
159
|
-
if value.first.is_a?(Statement::Comparitor)
|
|
160
|
-
value.all? { |value| value.check_against(partial_row) }
|
|
161
|
-
else
|
|
162
|
-
value.include?(partial_row[column])
|
|
163
|
-
end
|
|
164
|
-
else
|
|
165
|
-
partial_row[column] == value
|
|
166
|
-
end
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def filter_slices_row(filter, row)
|
|
172
|
-
filter.keys.inject({}) do |memo, key, _|
|
|
173
|
-
value = if key.is_a?(Array)
|
|
174
|
-
row.values_at(*key)
|
|
175
|
-
else
|
|
176
|
-
row[key]
|
|
177
|
-
end
|
|
178
|
-
memo.merge!(key => value)
|
|
234
|
+
def validate_range_query!(filter)
|
|
235
|
+
invalid_partition_range_column = partition_key_names[0..-2].find { |column| filter[column].is_a?(Array) }
|
|
236
|
+
if invalid_partition_range_column
|
|
237
|
+
raise Cassandra::Errors::InvalidError.new("Partition KEY part #{invalid_partition_range_column} cannot be restricted by IN relation (only the last part of the partition key can)", 'MockStatement')
|
|
179
238
|
end
|
|
180
239
|
end
|
|
181
240
|
|
|
182
|
-
def
|
|
183
|
-
missing_partition_keys =
|
|
241
|
+
def validate_partition_key_filter!(filter)
|
|
242
|
+
missing_partition_keys = @partition_key_set - filter.keys
|
|
184
243
|
raise Cassandra::Errors::InvalidError.new("Missing partition key part(s) #{missing_partition_keys.map(&:inspect) * ', '}", 'MockStatement') unless missing_partition_keys.empty?
|
|
185
244
|
end
|
|
186
245
|
|
|
@@ -203,6 +262,61 @@ module Cassandra
|
|
|
203
262
|
row[primary_key_names[primary_key_index]]
|
|
204
263
|
end
|
|
205
264
|
|
|
265
|
+
def record_attributes(records)
|
|
266
|
+
records.map do |record|
|
|
267
|
+
attributes = {}
|
|
268
|
+
column_names.each.with_index do |column, index|
|
|
269
|
+
attributes[column] = record[index]
|
|
270
|
+
end
|
|
271
|
+
attributes
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def partially_filtered_rows(partition_range, partition_key, cluster_key)
|
|
276
|
+
if partition_range
|
|
277
|
+
partition_range_records(partition_range, partition_key, cluster_key)
|
|
278
|
+
else
|
|
279
|
+
records_for_row(partition_key, cluster_key)
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def partition_range_records(partition_range, partition_key, cluster_key)
|
|
284
|
+
partition_range.map do |part|
|
|
285
|
+
records_for_row(partition_key + [part], cluster_key)
|
|
286
|
+
end.flatten(1)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def records_for_row(partition_key, cluster_key)
|
|
290
|
+
row = @rows[partition_key]
|
|
291
|
+
row.find_records(cluster_key)
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def filtered_rows(cluster_slice, rows)
|
|
295
|
+
cluster_slice ? apply_filter(cluster_slice, rows) : rows
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def apply_filter(cluster_slice, rows)
|
|
299
|
+
rows.select do |row|
|
|
300
|
+
partial_row = partial_row(cluster_slice, row)
|
|
301
|
+
|
|
302
|
+
if cluster_slice.is_a?(Statement::Comparitor)
|
|
303
|
+
cluster_slice.check_against(partial_row)
|
|
304
|
+
elsif cluster_slice.is_a?(Array)
|
|
305
|
+
cluster_slice.all? { |value| value.check_against(partial_row) }
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def partial_row(cluster_slice, row)
|
|
311
|
+
comparitor = cluster_slice.is_a?(Statement::Comparitor) ? cluster_slice : cluster_slice.first
|
|
312
|
+
value = if comparitor.column.is_a?(Array)
|
|
313
|
+
row.values_at(*comparitor.column)
|
|
314
|
+
else
|
|
315
|
+
row[comparitor.column]
|
|
316
|
+
end
|
|
317
|
+
{comparitor.column => value}
|
|
318
|
+
end
|
|
319
|
+
|
|
206
320
|
def primary_key_names
|
|
207
321
|
partition_key_names + clustering_key_names
|
|
208
322
|
end
|
|
@@ -219,8 +333,8 @@ module Cassandra
|
|
|
219
333
|
columns.map(&:name)
|
|
220
334
|
end
|
|
221
335
|
|
|
222
|
-
def
|
|
223
|
-
|
|
336
|
+
def field_names
|
|
337
|
+
column_names - (partition_key_names + clustering_key_names)
|
|
224
338
|
end
|
|
225
339
|
|
|
226
340
|
def column_map(partition_key, clustering_key, fields)
|
metadata
CHANGED
|
@@ -1,29 +1,49 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cassandra_mocks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 1.0.8
|
|
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
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: concurrent-ruby
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ~>
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 1.0.0
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ~>
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 1.0.0
|
|
27
47
|
- !ruby/object:Gem::Dependency
|
|
28
48
|
name: activesupport
|
|
29
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,7 +72,9 @@ files:
|
|
|
52
72
|
- lib/cassandra_mocks.rb
|
|
53
73
|
- lib/cassandra_mocks/cluster.rb
|
|
54
74
|
- lib/cassandra_mocks/keyspace.rb
|
|
75
|
+
- lib/cassandra_mocks/record.rb
|
|
55
76
|
- lib/cassandra_mocks/result_page.rb
|
|
77
|
+
- lib/cassandra_mocks/row.rb
|
|
56
78
|
- lib/cassandra_mocks/session.rb
|
|
57
79
|
- lib/cassandra_mocks/statement.rb
|
|
58
80
|
- lib/cassandra_mocks/statement/arithmetic.rb
|