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.
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