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
@@ -0,0 +1,71 @@
1
+ module Cequel
2
+ module Metal
3
+ #
4
+ # The Logger class encapsulates logging functionality for {Keyspace}.
5
+ #
6
+ # @api private
7
+ #
8
+ class Logger
9
+ extend Forwardable
10
+ # @return [::Logger] An instance of Logger from the standard library
11
+ attr_reader :out
12
+ # @return [Integer] The severity level for this logger
13
+ attr_reader :severity
14
+ # @return [Integer] Only log queries that take longer than threshold ms
15
+ attr_accessor :threshold
16
+
17
+ #
18
+ # @param out [::Logger] An instance of Logger from the standard library
19
+ # @param severity [Integer] The severity level for this logger
20
+ # @param threshold [Integer] Only log queries that take longer than
21
+ # `threshold` ms
22
+ #
23
+ def initialize(out, severity, threshold = 0)
24
+ @out, @severity, @threshold = out, severity, threshold
25
+ end
26
+
27
+ #
28
+ # Log a CQL statement
29
+ #
30
+ # @param label [String] a logical label for this statement
31
+ # @param timing [Integer] how long this statement took in ms
32
+ # @param statement [String] the CQL statement to log
33
+ # @param bind_vars [Array] bind variables for the CQL statement
34
+ # @return [void]
35
+ #
36
+ def log(label, timing, statement, bind_vars)
37
+ if timing >= threshold
38
+ out.add(severity) do
39
+ sprintf(
40
+ '%s (%dms) %s',
41
+ label, timing, sanitize(statement, bind_vars)
42
+ )
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def_delegator 'CassandraCQL::Statement', :sanitize
50
+ end
51
+
52
+ #
53
+ # Logger for queries that resulted in an exception
54
+ #
55
+ class ExceptionLogger < Logger
56
+ #
57
+ # Log a CQL statement that resulted in an exception
58
+ #
59
+ # @param label [String] a logical label for this statement
60
+ # @param statement [String] the CQL statement to log
61
+ # @param bind_vars [Array] bind variables for the CQL statement
62
+ # @return [void]
63
+ #
64
+ def log(label, statement, bind_vars)
65
+ out.add(severity) do
66
+ sprintf('%s (ERROR) %s', label, sanitize(statement, bind_vars))
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ module Cequel
2
+ module Metal
3
+ #
4
+ # Methods to handle logging for {Keyspace} instances
5
+ #
6
+ module Logging
7
+ def logger=(logger)
8
+ loggers << Logger.new(logger, ::Logger::DEBUG)
9
+ self.exception_logger = ExceptionLogger.new(logger, ::Logger::ERROR)
10
+ end
11
+
12
+ def slowlog=(slowlog)
13
+ warn "#slowlog= is deprecated and will be removed from a future " \
14
+ "version"
15
+ loggers << @slowlog = Logger.new(slowlog, ::Logger::WARN, 2000)
16
+ end
17
+
18
+ def slowlog_threshold=(threshold)
19
+ @slowlog.threshold = threshold
20
+ end
21
+
22
+ protected
23
+
24
+ attr_accessor :exception_logger
25
+
26
+ private
27
+
28
+ def log(label, statement, *bind_vars)
29
+ response = nil
30
+ begin
31
+ time = Benchmark.ms { response = yield }
32
+ loggers.each do |logger|
33
+ logger.log(label, time, statement, bind_vars)
34
+ end
35
+ rescue Exception => e
36
+ exception_logger.log(label, statement, bind_vars) if exception_logger
37
+ raise
38
+ end
39
+ response
40
+ end
41
+
42
+ def loggers
43
+ @loggers ||= []
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ begin
2
+ require 'new_relic/agent/method_tracer'
3
+ rescue LoadError => e
4
+ fail LoadError, "Can't use NewRelic instrumentation without NewRelic gem"
5
+ end
6
+
7
+ module Cequel
8
+ module Metal
9
+ #
10
+ # Provides NewRelic instrumentation for CQL queries.
11
+ #
12
+ module NewRelicInstrumentation
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ include NewRelic::Agent::MethodTracer
17
+
18
+ add_method_tracer :execute,
19
+ 'Database/Cassandra/#{args[0][/^[A-Z ]*[A-Z]/]' \
20
+ '.sub(/ FROM$/, \'\')}'
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ Cequel::Metal::Keyspace.module_eval { include NewRelicInstrumentation }
@@ -1,13 +1,25 @@
1
1
  module Cequel
2
-
3
2
  module Metal
4
-
5
- class Row < ActiveSupport::HashWithIndifferentAccess
6
-
3
+ #
4
+ # A result row from a CQL query. Acts as a hash of column names to values,
5
+ # but also exposes TTLs and writetimes
6
+ #
7
+ # @since 1.0.0
8
+ #
9
+ class Row < DelegateClass(ActiveSupport::HashWithIndifferentAccess)
10
+ #
11
+ # Encapsulate a row from CassandraCQL
12
+ #
13
+ # @param result_row [CassandraCQL::Row] row from underlying driver
14
+ # @return [Row] encapsulated row
15
+ #
16
+ # @api private
17
+ #
7
18
  def self.from_result_row(result_row)
