cequel 1.10.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +93 -65
- data/README.md +26 -5
- data/Vagrantfile +2 -2
- data/lib/cequel/errors.rb +2 -0
- data/lib/cequel/instrumentation.rb +5 -4
- data/lib/cequel/metal/batch.rb +21 -18
- data/lib/cequel/metal/data_set.rb +17 -28
- data/lib/cequel/metal/inserter.rb +3 -2
- data/lib/cequel/metal/keyspace.rb +56 -33
- data/lib/cequel/metal/request_logger.rb +22 -8
- data/lib/cequel/metal/row_specification.rb +9 -8
- data/lib/cequel/metal/statement.rb +23 -7
- data/lib/cequel/metal/updater.rb +12 -10
- data/lib/cequel/metal/writer.rb +5 -13
- data/lib/cequel/record/association_collection.rb +6 -33
- data/lib/cequel/record/collection.rb +2 -1
- data/lib/cequel/record/errors.rb +6 -0
- data/lib/cequel/record/persistence.rb +2 -2
- data/lib/cequel/record/record_set.rb +3 -4
- data/lib/cequel/record/validations.rb +5 -5
- data/lib/cequel/schema/table.rb +3 -5
- data/lib/cequel/schema/table_reader.rb +73 -111
- data/lib/cequel/schema/table_updater.rb +9 -15
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/metal/data_set_spec.rb +34 -46
- data/spec/examples/metal/keyspace_spec.rb +8 -6
- data/spec/examples/record/associations_spec.rb +8 -18
- data/spec/examples/record/persistence_spec.rb +6 -6
- data/spec/examples/record/record_set_spec.rb +39 -12
- data/spec/examples/record/timestamps_spec.rb +12 -5
- data/spec/examples/schema/keyspace_spec.rb +13 -37
- data/spec/examples/schema/table_reader_spec.rb +4 -1
- data/spec/examples/schema/table_updater_spec.rb +22 -7
- data/spec/examples/schema/table_writer_spec.rb +2 -3
- data/spec/examples/spec_helper.rb +1 -0
- data/spec/examples/spec_support/preparation_spec.rb +14 -7
- metadata +7 -8
@@ -37,16 +37,17 @@ module Cequel
|
|
37
37
|
|
38
38
|
define_method(:"__data_for_#{method_name}_instrumentation", &data_proc)
|
39
39
|
|
40
|
-
|
41
|
-
|
40
|
+
mod = Module.new
|
41
|
+
mod.module_eval <<-METH
|
42
|
+
def #{method_name}(*args)
|
42
43
|
instrument("#{topic}",
|
43
44
|
__data_for_#{method_name}_instrumentation(self)) do
|
44
|
-
|
45
|
+
super(*args)
|
45
46
|
end
|
46
47
|
end
|
47
48
|
METH
|
48
49
|
|
49
|
-
|
50
|
+
prepend mod
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
data/lib/cequel/metal/batch.rb
CHANGED
@@ -39,10 +39,9 @@ module Cequel
|
|
39
39
|
#
|
40
40
|
# @param (see Keyspace#execute)
|
41
41
|
#
|
42
|
-
def execute(
|
43
|
-
@statement
|
44
|
-
@
|
45
|
-
if @auto_apply && @statement_count >= @auto_apply
|
42
|
+
def execute(statement)
|
43
|
+
@statements << statement
|
44
|
+
if @auto_apply && @statements.size >= @auto_apply
|
46
45
|
apply
|
47
46
|
reset
|
48
47
|
end
|
@@ -52,13 +51,20 @@ module Cequel
|
|
52
51
|
# Send the batch to Cassandra
|
53
52
|
#
|
54
53
|
def apply
|
55
|
-
return if @
|
56
|
-
|
57
|
-
|
58
|
-
|
54
|
+
return if @statements.empty?
|
55
|
+
|
56
|
+
statement = @statements.first
|
57
|
+
if @statements.size > 1
|
58
|
+
statement =
|
59
|
+
if logged?
|
60
|
+
keyspace.client.logged_batch
|
61
|
+
else
|
62
|
+
keyspace.client.unlogged_batch
|
63
|
+
end
|
64
|
+
@statements.each { |s| statement.add(s.prepare(keyspace), arguments: s.bind_vars) }
|
59
65
|
end
|
60
|
-
|
61
|
-
|
66
|
+
|
67
|
+
keyspace.execute_with_options(statement, consistency: @consistency)
|
62
68
|
execute_on_complete_hooks
|
63
69
|
end
|
64
70
|
|
@@ -84,30 +90,27 @@ module Cequel
|
|
84
90
|
end
|
85
91
|
|
86
92
|
# @private
|
87
|
-
def
|
93
|
+
def execute_with_options(statement, options)
|
94
|
+
query_consistency = options.fetch(:consistency)
|
88
95
|
if query_consistency && query_consistency != @consistency
|
89
96
|
raise ArgumentError,
|
90
97
|
"Attempting to perform query with consistency " \
|
91
98
|
"#{query_consistency.to_s.upcase} in batch with consistency " \
|
92
99
|
"#{@consistency.upcase}"
|
93
100
|
end
|
94
|
-
execute(
|
101
|
+
execute(statement)
|
95
102
|
end
|
96
103
|
|
97
104
|
private
|
98
105
|
|
99
|
-
attr_reader :on_complete_hooks
|
106
|
+
attr_reader :on_complete_hooks, :keyspace
|
100
107
|
|
101
108
|
def reset
|
102
|
-
@
|
109
|
+
@statements = []
|
103
110
|
@statement_count = 0
|
104
111
|
@on_complete_hooks = []
|
105
112
|
end
|
106
113
|
|
107
|
-
def begin_statement
|
108
|
-
"BEGIN #{"UNLOGGED " if unlogged?}BATCH\n"
|
109
|
-
end
|
110
|
-
|
111
114
|
def execute_on_complete_hooks
|
112
115
|
on_complete_hooks.each { |hook| hook.call }
|
113
116
|
end
|
@@ -51,7 +51,7 @@ module Cequel
|
|
51
51
|
attr_reader :query_page_size
|
52
52
|
attr_reader :query_paging_state
|
53
53
|
|
54
|
-
def_delegator :keyspace, :
|
54
|
+
def_delegator :keyspace, :write_with_options
|
55
55
|
|
56
56
|
#
|
57
57
|
# @param table_name [Symbol] column family for this data set
|
@@ -189,8 +189,8 @@ module Cequel
|
|
189
189
|
# @example
|
190
190
|
# posts.list_prepend(:categories, ['CQL', 'ORMs'])
|
191
191
|
#
|
192
|
-
# @note
|
193
|
-
#
|
192
|
+
# @note A bug (CASSANDRA-8733) exists in Cassandra versions 0.3.0-2.0.12 and 2.1.0-2.1.2 which
|
193
|
+
# will make elements appear in REVERSE ORDER in the list.
|
194
194
|
# @note If a enclosed in a Keyspace#batch block, this method will be
|
195
195
|
# executed as part of the batch.
|
196
196
|
# @see #list_append
|
@@ -611,15 +611,16 @@ module Cequel
|
|
611
611
|
Row.from_result_row(row)
|
612
612
|
end
|
613
613
|
|
614
|
-
#
|
615
|
-
#
|
616
|
-
#
|
614
|
+
# @raise [DangerousQueryError] to prevent loading the entire record set
|
615
|
+
# to be counted
|
617
616
|
def count
|
618
|
-
|
617
|
+
raise Cequel::Record::DangerousQueryError.new
|
619
618
|
end
|
619
|
+
alias_method :length, :count
|
620
|
+
alias_method :size, :count
|
620
621
|
|
621
622
|
#
|
622
|
-
# @return [
|
623
|
+
# @return [Statement] CQL `SELECT` statement encoding this data set's scope.
|
623
624
|
#
|
624
625
|
def cql
|
625
626
|
statement = Statement.new
|
@@ -628,25 +629,13 @@ module Cequel
|
|
628
629
|
.append(*row_specifications_cql)
|
629
630
|
.append(sort_order_cql)
|
630
631
|
.append(limit_cql)
|
631
|
-
.args
|
632
|
-
end
|
633
|
-
|
634
|
-
#
|
635
|
-
# @return [String] CQL statement to get count of rows in this data set
|
636
|
-
#
|
637
|
-
def count_cql
|
638
|
-
Statement.new
|
639
|
-
.append("SELECT COUNT(*) FROM #{table_name}")
|
640
|
-
.append(*row_specifications_cql)
|
641
|
-
.append(limit_cql).args
|
642
632
|
end
|
643
633
|
|
644
634
|
#
|
645
635
|
# @return [String]
|
646
636
|
#
|
647
637
|
def inspect
|
648
|
-
"#<#{self.class.name}: "
|
649
|
-
"#{Keyspace.sanitize(cql.first, cql.drop(1))}>"
|
638
|
+
"#<#{self.class.name}: #{cql.inspect}>"
|
650
639
|
end
|
651
640
|
|
652
641
|
#
|
@@ -677,15 +666,15 @@ module Cequel
|
|
677
666
|
private
|
678
667
|
|
679
668
|
def results
|
680
|
-
@results ||= execute_cql(
|
669
|
+
@results ||= execute_cql(cql)
|
681
670
|
end
|
682
671
|
|
683
|
-
def execute_cql(
|
684
|
-
keyspace.execute_with_options(
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
672
|
+
def execute_cql(cql_stmt)
|
673
|
+
keyspace.execute_with_options(cql_stmt,
|
674
|
+
consistency: query_consistency,
|
675
|
+
page_size: query_page_size,
|
676
|
+
paging_state: query_paging_state
|
677
|
+
)
|
689
678
|
end
|
690
679
|
|
691
680
|
def inserter(&block)
|
@@ -23,8 +23,9 @@ module Cequel
|
|
23
23
|
statement = Statement.new
|
24
24
|
consistency = options.fetch(:consistency, data_set.query_consistency)
|
25
25
|
write_to_statement(statement, options)
|
26
|
-
data_set.
|
27
|
-
|
26
|
+
data_set.write_with_options(statement,
|
27
|
+
consistency: consistency
|
28
|
+
)
|
28
29
|
end
|
29
30
|
|
30
31
|
#
|
@@ -46,7 +46,7 @@ module Cequel
|
|
46
46
|
#
|
47
47
|
def_delegator :write_target, :execute, :write
|
48
48
|
|
49
|
-
# @!method
|
49
|
+
# @!method write_with_options(statement, bind_vars, consistency)
|
50
50
|
#
|
51
51
|
# Write data to this keyspace using a CQL query at the given
|
52
52
|
# consistency. Will be included the current batch operation if one is
|
@@ -55,8 +55,8 @@ module Cequel
|
|
55
55
|
# @param (see #execute_with_consistency)
|
56
56
|
# @return [void]
|
57
57
|
#
|
58
|
-
def_delegator :write_target, :
|
59
|
-
:
|
58
|
+
def_delegator :write_target, :execute_with_options,
|
59
|
+
:write_with_options
|
60
60
|
|
61
61
|
#
|
62
62
|
# @!method batch
|
@@ -187,16 +187,33 @@ module Cequel
|
|
187
187
|
# @see #execute_with_consistency
|
188
188
|
#
|
189
189
|
def execute(statement, *bind_vars)
|
190
|
-
|
190
|
+
execute_with_options(Statement.new(statement, bind_vars), { consistency: default_consistency })
|
191
191
|
end
|
192
192
|
|
193
|
-
|
193
|
+
#
|
194
|
+
# Execute a CQL query in this keyspace with the given options
|
195
|
+
#
|
196
|
+
# @param statement [String,Statement,Batch] statement to execute
|
197
|
+
# @param options [Options] options for statement execution
|
198
|
+
# @return [Enumerable] the results of the query
|
199
|
+
#
|
200
|
+
# @since 1.1.0
|
201
|
+
#
|
202
|
+
def execute_with_options(statement, options={})
|
194
203
|
options[:consistency] ||= default_consistency
|
195
204
|
|
196
205
|
retries = max_retries
|
197
|
-
|
206
|
+
cql, options = *case statement
|
207
|
+
when Statement
|
208
|
+
[client.prepare(statement.cql),
|
209
|
+
{arguments: statement.bind_vars}.merge(options)]
|
210
|
+
when Cassandra::Statements::Batch
|
211
|
+
[statement, options]
|
212
|
+
end
|
213
|
+
|
214
|
+
log('CQL', statement) do
|
198
215
|
begin
|
199
|
-
client.execute(
|
216
|
+
client.execute(cql, options)
|
200
217
|
rescue Cassandra::Errors::NoHostsAvailable,
|
201
218
|
Ione::Io::ConnectionError => e
|
202
219
|
clear_active_connections!
|
@@ -206,20 +223,6 @@ module Cequel
|
|
206
223
|
retry
|
207
224
|
end
|
208
225
|
end
|
209
|
-
|
210
|
-
end
|
211
|
-
#
|
212
|
-
# Execute a CQL query in this keyspace with the given consistency
|
213
|
-
#
|
214
|
-
# @param statement [String] CQL string
|
215
|
-
# @param bind_vars [Array] array of values for bind variables
|
216
|
-
# @param consistency [Symbol] consistency at which to execute query
|
217
|
-
# @return [Enumerable] the results of the query
|
218
|
-
#
|
219
|
-
# @since 1.1.0
|
220
|
-
#
|
221
|
-
def execute_with_consistency(statement, bind_vars, consistency)
|
222
|
-
execute_with_options(statement, bind_vars, {consistency: consistency || default_consistency})
|
223
226
|
end
|
224
227
|
|
225
228
|
#
|
@@ -250,14 +253,38 @@ module Cequel
|
|
250
253
|
|
251
254
|
# @return [Boolean] true if the keyspace exists
|
252
255
|
def exists?
|
256
|
+
cluster.has_keyspace?(name)
|
257
|
+
end
|
258
|
+
|
259
|
+
# @return [String] Cassandra version number
|
260
|
+
def cassandra_version
|
261
|
+
return @cassandra_version if @cassandra_version
|
262
|
+
|
253
263
|
statement = <<-CQL
|
254
|
-
SELECT
|
255
|
-
FROM system.
|
256
|
-
WHERE keyspace_name = ?
|
264
|
+
SELECT release_version
|
265
|
+
FROM system.local
|
257
266
|
CQL
|
258
267
|
|
259
|
-
log('CQL', statement
|
260
|
-
client_without_keyspace.execute(
|
268
|
+
log('CQL', statement) do
|
269
|
+
@cassandra_version = client_without_keyspace.execute(statement).first['release_version']
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# return true if Cassandra server version is known to include bug CASSANDRA-8733
|
274
|
+
def bug8733_version?
|
275
|
+
version_file = File.expand_path('../../../../.cassandra-versions', __FILE__)
|
276
|
+
@all_versions ||= File.read(version_file).split("\n").map(&:strip)
|
277
|
+
|
278
|
+
# bug exists in versions 0.3.0-2.0.12 and 2.1.0-2.1.2
|
279
|
+
@bug8733_versions ||= @all_versions[0..@all_versions.index('2.0.12')] +
|
280
|
+
@all_versions[@all_versions.index('2.1.0')..@all_versions.index('2.1.2')]
|
281
|
+
|
282
|
+
@bug8733_versions.include?(cassandra_version)
|
283
|
+
end
|
284
|
+
|
285
|
+
def cluster
|
286
|
+
synchronize do
|
287
|
+
@cluster ||= Cassandra.cluster(client_options)
|
261
288
|
end
|
262
289
|
end
|
263
290
|
|
@@ -271,11 +298,6 @@ module Cequel
|
|
271
298
|
def_delegator :lock, :synchronize
|
272
299
|
private :lock
|
273
300
|
|
274
|
-
def cluster
|
275
|
-
synchronize do
|
276
|
-
@cluster ||= Cassandra.cluster(client_options)
|
277
|
-
end
|
278
|
-
end
|
279
301
|
|
280
302
|
def client_without_keyspace
|
281
303
|
synchronize do
|
@@ -301,7 +323,7 @@ module Cequel
|
|
301
323
|
|
302
324
|
def extract_hosts_and_port(configuration)
|
303
325
|
hosts, ports = [], Set[]
|
304
|
-
ports << configuration[:port] if configuration.key?(:port)
|
326
|
+
ports << Integer(configuration[:port]) if configuration.key?(:port)
|
305
327
|
host_or_hosts =
|
306
328
|
configuration.fetch(:host, configuration.fetch(:hosts, '127.0.0.1'))
|
307
329
|
Array.wrap(host_or_hosts).each do |host_port|
|
@@ -311,7 +333,7 @@ module Cequel
|
|
311
333
|
warn "Specifying a hostname as host:port is deprecated. Specify " \
|
312
334
|
"only the host IP or hostname in :hosts, and specify a " \
|
313
335
|
"port for all nodes using the :port option."
|
314
|
-
ports << port
|
336
|
+
ports << Integer(port)
|
315
337
|
end
|
316
338
|
end
|
317
339
|
|
@@ -345,6 +367,7 @@ module Cequel
|
|
345
367
|
ssl_config.each { |key, value| ssl_config.delete(key) unless value }
|
346
368
|
ssl_config
|
347
369
|
end
|
370
|
+
|
348
371
|
end
|
349
372
|
end
|
350
373
|
end
|
@@ -22,18 +22,17 @@ module Cequel
|
|
22
22
|
# Log a CQL statement
|
23
23
|
#
|
24
24
|
# @param label [String] a logical label for this statement
|
25
|
-
# @param statement [String] the CQL statement to log
|
26
|
-
# @param bind_vars bind variables for the CQL statement
|
25
|
+
# @param statement [String,Statement,Batch] the CQL statement to log
|
27
26
|
# @return [void]
|
28
27
|
#
|
29
|
-
def log(label, statement
|
28
|
+
def log(label, statement)
|
30
29
|
return yield if logger.nil?
|
31
30
|
|
32
31
|
response = nil
|
33
32
|
begin
|
34
33
|
time = Benchmark.ms { response = yield }
|
35
34
|
generate_message = lambda do
|
36
|
-
format_for_log(label, "#{time.round.to_i}ms", statement
|
35
|
+
format_for_log(label, "#{time.round.to_i}ms", statement)
|
37
36
|
end
|
38
37
|
|
39
38
|
if time >= slowlog_threshold
|
@@ -42,7 +41,7 @@ module Cequel
|
|
42
41
|
logger.debug(&generate_message)
|
43
42
|
end
|
44
43
|
rescue Exception => e
|
45
|
-
logger.error { format_for_log(label, 'ERROR', statement
|
44
|
+
logger.error { format_for_log(label, 'ERROR', statement) }
|
46
45
|
raise
|
47
46
|
end
|
48
47
|
response
|
@@ -50,9 +49,24 @@ module Cequel
|
|
50
49
|
|
51
50
|
private
|
52
51
|
|
53
|
-
def format_for_log(label, timing, statement
|
54
|
-
|
55
|
-
|
52
|
+
def format_for_log(label, timing, statement)
|
53
|
+
cql_for_log =
|
54
|
+
case statement
|
55
|
+
when String
|
56
|
+
statement
|
57
|
+
when Statement
|
58
|
+
sanitize(statement.cql, limit_value_length(statement.bind_vars))
|
59
|
+
when Cassandra::Statements::Batch
|
60
|
+
batch_stmt = "BEGIN #{'UNLOGGED ' if statement.type == :unlogged}BATCH"
|
61
|
+
statement.statements.each { |s| batch_stmt << "\n" << sanitize(s.cql, limit_value_length(s.params)) }
|
62
|
+
batch_stmt << "END BATCH"
|
63
|
+
end
|
64
|
+
|
65
|
+
format('%s (%s) %s', label, timing, cql_for_log)
|
66
|
+
end
|
67
|
+
|
68
|
+
def limit_value_length(bind_vars)
|
69
|
+
bind_vars.map { |it| String === it ? limit_length(it) : it }
|
56
70
|
end
|
57
71
|
|
58
72
|
def limit_length(str)
|
@@ -35,15 +35,16 @@ module Cequel
|
|
35
35
|
# @return [String] row specification as CQL fragment
|
36
36
|
#
|
37
37
|
def cql
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
38
|
+
value = if Enumerable === @value && @value.count == 1
|
39
|
+
@value.first
|
40
|
+
else
|
41
|
+
@value
|
42
|
+
end
|
43
|
+
|
44
|
+
if Array === value
|
45
|
+
["#{@column} IN ?", value]
|
45
46
|
else
|
46
|
-
["#{@column} = ?",
|
47
|
+
["#{@column} = ?", value]
|
47
48
|
end
|
48
49
|
end
|
49
50
|
end
|
@@ -10,16 +10,30 @@ module Cequel
|
|
10
10
|
class Statement
|
11
11
|
# @return [Array] bind variables for CQL string
|
12
12
|
attr_reader :bind_vars
|
13
|
+
# @return [Array] cassandra type hints for bind variables
|
13
14
|
|
14
|
-
def initialize
|
15
|
-
|
15
|
+
def initialize(cql_or_prepared='', bind_vars=[])
|
16
|
+
cql, prepared = *case cql_or_prepared
|
17
|
+
when Cassandra::Statements::Prepared
|
18
|
+
[cql_or_prepared.cql, cql_or_prepared]
|
19
|
+
else
|
20
|
+
[cql_or_prepared.to_s, nil]
|
21
|
+
end
|
22
|
+
|
23
|
+
@cql, @prepared, @bind_vars = cql, prepared, bind_vars
|
16
24
|
end
|
17
25
|
|
18
26
|
#
|
19
27
|
# @return [String] CQL statement
|
20
28
|
#
|
21
|
-
def
|
22
|
-
@cql
|
29
|
+
def to_s
|
30
|
+
@cql
|
31
|
+
end
|
32
|
+
alias_method :cql, :to_s
|
33
|
+
|
34
|
+
# @return [Cassandra::Statements::Prepared] prepared version of this statement
|
35
|
+
def prepare(keyspace)
|
36
|
+
@prepared ||= keyspace.client.prepare(cql)
|
23
37
|
end
|
24
38
|
|
25
39
|
#
|
@@ -30,7 +44,7 @@ module Cequel
|
|
30
44
|
# @return [void]
|
31
45
|
#
|
32
46
|
def prepend(cql, *bind_vars)
|
33
|
-
@cql.
|
47
|
+
@cql.prepend(cql)
|
34
48
|
@bind_vars.unshift(*bind_vars)
|
35
49
|
end
|
36
50
|
|
@@ -43,8 +57,10 @@ module Cequel
|
|
43
57
|
# @return [void]
|
44
58
|
#
|
45
59
|
def append(cql, *bind_vars)
|
46
|
-
|
47
|
-
|
60
|
+
unless cql.nil?
|
61
|
+
@cql << cql
|
62
|
+
@bind_vars.concat(bind_vars)
|
63
|
+
end
|
48
64
|
self
|
49
65
|
end
|
50
66
|
|