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 CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'active_support/core_ext'
2
2
  require 'cassandra-cql'
3
+ require 'connection_pool'
3
4
 
4
5
  require 'cequel/batch'
5
6
  require 'cequel/errors'
@@ -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.
@@ -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
- @name = configuration[:keyspace]
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 ||= CassandraCQL::Database.new(
25
- @hosts, {:keyspace => @name}, @thrift_options
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
- connection.execute(statement, *bind_vars)
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 @batch
59
- @batch.execute(statement, *bind_vars)
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, @batch = @batch, Batch.new(self, options)
137
+ old_batch = get_batch
138
+ new_batch = Batch.new(self, options)
139
+ set_batch(new_batch)
80
140
  yield
81
- @batch.apply
141
+ new_batch.apply
82
142
  ensure
83
- @batch = old_batch
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 @logger || @slowlog
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
- @logger.debug(&generate_message) if @logger
99
- threshold = @slowlog_threshold || 2000
100
- @slowlog.warn(&generate_message) if @slowlog && time >= threshold
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
- @keyspace ||= Cequel.connect(@configuration).tap do |keyspace|
67
- keyspace.logger = @logger if @logger
68
- keyspace.slowlog = @slowlog if @slowlog
69
- keyspace.slowlog_threshold = @slowlog_threshold if @slowlog_threshold
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
 
@@ -7,7 +7,7 @@ module Cequel
7
7
  #
8
8
  class ClassInternals
9
9
 
10
- attr_accessor :key, :current_scope, :default_scope
10
+ attr_accessor :key, :default_scope
11
11
  attr_reader :columns, :associations, :index_preference
12
12
 
13
13
  def initialize(clazz)
@@ -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
- include Enumerable
62
-
63
- def initialize(key)
64
- @key = key
65
- setup
66
- end
67
-
68
- def []=(column, value)
69
- if value.nil?
70
- @deleted_columns << column
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
- @row[column] = value
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 = scope.select(column).first[column]
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
- if @loaded
98
- @row.slice(*columns)
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 key?(column)
108
- @row.key?(column) || load_raw_slice([column])[column].present?
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
- @row = {}
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