cequel 0.4.2 → 0.5.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.
- data/lib/cequel.rb +1 -0
- data/lib/cequel/data_set.rb +23 -2
- data/lib/cequel/keyspace.rb +103 -20
- data/lib/cequel/model.rb +9 -4
- data/lib/cequel/model/class_internals.rb +1 -1
- data/lib/cequel/model/counter.rb +35 -0
- data/lib/cequel/model/dictionary.rb +31 -133
- data/lib/cequel/model/railtie.rb +1 -1
- data/lib/cequel/model/readable_dictionary.rb +169 -0
- data/lib/cequel/model/scoped.rb +16 -4
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/data_set_spec.rb +26 -0
- data/spec/examples/keyspace_spec.rb +1 -1
- data/spec/examples/model/counter_spec.rb +94 -0
- data/spec/examples/model/dictionary_spec.rb +2 -125
- data/spec/examples/spec_helper.rb +3 -0
- data/spec/models/comment_counts.rb +8 -0
- data/spec/shared/readable_dictionary.rb +174 -0
- metadata +26 -18
data/lib/cequel.rb
CHANGED
data/lib/cequel/data_set.rb
CHANGED
@@ -60,13 +60,34 @@ module Cequel
|
|
60
60
|
append(generate_upsert_options(options)).
|
61
61
|
append(" SET " << data.keys.map { |k| "? = ?" }.join(', '), *data.to_a.flatten).
|
62
62
|
append(*row_specifications_cql)
|
63
|
-
|
64
63
|
@keyspace.write(*statement.args)
|
65
64
|
rescue EmptySubquery
|
66
65
|
# Noop -- no rows to update
|
67
66
|
end
|
68
67
|
|
69
|
-
|
68
|
+
def increment(data, options = {})
|
69
|
+
operations = data.map do |key, value|
|
70
|
+
operator = value < 0 ? '-' : '+'
|
71
|
+
"? = ? #{operator} ?"
|
72
|
+
end
|
73
|
+
statement = Statement.new.
|
74
|
+
append("UPDATE #{@column_family}").
|
75
|
+
append(generate_upsert_options(options)).
|
76
|
+
append(
|
77
|
+
" SET " << operations.join(', '),
|
78
|
+
*data.flat_map { |column, count| [column, column, count.abs] }
|
79
|
+
).append(*row_specifications_cql)
|
80
|
+
|
81
|
+
@keyspace.write(*statement.args)
|
82
|
+
end
|
83
|
+
alias_method :incr, :increment
|
84
|
+
|
85
|
+
def decrement(data, options = {})
|
86
|
+
increment(Hash[data.map { |column, count| [column, -count] }], options)
|
87
|
+
end
|
88
|
+
alias_method :decr, :decrement
|
89
|
+
|
90
|
+
#
|
70
91
|
# Delete data from the column family
|
71
92
|
#
|
72
93
|
# @param columns zero or more columns to delete. Deletes the entire row if none specified.
|
data/lib/cequel/keyspace.rb
CHANGED
@@ -5,25 +5,81 @@ module Cequel
|
|
5
5
|
#
|
6
6
|
class Keyspace
|
7
7
|
|
8
|
-
#
|
9
|
-
# Set a logger for logging queries. Queries logged at INFO level
|
10
|
-
#
|
11
|
-
attr_writer :logger, :slowlog, :slowlog_threshold, :connection
|
12
|
-
|
13
8
|
#
|
14
9
|
# @api private
|
15
10
|
# @see Cequel.connect
|
16
11
|
#
|
17
|
-
def initialize(configuration
|
18
|
-
|
12
|
+
def initialize(configuration={})
|
13
|
+
configure(configuration)
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection=(connection)
|
17
|
+
@connection = connection
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure(configuration = {})
|
21
|
+
@configuration = configuration
|
19
22
|
@hosts = configuration[:host] || configuration[:hosts]
|
20
23
|
@thrift_options = configuration[:thrift].try(:symbolize_keys) || {}
|
24
|
+
@keyspace = configuration[:keyspace]
|
25
|
+
# reset the connections
|
26
|
+
clear_active_connections!
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger=(logger)
|
30
|
+
@logger = logger
|
31
|
+
end
|
32
|
+
|
33
|
+
def logger
|
34
|
+
@logger
|
35
|
+
end
|
36
|
+
|
37
|
+
def slowlog=(slowlog)
|
38
|
+
@slowlog = slowlog
|
39
|
+
end
|
40
|
+
|
41
|
+
def slowlog
|
42
|
+
@slowlog
|
43
|
+
end
|
44
|
+
|
45
|
+
def slowlog_threshold=(slowlog_threshold)
|
46
|
+
@slowlog_threshold = slowlog_threshold
|
47
|
+
end
|
48
|
+
|
49
|
+
def slowlog_threshold
|
50
|
+
@slowlog_threshold
|
51
|
+
end
|
52
|
+
|
53
|
+
def connection_pool
|
54
|
+
return @connection_pool if defined? @connection_pool
|
55
|
+
if @configuration[:pool]
|
56
|
+
options = {
|
57
|
+
:size => configuration[:pool],
|
58
|
+
:timeout => configuration[:pool_timeout]
|
59
|
+
}
|
60
|
+
@connection_pool = ConnectionPool.new(:size => 10, :timeout => 5) do
|
61
|
+
build_connection
|
62
|
+
end
|
63
|
+
else
|
64
|
+
@connection_pool = nil
|
65
|
+
end
|
21
66
|
end
|
22
67
|
|
23
68
|
def connection
|
24
|
-
@connection ||=
|
25
|
-
|
26
|
-
|
69
|
+
@connection ||= build_connection
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear_active_connections!
|
73
|
+
remove_instance_variable(:@connection) if defined? @connection
|
74
|
+
remove_instance_variable(:@connection_pool) if defined? @connection_pool
|
75
|
+
end
|
76
|
+
|
77
|
+
def with_connection(&block)
|
78
|
+
if connection_pool
|
79
|
+
connection_pool.with(&block)
|
80
|
+
else
|
81
|
+
yield connection
|
82
|
+
end
|
27
83
|
end
|
28
84
|
|
29
85
|
#
|
@@ -44,7 +100,9 @@ module Cequel
|
|
44
100
|
#
|
45
101
|
def execute(statement, *bind_vars)
|
46
102
|
log('CQL', statement, *bind_vars) do
|
47
|
-
|
103
|
+
with_connection do |conn|
|
104
|
+
conn.execute(statement, *bind_vars)
|
105
|
+
end
|
48
106
|
end
|
49
107
|
end
|
50
108
|
|
@@ -55,8 +113,8 @@ module Cequel
|
|
55
113
|
# @param (see #execute)
|
56
114
|
#
|
57
115
|
def write(statement, *bind_vars)
|
58
|
-
if
|
59
|
-
|
116
|
+
if get_batch
|
117
|
+
get_batch.execute(statement, *bind_vars)
|
60
118
|
else
|
61
119
|
execute(statement, *bind_vars)
|
62
120
|
end
|
@@ -76,17 +134,40 @@ module Cequel
|
|
76
134
|
# end
|
77
135
|
#
|
78
136
|
def batch(options = {})
|
79
|
-
old_batch
|
137
|
+
old_batch = get_batch
|
138
|
+
new_batch = Batch.new(self, options)
|
139
|
+
set_batch(new_batch)
|
80
140
|
yield
|
81
|
-
|
141
|
+
new_batch.apply
|
82
142
|
ensure
|
83
|
-
|
143
|
+
set_batch(old_batch)
|
84
144
|
end
|
85
145
|
|
86
146
|
private
|
87
147
|
|
148
|
+
def build_connection
|
149
|
+
options = @keyspace ? {:keyspace => @keyspace } : {}
|
150
|
+
CassandraCQL::Database.new(
|
151
|
+
@hosts,
|
152
|
+
options,
|
153
|
+
@thrift_options
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
def get_batch
|
158
|
+
::Thread.current[batch_key]
|
159
|
+
end
|
160
|
+
|
161
|
+
def set_batch(batch)
|
162
|
+
::Thread.current[batch_key] = batch
|
163
|
+
end
|
164
|
+
|
165
|
+
def batch_key
|
166
|
+
:"cequel-batch-#{object_id}"
|
167
|
+
end
|
168
|
+
|
88
169
|
def log(label, statement, *bind_vars)
|
89
|
-
return yield unless
|
170
|
+
return yield unless logger || slowlog
|
90
171
|
response = nil
|
91
172
|
time = Benchmark.ms { response = yield }
|
92
173
|
generate_message = proc do
|
@@ -95,9 +176,11 @@ module Cequel
|
|
95
176
|
CassandraCQL::Statement.sanitize(statement, bind_vars)
|
96
177
|
)
|
97
178
|
end
|
98
|
-
|
99
|
-
threshold =
|
100
|
-
|
179
|
+
logger.debug(&generate_message) if self.logger
|
180
|
+
threshold = self.slowlog_threshold || 2000
|
181
|
+
if slowlog && time >= threshold
|
182
|
+
slowlog.warn(&generate_message)
|
183
|
+
end
|
101
184
|
response
|
102
185
|
end
|
103
186
|
|
data/lib/cequel/model.rb
CHANGED
@@ -5,6 +5,7 @@ require 'cequel/model/associations'
|
|
5
5
|
require 'cequel/model/callbacks'
|
6
6
|
require 'cequel/model/class_internals'
|
7
7
|
require 'cequel/model/column'
|
8
|
+
require 'cequel/model/counter'
|
8
9
|
require 'cequel/model/dictionary'
|
9
10
|
require 'cequel/model/dirty'
|
10
11
|
require 'cequel/model/dynamic'
|
@@ -37,6 +38,8 @@ module Cequel
|
|
37
38
|
#
|
38
39
|
module Model
|
39
40
|
|
41
|
+
@lock = Monitor.new
|
42
|
+
|
40
43
|
extend ActiveSupport::Concern
|
41
44
|
extend ActiveModel::Observing::ClassMethods
|
42
45
|
|
@@ -63,10 +66,12 @@ module Cequel
|
|
63
66
|
end
|
64
67
|
|
65
68
|
def self.keyspace
|
66
|
-
@
|
67
|
-
keyspace
|
68
|
-
|
69
|
-
|
69
|
+
@lock.synchronize do
|
70
|
+
@keyspace ||= Cequel.connect(@configuration).tap do |keyspace|
|
71
|
+
keyspace.logger = @logger if @logger
|
72
|
+
keyspace.slowlog = @slowlog if @slowlog
|
73
|
+
keyspace.slowlog_threshold = @slowlog_threshold if @slowlog_threshold
|
74
|
+
end
|
70
75
|
end
|
71
76
|
end
|
72
77
|
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cequel/model/readable_dictionary'
|
2
|
+
|
3
|
+
module Cequel
|
4
|
+
|
5
|
+
module Model
|
6
|
+
|
7
|
+
class Counter < ReadableDictionary
|
8
|
+
|
9
|
+
def increment(columns_or_deltas, delta = 1)
|
10
|
+
scope.increment(construct_deltas(columns_or_deltas, delta))
|
11
|
+
end
|
12
|
+
|
13
|
+
def decrement(columns_or_deltas, delta = 1)
|
14
|
+
scope.decrement(construct_deltas(columns_or_deltas, delta))
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def construct_deltas(columns_or_deltas, delta)
|
20
|
+
if Hash === columns_or_deltas
|
21
|
+
columns_or_deltas
|
22
|
+
else
|
23
|
+
{}.tap do |deltas|
|
24
|
+
Array.wrap(columns_or_deltas).each do |column|
|
25
|
+
deltas[column] = delta
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -1,111 +1,67 @@
|
|
1
|
+
require 'cequel/model/readable_dictionary'
|
2
|
+
|
1
3
|
module Cequel
|
2
4
|
|
3
5
|
module Model
|
4
6
|
|
5
|
-
class Dictionary
|
7
|
+
class Dictionary < ReadableDictionary
|
6
8
|
|
7
9
|
class <<self
|
8
10
|
|
9
|
-
attr_writer :column_family, :default_batch_size
|
10
|
-
|
11
|
-
def key_alias
|
12
|
-
@key_alias ||= :KEY
|
13
|
-
end
|
14
|
-
|
15
|
-
def key_type
|
16
|
-
@key_type ||= :text
|
17
|
-
end
|
18
|
-
|
19
|
-
def comparator
|
20
|
-
@comparator ||= :text
|
21
|
-
end
|
22
|
-
|
23
11
|
def validation
|
24
12
|
@validation ||= :text
|
25
13
|
end
|
26
14
|
|
27
|
-
def key(key_alias, type)
|
28
|
-
@key_alias, @key_type = key_alias, type
|
29
|
-
|
30
|
-
module_eval(<<-RUBY)
|
31
|
-
def #{key_alias.downcase}
|
32
|
-
@key
|
33
|
-
end
|
34
|
-
RUBY
|
35
|
-
end
|
36
|
-
|
37
15
|
def maps(options)
|
38
16
|
@comparator, @validation = *options.first
|
39
17
|
end
|
40
18
|
|
41
|
-
def column_family
|
42
|
-
return @column_family if @column_family
|
43
|
-
self.column_family_name = name.underscore.to_sym
|
44
|
-
@column_family
|
45
|
-
end
|
46
|
-
|
47
|
-
def column_family_name=(column_family_name)
|
48
|
-
self.column_family = Cequel::Model.keyspace[column_family_name]
|
49
|
-
end
|
50
|
-
|
51
|
-
def default_batch_size
|
52
|
-
@default_batch_size || 1000
|
53
|
-
end
|
54
|
-
|
55
|
-
def [](key)
|
56
|
-
new(key)
|
57
|
-
end
|
58
|
-
private :new
|
59
19
|
end
|
60
20
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
@changed_columns.delete(column)
|
72
|
-
else
|
73
|
-
@changed_columns << column
|
74
|
-
@deleted_columns.delete(column)
|
21
|
+
def each_pair(options = {})
|
22
|
+
return super if !block_given? || @loaded
|
23
|
+
new_columns = @changed_columns.dup
|
24
|
+
super do |column, value|
|
25
|
+
if @changed_columns.include?(column)
|
26
|
+
new_columns.delete(column)
|
27
|
+
yield column, @row[column]
|
28
|
+
elsif !@deleted_columns.include?(column)
|
29
|
+
yield column, value
|
30
|
+
end
|
75
31
|
end
|
76
|
-
|
32
|
+
new_columns.each do |column|
|
33
|
+
yield column, @row[column]
|
34
|
+
end
|
35
|
+
self
|
77
36
|
end
|
78
37
|
|
79
38
|
def [](column)
|
80
39
|
if @loaded || @changed_columns.include?(column)
|
81
40
|
@row[column]
|
82
41
|
elsif !@deleted_columns.include?(column)
|
83
|
-
value =
|
42
|
+
value = super
|
84
43
|
deserialize_value(column, value) if value
|
85
44
|
end
|
86
45
|
end
|
87
46
|
|
88
|
-
def keys
|
89
|
-
@loaded ? @row.keys : each_pair.map { |key, value| key }
|
90
|
-
end
|
91
|
-
|
92
|
-
def values
|
93
|
-
@loaded ? @row.values : each_pair.map { |key, value| value }
|
94
|
-
end
|
95
|
-
|
96
47
|
def slice(*columns)
|
97
|
-
|
98
|
-
@
|
99
|
-
else
|
100
|
-
deserialize_row(load_raw_slice(columns)).tap do |slice|
|
48
|
+
super.tap do |slice|
|
49
|
+
unless @loaded
|
101
50
|
slice.merge!(@row.slice(*columns))
|
102
51
|
@deleted_columns.each { |column| slice.delete(column) }
|
103
52
|
end
|
104
53
|
end
|
105
54
|
end
|
106
55
|
|
107
|
-
def
|
108
|
-
|
56
|
+
def []=(column, value)
|
57
|
+
if value.nil?
|
58
|
+
@deleted_columns << column
|
59
|
+
@changed_columns.delete(column)
|
60
|
+
else
|
61
|
+
@changed_columns << column
|
62
|
+
@deleted_columns.delete(column)
|
63
|
+
end
|
64
|
+
@row[column] = value
|
109
65
|
end
|
110
66
|
|
111
67
|
def destroy
|
@@ -125,68 +81,14 @@ module Cequel
|
|
125
81
|
self
|
126
82
|
end
|
127
83
|
|
128
|
-
def each_pair(options = {}, &block)
|
129
|
-
return Enumerator.new(self, :each_pair, options) unless block
|
130
|
-
return @row.each_pair(&block) if @loaded
|
131
|
-
new_columns = @changed_columns.dup
|
132
|
-
batch_size = options[:batch_size] || self.class.default_batch_size
|
133
|
-
each_slice(batch_size) do |batch_results|
|
134
|
-
batch_results.each_pair do |key, value|
|
135
|
-
if @changed_columns.include?(key)
|
136
|
-
new_columns.delete(key)
|
137
|
-
yield key, @row[key]
|
138
|
-
elsif !@deleted_columns.include?(key)
|
139
|
-
yield key, value
|
140
|
-
end
|
141
|
-
end
|
142
|
-
end
|
143
|
-
new_columns.each do |key|
|
144
|
-
yield key, @row[key]
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def each(&block)
|
149
|
-
each_pair(&block)
|
150
|
-
end
|
151
|
-
|
152
|
-
def each_slice(batch_size)
|
153
|
-
batch_scope = scope.select(:first => batch_size)
|
154
|
-
key_alias = self.class.key_alias
|
155
|
-
last_key = nil
|
156
|
-
begin
|
157
|
-
batch_results = batch_scope.first
|
158
|
-
batch_results.delete(key_alias)
|
159
|
-
result_length = batch_results.length
|
160
|
-
batch_results.delete(last_key) unless last_key.nil?
|
161
|
-
yield deserialize_row(batch_results)
|
162
|
-
last_key = batch_results.keys.last
|
163
|
-
batch_scope = batch_scope.select(:from => last_key)
|
164
|
-
end while result_length == batch_size
|
165
|
-
end
|
166
|
-
|
167
|
-
def load
|
168
|
-
@row = {}
|
169
|
-
each_pair { |column, value| @row[column] = value }
|
170
|
-
@loaded = true
|
171
|
-
self
|
172
|
-
end
|
173
|
-
|
174
|
-
def loaded?
|
175
|
-
!!@loaded
|
176
|
-
end
|
177
|
-
|
178
84
|
private
|
179
85
|
|
180
|
-
def setup
|
181
|
-
|
86
|
+
def setup(init_row = nil)
|
87
|
+
super
|
182
88
|
@changed_columns = Set[]
|
183
89
|
@deleted_columns = Set[]
|
184
90
|
end
|
185
91
|
|
186
|
-
def scope
|
187
|
-
self.class.column_family.where(self.class.key_alias => @key)
|
188
|
-
end
|
189
|
-
|
190
92
|
#
|
191
93
|
# Subclasses may override this method to implement custom serialization
|
192
94
|
# strategies
|
@@ -211,10 +113,6 @@ module Cequel
|
|
211
113
|
end
|
212
114
|
end
|
213
115
|
|
214
|
-
def load_raw_slice(columns)
|
215
|
-
row = scope.select(*columns).first.except(self.class.key_alias)
|
216
|
-
end
|
217
|
-
|
218
116
|
end
|
219
117
|
|
220
118
|
end
|