cequel 1.0.0.rc1 → 1.0.0.rc2
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/cequel.rb +18 -0
- data/lib/cequel/errors.rb +8 -4
- data/lib/cequel/metal.rb +14 -0
- data/lib/cequel/metal/batch.rb +21 -11
- data/lib/cequel/metal/batch_manager.rb +74 -0
- data/lib/cequel/metal/cql_row_specification.rb +19 -6
- data/lib/cequel/metal/data_set.rb +400 -163
- data/lib/cequel/metal/deleter.rb +45 -11
- data/lib/cequel/metal/incrementer.rb +23 -10
- data/lib/cequel/metal/inserter.rb +19 -6
- data/lib/cequel/metal/keyspace.rb +82 -159
- data/lib/cequel/metal/logger.rb +71 -0
- data/lib/cequel/metal/logging.rb +47 -0
- data/lib/cequel/metal/new_relic_instrumentation.rb +26 -0
- data/lib/cequel/metal/row.rb +36 -10
- data/lib/cequel/metal/row_specification.rb +21 -8
- data/lib/cequel/metal/statement.rb +30 -6
- data/lib/cequel/metal/updater.rb +89 -12
- data/lib/cequel/metal/writer.rb +23 -14
- data/lib/cequel/record.rb +52 -6
- data/lib/cequel/record/association_collection.rb +13 -6
- data/lib/cequel/record/associations.rb +146 -54
- data/lib/cequel/record/belongs_to_association.rb +34 -7
- data/lib/cequel/record/bound.rb +69 -12
- data/lib/cequel/record/bulk_writes.rb +29 -1
- data/lib/cequel/record/callbacks.rb +22 -6
- data/lib/cequel/record/collection.rb +273 -36
- data/lib/cequel/record/configuration_generator.rb +5 -0
- data/lib/cequel/record/data_set_builder.rb +86 -0
- data/lib/cequel/record/dirty.rb +11 -8
- data/lib/cequel/record/errors.rb +38 -4
- data/lib/cequel/record/has_many_association.rb +42 -9
- data/lib/cequel/record/lazy_record_collection.rb +39 -10
- data/lib/cequel/record/mass_assignment.rb +14 -6
- data/lib/cequel/record/persistence.rb +157 -20
- data/lib/cequel/record/properties.rb +147 -24
- data/lib/cequel/record/railtie.rb +15 -2
- data/lib/cequel/record/record_set.rb +504 -75
- data/lib/cequel/record/schema.rb +77 -13
- data/lib/cequel/record/scoped.rb +16 -11
- data/lib/cequel/record/secondary_indexes.rb +42 -6
- data/lib/cequel/record/tasks.rb +2 -1
- data/lib/cequel/record/validations.rb +51 -11
- data/lib/cequel/schema.rb +9 -0
- data/lib/cequel/schema/column.rb +172 -33
- data/lib/cequel/schema/create_table_dsl.rb +62 -31
- data/lib/cequel/schema/keyspace.rb +106 -7
- data/lib/cequel/schema/migration_validator.rb +128 -0
- data/lib/cequel/schema/table.rb +183 -20
- data/lib/cequel/schema/table_property.rb +92 -34
- data/lib/cequel/schema/table_reader.rb +45 -15
- data/lib/cequel/schema/table_synchronizer.rb +101 -43
- data/lib/cequel/schema/table_updater.rb +114 -19
- data/lib/cequel/schema/table_writer.rb +31 -13
- data/lib/cequel/schema/update_table_dsl.rb +71 -40
- data/lib/cequel/type.rb +214 -53
- data/lib/cequel/util.rb +6 -9
- data/lib/cequel/version.rb +2 -1
- data/spec/examples/record/associations_spec.rb +12 -12
- data/spec/examples/record/persistence_spec.rb +5 -5
- data/spec/examples/record/record_set_spec.rb +62 -50
- data/spec/examples/schema/table_synchronizer_spec.rb +37 -11
- data/spec/examples/schema/table_updater_spec.rb +3 -3
- data/spec/examples/spec_helper.rb +2 -11
- data/spec/examples/type_spec.rb +3 -3
- metadata +23 -4
- data/lib/cequel/new_relic_instrumentation.rb +0 -22
@@ -0,0 +1,71 @@
|
|
1
|
+
module Cequel
|
2
|
+
module Metal
|
3
|
+
#
|
4
|
+
# The Logger class encapsulates logging functionality for {Keyspace}.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
class Logger
|
9
|
+
extend Forwardable
|
10
|
+
# @return [::Logger] An instance of Logger from the standard library
|
11
|
+
attr_reader :out
|
12
|
+
# @return [Integer] The severity level for this logger
|
13
|
+
attr_reader :severity
|
14
|
+
# @return [Integer] Only log queries that take longer than threshold ms
|
15
|
+
attr_accessor :threshold
|
16
|
+
|
17
|
+
#
|
18
|
+
# @param out [::Logger] An instance of Logger from the standard library
|
19
|
+
# @param severity [Integer] The severity level for this logger
|
20
|
+
# @param threshold [Integer] Only log queries that take longer than
|
21
|
+
# `threshold` ms
|
22
|
+
#
|
23
|
+
def initialize(out, severity, threshold = 0)
|
24
|
+
@out, @severity, @threshold = out, severity, threshold
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Log a CQL statement
|
29
|
+
#
|
30
|
+
# @param label [String] a logical label for this statement
|
31
|
+
# @param timing [Integer] how long this statement took in ms
|
32
|
+
# @param statement [String] the CQL statement to log
|
33
|
+
# @param bind_vars [Array] bind variables for the CQL statement
|
34
|
+
# @return [void]
|
35
|
+
#
|
36
|
+
def log(label, timing, statement, bind_vars)
|
37
|
+
if timing >= threshold
|
38
|
+
out.add(severity) do
|
39
|
+
sprintf(
|
40
|
+
'%s (%dms) %s',
|
41
|
+
label, timing, sanitize(statement, bind_vars)
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def_delegator 'CassandraCQL::Statement', :sanitize
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Logger for queries that resulted in an exception
|
54
|
+
#
|
55
|
+
class ExceptionLogger < Logger
|
56
|
+
#
|
57
|
+
# Log a CQL statement that resulted in an exception
|
58
|
+
#
|
59
|
+
# @param label [String] a logical label for this statement
|
60
|
+
# @param statement [String] the CQL statement to log
|
61
|
+
# @param bind_vars [Array] bind variables for the CQL statement
|
62
|
+
# @return [void]
|
63
|
+
#
|
64
|
+
def log(label, statement, bind_vars)
|
65
|
+
out.add(severity) do
|
66
|
+
sprintf('%s (ERROR) %s', label, sanitize(statement, bind_vars))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Cequel
|
2
|
+
module Metal
|
3
|
+
#
|
4
|
+
# Methods to handle logging for {Keyspace} instances
|
5
|
+
#
|
6
|
+
module Logging
|
7
|
+
def logger=(logger)
|
8
|
+
loggers << Logger.new(logger, ::Logger::DEBUG)
|
9
|
+
self.exception_logger = ExceptionLogger.new(logger, ::Logger::ERROR)
|
10
|
+
end
|
11
|
+
|
12
|
+
def slowlog=(slowlog)
|
13
|
+
warn "#slowlog= is deprecated and will be removed from a future " \
|
14
|
+
"version"
|
15
|
+
loggers << @slowlog = Logger.new(slowlog, ::Logger::WARN, 2000)
|
16
|
+
end
|
17
|
+
|
18
|
+
def slowlog_threshold=(threshold)
|
19
|
+
@slowlog.threshold = threshold
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
attr_accessor :exception_logger
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def log(label, statement, *bind_vars)
|
29
|
+
response = nil
|
30
|
+
begin
|
31
|
+
time = Benchmark.ms { response = yield }
|
32
|
+
loggers.each do |logger|
|
33
|
+
logger.log(label, time, statement, bind_vars)
|
34
|
+
end
|
35
|
+
rescue Exception => e
|
36
|
+
exception_logger.log(label, statement, bind_vars) if exception_logger
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
response
|
40
|
+
end
|
41
|
+
|
42
|
+
def loggers
|
43
|
+
@loggers ||= []
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
begin
|
2
|
+
require 'new_relic/agent/method_tracer'
|
3
|
+
rescue LoadError => e
|
4
|
+
fail LoadError, "Can't use NewRelic instrumentation without NewRelic gem"
|
5
|
+
end
|
6
|
+
|
7
|
+
module Cequel
|
8
|
+
module Metal
|
9
|
+
#
|
10
|
+
# Provides NewRelic instrumentation for CQL queries.
|
11
|
+
#
|
12
|
+
module NewRelicInstrumentation
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
include NewRelic::Agent::MethodTracer
|
17
|
+
|
18
|
+
add_method_tracer :execute,
|
19
|
+
'Database/Cassandra/#{args[0][/^[A-Z ]*[A-Z]/]' \
|
20
|
+
'.sub(/ FROM$/, \'\')}'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Cequel::Metal::Keyspace.module_eval { include NewRelicInstrumentation }
|
data/lib/cequel/metal/row.rb
CHANGED
@@ -1,13 +1,25 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
#
|
4
|
+
# A result row from a CQL query. Acts as a hash of column names to values,
|
5
|
+
# but also exposes TTLs and writetimes
|
6
|
+
#
|
7
|
+
# @since 1.0.0
|
8
|
+
#
|
9
|
+
class Row < DelegateClass(ActiveSupport::HashWithIndifferentAccess)
|
10
|
+
#
|
11
|
+
# Encapsulate a row from CassandraCQL
|
12
|
+
#
|
13
|
+
# @param result_row [CassandraCQL::Row] row from underlying driver
|
14
|
+
# @return [Row] encapsulated row
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
#
|
7
18
|
def self.from_result_row(result_row)
|
8
19
|
if result_row
|
9
20
|
new.tap do |row|
|
10
|
-
result_row.column_names
|
21
|
+
names, values = result_row.column_names, result_row.column_values
|
22
|
+
names.zip(values) do |name, value|
|
11
23
|
if name =~ /^(ttl|writetime)\((.+)\)$/
|
12
24
|
if $1 == 'ttl' then row.set_ttl($2, value)
|
13
25
|
else row.set_writetime($2, value)
|
@@ -19,30 +31,44 @@ module Cequel
|
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
22
|
-
|
23
|
-
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
#
|
37
|
+
def initialize
|
38
|
+
super(ActiveSupport::HashWithIndifferentAccess.new)
|
24
39
|
@ttls = ActiveSupport::HashWithIndifferentAccess.new
|
25
40
|
@writetimes = ActiveSupport::HashWithIndifferentAccess.new
|
26
41
|
end
|
27
42
|
|
43
|
+
#
|
44
|
+
# Get the TTL (time-to-live) of a column
|
45
|
+
#
|
46
|
+
# @param column [Symbol] column name
|
47
|
+
# @return [Integer] TTL of column in seconds
|
48
|
+
#
|
28
49
|
def ttl(column)
|
29
50
|
@ttls[column]
|
30
51
|
end
|
31
52
|
|
53
|
+
#
|
54
|
+
# Get the writetime of a column
|
55
|
+
#
|
56
|
+
# @param column [Symbol] column name
|
57
|
+
# @return [Integer] writetime of column in nanoseconds since epoch
|
58
|
+
#
|
32
59
|
def writetime(column)
|
33
60
|
@writetimes[column]
|
34
61
|
end
|
35
62
|
|
63
|
+
# @private
|
36
64
|
def set_ttl(column, value)
|
37
65
|
@ttls[column] = value
|
38
66
|
end
|
39
67
|
|
68
|
+
# @private
|
40
69
|
def set_writetime(column, value)
|
41
70
|
@writetimes[column] = value
|
42
71
|
end
|
43
|
-
|
44
72
|
end
|
45
|
-
|
46
73
|
end
|
47
|
-
|
48
74
|
end
|
@@ -1,22 +1,38 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
5
3
|
#
|
6
|
-
#
|
4
|
+
# Encapsulates a row specification (`WHERE` clause) constructed from a
|
5
|
+
# column ane one or more values to match
|
6
|
+
#
|
7
|
+
# @api private
|
7
8
|
#
|
8
9
|
class RowSpecification
|
9
|
-
|
10
|
+
#
|
11
|
+
# Build one or more row specifications
|
12
|
+
#
|
13
|
+
# @param column_values [Hash] map of column name to value or values
|
14
|
+
# @return [Array<RowSpecification>] collection of row specifications
|
15
|
+
#
|
10
16
|
def self.build(column_values)
|
11
17
|
column_values.map { |column, value| new(column, value) }
|
12
18
|
end
|
13
19
|
|
14
|
-
|
20
|
+
# @return [Symbol] column name
|
21
|
+
attr_reader :column
|
22
|
+
# @return [Object, Array] value or values to match
|
23
|
+
attr_reader :value
|
15
24
|
|
25
|
+
#
|
26
|
+
# @param column [Symbol] column name
|
27
|
+
# @param value [Object,Array] value or values to match
|
28
|
+
#
|
16
29
|
def initialize(column, value)
|
17
30
|
@column, @value = column, value
|
18
31
|
end
|
19
32
|
|
33
|
+
#
|
34
|
+
# @return [String] row specification as CQL fragment
|
35
|
+
#
|
20
36
|
def cql
|
21
37
|
case @value
|
22
38
|
when Array
|
@@ -29,9 +45,6 @@ module Cequel
|
|
29
45
|
["#{@column} = ?", @value]
|
30
46
|
end
|
31
47
|
end
|
32
|
-
|
33
48
|
end
|
34
|
-
|
35
49
|
end
|
36
|
-
|
37
50
|
end
|
@@ -1,35 +1,59 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
3
|
+
#
|
4
|
+
# Builder for CQL statements. Contains a CQL string with bind substitutions
|
5
|
+
# and a collection of bind variables
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
5
9
|
class Statement
|
6
|
-
|
10
|
+
# @return [Array] bind variables for CQL string
|
11
|
+
attr_reader :bind_vars
|
7
12
|
|
8
13
|
def initialize
|
9
14
|
@cql, @bind_vars = [], []
|
10
15
|
end
|
11
16
|
|
17
|
+
#
|
18
|
+
# @return [String] CQL statement
|
19
|
+
#
|
12
20
|
def cql
|
13
21
|
@cql.join
|
14
22
|
end
|
15
23
|
|
24
|
+
#
|
25
|
+
# Add a CQL fragment with optional bind variables to the beginning of
|
26
|
+
# the statement
|
27
|
+
#
|
28
|
+
# @param (see #append)
|
29
|
+
# @return [void]
|
30
|
+
#
|
16
31
|
def prepend(cql, *bind_vars)
|
17
32
|
@cql.unshift(cql)
|
18
33
|
@bind_vars.unshift(*bind_vars)
|
19
34
|
end
|
20
35
|
|
36
|
+
#
|
37
|
+
# Add a CQL fragment with optional bind variables to the end of the
|
38
|
+
# statement
|
39
|
+
#
|
40
|
+
# @param cql [String] CQL fragment
|
41
|
+
# @param bind_vars [Object] zero or more bind variables
|
42
|
+
# @return [void]
|
43
|
+
#
|
21
44
|
def append(cql, *bind_vars)
|
22
45
|
@cql << cql
|
23
46
|
@bind_vars.concat(bind_vars)
|
24
47
|
self
|
25
48
|
end
|
26
49
|
|
50
|
+
#
|
51
|
+
# @return [Array] this statement as an array of arguments to
|
52
|
+
# Keyspace#execute (CQL string followed by bind variables)
|
53
|
+
#
|
27
54
|
def args
|
28
55
|
[cql, *bind_vars]
|
29
56
|
end
|
30
|
-
|
31
57
|
end
|
32
|
-
|
33
58
|
end
|
34
|
-
|
35
59
|
end
|
data/lib/cequel/metal/updater.rb
CHANGED
@@ -1,9 +1,25 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
3
|
+
#
|
4
|
+
# Builder for `UPDATE` statement containing heterogeneous operations (set
|
5
|
+
# columns, atomically mutate collections)
|
6
|
+
#
|
7
|
+
# @see DataSet#update
|
8
|
+
# @see Deleter
|
9
|
+
# @see
|
10
|
+
# http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/cql_reference/update_r.html
|
11
|
+
# CQL UPDATE documentation
|
12
|
+
# @since 1.0.0
|
13
|
+
#
|
5
14
|
class Updater < Writer
|
6
|
-
|
15
|
+
#
|
16
|
+
# Directly set column values
|
17
|
+
#
|
18
|
+
# @param data [Hash] map of column names to values
|
19
|
+
# @return [void]
|
20
|
+
#
|
21
|
+
# @see DataSet#update
|
22
|
+
#
|
7
23
|
def set(data)
|
8
24
|
data.each_pair do |column, value|
|
9
25
|
prepare_upsert_value(value) do |binding, *values|
|
@@ -13,36 +29,100 @@ module Cequel
|
|
13
29
|
end
|
14
30
|
end
|
15
31
|
|
32
|
+
#
|
33
|
+
# Prepend elements to a list column
|
34
|
+
#
|
35
|
+
# @param column [Symbol] column name
|
36
|
+
# @param elements [Array<Object>] elements to prepend
|
37
|
+
# @return [void]
|
38
|
+
#
|
39
|
+
# @see DataSet#list_prepend
|
40
|
+
#
|
16
41
|
def list_prepend(column, elements)
|
17
42
|
statements << "#{column} = [?] + #{column}"
|
18
43
|
bind_vars << elements
|
19
44
|
end
|
20
45
|
|
46
|
+
#
|
47
|
+
# Append elements to a list column
|
48
|
+
#
|
49
|
+
# @param column [Symbol] column name
|
50
|
+
# @param elements [Array] elements to append
|
51
|
+
# @return [void]
|
52
|
+
#
|
53
|
+
# @see DataSet#list_append
|
54
|
+
#
|
21
55
|
def list_append(column, elements)
|
22
56
|
statements << "#{column} = #{column} + [?]"
|
23
57
|
bind_vars << elements
|
24
58
|
end
|
25
59
|
|
60
|
+
#
|
61
|
+
# Remove all occurrences of an element from a list
|
62
|
+
#
|
63
|
+
# @param column [Symbol] column name
|
64
|
+
# @param value value to remove
|
65
|
+
# @return [void]
|
66
|
+
#
|
67
|
+
# @see DataSet#list_remove
|
68
|
+
#
|
26
69
|
def list_remove(column, value)
|
27
70
|
statements << "#{column} = #{column} - [?]"
|
28
71
|
bind_vars << value
|
29
72
|
end
|
30
73
|
|
74
|
+
#
|
75
|
+
# Replace a list item at a given position
|
76
|
+
#
|
77
|
+
# @param column [Symbol] column name
|
78
|
+
# @param index [Integer] index at which to replace value
|
79
|
+
# @param value new value for position
|
80
|
+
# @return [void]
|
81
|
+
#
|
82
|
+
# @see DataSet#list_replace
|
83
|
+
#
|
31
84
|
def list_replace(column, index, value)
|
32
85
|
statements << "#{column}[#{index}] = ?"
|
33
86
|
bind_vars << value
|
34
87
|
end
|
35
88
|
|
89
|
+
#
|
90
|
+
# Add elements to a set
|
91
|
+
#
|
92
|
+
# @param column [Symbol] column name
|
93
|
+
# @param values [Set] elements to add to set
|
94
|
+
# @return [void]
|
95
|
+
#
|
96
|
+
# @see DataSet#set_add
|
97
|
+
#
|
36
98
|
def set_add(column, values)
|
37
99
|
statements << "#{column} = #{column} + {?}"
|
38
100
|
bind_vars << values
|
39
101
|
end
|
40
102
|
|
41
|
-
|
103
|
+
#
|
104
|
+
# Remove elements from a set
|
105
|
+
#
|
106
|
+
# @param column [Symbol] column name
|
107
|
+
# @param values [Set] elements to remove from set
|
108
|
+
# @return [void]
|
109
|
+
#
|
110
|
+
# @see DataSet#set_remove
|
111
|
+
#
|
112
|
+
def set_remove(column, values)
|
42
113
|
statements << "#{column} = #{column} - {?}"
|
43
|
-
bind_vars << ::Kernel.Array(
|
114
|
+
bind_vars << ::Kernel.Array(values)
|
44
115
|
end
|
45
116
|
|
117
|
+
#
|
118
|
+
# Add or update elements in a map
|
119
|
+
#
|
120
|
+
# @param column [Symbol] column name
|
121
|
+
# @param updates [Hash] map of keys to values to update in map
|
122
|
+
# @return [void]
|
123
|
+
#
|
124
|
+
# @see DataSet#map_update
|
125
|
+
#
|
46
126
|
def map_update(column, updates)
|
47
127
|
binding_pairs = ::Array.new(updates.length) { '?:?' }.join(',')
|
48
128
|
statements << "#{column} = #{column} + {#{binding_pairs}}"
|
@@ -52,14 +132,11 @@ module Cequel
|
|
52
132
|
private
|
53
133
|
|
54
134
|
def write_to_statement(statement)
|
55
|
-
statement.append("UPDATE #{table_name}")
|
56
|
-
append(generate_upsert_options)
|
57
|
-
append(" SET ")
|
58
|
-
append(statements.join(', '), *bind_vars)
|
135
|
+
statement.append("UPDATE #{table_name}")
|
136
|
+
.append(generate_upsert_options)
|
137
|
+
.append(" SET ")
|
138
|
+
.append(statements.join(', '), *bind_vars)
|
59
139
|
end
|
60
|
-
|
61
140
|
end
|
62
|
-
|
63
141
|
end
|
64
|
-
|
65
142
|
end
|