8
19
  if result_row
9
20
  new.tap do |row|
10
- result_row.column_names.zip(result_row.column_values) do |name, value|
21
+ names, values = result_row.column_names, result_row.column_values
22
+ names.zip(values) do |name, value|
11
23
  if name =~ /^(ttl|writetime)\((.+)\)$/
12
24
  if $1 == 'ttl' then row.set_ttl($2, value)
13
25
  else row.set_writetime($2, value)
@@ -19,30 +31,44 @@ module Cequel
19
31
  end
20
32
  end
21
33
 
22
- def initialize(*_)
23
- super
34
+ #
35
+ # @api private
36
+ #
37
+ def initialize
38
+ super(ActiveSupport::HashWithIndifferentAccess.new)
24
39
  @ttls = ActiveSupport::HashWithIndifferentAccess.new
25
40
  @writetimes = ActiveSupport::HashWithIndifferentAccess.new
26
41
  end
27
42
 
43
+ #
44
+ # Get the TTL (time-to-live) of a column
45
+ #
46
+ # @param column [Symbol] column name
47
+ # @return [Integer] TTL of column in seconds
48
+ #
28
49
  def ttl(column)
29
50
  @ttls[column]
30
51
  end
31
52
 
53
+ #
54
+ # Get the writetime of a column
55
+ #
56
+ # @param column [Symbol] column name
57
+ # @return [Integer] writetime of column in nanoseconds since epoch
58
+ #
32
59
  def writetime(column)
33
60
  @writetimes[column]
34
61
  end
35
62
 
63
+ # @private
36
64
  def set_ttl(column, value)
37
65
  @ttls[column] = value
38
66
  end
39
67
 
68
+ # @private
40
69
  def set_writetime(column, value)
41
70
  @writetimes[column] = value
42
71
  end
43
-
44
72
  end
45
-
46
73
  end
47
-
48
74
  end
@@ -1,22 +1,38 @@
1
1
  module Cequel
2
-
3
2
  module Metal
4
-
5
3
  #
6
- # @private
4
+ # Encapsulates a row specification (`WHERE` clause) constructed from a
5
+ # column ane one or more values to match
6
+ #
7
+ # @api private
7
8
  #
8
9
  class RowSpecification
9
-
10
+ #
11
+ # Build one or more row specifications
12
+ #
13
+ # @param column_values [Hash] map of column name to value or values
14
+ # @return [Array<RowSpecification>] collection of row specifications
15
+ #
10
16
  def self.build(column_values)
11
17
  column_values.map { |column, value| new(column, value) }
12
18
  end
13
19
 
14
- attr_reader :column, :value
20
+ # @return [Symbol] column name
21
+ attr_reader :column
22
+ # @return [Object, Array] value or values to match
23
+ attr_reader :value
15
24
 
25
+ #
26
+ # @param column [Symbol] column name
27
+ # @param value [Object,Array] value or values to match
28
+ #
16
29
  def initialize(column, value)
17
30
  @column, @value = column, value
18
31
  end
19
32
 
33
+ #
34
+ # @return [String] row specification as CQL fragment
35
+ #
20
36
  def cql
21
37
  case @value
22
38
  when Array
@@ -29,9 +45,6 @@ module Cequel
29
45
  ["#{@column} = ?", @value]
30
46
  end
31
47
  end
32
-
33
48
  end
34
-
35
49
  end
36
-
37
50
  end
@@ -1,35 +1,59 @@
1
1
  module Cequel
2
-
3
2
  module Metal
4
-
3
+ #
4
+ # Builder for CQL statements. Contains a CQL string with bind substitutions
5
+ # and a collection of bind variables
6
+ #
7
+ # @api private
8
+ #
5
9
  class Statement
6
- attr_reader :bind_vars, :length
10
+ # @return [Array] bind variables for CQL string
11
+ attr_reader :bind_vars
7
12
 
8
13
  def initialize
9
14
  @cql, @bind_vars = [], []
10
15
  end
11
16
 
17
+ #
18
+ # @return [String] CQL statement
19
+ #
12
20
  def cql
13
21
  @cql.join
14
22
  end
15
23
 
24
+ #
25
+ # Add a CQL fragment with optional bind variables to the beginning of
26
+ # the statement
27
+ #
28
+ # @param (see #append)
29
+ # @return [void]
30
+ #
16
31
  def prepend(cql, *bind_vars)
17
32
  @cql.unshift(cql)
18
33
  @bind_vars.unshift(*bind_vars)
19
34
  end
20
35
 
36
+ #
37
+ # Add a CQL fragment with optional bind variables to the end of the
38
+ # statement
39
+ #
40
+ # @param cql [String] CQL fragment
41
+ # @param bind_vars [Object] zero or more bind variables
42
+ # @return [void]
43
+ #
21
44
  def append(cql, *bind_vars)
22
45
  @cql << cql
23
46
  @bind_vars.concat(bind_vars)
24
47
  self
25
48
  end
26
49
 
