cequel 0.4.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|