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.
- checksums.yaml +4 -4
- data/lib/cequel.rb +18 -0
- data/lib/cequel/errors.rb +8 -4
- data/lib/cequel/metal.rb +14 -0
- data/lib/cequel/metal/batch.rb +21 -11
- data/lib/cequel/metal/batch_manager.rb +74 -0
- data/lib/cequel/metal/cql_row_specification.rb +19 -6
- data/lib/cequel/metal/data_set.rb +400 -163
- data/lib/cequel/metal/deleter.rb +45 -11
- data/lib/cequel/metal/incrementer.rb +23 -10
- data/lib/cequel/metal/inserter.rb +19 -6
- data/lib/cequel/metal/keyspace.rb +82 -159
- data/lib/cequel/metal/logger.rb +71 -0
- data/lib/cequel/metal/logging.rb +47 -0
- data/lib/cequel/metal/new_relic_instrumentation.rb +26 -0
- data/lib/cequel/metal/row.rb +36 -10
- data/lib/cequel/metal/row_specification.rb +21 -8
- data/lib/cequel/metal/statement.rb +30 -6
- data/lib/cequel/metal/updater.rb +89 -12
- data/lib/cequel/metal/writer.rb +23 -14
- data/lib/cequel/record.rb +52 -6
- data/lib/cequel/record/association_collection.rb +13 -6
- data/lib/cequel/record/associations.rb +146 -54
- data/lib/cequel/record/belongs_to_association.rb +34 -7
- data/lib/cequel/record/bound.rb +69 -12
- data/lib/cequel/record/bulk_writes.rb +29 -1
- data/lib/cequel/record/callbacks.rb +22 -6
- data/lib/cequel/record/collection.rb +273 -36
- data/lib/cequel/record/configuration_generator.rb +5 -0
- data/lib/cequel/record/data_set_builder.rb +86 -0
- data/lib/cequel/record/dirty.rb +11 -8
- data/lib/cequel/record/errors.rb +38 -4
- data/lib/cequel/record/has_many_association.rb +42 -9
- data/lib/cequel/record/lazy_record_collection.rb +39 -10
- data/lib/cequel/record/mass_assignment.rb +14 -6
- data/lib/cequel/record/persistence.rb +157 -20
- data/lib/cequel/record/properties.rb +147 -24
- data/lib/cequel/record/railtie.rb +15 -2
- data/lib/cequel/record/record_set.rb +504 -75
- data/lib/cequel/record/schema.rb +77 -13
- data/lib/cequel/record/scoped.rb +16 -11
- data/lib/cequel/record/secondary_indexes.rb +42 -6
- data/lib/cequel/record/tasks.rb +2 -1
- data/lib/cequel/record/validations.rb +51 -11
- data/lib/cequel/schema.rb +9 -0
- data/lib/cequel/schema/column.rb +172 -33
- data/lib/cequel/schema/create_table_dsl.rb +62 -31
- data/lib/cequel/schema/keyspace.rb +106 -7
- data/lib/cequel/schema/migration_validator.rb +128 -0
- data/lib/cequel/schema/table.rb +183 -20
- data/lib/cequel/schema/table_property.rb +92 -34
- data/lib/cequel/schema/table_reader.rb +45 -15
- data/lib/cequel/schema/table_synchronizer.rb +101 -43
- data/lib/cequel/schema/table_updater.rb +114 -19
- data/lib/cequel/schema/table_writer.rb +31 -13
- data/lib/cequel/schema/update_table_dsl.rb +71 -40
- data/lib/cequel/type.rb +214 -53
- data/lib/cequel/util.rb +6 -9
- data/lib/cequel/version.rb +2 -1
- data/spec/examples/record/associations_spec.rb +12 -12
- data/spec/examples/record/persistence_spec.rb +5 -5
- data/spec/examples/record/record_set_spec.rb +62 -50
- data/spec/examples/schema/table_synchronizer_spec.rb +37 -11
- data/spec/examples/schema/table_updater_spec.rb +3 -3
- data/spec/examples/spec_helper.rb +2 -11
- data/spec/examples/type_spec.rb +3 -3
- metadata +23 -4
- data/lib/cequel/new_relic_instrumentation.rb +0 -22
data/lib/cequel/metal/writer.rb
CHANGED
@@ -1,19 +1,36 @@
|
|
1
|
-
require 'delegate'
|
2
|
-
|
3
1
|
module Cequel
|
4
|
-
|
5
2
|
module Metal
|
6
|
-
|
3
|
+
#
|
4
|
+
# Internal representation of a data manipulation statement
|
5
|
+
#
|
6
|
+
# @abstract Subclasses must implement #write_to_statement, which writes
|
7
|
+
# internal state to a Statement instance
|
8
|
+
#
|
9
|
+
# @since 1.0.0
|
10
|
+
# @api private
|
11
|
+
#
|
7
12
|
class Writer
|
8
|
-
|
9
13
|
extend Forwardable
|
10
14
|
|
15
|
+
#
|
16
|
+
# @param data_set [DataSet] data set to write to
|
17
|
+
# @param options [Options] options
|
18
|
+
# @option options [Integer] :ttl time-to-live in seconds for the written
|
19
|
+
# data
|
20
|
+
# @option options [Time,Integer] :timestamp the timestamp associated with
|
21
|
+
# the column values
|
22
|
+
#
|
11
23
|
def initialize(data_set, options = {}, &block)
|
12
24
|
@data_set, @options, @block = data_set, options, block
|
13
25
|
@statements, @bind_vars = [], []
|
14
26
|
SimpleDelegator.new(self).instance_eval(&block) if block
|
15
27
|
end
|
16
28
|
|
29
|
+
#
|
30
|
+
# Execute the statement as a write operation
|
31
|
+
#
|
32
|
+
# @return [void]
|
33
|
+
#
|
17
34
|
def execute
|
18
35
|
return if empty?
|
19
36
|
statement = Statement.new
|
@@ -23,6 +40,7 @@ module Cequel
|
|
23
40
|
end
|
24
41
|
|
25
42
|
private
|
43
|
+
|
26
44
|
attr_reader :data_set, :options, :statements, :bind_vars
|
27
45
|
def_delegator :data_set, :table_name
|
28
46
|
def_delegator :statements, :empty?
|
@@ -44,11 +62,6 @@ module Cequel
|
|
44
62
|
#
|
45
63
|
# Generate CQL option statement for inserts and updates
|
46
64
|
#
|
47
|
-
# @param [Hash] options options for insert
|
48
|
-
# @option options [Symbol,String] :consistency required consistency for the write
|
49
|
-
# @option options [Integer] :ttl time-to-live in seconds for the written data
|
50
|
-
# @option options [Time,Integer] :timestamp the timestamp associated with the column values
|
51
|
-
#
|
52
65
|
def generate_upsert_options
|
53
66
|
if options.empty?
|
54
67
|
''
|
@@ -57,7 +70,6 @@ module Cequel
|
|
57
70
|
options.map do |key, value|
|
58
71
|
serialized_value =
|
59
72
|
case key
|
60
|
-
when :consistency then value.to_s.upcase
|
61
73
|
when :timestamp then (value.to_f * 1_000_000).to_i
|
62
74
|
else value
|
63
75
|
end
|
@@ -65,9 +77,6 @@ module Cequel
|
|
65
77
|
end.join(' AND ')
|
66
78
|
end
|
67
79
|
end
|
68
|
-
|
69
80
|
end
|
70
|
-
|
71
81
|
end
|
72
|
-
|
73
82
|
end
|
data/lib/cequel/record.rb
CHANGED
@@ -8,6 +8,7 @@ require 'cequel/record/collection'
|
|
8
8
|
require 'cequel/record/persistence'
|
9
9
|
require 'cequel/record/bulk_writes'
|
10
10
|
require 'cequel/record/record_set'
|
11
|
+
require 'cequel/record/data_set_builder'
|
11
12
|
require 'cequel/record/bound'
|
12
13
|
require 'cequel/record/lazy_record_collection'
|
13
14
|
require 'cequel/record/scoped'
|
@@ -28,9 +29,50 @@ if defined? Rails
|
|
28
29
|
end
|
29
30
|
|
30
31
|
module Cequel
|
31
|
-
|
32
|
+
#
|
33
|
+
# Cequel::Record is an active record-style data modeling library and
|
34
|
+
# object-row mapper. Model classes inherit from Cequel::Record, define their
|
35
|
+
# columns in the class definition, and have access to a full and robust set
|
36
|
+
# of read and write functionality.
|
37
|
+
#
|
38
|
+
# Individual components are documented in their respective modules. See below
|
39
|
+
# for links.
|
40
|
+
#
|
41
|
+
# @example A Record class showing off many of the possibilities
|
42
|
+
# class Post
|
43
|
+
# include Cequel::Record
|
44
|
+
#
|
45
|
+
# belongs_to :blog
|
46
|
+
# key :id, :timeuuid, auto: true
|
47
|
+
# column :title, :text
|
48
|
+
# column :body, :text
|
49
|
+
# column :author_id, :uuid, index: true
|
50
|
+
# set :categories
|
51
|
+
#
|
52
|
+
# has_many :comments, dependent: destroy
|
53
|
+
#
|
54
|
+
# after_create :notify_followers
|
55
|
+
#
|
56
|
+
# validates :title, presence: true
|
57
|
+
#
|
58
|
+
# def self.for_author(author_id)
|
59
|
+
# where(:author_id, author_id)
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# @see Properties Defining properties
|
64
|
+
# @see Collection Collection columns
|
65
|
+
# @see SecondaryIndexes Defining secondary indexes
|
66
|
+
# @see Associations Defining associations between records
|
67
|
+
# @see Persistence Creating, updating, and destroying records
|
68
|
+
# @see BulkWrites Updating and destroying records in bulk
|
69
|
+
# @see RecordSet Loading records from the database
|
70
|
+
# @see MassAssignment Mass-assignment protection and strong attributes
|
71
|
+
# @see Callbacks Lifecycle hooks
|
72
|
+
# @see Validations
|
73
|
+
# @see Dirty Dirty attribute tracking
|
74
|
+
#
|
32
75
|
module Record
|
33
|
-
|
34
76
|
extend ActiveSupport::Concern
|
35
77
|
extend Forwardable
|
36
78
|
|
@@ -48,18 +90,22 @@ module Cequel
|
|
48
90
|
extend ActiveModel::Naming
|
49
91
|
include ActiveModel::Serializers::JSON
|
50
92
|
include ActiveModel::Serializers::Xml
|
51
|
-
|
52
93
|
end
|
53
94
|
|
54
95
|
class <<self
|
96
|
+
# @return [Metal::Keyspace] the keyspace used for record persistence
|
55
97
|
attr_accessor :connection
|
56
98
|
|
99
|
+
#
|
100
|
+
# Establish a connection with the given configuration
|
101
|
+
#
|
102
|
+
# @param (see Cequel.connect)
|
103
|
+
# @option (see Cequel.connect)
|
104
|
+
# @return [void]
|
105
|
+
#
|
57
106
|
def establish_connection(configuration)
|
58
107
|
self.connection = Cequel.connect(configuration)
|
59
108
|
end
|
60
|
-
|
61
109
|
end
|
62
|
-
|
63
110
|
end
|
64
|
-
|
65
111
|
end
|
@@ -1,11 +1,21 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Record
|
4
|
-
|
3
|
+
#
|
4
|
+
# Collection of records from a
|
5
|
+
# {Associations::ClassMethods#has_many has_many} associaiton. Encapsulates
|
6
|
+
# and behaves like a {RecordSet}, but unlike a normal RecordSet the loaded
|
7
|
+
# records are held in memory after they are loaded.
|
8
|
+
#
|
9
|
+
# @see Associations::ClassMethods#has_many
|
10
|
+
# @since 1.0.0
|
11
|
+
#
|
5
12
|
class AssociationCollection < DelegateClass(RecordSet)
|
6
|
-
|
7
13
|
include Enumerable
|
8
14
|
|
15
|
+
#
|
16
|
+
# @yield [Record]
|
17
|
+
# @return [void]
|
18
|
+
#
|
9
19
|
def each(&block)
|
10
20
|
target.each(&block)
|
11
21
|
end
|
@@ -15,9 +25,6 @@ module Cequel
|
|
15
25
|
def target
|
16
26
|
@target ||= __getobj__.entries
|
17
27
|
end
|
18
|
-
|
19
28
|
end
|
20
|
-
|
21
29
|
end
|
22
|
-
|
23
30
|
end
|
@@ -1,9 +1,55 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Record
|
4
|
-
|
3
|
+
#
|
4
|
+
# Cequel records can have parent-child relationships defined by
|
5
|
+
# {ClassMethods#belongs_to belongs_to} and {ClassMethods#has_many has_many}
|
6
|
+
# associations. Unlike in a relational database ORM, associations are not
|
7
|
+
# represented by foreign keys; instead they use CQL3's compound primary
|
8
|
+
# keys. A child object's primary key begins with it's parent's primary key.
|
9
|
+
#
|
10
|
+
# In the below example, the `blogs` table has a one-column primary key
|
11
|
+
# `(subdomain)`, and the `posts` table has a two-column primary key
|
12
|
+
# `(blog_subdomain, permalink)`. All posts that belong to the blog with
|
13
|
+
# subdomain `"cassandra"` will have `"cassandra"` as their
|
14
|
+
# `blog_subdomain`.
|
15
|
+
#
|
16
|
+
# @example Blogs and Posts
|
17
|
+
#
|
18
|
+
# class Blog
|
19
|
+
# include Cequel::Record
|
20
|
+
#
|
21
|
+
# key :subdomain, :text
|
22
|
+
#
|
23
|
+
# column :name, :text
|
24
|
+
#
|
25
|
+
# has_many :posts
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# class Post
|
29
|
+
# include Cequel::Record
|
30
|
+
#
|
31
|
+
# # This defines the first primary key column as `blog_subdomain`.
|
32
|
+
# # Because `belongs_to` associations implicitly define columns in the
|
33
|
+
# # primary key, it must come before any explicit key definition. For
|
34
|
+
# # the same reason, a Record class can only have a single `belongs_to`
|
35
|
+
# # declaration.
|
36
|
+
# belongs_to :blog
|
37
|
+
#
|
38
|
+
# # We also define an additional primary key column so that each post
|
39
|
+
# # has a unique compound primary key
|
40
|
+
# key :permalink
|
41
|
+
#
|
42
|
+
# column :title, :text
|
43
|
+
# column :body, :text
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# blog = Blog.new(subdomain: 'cassandra')
|
47
|
+
# post = blog.posts.new(permalink: 'cequel')
|
48
|
+
# post.blog_subdomain #=> "cassandra"
|
49
|
+
#
|
50
|
+
# @since 1.0.0
|
51
|
+
#
|
5
52
|
module Associations
|
6
|
-
|
7
53
|
extend ActiveSupport::Concern
|
8
54
|
|
9
55
|
included do
|
@@ -12,43 +58,83 @@ module Cequel
|
|
12
58
|
self.child_associations = {}
|
13
59
|
end
|
14
60
|
|
61
|
+
#
|
62
|
+
# Class macros for declaring associations
|
63
|
+
#
|
64
|
+
# @see Associations
|
65
|
+
#
|
15
66
|
module ClassMethods
|
16
|
-
|
17
67
|
include Forwardable
|
18
68
|
|
19
|
-
|
69
|
+
# @!attribute parent_association
|
70
|
+
# @return [BelongsToAssociation] association declared by
|
71
|
+
# {#belongs_to}
|
72
|
+
# @!attribute child_associations
|
73
|
+
# @return [Hash<Symbol,HasManyAssociation>] associations declared by
|
74
|
+
# {#has_many}
|
75
|
+
|
76
|
+
#
|
77
|
+
# Declare the parent association for this record. The name of the class
|
78
|
+
# is inferred from the name of the association. The `belongs_to`
|
79
|
+
# declaration also serves to define key columns, which are derived from
|
80
|
+
# the key columns of the parent class. So, if the parent class `Blog`
|
81
|
+
# has a primary key `(subdomain)`, this will declare a key column
|
82
|
+
# `blog_subdomain` of the same type.
|
83
|
+
#
|
84
|
+
# Parent associations are read/write, so declaring `belongs_to :blog`
|
85
|
+
# will define a `blog` getter and `blog=` setter, which will update the
|
86
|
+
# underlying key column. Note that a record's parent cannot be changed
|
87
|
+
# once the record has been saved.
|
88
|
+
#
|
89
|
+
# @param name [Symbol] name of the parent association
|
90
|
+
# @param options [Options] options for association
|
91
|
+
# @option (see BelongsToAssociation#initialize)
|
92
|
+
# @return [void]
|
93
|
+
#
|
94
|
+
# @see Associations
|
95
|
+
#
|
96
|
+
def belongs_to(name, options = {})
|
20
97
|
if parent_association
|
21
|
-
|
22
|
-
|
98
|
+
fail InvalidRecordConfiguration,
|
99
|
+
"Can't declare more than one belongs_to association"
|
23
100
|
end
|
24
101
|
if table_schema.key_columns.any?
|
25
|
-
|
26
|
-
|
102
|
+
fail InvalidRecordConfiguration,
|
103
|
+
"belongs_to association must be declared before declaring " \
|
104
|
+
"key(s)"
|
27
105
|
end
|
28
|
-
|
106
|
+
|
107
|
+
self.parent_association =
|
108
|
+
BelongsToAssociation.new(self, name.to_sym, options)
|
109
|
+
|
29
110
|
parent_association.association_key_columns.each do |column|
|
30
111
|
key :"#{name}_#{column.name}", column.type
|
31
112
|
end
|
32
113
|
def_parent_association_accessors
|
33
114
|
end
|
34
115
|
|
116
|
+
#
|
117
|
+
# Declare a child association. The child association should have a
|
118
|
+
# `belongs_to` referencing this class or, at a minimum, must have a
|
119
|
+
# primary key whose first N columns have the same types as the N
|
120
|
+
# columns in this class's primary key.
|
121
|
+
#
|
122
|
+
# `has_many` associations are read-only, so `has_many :posts` will
|
123
|
+
# define a `posts` reader but not a `posts=` writer; and the collection
|
124
|
+
# returned by `posts` will be immutable.
|
125
|
+
#
|
126
|
+
# @param name [Symbol] plural name of association
|
127
|
+
# @param options [Options] options for association
|
128
|
+
# @option (see HasManyAssociation#initialize)
|
129
|
+
# @return [void]
|
130
|
+
#
|
131
|
+
# @see Associations
|
132
|
+
#
|
35
133
|
def has_many(name, options = {})
|
36
|
-
|
37
|
-
|
38
|
-
association = HasManyAssociation.new(self, name.to_sym)
|
134
|
+
association = HasManyAssociation.new(self, name.to_sym, options)
|
39
135
|
self.child_associations =
|
40
136
|
child_associations.merge(name => association)
|
41
137
|
def_child_association_reader(association)
|
42
|
-
|
43
|
-
case options[:dependent]
|
44
|
-
when :destroy
|
45
|
-
after_destroy { delete_children(name, true) }
|
46
|
-
when :delete
|
47
|
-
after_destroy { delete_children(name) }
|
48
|
-
when nil
|
49
|
-
else
|
50
|
-
raise ArgumentError, "Invalid option #{options[:dependent].inspect} provided for :dependent. Specify :destroy or :delete."
|
51
|
-
end
|
52
138
|
end
|
53
139
|
|
54
140
|
private
|
@@ -60,12 +146,12 @@ module Cequel
|
|
60
146
|
|
61
147
|
def def_parent_association_reader
|
62
148
|
def_delegator 'self', :read_parent_association,
|
63
|
-
|
149
|
+
parent_association.name
|
64
150
|
end
|
65
151
|
|
66
152
|
def def_parent_association_writer
|
67
153
|
def_delegator 'self', :write_parent_association,
|
68
|
-
|
154
|
+
"#{parent_association.name}="
|
69
155
|
end
|
70
156
|
|
71
157
|
def def_child_association_reader(association)
|
@@ -75,7 +161,22 @@ module Cequel
|
|
75
161
|
end
|
76
162
|
RUBY
|
77
163
|
end
|
164
|
+
end
|
78
165
|
|
166
|
+
#
|
167
|
+
# @private
|
168
|
+
#
|
169
|
+
def destroy(*)
|
170
|
+
super.tap do
|
171
|
+
self.class.child_associations.each_value do |association|
|
172
|
+
case association.dependent
|
173
|
+
when :destroy
|
174
|
+
__send__(association.name).destroy_all
|
175
|
+
when :delete
|
176
|
+
__send__(association.name).delete_all
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
79
180
|
end
|
80
181
|
|
81
182
|
private
|
@@ -85,11 +186,11 @@ module Cequel
|
|
85
186
|
if instance_variable_defined?(ivar_name)
|
86
187
|
return instance_variable_get(ivar_name)
|
87
188
|
end
|
88
|
-
parent_key_values = key_values
|
89
|
-
first(parent_association.association_key_columns.length)
|
189
|
+
parent_key_values = key_values
|
190
|
+
.first(parent_association.association_key_columns.length)
|
90
191
|
if parent_key_values.none? { |value| value.nil? }
|
91
192
|
clazz = parent_association.association_class
|
92
|
-
parent = parent_key_values.
|
193
|
+
parent = parent_key_values.reduce(clazz) do |record_set, key_value|
|
93
194
|
record_set[key_value]
|
94
195
|
end
|
95
196
|
instance_variable_set(ivar_name, parent)
|
@@ -98,19 +199,20 @@ module Cequel
|
|
98
199
|
|
99
200
|
def write_parent_association(parent)
|
100
201
|
unless parent.is_a?(parent_association.association_class)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
202
|
+
fail ArgumentError,
|
203
|
+
"Wrong class for #{parent_association.name}; expected " \
|
204
|
+
"#{parent_association.association_class.name}, got " \
|
205
|
+
"#{parent.class.name}"
|
105
206
|
end
|
106
207
|
instance_variable_set "@#{parent_association.name}", parent
|
107
208
|
key_column_names = self.class.key_column_names
|
108
|
-
parent.key_attributes
|
109
|
-
zip(key_column_names) do |(parent_column_name, value), column_name|
|
209
|
+
parent.key_attributes
|
210
|
+
.zip(key_column_names) do |(parent_column_name, value), column_name|
|
110
211
|
if value.nil?
|
111
|
-
|
112
|
-
|
113
|
-
|
212
|
+
fail ArgumentError,
|
213
|
+
"Can't set parent association " \
|
214
|
+
"#{parent_association.name.inspect} " \
|
215
|
+
"without value in key #{parent_column_name.inspect}"
|
114
216
|
end
|
115
217
|
write_attribute(column_name, value)
|
116
218
|
end
|
@@ -122,26 +224,16 @@ module Cequel
|
|
122
224
|
if !reload && instance_variable_defined?(ivar)
|
123
225
|
return instance_variable_get(ivar)
|
124
226
|
end
|
125
|
-
association_record_set = key_values.inject(association.association_class) do |record_set, key_value|
|
126
|
-
record_set[key_value]
|
127
|
-
end
|
128
|
-
instance_variable_set(
|
129
|
-
ivar, AssociationCollection.new(association_record_set))
|
130
|
-
end
|
131
227
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
228
|
+
base_scope = association.association_class
|
229
|
+
association_record_set =
|
230
|
+
key_values.reduce(base_scope) do |record_set, key_value|
|
231
|
+
record_set[key_value]
|
136
232
|
end
|
137
|
-
end
|
138
|
-
connection[association_name].where(
|
139
|
-
send(association_name).scoped_key_attributes
|
140
|
-
).delete
|
141
|
-
end
|
142
233
|
|
234
|
+
instance_variable_set(
|
235
|
+
ivar, AssociationCollection.new(association_record_set))
|
236
|
+
end
|
143
237
|
end
|
144
|
-
|
145
238
|
end
|
146
|
-
|
147
239
|
end
|