cequel 1.0.0.rc1 → 1.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/cequel.rb +18 -0
  3. data/lib/cequel/errors.rb +8 -4
  4. data/lib/cequel/metal.rb +14 -0
  5. data/lib/cequel/metal/batch.rb +21 -11
  6. data/lib/cequel/metal/batch_manager.rb +74 -0
  7. data/lib/cequel/metal/cql_row_specification.rb +19 -6
  8. data/lib/cequel/metal/data_set.rb +400 -163
  9. data/lib/cequel/metal/deleter.rb +45 -11
  10. data/lib/cequel/metal/incrementer.rb +23 -10
  11. data/lib/cequel/metal/inserter.rb +19 -6
  12. data/lib/cequel/metal/keyspace.rb +82 -159
  13. data/lib/cequel/metal/logger.rb +71 -0
  14. data/lib/cequel/metal/logging.rb +47 -0
  15. data/lib/cequel/metal/new_relic_instrumentation.rb +26 -0
  16. data/lib/cequel/metal/row.rb +36 -10
  17. data/lib/cequel/metal/row_specification.rb +21 -8
  18. data/lib/cequel/metal/statement.rb +30 -6
  19. data/lib/cequel/metal/updater.rb +89 -12
  20. data/lib/cequel/metal/writer.rb +23 -14
  21. data/lib/cequel/record.rb +52 -6
  22. data/lib/cequel/record/association_collection.rb +13 -6
  23. data/lib/cequel/record/associations.rb +146 -54
  24. data/lib/cequel/record/belongs_to_association.rb +34 -7
  25. data/lib/cequel/record/bound.rb +69 -12
  26. data/lib/cequel/record/bulk_writes.rb +29 -1
  27. data/lib/cequel/record/callbacks.rb +22 -6
  28. data/lib/cequel/record/collection.rb +273 -36
  29. data/lib/cequel/record/configuration_generator.rb +5 -0
  30. data/lib/cequel/record/data_set_builder.rb +86 -0
  31. data/lib/cequel/record/dirty.rb +11 -8
  32. data/lib/cequel/record/errors.rb +38 -4
  33. data/lib/cequel/record/has_many_association.rb +42 -9
  34. data/lib/cequel/record/lazy_record_collection.rb +39 -10
  35. data/lib/cequel/record/mass_assignment.rb +14 -6
  36. data/lib/cequel/record/persistence.rb +157 -20
  37. data/lib/cequel/record/properties.rb +147 -24
  38. data/lib/cequel/record/railtie.rb +15 -2
  39. data/lib/cequel/record/record_set.rb +504 -75
  40. data/lib/cequel/record/schema.rb +77 -13
  41. data/lib/cequel/record/scoped.rb +16 -11
  42. data/lib/cequel/record/secondary_indexes.rb +42 -6
  43. data/lib/cequel/record/tasks.rb +2 -1
  44. data/lib/cequel/record/validations.rb +51 -11
  45. data/lib/cequel/schema.rb +9 -0
  46. data/lib/cequel/schema/column.rb +172 -33
  47. data/lib/cequel/schema/create_table_dsl.rb +62 -31
  48. data/lib/cequel/schema/keyspace.rb +106 -7
  49. data/lib/cequel/schema/migration_validator.rb +128 -0
  50. data/lib/cequel/schema/table.rb +183 -20
  51. data/lib/cequel/schema/table_property.rb +92 -34
  52. data/lib/cequel/schema/table_reader.rb +45 -15
  53. data/lib/cequel/schema/table_synchronizer.rb +101 -43
  54. data/lib/cequel/schema/table_updater.rb +114 -19
  55. data/lib/cequel/schema/table_writer.rb +31 -13
  56. data/lib/cequel/schema/update_table_dsl.rb +71 -40
  57. data/lib/cequel/type.rb +214 -53
  58. data/lib/cequel/util.rb +6 -9
  59. data/lib/cequel/version.rb +2 -1
  60. data/spec/examples/record/associations_spec.rb +12 -12
  61. data/spec/examples/record/persistence_spec.rb +5 -5
  62. data/spec/examples/record/record_set_spec.rb +62 -50
  63. data/spec/examples/schema/table_synchronizer_spec.rb +37 -11
  64. data/spec/examples/schema/table_updater_spec.rb +3 -3
  65. data/spec/examples/spec_helper.rb +2 -11
  66. data/spec/examples/type_spec.rb +3 -3
  67. metadata +23 -4
  68. data/lib/cequel/new_relic_instrumentation.rb +0 -22
@@ -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.concat(positions.map { |position| "#{column}[#{position}]" })
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
- raise ArgumentError, "No targets given for deletion!"
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
- def name
20
- @keyspace
21
- end
22
-
23
- def connection=(connection)
24
- @connection = connection
25
- end
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(:host, configuration.fetch(:hosts, '127.0.0.1:9160'))
62
+ @hosts = configuration.fetch(
63
+ :host, configuration.fetch(:hosts, '127.0.0.1:9160'))
30
64
  @thrift_options = configuration[:thrift].try(:symbolize_keys) || {}
31
- @keyspace = configuration[:keyspace]
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
- # Get DataSet encapsulating a column family in this keyspace
78
+ # @param table_name [Symbol] the name of the table
79
+ # @return [DataSet] data set encapsulating table
98
80
  #
99
- # @param column_family_name [Symbol] the name of the column family
100
- # @return [DataSet] a column family
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 *bind_vars [Object] values for bind variables
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
- # Write data to this keyspace using a CQL query. Will be included the
122
- # current batch operation if one is present.
101
+ # Clears all active connections
123
102
  #
124
- # @param (see #execute)
103
+ # @return [void]
125
104
  #
126
- def write(statement, *bind_vars)
127
- if get_batch
128
- get_batch.execute(statement, *bind_vars)
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
- begin
162
- set_batch(new_batch)
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
- private
116
+ def_delegator :batch_manager, :current_batch
117
+ private :current_batch
170
118
 
171
119
  def build_connection
172
- options = {:cql_version => '3.0.0'}
173
- options[:keyspace] = @keyspace if @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 get_batch
182
- ::Thread.current[batch_key]
183
- end
184
-
185
- def set_batch(batch)
186
- ::Thread.current[batch_key] = batch
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 batch_key
190
- :"cequel-batch-#{object_id}"
140
+ def batch_manager
141
+ @batch_manager ||= BatchManager.new(self)
191
142
  end
192
143
 
193
- def log(label, statement, *bind_vars)
194
- return yield unless logger || slowlog
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