cassandra_mocks 0.0.8.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e116917fa04c58c22eba8ccc9c08e9787a9ba54d
4
+ data.tar.gz: a860d89cdece466b8a74af23abb2fd646a6846b5
5
+ SHA512:
6
+ metadata.gz: e98abfe9fe97cb40bcf5f97829d86673902a5e7d0e97257bf84cbbecc5a3f7a67eb20270c6a10ddfc3affb23e88a6ff3f2ab7e0645dd751163c4e6f2f72b7ed1
7
+ data.tar.gz: 2898cbb5fd7759b4ddc80d922ee57daf67827887fd7cf24abf1ab638e69bccd28893a79dc88eaecb8dbe55bade37bf8824c2faf3eec4de549e38057fedf41aba
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2014-2015 Thomas RM Rogers
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # cassandra_mocks
2
+
3
+ ## Copyright
4
+
5
+ Copyright 2015 Thomas Rogers.
6
+
7
+ 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
8
+
9
+ [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
10
+
11
+ Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
12
+
13
+
14
+
15
+
16
+
17
+
@@ -0,0 +1,52 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Cluster
4
+ extend Forwardable
5
+
6
+ def_delegator :@keyspaces, :[], :keyspace
7
+
8
+ def initialize
9
+ @keyspaces = {}
10
+ end
11
+
12
+ def connect_async(keyspace = nil)
13
+ session = Session.new(keyspace, self)
14
+ Cassandra::Future.value(session)
15
+ end
16
+
17
+ def close_async
18
+ Cassandra::Future.value(nil)
19
+ end
20
+
21
+ def close
22
+ close_async.get
23
+ end
24
+
25
+ def connect(keyspace = nil)
26
+ connect_async(keyspace).get
27
+ end
28
+
29
+ def add_keyspace(name)
30
+ raise Errors::AlreadyExistsError.new('Cannot create already existing keyspace', 'MockStatement', name, nil) if @keyspaces[name]
31
+ @keyspaces[name] = Keyspace.new(name)
32
+ end
33
+
34
+ def drop_keyspace(name)
35
+ @keyspaces.delete(name)
36
+ end
37
+
38
+ def each_keyspace(&block)
39
+ @keyspaces.values.each(&block)
40
+ end
41
+
42
+ def hosts
43
+ %w(DummyHost)
44
+ end
45
+
46
+ def find_replicas(*_)
47
+ []
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Keyspace < ::Cassandra::Keyspace
4
+
5
+ def initialize(name)
6
+ replication = Replication.new('mock', {})
7
+ super(name, false, replication, {})
8
+ end
9
+
10
+ def add_table(table_name, primary_key, columns)
11
+ raise Errors::InvalidError.new("Table name '#{table_name}' cannot be greater than 48 characters", 'MockStatement') if table_name.length > 48
12
+ raise Errors::AlreadyExistsError.new('Cannot create already existing table', 'MockStatement', nil, table_name) if @tables[table_name]
13
+
14
+ partition_key = primary_key.shift
15
+ partition_key_columns = partition_key_part(columns, partition_key)
16
+ clustering_columns = partition_key_part(columns, primary_key)
17
+ fields = fields(columns, partition_key, primary_key)
18
+ @tables[table_name] = Table.new(name, table_name, partition_key_columns, clustering_columns, fields)
19
+ end
20
+
21
+ def drop_table(table_name)
22
+ @tables.delete(table_name)
23
+ end
24
+
25
+ private
26
+
27
+ def partition_key_part(columns, primary_key)
28
+ primary_key.map { |name| Cassandra::Column.new(name, columns[name], :asc) }
29
+ end
30
+
31
+ def fields(columns, partition_key, primary_key)
32
+ columns.except(partition_key + primary_key).map { |name, type| Cassandra::Column.new(name, type, :asc) }
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class ResultPage < Array
4
+
5
+ def last_page?
6
+ true
7
+ end
8
+
9
+ def next_page
10
+ end
11
+
12
+ def next_page_async
13
+ Cassandra::Future.value(next_page)
14
+ end
15
+
16
+ def paging_state
17
+ end
18
+
19
+ def execution_info
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,117 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Session
4
+ attr_reader :keyspace, :cluster
5
+
6
+ def initialize(keyspace, cluster)
7
+ @keyspace = keyspace
8
+ @cluster = cluster
9
+ end
10
+
11
+ def close_async
12
+ Cassandra::Future.value(nil)
13
+ end
14
+
15
+ def close
16
+ close_async.get
17
+ end
18
+
19
+ def prepare_async(cql)
20
+ Cassandra::Future.value(Statement.new(cql, []))
21
+ end
22
+
23
+ def prepare(cql)
24
+ prepare_async(cql).get
25
+ end
26
+
27
+ def execute_async(cql, *args)
28
+ if cql.is_a?(Cassandra::Statements::Batch)
29
+ futures = cql.statements.map do |batched_statement|
30
+ execute_async(batched_statement.statement, *batched_statement.args)
31
+ end
32
+ return Cassandra::Future.all(futures).then { ResultPage.new }
33
+ end
34
+
35
+ future = cql.is_a?(Statement) ? Cassandra::Future.value(cql.fill_params(args)) : prepare_async(cql)
36
+ future.then do |statement|
37
+ result = ResultPage.new
38
+ case statement.action
39
+ when :create_keyspace
40
+ cluster.add_keyspace(statement.args[:keyspace])
41
+ when :create_table
42
+ cluster.keyspace(keyspace).add_table(statement.args[:table], statement.args[:primary_key], statement.args[:columns])
43
+ when :insert
44
+ insert_query(result, statement)
45
+ when :update
46
+ update_query(statement)
47
+ when :truncate
48
+ cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table]).rows.clear
49
+ when :drop_keyspace
50
+ cluster.drop_keyspace(statement.args[:keyspace])
51
+ when :drop_table
52
+ cluster.keyspace(keyspace_for_statement(statement)).drop_table(statement.args[:table])
53
+ when :select
54
+ result = select_query(statement)
55
+ when :delete
56
+ delete_query(statement)
57
+ end
58
+ result
59
+ end
60
+ end
61
+
62
+ def execute(cql)
63
+ execute_async(cql).get
64
+ end
65
+
66
+ def ==(rhs)
67
+ rhs.is_a?(Session) &&
68
+ rhs.keyspace == keyspace &&
69
+ rhs.cluster == cluster
70
+ end
71
+
72
+ private
73
+
74
+ def insert_query(result, statement)
75
+ check_exists = !!statement.args[:check_exists]
76
+ inserted = cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table]).insert(statement.args[:values], check_exists: check_exists)
77
+ result << {'[applied]' => inserted} if check_exists
78
+ end
79
+
80
+ def select_query(statement)
81
+ table = cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table])
82
+ options = statement.args[:filter].merge(limit: statement.args[:limit], order: statement.args[:order])
83
+ table.select(*statement.args[:columns], options)
84
+ end
85
+
86
+ def delete_query(statement)
87
+ table = cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table])
88
+ table.delete(statement.args[:filter])
89
+ end
90
+
91
+ def update_query(statement)
92
+ table = cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table])
93
+ rows_to_update = table.select('*', statement.args[:filter])
94
+ rows_to_update = [statement.args[:filter].dup] if rows_to_update.empty?
95
+ rows_to_update.each do |row|
96
+ updated_row = updated_row(row, statement)
97
+ cluster.keyspace(keyspace_for_statement(statement)).table(statement.args[:table]).insert(updated_row)
98
+ end
99
+ end
100
+
101
+ def updated_row(row, statement)
102
+ statement.args[:values].inject(row.dup) do |memo, (column, value)|
103
+ if value.is_a?(Statement::Arithmetic)
104
+ value.apply(memo)
105
+ else
106
+ memo.merge!(column => value)
107
+ end
108
+ end
109
+ end
110
+
111
+ def keyspace_for_statement(statement)
112
+ statement.args[:keyspace] || keyspace
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,19 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Statement
4
+ class Arithmetic < Struct.new(:operation, :column, :amount)
5
+
6
+ def apply(row)
7
+ row.merge(column => (row[column] || 0).public_send(operator, amount))
8
+ end
9
+
10
+ private
11
+
12
+ def operator
13
+ operation == :plus ? :+ : :-
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ module Cassandra
2
+ module Statements
3
+ class Batch
4
+ BatchedStatement = Struct.new(:statement, :args)
5
+
6
+ def add(statement, *args)
7
+ @statements << BatchedStatement.new(statement, args)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,41 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Statement
4
+ class Comparitor < Struct.new(:operation, :column, :value)
5
+
6
+ COMPARISON_MAP = {
7
+ lt: [-1],
8
+ le: [-1, 0],
9
+ eq: [0],
10
+ ge: [0, 1],
11
+ gt: [1]
12
+ }
13
+
14
+ def initialize(*args)
15
+ super
16
+ @comparitor = COMPARISON_MAP[operation]
17
+ end
18
+
19
+ def check_against(row)
20
+ if column.is_a?(Array)
21
+ check_against_array(row[column])
22
+ else
23
+ @comparitor.include?(row[column] <=> value)
24
+ end
25
+ end
26
+
27
+ def check_against_array(row_values, index = 0, prev_result = false)
28
+ row_value = row_values[index]
29
+ comparison_value = value[index]
30
+ return prev_result unless row_value
31
+
32
+ comparison = @comparitor.include?(row_value <=> comparison_value)
33
+ return comparison if row_value != comparison_value
34
+
35
+ check_against_array(row_values, index+1, comparison)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Statement
4
+ class Token < Struct.new(:type, :value)
5
+ def normalized_value
6
+ case type
7
+ when :int
8
+ value.to_i
9
+ when :float
10
+ value.to_f
11
+ else
12
+ value
13
+ end
14
+ end
15
+
16
+ def respond_to?(method)
17
+ method_inquiry?(method) || super
18
+ end
19
+
20
+ def method_missing(method, *args)
21
+ if method_inquiry?(method)
22
+ method[/[^\?]+/].to_sym == type
23
+ else
24
+ super
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def method_inquiry?(method)
31
+ method =~ /\?$/
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,123 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Statement
4
+ class Tokenizer
5
+ #noinspection RubyStringKeysInHashInspection
6
+ KEYWORD_MAP = {
7
+ 'CREATE' => :create,
8
+ 'DROP' => :drop,
9
+ 'TRUNCATE' => :truncate,
10
+ 'PRIMARY' => :primary,
11
+ 'KEY' => :key,
12
+ 'TABLE' => :table,
13
+ 'KEYSPACE' => :keyspace,
14
+ 'INSERT' => :insert,
15
+ 'UPDATE' => :update,
16
+ 'SET' => :set,
17
+ 'VALUES' => :values,
18
+ 'SELECT' => :select,
19
+ 'DELETE' => :delete,
20
+ 'FROM' => :from,
21
+ 'WHERE' => :where,
22
+ 'ORDER' => :order,
23
+ 'BY' => :by,
24
+ 'ASC' => :asc,
25
+ 'DESC' => :desc,
26
+ 'LIMIT' => :limit,
27
+ 'AND' => :and,
28
+ 'IN' => :in,
29
+ 'IF' => :if,
30
+ 'NOT' => :not,
31
+ 'EXISTS' => :exists,
32
+ '(' => :lparen,
33
+ ')' => :rparen,
34
+ '<' => :ltri,
35
+ '>' => :rtri,
36
+ ',' => :comma,
37
+ '.' => :dot,
38
+ '[' => :lbracket,
39
+ ']' => :rbracket,
40
+ '=' => :eql,
41
+ '+' => :plus,
42
+ '-' => :minus,
43
+ '*' => :star,
44
+ '?' => :parameter,
45
+ }
46
+
47
+ attr_reader :tokens
48
+
49
+ def initialize(cql)
50
+ @tokens = []
51
+ current_token = ''
52
+
53
+ in_string = false
54
+ in_name = false
55
+ prev_char = nil
56
+ tokenize(cql, current_token, in_name, in_string, prev_char)
57
+ end
58
+
59
+ def token_queue
60
+ Queue.new.tap do |queue|
61
+ tokens.each { |token| queue << token }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def tokenize(cql, current_token, in_name, in_string, prev_char)
68
+ cql.chars.each do |char|
69
+ if char == '"' && prev_char != '\\'
70
+ in_name = tokenize_string(:name, in_name, current_token)
71
+ current_token = '' unless in_name
72
+ elsif char == "'" && prev_char != '\\'
73
+ in_string = tokenize_string(:string, in_string, current_token)
74
+ current_token = '' unless in_string
75
+ elsif !in_name && !in_string && char == '.' && prev_char !~ /\d/
76
+ translate_multiple_tokens(char, current_token)
77
+ current_token = ''
78
+ elsif !in_name && !in_string && %w(, ( ) < > = ? + -).include?(char)
79
+ translate_multiple_tokens(char, current_token)
80
+ current_token = ''
81
+ elsif !in_name && !in_string && char == ' '
82
+ translate_token(current_token)
83
+ current_token = ''
84
+ elsif char == '\\'
85
+ # do nothing...
86
+ else
87
+ current_token << char
88
+ end
89
+ prev_char = char
90
+ end
91
+ translate_token(current_token) if current_token.present?
92
+ end
93
+
94
+ def translate_multiple_tokens(char, current_token)
95
+ translate_token(current_token)
96
+ translate_token(char)
97
+ end
98
+
99
+ def tokenize_string(type, in_string, current_token)
100
+ if in_string
101
+ @tokens << Token.new(type, current_token)
102
+ false
103
+ else
104
+ true
105
+ end
106
+ end
107
+
108
+ def translate_token(current_token)
109
+ if current_token.present?
110
+ @tokens << if current_token =~ /^\d+\.\d+$/
111
+ Token.new(:float, current_token)
112
+ elsif current_token =~ /^\d+$/
113
+ Token.new(:int, current_token)
114
+ else
115
+ Token.new(KEYWORD_MAP[current_token.upcase] || :id, current_token)
116
+ end
117
+ end
118
+ end
119
+
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,364 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Statement
4
+ attr_reader :cql, :action, :args
5
+
6
+ def initialize(cql, args)
7
+ @cql = cql
8
+ @input_args = param_queue(args)
9
+
10
+ type_token = next_token
11
+ @action = type_token.type
12
+ if type_token.create?
13
+ create_type_token = next_token
14
+ if create_type_token.table?
15
+ @action = :create_table
16
+ parse_create_table
17
+ else
18
+ @action = :create_keyspace
19
+ @args = {keyspace: next_token.value}
20
+ end
21
+ elsif type_token.truncate?
22
+ parse_truncate_query
23
+ elsif type_token.drop?
24
+ if next_token.keyspace?
25
+ @action = :drop_keyspace
26
+ @args = {keyspace: next_token.value}
27
+ else
28
+ @action = :drop_table
29
+ parse_truncate_query
30
+ end
31
+ elsif type_token.insert?
32
+ parse_insert_query
33
+ elsif type_token.update?
34
+ parse_update_query
35
+ elsif type_token.select?
36
+ parse_select_query
37
+ elsif type_token.delete?
38
+ next_token
39
+ parse_table_and_filter
40
+ end
41
+ end
42
+
43
+ def fill_params(params)
44
+ Statement.allocate.tap do |statement|
45
+ statement.cql = cql
46
+ statement.action = action
47
+ statement.args = args.dup
48
+ params = param_queue(params)
49
+ case action
50
+ when :insert
51
+ parameterize_args!(:values, params, statement)
52
+ when :select, :delete
53
+ parameterize_args!(:filter, params, statement)
54
+ when :update
55
+ parameterize_args!(:values, params, statement)
56
+ parameterize_args!(:filter, params, statement)
57
+ end
58
+ end
59
+ end
60
+
61
+ def bind(*args)
62
+ fill_params(args)
63
+ end
64
+
65
+ def ==(rhs)
66
+ rhs.is_a?(Statement) &&
67
+ rhs.action == action &&
68
+ rhs.args == args
69
+ end
70
+
71
+ protected
72
+
73
+ attr_writer :cql, :action, :args
74
+
75
+ private
76
+
77
+ attr_reader :last_token
78
+
79
+ def parameterize_args!(key, params, statement)
80
+ values = args[key].inject({}) do |memo, (column, value)|
81
+ updated_value = if value.is_a?(Arithmetic)
82
+ Arithmetic.new(value.operation, value.column, pending_value(value.amount, params))
83
+ elsif value.is_a?(Comparitor)
84
+ if value.value.is_a?(Array)
85
+ new_values = value.value.map { |value| pending_value(value, params) }
86
+ Comparitor.new(value.operation, value.column, new_values)
87
+ else
88
+ Comparitor.new(value.operation, value.column, pending_value(value.value, params))
89
+ end
90
+ elsif value.is_a?(Array)
91
+ value.map { |value| pending_value(value, params) }
92
+ else
93
+ pending_value(value, params)
94
+ end
95
+ memo.merge!(column => updated_value)
96
+ end
97
+ statement.args.merge!(key => values)
98
+ end
99
+
100
+ def pending_value(value, params)
101
+ if value == :value_pending
102
+ raise Errors::InvalidError.new('Not enough params provided to #fill_params', 'MockStatement') if params.empty?
103
+ params.pop
104
+ else
105
+ value
106
+ end
107
+ end
108
+
109
+ def tokens
110
+ @tokens ||= Tokenizer.new(@cql).token_queue
111
+ end
112
+
113
+ def next_token
114
+ if tokens.empty?
115
+ Token.new(:eof, nil)
116
+ else
117
+ @last_token = tokens.pop
118
+ end
119
+ end
120
+
121
+ def parse_create_table
122
+ table_name_token = next_token
123
+ check_exists = if table_name_token.if?
124
+ next_token
125
+ next_token
126
+ table_name_token = next_token
127
+ end
128
+ table_name = table_name_token.value
129
+
130
+ next_token
131
+ column_name = next_token.value
132
+ column_type = next_token.value
133
+ primary_key = nil
134
+ if next_token.primary?
135
+ primary_key = [[column_name]]
136
+ 2.times { next_token }
137
+ end
138
+
139
+ additional_columns = if tokens.empty?
140
+ {}
141
+ else
142
+ parenthesis_values(:rparen, :primary).each_slice(2).inject({}) do |memo, (name, type)|
143
+ memo.merge!(name => type)
144
+ end
145
+ end
146
+
147
+ if !tokens.empty? && next_token.key?
148
+ next_token
149
+ primary_key_parts = parenthesis_values(:rparen)
150
+ partition_key = primary_key_parts.shift
151
+ partition_key = [partition_key] unless partition_key.is_a?(Array)
152
+ primary_key = [partition_key, *primary_key_parts]
153
+ end
154
+
155
+ @args = {table: table_name, check_exists: !!check_exists, columns: additional_columns.merge({column_name => column_type}), primary_key: primary_key}
156
+ end
157
+
158
+ def parse_truncate_query
159
+ keyspace_name, table_name = parsed_keyspace_and_table
160
+
161
+ @args = {keyspace: keyspace_name, table: table_name}
162
+ end
163
+
164
+ def parse_insert_query
165
+ next_token
166
+
167
+ keyspace_name, table_name = parsed_keyspace_and_table
168
+ next_token unless keyspace_name.nil?
169
+
170
+ insert_keys = parenthesis_values(:rparen)
171
+ 2.times { next_token }
172
+ insert_values = parenthesis_values(:rparen)
173
+
174
+ values = insert_args(insert_keys, insert_values)
175
+ if next_token.if?
176
+ @args = {keyspace: keyspace_name, table: table_name, values: values, check_exists: true}
177
+ else
178
+ @args = {keyspace: keyspace_name, table: table_name, values: values}
179
+ end
180
+ end
181
+
182
+ def parse_update_query
183
+ keyspace_name, table_name = parsed_keyspace_and_table
184
+ next_token if keyspace_name
185
+ values = parsed_filter(:where)
186
+ filter = parsed_filter(:eof)
187
+ @args = {keyspace: keyspace_name, table: table_name, values: values, filter: filter}
188
+ end
189
+
190
+ def parse_select_query
191
+ select_columns = parenthesis_values(:from)
192
+ parse_table_and_filter
193
+ @args.merge!(columns: select_columns)
194
+ end
195
+
196
+ def parse_table_and_filter
197
+ keyspace_name, table_name = parsed_keyspace_and_table
198
+ next_token if keyspace_name
199
+
200
+ filter = parsed_filter(:limit, :order)
201
+
202
+ @args = {keyspace: keyspace_name, table: table_name, filter: filter}
203
+
204
+ if last_token.order?
205
+ next_token
206
+ @args.merge!(order: parse_select_order)
207
+ end
208
+
209
+ if last_token.limit?
210
+ limit = next_token.normalized_value
211
+ @args = @args.merge!(limit: limit)
212
+ end
213
+ end
214
+
215
+ def parse_select_order
216
+ order = {}
217
+ prev_column = nil
218
+ token = next_token
219
+ until token.eof? || token.limit?
220
+ if token.desc?
221
+ order[prev_column] = :desc
222
+ elsif token.asc?
223
+ order[prev_column] = :asc
224
+ elsif token.comma?
225
+ else
226
+ order[token.value] = :asc
227
+ prev_column = token.value
228
+ end
229
+ token = next_token
230
+ end
231
+ order
232
+ end
233
+
234
+ def parsed_filter(*end_tokens)
235
+ filter_keys = []
236
+ filter_values = []
237
+ until tokens.empty? || end_tokens.include?(last_token.type)
238
+ filter_keys << next_filter_key(next_token)
239
+
240
+ restrictor_token = next_token
241
+ if restrictor_token.type == :in
242
+ next_token
243
+ filter_values << parenthesis_values(:rparen)
244
+ next_token
245
+ elsif restrictor_token.ltri? || restrictor_token.rtri?
246
+ parse_comparison_restriction(filter_keys, filter_values, restrictor_token)
247
+ else
248
+ parse_single_restriction(filter_values)
249
+ end
250
+ end
251
+
252
+ insert_args(filter_keys, filter_values)
253
+ end
254
+
255
+ def parse_comparison_restriction(filter_keys, filter_values, restrictor_token)
256
+ value_token = next_token
257
+ eql_comparison = if value_token.type == :eql
258
+ value_token = next_token
259
+ true
260
+ end
261
+ value = next_filter_key(value_token)
262
+
263
+ comparison_operator = comparison_operator(eql_comparison, restrictor_token)
264
+ filter_values << Comparitor.new(comparison_operator, filter_keys.last, value)
265
+ next_token
266
+ end
267
+
268
+ def next_filter_key(next_key)
269
+ if next_key.lparen?
270
+ parenthesis_values(:rparen)
271
+ else
272
+ next_key.normalized_value
273
+ end
274
+ end
275
+
276
+ def parse_single_restriction(filter_values)
277
+ value_token = next_token
278
+ next_token
279
+ value = value_token.normalized_value
280
+ update_value = update_value(last_token, value)
281
+ if update_value
282
+ value = update_value
283
+ next_token
284
+ end
285
+ filter_values << value
286
+ end
287
+
288
+ def comparison_operator(eql_comparison, restrictor_token)
289
+ if eql_comparison
290
+ restrictor_token.rtri? ? :ge : :le
291
+ else
292
+ restrictor_token.rtri? ? :gt : :lt
293
+ end
294
+ end
295
+
296
+ def update_value(prev_token, value)
297
+ if prev_token.plus? || prev_token.minus?
298
+ column = value
299
+ amount = next_token.normalized_value
300
+ Arithmetic.new(prev_token.type, column, amount)
301
+ end
302
+ end
303
+
304
+ def parsed_keyspace_and_table
305
+ keyspace_name = nil
306
+ table_name = next_token.value
307
+ if next_token.dot?
308
+ keyspace_name = table_name
309
+ table_name = next_token.value
310
+ end
311
+ [keyspace_name, table_name]
312
+ end
313
+
314
+ def parenthesis_values(*terminators)
315
+ [].tap do |insert_values|
316
+ until terminators.include?((key = next_token).type)
317
+ if key.lparen?
318
+ insert_values << parenthesis_values(:rparen)
319
+ elsif !key.comma?
320
+ insert_values << parameterized_value(key.normalized_value) unless key.comma?
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ def insert_args(insert_keys, insert_values)
327
+ insert_keys.count.times.inject({}) do |memo, index|
328
+ value = mapped_value(insert_values[index])
329
+ memo.merge!(insert_keys[index] => value)
330
+ end
331
+ end
332
+
333
+ def mapped_value(value)
334
+ if value.is_a?(Array)
335
+ value.map { |value| parameterized_value(value) }
336
+ elsif value.is_a?(Arithmetic)
337
+ updated_amount = parameterized_value(value.amount)
338
+ Arithmetic.new(value.operation, value.column, updated_amount)
339
+ elsif value.is_a?(Comparitor)
340
+ updated_amount = parameterized_value(value.value)
341
+ Comparitor.new(value.operation, value.column, updated_amount)
342
+ else
343
+ parameterized_value(value)
344
+ end
345
+ end
346
+
347
+ def parameterized_value(value)
348
+ if value == '?'
349
+ if @input_args.empty?
350
+ :value_pending
351
+ else
352
+ @input_args.pop
353
+ end
354
+ else
355
+ value
356
+ end
357
+ end
358
+
359
+ def param_queue(args)
360
+ Queue.new.tap { |queue| args.each { |arg| queue << arg } }
361
+ end
362
+ end
363
+ end
364
+ end
@@ -0,0 +1,223 @@
1
+ module Cassandra
2
+ module Mocks
3
+ class Table < Cassandra::Table
4
+ def initialize(keyspace, name, partition_key, clustering_key, fields)
5
+ compaction = Cassandra::Table::Compaction.new('mock', {})
6
+ options = Cassandra::Table::Options.new({}, compaction, {}, false, 'mock')
7
+ column_map = column_map(partition_key, clustering_key, fields)
8
+ super(keyspace, name, partition_key, clustering_key, column_map, options, [])
9
+ end
10
+
11
+ def insert(attributes, options = {})
12
+ validate_columns!(attributes)
13
+ validate_primary_key_presence!(attributes)
14
+
15
+ prev_row_index = rows.find_index do |row|
16
+ row.slice(*primary_key_names) == attributes.slice(*primary_key_names)
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
26
+ end
27
+
28
+ def select(*columns)
29
+ filter = select_filter(columns)
30
+ limit = filter.delete(:limit)
31
+ order = select_order(filter)
32
+ unless filter.empty?
33
+ validate_partion_key_filter!(filter)
34
+ validate_clustering_column_filter!(filter)
35
+ end
36
+
37
+ filtered_rows = filtered_rows(filter)
38
+ sorted_rows = filtered_rows.sort do |lhs, rhs|
39
+ compare_rows(0, lhs, rhs, order)
40
+ end
41
+
42
+ sorted_rows = sorted_rows[0...limit] if limit
43
+
44
+ result_rows = sorted_rows.map do |row|
45
+ (columns.first == '*') ? row : row.slice(*columns)
46
+ end
47
+ ResultPage.new(result_rows)
48
+ end
49
+
50
+ def delete(filter)
51
+ rows_to_remove = select('*', filter)
52
+ @rows.reject! { |row| rows_to_remove.include?(row) }
53
+ end
54
+
55
+ def rows
56
+ @rows ||= []
57
+ end
58
+
59
+ # make #partition_key public
60
+ def partition_key
61
+ super
62
+ end
63
+
64
+ # make #clustering_columns public
65
+ def clustering_columns
66
+ super
67
+ end
68
+
69
+ private
70
+
71
+ def select_filter(columns)
72
+ columns.last.is_a?(Hash) ? columns.pop : {}
73
+ end
74
+
75
+ def select_order(filter)
76
+ (filter.delete(:order) || {}).inject(Hash.new(1)) do |memo, (key, value)|
77
+ memo.merge!(key => value == :asc ? 1 : -1)
78
+ end
79
+ end
80
+
81
+ def validate_columns!(attributes)
82
+ attributes.each do |column_name, value|
83
+ column = find_column(column_name)
84
+ unless column
85
+ raise Errors::InvalidError.new(%Q{Invalid column, "#{column_name}", specified}, 'MockStatement')
86
+ end
87
+
88
+ if value
89
+ case column.type
90
+ when 'double'
91
+ raise_unless_valid_type(column_name, Float, value)
92
+ when 'string'
93
+ raise_unless_valid_type(column_name, String, value)
94
+ when 'text'
95
+ raise_unless_valid_type(column_name, String, value)
96
+ when 'varchar'
97
+ raise_unless_valid_type(column_name, String, value)
98
+ when 'blob'
99
+ raise_unless_valid_type(column_name, String, value)
100
+ when 'int'
101
+ raise_unless_valid_type(column_name, Fixnum, value)
102
+ when 'uuid'
103
+ raise_unless_valid_type(column_name, Cassandra::Uuid, value)
104
+ when 'timeuuid'
105
+ raise_unless_valid_type(column_name, Cassandra::TimeUuid, value)
106
+ when 'timestamp'
107
+ raise_unless_valid_type(column_name, Time, value)
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def raise_unless_valid_type(column_name, ruby_type, value)
114
+ unless value.is_a?(ruby_type)
115
+ raise Errors::InvalidError.new(%Q{Expected column "#{column_name}" to be of type "#{ruby_type}", got a(n) "#{value.class}"}, 'MockStatement')
116
+ end
117
+ end
118
+
119
+ def validate_primary_key_presence!(attributes)
120
+ primary_key_names.each do |column|
121
+ raise Errors::InvalidError.new(%Q{Invalid null primary key part, "#{column}"}, 'MockStatement') unless filter_has_column?(attributes, column)
122
+ end
123
+ end
124
+
125
+ def validate_clustering_column_filter!(filter)
126
+ prev_columns = []
127
+ clustering_key_names.inject(true) do |hit_column, column|
128
+ if filter_has_column?(filter, column)
129
+ raise Cassandra::Errors::InvalidError.new("Clustering key part(s) #{prev_columns.map(&:inspect) * ', '} must be restricted", 'MockStatement') unless hit_column
130
+ else
131
+ prev_columns << column
132
+ end
133
+ filter_has_column?(filter, column)
134
+ end
135
+ end
136
+
137
+ def filter_has_column?(filter, column)
138
+ filter[column]
139
+ end
140
+
141
+ def filtered_rows(filter)
142
+ filter ? apply_filter(filter) : rows
143
+ end
144
+
145
+ def apply_filter(filter)
146
+ rows.select do |row|
147
+ partial_row = filter_slices_row(filter, row)
148
+ filter.all? do |column, value|
149
+ if value.is_a?(Statement::Comparitor)
150
+ value.check_against(partial_row)
151
+ elsif value.is_a?(Array)
152
+ value.include?(partial_row[column])
153
+ else
154
+ partial_row[column] == value
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ def filter_slices_row(filter, row)
161
+ filter.keys.inject({}) do |memo, key, _|
162
+ value = if key.is_a?(Array)
163
+ row.values_at(*key)
164
+ else
165
+ row[key]
166
+ end
167
+ memo.merge!(key => value)
168
+ end
169
+ end
170
+
171
+ def validate_partion_key_filter!(filter)
172
+ missing_partition_keys = Set.new(partition_key_names) - filter.keys
173
+ raise Cassandra::Errors::InvalidError.new("Missing partition key part(s) #{missing_partition_keys.map(&:inspect) * ', '}", 'MockStatement') unless missing_partition_keys.empty?
174
+ end
175
+
176
+ def compare_rows(primary_key_index, lhs, rhs, order)
177
+ return 0 if primary_key_names[primary_key_index].nil?
178
+
179
+ if primary_key_part(lhs, primary_key_index) == primary_key_part(rhs, primary_key_index)
180
+ compare_rows(primary_key_index + 1, lhs, rhs, order)
181
+ else
182
+ comparison = primary_key_part(lhs, primary_key_index) <=> primary_key_part(rhs, primary_key_index)
183
+ order_comparison(comparison, order, primary_key_names[primary_key_index])
184
+ end
185
+ end
186
+
187
+ def order_comparison(comparison, order, primary_key)
188
+ comparison * order[primary_key]
189
+ end
190
+
191
+ def primary_key_part(row, primary_key_index)
192
+ row[primary_key_names[primary_key_index]]
193
+ end
194
+
195
+ def primary_key_names
196
+ partition_key_names + clustering_key_names
197
+ end
198
+
199
+ def partition_key_names
200
+ partition_key.map(&:name)
201
+ end
202
+
203
+ def clustering_key_names
204
+ clustering_columns.map(&:name)
205
+ end
206
+
207
+ def column_names
208
+ columns.map(&:name)
209
+ end
210
+
211
+ def find_column(name)
212
+ columns.find { |column| column.name == name }
213
+ end
214
+
215
+ def column_map(partition_key, clustering_key, fields)
216
+ (partition_key + clustering_key + fields).inject({}) do |memo, column|
217
+ memo.merge!(column.name => column)
218
+ end
219
+ end
220
+
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,7 @@
1
+ module Cassandra
2
+ class Uuid
3
+ def <=>(rhs)
4
+ to_s <=> rhs.to_s
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,32 @@
1
+ #--
2
+ # Copyright 2014-2015 Thomas RM Rogers
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #++
16
+
17
+ require 'cassandra'
18
+ require 'active_support/all'
19
+ require 'active_support/core_ext/class/attribute_accessors'
20
+
21
+ require 'cassandra_mocks/result_page'
22
+ require 'cassandra_mocks/table'
23
+ require 'cassandra_mocks/statement/batch'
24
+ require 'cassandra_mocks/statement/arithmetic'
25
+ require 'cassandra_mocks/statement/comparitor'
26
+ require 'cassandra_mocks/statement/token'
27
+ require 'cassandra_mocks/statement/tokenizer'
28
+ require 'cassandra_mocks/statement'
29
+ require 'cassandra_mocks/keyspace'
30
+ require 'cassandra_mocks/cluster'
31
+ require 'cassandra_mocks/session'
32
+ require 'cassandra_mocks/uuid'
@@ -0,0 +1,15 @@
1
+ module CassandraModel
2
+ class RawConnection
3
+ def cluster
4
+ @cluster ||= begin
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)
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cassandra_mocks
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.8.2
5
+ platform: ruby
6
+ authors:
7
+ - Thomas RM Rogers
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-12-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cassandra-driver
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ description: |-
42
+ Cassandra mocking framework intended to replace having
43
+ a full blown Cassandra running for a unit testing environment. Aims to be a fast
44
+ and accurate representation of running CQL 3 against the Ruby Cassandra driver
45
+ email: thomasrogers03@gmail.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - LICENSE.txt
51
+ - README.md
52
+ - lib/cassandra_mocks.rb
53
+ - lib/cassandra_mocks/cluster.rb
54
+ - lib/cassandra_mocks/keyspace.rb
55
+ - lib/cassandra_mocks/result_page.rb
56
+ - lib/cassandra_mocks/session.rb
57
+ - lib/cassandra_mocks/statement.rb
58
+ - lib/cassandra_mocks/statement/arithmetic.rb
59
+ - lib/cassandra_mocks/statement/batch.rb
60
+ - lib/cassandra_mocks/statement/comparitor.rb
61
+ - lib/cassandra_mocks/statement/token.rb
62
+ - lib/cassandra_mocks/statement/tokenizer.rb
63
+ - lib/cassandra_mocks/table.rb
64
+ - lib/cassandra_mocks/uuid.rb
65
+ - lib/cassandra_model/mock_connection.rb
66
+ homepage: https://www.github.com/thomasrogers03/cassandra_mocks
67
+ licenses:
68
+ - Apache License 2.0
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.8
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Cassandra mocking framework
90
+ test_files: []