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
data/lib/cequel/metal/deleter.rb
CHANGED
@@ -1,21 +1,58 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
3
|
+
#
|
4
|
+
# DSL for the construction of a DELETE statement comprising multiple
|
5
|
+
# operations (e.g. deleting a column value, deleting an element from a
|
6
|
+
# list, etc.)
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# @note This class should not be instantiated directly
|
10
|
+
# @see DataSet#delete
|
11
|
+
# @see
|
12
|
+
# http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/cql_reference/delete_r.html
|
13
|
+
# CQL documentation for DELETE
|
14
|
+
# @since 1.0.0
|
15
|
+
#
|
5
16
|
class Deleter < Writer
|
6
|
-
|
17
|
+
#
|
18
|
+
# Delete the entire row or rows matched by the data set
|
19
|
+
#
|
20
|
+
# @return [void]
|
21
|
+
#
|
7
22
|
def delete_row
|
8
23
|
@delete_row = true
|
9
24
|
end
|
10
25
|
|
26
|
+
#
|
27
|
+
# Delete specified columns
|
28
|
+
#
|
29
|
+
# @param columns [Symbol] column names to delete
|
30
|
+
# @return [void]
|
31
|
+
#
|
11
32
|
def delete_columns(*columns)
|
12
33
|
statements.concat(columns)
|
13
34
|
end
|
14
35
|
|
36
|
+
#
|
37
|
+
# Remove elements from a list by position
|
38
|
+
#
|
39
|
+
# @param column [Symbol] name of list column
|
40
|
+
# @param positions [Integer] positions in list from which to delete
|
41
|
+
# elements
|
42
|
+
# @return [void]
|
43
|
+
#
|
15
44
|
def list_remove_at(column, *positions)
|
16
|
-
statements
|
45
|
+
statements
|
46
|
+
.concat(positions.map { |position| "#{column}[#{position}]" })
|
17
47
|
end
|
18
48
|
|
49
|
+
#
|
50
|
+
# Remote elements from a map by key
|
51
|
+
#
|
52
|
+
# @param column [Symbol] name of map column
|
53
|
+
# @param keys [Object] keys to delete from map
|
54
|
+
# @return [void]
|
55
|
+
#
|
19
56
|
def map_remove(column, *keys)
|
20
57
|
statements.concat(keys.length.times.map { "#{column}[?]" })
|
21
58
|
bind_vars.concat(keys)
|
@@ -27,11 +64,11 @@ module Cequel
|
|
27
64
|
if @delete_row
|
28
65
|
statement.append("DELETE FROM #{table_name}")
|
29
66
|
elsif statements.empty?
|
30
|
-
|
67
|
+
fail ArgumentError, "No targets given for deletion!"
|
31
68
|
else
|
32
|
-
statement.append("DELETE ")
|
33
|
-
append(statements.join(','), *bind_vars)
|
34
|
-
append(" FROM #{table_name}")
|
69
|
+
statement.append("DELETE ")
|
70
|
+
.append(statements.join(','), *bind_vars)
|
71
|
+
.append(" FROM #{table_name}")
|
35
72
|
end
|
36
73
|
statement.append(generate_upsert_options)
|
37
74
|
end
|
@@ -39,9 +76,6 @@ module Cequel
|
|
39
76
|
def empty?
|
40
77
|
super && !@delete_row
|
41
78
|
end
|
42
|
-
|
43
79
|
end
|
44
|
-
|
45
80
|
end
|
46
|
-
|
47
81
|
end
|
@@ -1,9 +1,19 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
3
|
+
#
|
4
|
+
# Encapsulates a counter `UPDATE` operation comprising multiple increment
|
5
|
+
# or decrement operations
|
6
|
+
#
|
7
|
+
# @see DataSet#increment
|
8
|
+
# @since 1.0.0
|
9
|
+
#
|
5
10
|
class Incrementer < Writer
|
6
|
-
|
11
|
+
#
|
12
|
+
# Increment one or more columns by given deltas
|
13
|
+
#
|
14
|
+
# @param data [Hash<Symbol,Integer>] map of column names to deltas
|
15
|
+
# @return [void]
|
16
|
+
#
|
7
17
|
def increment(data)
|
8
18
|
data.each_pair do |column_name, delta|
|
9
19
|
operator = delta < 0 ? '-' : '+'
|
@@ -12,6 +22,12 @@ module Cequel
|
|
12
22
|
end
|
13
23
|
end
|
14
24
|
|
25
|
+
#
|
26
|
+
# Decrement one or more columns by given deltas
|
27
|
+
#
|
28
|
+
# @param data [Hash<Symbol,Integer>] map of column names to deltas
|
29
|
+
# @return [void]
|
30
|
+
#
|
15
31
|
def decrement(data)
|
16
32
|
increment(Hash[data.map { |column, count| [column, -count] }])
|
17
33
|
end
|
@@ -19,17 +35,14 @@ module Cequel
|
|
19
35
|
private
|
20
36
|
|
21
37
|
def write_to_statement(statement)
|
22
|
-
statement
|
23
|
-
append("UPDATE #{table_name}")
|
24
|
-
append(generate_upsert_options)
|
25
|
-
append(
|
38
|
+
statement
|
39
|
+
.append("UPDATE #{table_name}")
|
40
|
+
.append(generate_upsert_options)
|
41
|
+
.append(
|
26
42
|
" SET " << statements.join(', '),
|
27
43
|
*bind_vars
|
28
44
|
)
|
29
45
|
end
|
30
|
-
|
31
46
|
end
|
32
|
-
|
33
47
|
end
|
34
|
-
|
35
48
|
end
|
@@ -1,25 +1,41 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
3
|
+
#
|
4
|
+
# Encapsulates an `INSERT` statement
|
5
|
+
#
|
6
|
+
# @see DataSet#insert
|
7
|
+
# @since 1.0.0
|
8
|
+
#
|
5
9
|
class Inserter < Writer
|
6
|
-
|
10
|
+
#
|
11
|
+
# (see Writer#initialize)
|
12
|
+
#
|
7
13
|
def initialize(data_set, options = {})
|
8
14
|
@row = {}
|
9
15
|
super
|
10
16
|
end
|
11
17
|
|
18
|
+
#
|
19
|
+
# (see Writer#execute)
|
20
|
+
#
|
12
21
|
def execute
|
13
22
|
statement = Statement.new
|
14
23
|
write_to_statement(statement)
|
15
24
|
data_set.write(*statement.args)
|
16
25
|
end
|
17
26
|
|
27
|
+
#
|
28
|
+
# Insert the given data into the table
|
29
|
+
#
|
30
|
+
# @param data [Hash<Symbol,Object>] map of column names to values
|
31
|
+
# @return [void]
|
32
|
+
#
|
18
33
|
def insert(data)
|
19
34
|
@row.merge!(data.symbolize_keys)
|
20
35
|
end
|
21
36
|
|
22
37
|
private
|
38
|
+
|
23
39
|
attr_reader :row
|
24
40
|
|
25
41
|
def column_names
|
@@ -45,9 +61,6 @@ module Cequel
|
|
45
61
|
*bind_vars)
|
46
62
|
statement.append(generate_upsert_options)
|
47
63
|
end
|
48
|
-
|
49
64
|
end
|
50
|
-
|
51
65
|
end
|
52
|
-
|
53
66
|
end
|
@@ -1,113 +1,93 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Metal
|
4
|
-
|
5
3
|
#
|
6
|
-
# Handle to a Cassandra keyspace.
|
4
|
+
# Handle to a Cassandra keyspace (database). Keyspace objects are factories
|
5
|
+
# for DataSet instances and provide a handle to a Schema::Keyspace
|
6
|
+
# instance.
|
7
7
|
#
|
8
8
|
class Keyspace
|
9
|
+
extend Forwardable
|
10
|
+
include Logging
|
11
|
+
|
12
|
+
# @return [Hash] configuration options for this keyspace
|
9
13
|
attr_reader :configuration
|
14
|
+
# @return [String] name of the keyspace
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
#
|
18
|
+
# @!method write(statement, *bind_vars)
|
19
|
+
#
|
20
|
+
# Write data to this keyspace using a CQL query. Will be included the
|
21
|
+
# current batch operation if one is present.
|
22
|
+
#
|
23
|
+
# @param (see #execute)
|
24
|
+
# @return [void]
|
25
|
+
#
|
26
|
+
def_delegator :write_target, :execute, :write
|
27
|
+
|
28
|
+
#
|
29
|
+
# @!method batch
|
30
|
+
# (see Cequel::Metal::BatchManager#batch)
|
31
|
+
#
|
32
|
+
def_delegator :batch_manager, :batch
|
10
33
|
|
11
34
|
#
|
12
35
|
# @api private
|
36
|
+
# @param configuration [Options]
|
37
|
+
# @option (see #configure)
|
13
38
|
# @see Cequel.connect
|
14
39
|
#
|
15
40
|
def initialize(configuration={})
|
16
41
|
configure(configuration)
|
17
42
|
end
|
18
43
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
44
|
+
#
|
45
|
+
# Configure this keyspace from a hash of options
|
46
|
+
#
|
47
|
+
# @param configuration [Options] configuration options
|
48
|
+
# @option configuration [String] :host ('127.0.0.1:9160') host/port of
|
49
|
+
# single Cassandra instance to connect to
|
50
|
+
# @option configuration [Array<String>] :hosts list of Cassandra
|
51
|
+
# instances to connect to
|
52
|
+
# @option configuration [Hash] :thrift Thrift options to be passed
|
53
|
+
# directly to Thrift client
|
54
|
+
# @option configuration [String] :keyspace name of keyspace to connect to
|
55
|
+
# @option configuration [Integer] :pool (1) size of connection pool
|
56
|
+
# @option configuration [Integer] :pool_timeout (0) timeout when
|
57
|
+
# attempting to check out connection from pool
|
58
|
+
# @return [void]
|
59
|
+
#
|
27
60
|
def configure(configuration = {})
|
28
61
|
@configuration = configuration
|
29
|
-
@hosts = configuration.fetch(
|
62
|
+
@hosts = configuration.fetch(
|
63
|
+
:host, configuration.fetch(:hosts, '127.0.0.1:9160'))
|
30
64
|
@thrift_options = configuration[:thrift].try(:symbolize_keys) || {}
|
31
|
-
@
|
65
|
+
@name = configuration[:keyspace]
|
32
66
|
# reset the connections
|
33
67
|
clear_active_connections!
|
34
68
|
end
|
35
69
|
|
70
|
+
#
|
71
|
+
# @return [Schema::Keyspace] schema object providing full read/write
|
72
|
+
# access to database schema
|
36
73
|
def schema
|
37
74
|
Schema::Keyspace.new(self)
|
38
75
|
end
|
39
76
|
|
40
|
-
def logger=(logger)
|
41
|
-
@logger = logger
|
42
|
-
end
|
43
|
-
|
44
|
-
def logger
|
45
|
-
@logger
|
46
|
-
end
|
47
|
-
|
48
|
-
def slowlog=(slowlog)
|
49
|
-
@slowlog = slowlog
|
50
|
-
end
|
51
|
-
|
52
|
-
def slowlog
|
53
|
-
@slowlog
|
54
|
-
end
|
55
|
-
|
56
|
-
def slowlog_threshold=(slowlog_threshold)
|
57
|
-
@slowlog_threshold = slowlog_threshold
|
58
|
-
end
|
59
|
-
|
60
|
-
def slowlog_threshold
|
61
|
-
@slowlog_threshold
|
62
|
-
end
|
63
|
-
|
64
|
-
def connection_pool
|
65
|
-
return @connection_pool if defined? @connection_pool
|
66
|
-
if @configuration[:pool]
|
67
|
-
options = {
|
68
|
-
:size => @configuration[:pool] || 10,
|
69
|
-
:timeout => @configuration[:pool_timeout] || 5
|
70
|
-
}
|
71
|
-
@connection_pool = ConnectionPool.new(options) do
|
72
|
-
build_connection
|
73
|
-
end
|
74
|
-
else
|
75
|
-
@connection_pool = nil
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def connection
|
80
|
-
@connection ||= build_connection
|
81
|
-
end
|
82
|
-
|
83
|
-
def clear_active_connections!
|
84
|
-
remove_instance_variable(:@connection) if defined? @connection
|
85
|
-
remove_instance_variable(:@connection_pool) if defined? @connection_pool
|
86
|
-
end
|
87
|
-
|
88
|
-
def with_connection(&block)
|
89
|
-
if connection_pool
|
90
|
-
connection_pool.with(&block)
|
91
|
-
else
|
92
|
-
yield connection
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
77
|
#
|
97
|
-
#
|
78
|
+
# @param table_name [Symbol] the name of the table
|
79
|
+
# @return [DataSet] data set encapsulating table
|
98
80
|
#
|
99
|
-
|
100
|
-
|
101
|
-
#
|
102
|
-
def [](column_family_name)
|
103
|
-
DataSet.new(column_family_name.to_sym, self)
|
81
|
+
def [](table_name)
|
82
|
+
DataSet.new(table_name.to_sym, self)
|
104
83
|
end
|
105
84
|
|
106
85
|
#
|
107
|
-
# Execute a CQL query in this keyspace
|
86
|
+
# Execute a CQL query in this keyspace
|
108
87
|
#
|
109
88
|
# @param statement [String] CQL string
|
110
|
-
# @param
|
89
|
+
# @param bind_vars [Object] values for bind variables
|
90
|
+
# @return [void]
|
111
91
|
#
|
112
92
|
def execute(statement, *bind_vars)
|
113
93
|
log('CQL', statement, *bind_vars) do
|
@@ -118,59 +98,27 @@ module Cequel
|
|
118
98
|
end
|
119
99
|
|
120
100
|
#
|
121
|
-
#
|
122
|
-
# current batch operation if one is present.
|
101
|
+
# Clears all active connections
|
123
102
|
#
|
124
|
-
# @
|
103
|
+
# @return [void]
|
125
104
|
#
|
126
|
-
def
|
127
|
-
if
|
128
|
-
|
129
|
-
else
|
130
|
-
execute(statement, *bind_vars)
|
105
|
+
def clear_active_connections!
|
106
|
+
if defined? @connection_pool
|
107
|
+
remove_instance_variable(:@connection_pool)
|
131
108
|
end
|
132
109
|
end
|
133
110
|
|
134
|
-
|
135
|
-
# Execute write operations in a batch. Any inserts, updates, and deletes
|
136
|
-
# inside this method's block will be executed inside a CQL BATCH operation.
|
137
|
-
#
|
138
|
-
# @param options [Hash]
|
139
|
-
# @option options [Fixnum] :auto_apply Automatically send batch to Cassandra after this many statements
|
140
|
-
#
|
141
|
-
# @example Perform inserts in a batch
|
142
|
-
# DB.batch do
|
143
|
-
# DB[:posts].insert(:id => 1, :title => 'One')
|
144
|
-
# DB[:posts].insert(:id => 2, :title => 'Two')
|
145
|
-
# end
|
146
|
-
#
|
147
|
-
def batch(options = {})
|
148
|
-
new_batch = Batch.new(self, options)
|
149
|
-
|
150
|
-
if get_batch
|
151
|
-
if get_batch.unlogged? && new_batch.logged?
|
152
|
-
raise ArgumentError,
|
153
|
-
"Already in a logged batch; can't start an unlogged batch."
|
154
|
-
elsif get_batch.logged? && new_batch.unlogged?
|
155
|
-
raise ArgumentError,
|
156
|
-
"Already in an unlogged batch; can't start a logged batch."
|
157
|
-
end
|
158
|
-
return yield
|
159
|
-
end
|
111
|
+
private
|
160
112
|
|
161
|
-
|
162
|
-
|
163
|
-
yield.tap { new_batch.apply }
|
164
|
-
ensure
|
165
|
-
set_batch(nil)
|
166
|
-
end
|
167
|
-
end
|
113
|
+
def_delegator :connection_pool, :with, :with_connection
|
114
|
+
private :with_connection
|
168
115
|
|
169
|
-
|
116
|
+
def_delegator :batch_manager, :current_batch
|
117
|
+
private :current_batch
|
170
118
|
|
171
119
|
def build_connection
|
172
|
-
options = {:
|
173
|
-
options[:keyspace] =
|
120
|
+
options = {cql_version: '3.0.0'}
|
121
|
+
options[:keyspace] = name if name
|
174
122
|
CassandraCQL::Database.new(
|
175
123
|
@hosts,
|
176
124
|
options,
|
@@ -178,49 +126,24 @@ module Cequel
|
|
178
126
|
)
|
179
127
|
end
|
180
128
|
|
181
|
-
def
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
129
|
+
def connection_pool
|
130
|
+
return @connection_pool if defined? @connection_pool
|
131
|
+
options = {
|
132
|
+
size: @configuration.fetch(:pool, 1),
|
133
|
+
timeout: @configuration.fetch(:pool_timeout, 0)
|
134
|
+
}
|
135
|
+
@connection_pool = ConnectionPool.new(options) do
|
136
|
+
build_connection
|
137
|
+
end
|
187
138
|
end
|
188
139
|
|
189
|
-
def
|
190
|
-
|
140
|
+
def batch_manager
|
141
|
+
@batch_manager ||= BatchManager.new(self)
|
191
142
|
end
|
192
143
|
|
193
|
-
def
|
194
|
-
|
195
|
-
response = nil
|
196
|
-
begin
|
197
|
-
time = Benchmark.ms { response = yield }
|
198
|
-
rescue Exception => e
|
199
|
-
generate_message = proc do
|
200
|
-
sprintf(
|
201
|
-
'%s (ERROR) %s', label,
|
202
|
-
CassandraCQL::Statement.sanitize(statement, bind_vars)
|
203
|
-
)
|
204
|
-
end
|
205
|
-
logger.debug(&generate_message) if self.logger
|
206
|
-
raise
|
207
|
-
end
|
208
|
-
generate_message = proc do
|
209
|
-
sprintf(
|
210
|
-
'%s (%dms) %s', label, time.to_i,
|
211
|
-
CassandraCQL::Statement.sanitize(statement, bind_vars)
|
212
|
-
)
|
213
|
-
end
|
214
|
-
logger.debug(&generate_message) if self.logger
|
215
|
-
threshold = self.slowlog_threshold || 2000
|
216
|
-
if slowlog && time >= threshold
|
217
|
-
slowlog.warn(&generate_message)
|
218
|
-
end
|
219
|
-
response
|
144
|
+
def write_target
|
145
|
+
current_batch || self
|
220
146
|
end
|
221
|
-
|
222
147
|
end
|
223
|
-
|
224
148
|
end
|
225
|
-
|
226
149
|
end
|