cequel 1.10.0 → 2.0.0
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/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
|
|