50
+ #
51
+ # @return [Array] this statement as an array of arguments to
52
+ # Keyspace#execute (CQL string followed by bind variables)
53
+ #
27
54
  def args
28
55
  [cql, *bind_vars]
29
56
  end
30
-
31
57
  end
32
-
33
58
  end
34
-
35
59
  end
@@ -1,9 +1,25 @@
1
1
  module Cequel
2
-
3
2
  module Metal
4
-
3
+ #
4
+ # Builder for `UPDATE` statement containing heterogeneous operations (set
5
+ # columns, atomically mutate collections)
6
+ #
7
+ # @see DataSet#update
8
+ # @see Deleter
9
+ # @see
10
+ # http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/cql_reference/update_r.html
11
+ # CQL UPDATE documentation
12
+ # @since 1.0.0
13
+ #
5
14
  class Updater < Writer
6
-
15
+ #
16
+ # Directly set column values
17
+ #
18
+ # @param data [Hash] map of column names to values
19
+ # @return [void]
20
+ #
21
+ # @see DataSet#update
22
+ #
7
23
  def set(data)
8
24
  data.each_pair do |column, value|
9
25
  prepare_upsert_value(value) do |binding, *values|
@@ -13,36 +29,100 @@ module Cequel
13
29
  end
14
30
  end
15
31
 
32
+ #
33
+ # Prepend elements to a list column
34
+ #
35
+ # @param column [Symbol] column name
36
+ # @param elements [Array<Object>] elements to prepend
37
+ # @return [void]
38
+ #
39
+ # @see DataSet#list_prepend
40
+ #
16
41
  def list_prepend(column, elements)
17
42
  statements << "#{column} = [?] + #{column}"
18
43
  bind_vars << elements
19
44
  end
20
45
 
46
+ #
47
+ # Append elements to a list column
48
+ #
49
+ # @param column [Symbol] column name
50
+ # @param elements [Array] elements to append
51
+ # @return [void]
52
+ #
53
+ # @see DataSet#list_append
54
+ #
21
55
  def list_append(column, elements)
22
56
  statements << "#{column} = #{column} + [?]"
23
57
  bind_vars << elements
24
58
  end
25
59
 
60
+ #
61
+ # Remove all occurrences of an element from a list
62
+ #
63
+ # @param column [Symbol] column name
64
+ # @param value value to remove
65
+ # @return [void]
66
+ #
67
+ # @see DataSet#list_remove
68
+ #
26
69
  def list_remove(column, value)
27
70
  statements << "#{column} = #{column} - [?]"
28
71
  bind_vars << value
29
72
  end
30
73
 
74
+ #
75
+ # Replace a list item at a given position
76
+ #
77
+ # @param column [Symbol] column name
78
+ # @param index [Integer] index at which to replace value
79
+ # @param value new value for position
80
+ # @return [void]
81
+ #
82
+ # @see DataSet#list_replace
83
+ #
31
84
  def list_replace(column, index, value)
32
85
  statements << "#{column}[#{index}] = ?"
33
86
  bind_vars << value
34
87
  end
35
88
 
89
+ #
90
+ # Add elements to a set
91
+ #
92
+ # @param column [Symbol] column name
93
+ # @param values [Set] elements to add to set
94
+ # @return [void]
95
+ #
96
+ # @see DataSet#set_add
97
+ #
36
98
  def set_add(column, values)
37
99
  statements << "#{column} = #{column} + {?}"
38
100
  bind_vars << values
39
101
  end
40
102
 
41
- def set_remove(column, value)
103
+ #
104
+ # Remove elements from a set
105
+ #
106
+ # @param column [Symbol] column name
107
+ # @param values [Set] elements to remove from set
108
+ # @return [void]
109
+ #
110
+ # @see DataSet#set_remove
111
+ #
112
+ def set_remove(column, values)
42
113
  statements << "#{column} = #{column} - {?}"
43
- bind_vars << ::Kernel.Array(value)
114
+ bind_vars << ::Kernel.Array(values)
44
115
  end
45
116
 
117
+ #
118
+ # Add or update elements in a map
119
+ #
120
+ # @param column [Symbol] column name
121
+ # @param updates [Hash] map of keys to values to update in map
122
+ # @return [void]
123
+ #
124
+ # @see DataSet#map_update
125
+ #
46
126
  def map_update(column, updates)
47
127
  binding_pairs = ::Array.new(updates.length) { '?:?' }.join(',')
48
128
  statements << "#{column} = #{column} + {#{binding_pairs}}"
@@ -52,14 +132,11 @@ module Cequel
52
132
  private
53
133
 
54
134
  def write_to_statement(statement)
55
- statement.append("UPDATE #{table_name}").
56
- append(generate_upsert_options).
57
- append(" SET ").
58
- append(statements.join(', '), *bind_vars)
135
+ statement.append("UPDATE #{table_name}")
136
+ .append(generate_upsert_options)
137
+ .append(" SET ")
138
+ .append(statements.join(', '), *bind_vars)
59
139
  end
60
-
61
140
  end
62
-
63
141
  end
64
-
65
142
  end