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
@@ -1,13 +1,42 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Record
|
4
|
-
|
3
|
+
#
|
4
|
+
# Properties on a Cequel record acts as attributes on record instances, and
|
5
|
+
# are persisted as column values to Cassandra. Properties are declared
|
6
|
+
# explicitly on a record instance in the body.
|
7
|
+
#
|
8
|
+
# Properties can be **key columns**, **data columns**, or **collection
|
9
|
+
# columns**. Key columns combine to form the primary key for the record;
|
10
|
+
# they cannot be changed once a record has been saved. Data columns contain
|
11
|
+
# scalar data values like strings, integers, and timestamps. Collection
|
12
|
+
# columns are lists, sets, or maps that can be atomically updated.
|
13
|
+
#
|
14
|
+
# All varieties of column have a type; see {Cequel::Type} for the full
|
15
|
+
# list of possibilities. A collection column's type is the type of its
|
16
|
+
# elements (in the case of a map collection, there is both a key type and a
|
17
|
+
# value type).
|
18
|
+
#
|
19
|
+
# @example
|
20
|
+
# class Post
|
21
|
+
# key :blog_subdomain, :text
|
22
|
+
# key :id, :timeuuid, auto: true
|
23
|
+
#
|
24
|
+
# column :title, :text
|
25
|
+
# column :body, :text
|
26
|
+
# column :updated_at, :timestamp
|
27
|
+
#
|
28
|
+
# list :categories, :text
|
29
|
+
# set :tags, :text
|
30
|
+
# map :referers, :text, :integer
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# @see ClassMethods Methods for defining properties
|
34
|
+
#
|
5
35
|
module Properties
|
6
|
-
|
7
36
|
extend ActiveSupport::Concern
|
8
37
|
|
9
38
|
included do
|
10
|
-
class_attribute :default_attributes, :
|
39
|
+
class_attribute :default_attributes, instance_writer: false
|
11
40
|
self.default_attributes = {}
|
12
41
|
|
13
42
|
class <<self; alias_method :new_empty, :new; end
|
@@ -17,47 +46,124 @@ module Cequel
|
|
17
46
|
private :collection_proxies
|
18
47
|
end
|
19
48
|
|
49
|
+
# @private
|
20
50
|
module ConstructorMethods
|
21
|
-
|
22
51
|
def new(*args, &block)
|
23
52
|
new_empty.tap do |record|
|
24
53
|
record.__send__(:initialize_new_record, *args)
|
25
54
|
yield record if block_given?
|
26
55
|
end
|
27
56
|
end
|
28
|
-
|
29
57
|
end
|
30
58
|
|
59
|
+
#
|
60
|
+
# Methods for defining columns on a record
|
61
|
+
#
|
62
|
+
# @see Properties
|
63
|
+
#
|
31
64
|
module ClassMethods
|
32
|
-
|
33
65
|
protected
|
34
66
|
|
67
|
+
# @!visibility public
|
68
|
+
|
69
|
+
#
|
70
|
+
# Define a key column. By default, the first key column defined for a
|
71
|
+
# record will be a partition key, and the following keys will be
|
72
|
+
# clustering columns. This behavior can be changed using the
|
73
|
+
# `:partition` option
|
74
|
+
#
|
75
|
+
# @param name [Symbol] the name of the key column
|
76
|
+
# @param type [Symbol] the type of the key column
|
77
|
+
# @param options [Options] options for the key column
|
78
|
+
# @option options [Boolean] :partition (false) make this a partition
|
79
|
+
# key even if it is not the first key column
|
80
|
+
# @option options [Boolean] :auto (false) automatically initialize this
|
81
|
+
# key with a UUID value for new records. Only valid for `uuid` and
|
82
|
+
# `timeuuid` columns.
|
83
|
+
# @return [void]
|
84
|
+
#
|
85
|
+
# @note {Associations::ClassMethods#belongs_to belongs_to} implicitly
|
86
|
+
# defines key columns.
|
87
|
+
#
|
88
|
+
# @see
|
89
|
+
# http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/ddl/ddl_anatomy_table_c.html#concept_ds_cz4_lmy_zj
|
90
|
+
# CQL documentation on compound primary keys
|
91
|
+
#
|
35
92
|
def key(name, type, options = {})
|
36
93
|
def_accessors(name)
|
37
94
|
if options.fetch(:auto, false)
|
38
95
|
unless Type[type].is_a?(Cequel::Type::Uuid)
|
39
|
-
|
96
|
+
fail ArgumentError, ":auto option only valid for UUID columns"
|
40
97
|
end
|
41
|
-
default = -> { CassandraCQL::UUID.new } if options
|
98
|
+
default = -> { CassandraCQL::UUID.new } if options[:auto]
|
42
99
|
end
|
43
100
|
set_attribute_default(name, default)
|
44
101
|
end
|
45
102
|
|
103
|
+
#
|
104
|
+
# Define a data column
|
105
|
+
#
|
106
|
+
# @param name [Symbol] the name of the column
|
107
|
+
# @param type [Symbol] the type of the column
|
108
|
+
# @param options [Options] options for the column
|
109
|
+
# @option options [Object,Proc] :default a default value for the
|
110
|
+
# column, or a proc that returns a default value for the column
|
111
|
+
# @return [void]
|
112
|
+
#
|
46
113
|
def column(name, type, options = {})
|
47
114
|
def_accessors(name)
|
48
115
|
set_attribute_default(name, options[:default])
|
49
116
|
end
|
50
117
|
|
118
|
+
#
|
119
|
+
# Define a list column
|
120
|
+
#
|
121
|
+
# @param name [Symbol] the name of the list
|
122
|
+
# @param type [Symbol] the type of the elements in the list
|
123
|
+
# @param options [Options] options for the list
|
124
|
+
# @option options [Object,Proc] :default ([]) a default value for the
|
125
|
+
# column, or a proc that returns a default value for the column
|
126
|
+
# @return [void]
|
127
|
+
#
|
128
|
+
# @see Record::List
|
129
|
+
# @since 1.0.0
|
130
|
+
#
|
51
131
|
def list(name, type, options = {})
|
52
132
|
def_collection_accessors(name, List)
|
53
133
|
set_attribute_default(name, options.fetch(:default, []))
|
54
134
|
end
|
55
135
|
|
136
|
+
#
|
137
|
+
# Define a set column
|
138
|
+
#
|
139
|
+
# @param name [Symbol] the name of the set
|
140
|
+
# @param type [Symbol] the type of the elements in the set
|
141
|
+
# @param options [Options] options for the set
|
142
|
+
# @option options [Object,Proc] :default (Set[]) a default value for
|
143
|
+
# the column, or a proc that returns a default value for the column
|
144
|
+
# @return [void]
|
145
|
+
#
|
146
|
+
# @see Record::Set
|
147
|
+
# @since 1.0.0
|
148
|
+
#
|
56
149
|
def set(name, type, options = {})
|
57
150
|
def_collection_accessors(name, Set)
|
58
151
|
set_attribute_default(name, options.fetch(:default, ::Set[]))
|
59
152
|
end
|
60
153
|
|
154
|
+
#
|
155
|
+
# Define a map column
|
156
|
+
#
|
157
|
+
# @param name [Symbol] the name of the map
|
158
|
+
# @param key_type [Symbol] the type of the keys in the set
|
159
|
+
# @param options [Options] options for the set
|
160
|
+
# @option options [Object,Proc] :default ({}) a default value for the
|
161
|
+
# column, or a proc that returns a default value for the column
|
162
|
+
# @return [void]
|
163
|
+
#
|
164
|
+
# @see Record::Map
|
165
|
+
# @since 1.0.0
|
166
|
+
#
|
61
167
|
def map(name, key_type, value_type, options = {})
|
62
168
|
def_collection_accessors(name, Map)
|
63
169
|
set_attribute_default(name, options.fetch(:default, {}))
|
@@ -108,31 +214,47 @@ module Cequel
|
|
108
214
|
def set_attribute_default(name, default)
|
109
215
|
default_attributes[name.to_sym] = default
|
110
216
|
end
|
111
|
-
|
112
217
|
end
|
113
218
|
|
114
|
-
#
|
219
|
+
# @private
|
115
220
|
def initialize(attributes = {}, record_collection = nil)
|
116
221
|
@attributes, @record_collection = attributes, record_collection
|
117
222
|
@collection_proxies = {}
|
118
223
|
end
|
119
224
|
|
225
|
+
#
|
226
|
+
# @return [Array<Symbol>] list of names of attributes on this record
|
227
|
+
#
|
120
228
|
def attribute_names
|
121
229
|
@attributes.keys
|
122
230
|
end
|
123
231
|
|
232
|
+
#
|
233
|
+
# @return [Hash<Symbol,Object>] map of column names to values currently
|
234
|
+
# set on this record
|
235
|
+
#
|
124
236
|
def attributes
|
125
237
|
attribute_names.each_with_object({}) do |name, attributes|
|
126
238
|
attributes[name] = read_attribute(name)
|
127
239
|
end
|
128
240
|
end
|
129
241
|
|
242
|
+
#
|
243
|
+
# Set attributes on the record. Each attribute is set via the setter
|
244
|
+
# method; virtual (non-column) attributes are allowed.
|
245
|
+
#
|
246
|
+
# @param attributes [Hash] map of attribute names to values
|
247
|
+
# @return [void]
|
248
|
+
#
|
130
249
|
def attributes=(attributes)
|
131
250
|
attributes.each_pair do |attribute, value|
|
132
251
|
__send__(:"#{attribute}=", value)
|
133
252
|
end
|
134
253
|
end
|
135
254
|
|
255
|
+
#
|
256
|
+
# @return [Boolean] true if this record has the same type and key
|
257
|
+
# attributes as the other record
|
136
258
|
def ==(other)
|
137
259
|
if key_values.any? { |value| value.nil? }
|
138
260
|
super
|
@@ -141,6 +263,9 @@ module Cequel
|
|
141
263
|
end
|
142
264
|
end
|
143
265
|
|
266
|
+
#
|
267
|
+
# @return [String] string representation of the record
|
268
|
+
#
|
144
269
|
def inspect
|
145
270
|
inspected_attributes = attributes.each_pair.map do |attr, value|
|
146
271
|
inspected_value = value.is_a?(CassandraCQL::UUID) ?
|
@@ -157,9 +282,9 @@ module Cequel
|
|
157
282
|
@attributes.fetch(name)
|
158
283
|
rescue KeyError
|
159
284
|
if self.class.reflect_on_column(name)
|
160
|
-
|
285
|
+
fail MissingAttributeError, "missing attribute: #{name}"
|
161
286
|
else
|
162
|
-
|
287
|
+
fail UnknownAttributeError, "unknown attribute: #{name}"
|
163
288
|
end
|
164
289
|
end
|
165
290
|
|
@@ -179,20 +304,18 @@ module Cequel
|
|
179
304
|
end
|
180
305
|
|
181
306
|
def initialize_new_record(attributes = {})
|
182
|
-
dynamic_defaults = default_attributes
|
183
|
-
select { |name, value| value.is_a?(Proc) }
|
307
|
+
dynamic_defaults = default_attributes
|
308
|
+
.select { |name, value| value.is_a?(Proc) }
|
184
309
|
@attributes = Marshal.load(Marshal.dump(
|
185
310
|
default_attributes.except(*dynamic_defaults.keys)))
|
186
|
-
|
187
|
-
@new_record = true
|
188
|
-
yield self if block_given?
|
189
|
-
self.attributes = attributes
|
190
|
-
loaded!
|
191
|
-
self
|
192
|
-
end
|
311
|
+
dynamic_defaults.each { |name, p| @attributes[name] = p.call() }
|
193
312
|
|
313
|
+
@new_record = true
|
314
|
+
yield self if block_given?
|
315
|
+
self.attributes = attributes
|
316
|
+
loaded!
|
317
|
+
self
|
318
|
+
end
|
194
319
|
end
|
195
|
-
|
196
320
|
end
|
197
|
-
|
198
321
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
module Cequel
|
2
2
|
module Record
|
3
|
+
# @private
|
4
|
+
# @since 0.1.0
|
3
5
|
class Railtie < Rails::Railtie
|
4
6
|
config.cequel = Record
|
5
7
|
|
@@ -11,8 +13,8 @@ module Cequel
|
|
11
13
|
config_path = Rails.root.join('config/cequel.yml').to_s
|
12
14
|
|
13
15
|
if File.exist?(config_path)
|
14
|
-
config = YAML
|
15
|
-
deep_symbolize_keys
|
16
|
+
config = YAML.load(ERB.new(IO.read(config_path)).result)[Rails.env]
|
17
|
+
.deep_symbolize_keys
|
16
18
|
else
|
17
19
|
config = {host: '127.0.0.1:9160'}
|
18
20
|
end
|
@@ -23,6 +25,17 @@ module Cequel
|
|
23
25
|
Record.connection = connection
|
24
26
|
end
|
25
27
|
|
28
|
+
initializer "cequel.add_new_relic" do
|
29
|
+
begin
|
30
|
+
require 'new_relic/agent/method_tracer'
|
31
|
+
rescue LoadError => e
|
32
|
+
Rails.logger.debug(
|
33
|
+
"New Relic not installed; skipping New Relic integration")
|
34
|
+
else
|
35
|
+
require 'cequel/metal/new_relic_instrumentation'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
26
39
|
rake_tasks do
|
27
40
|
require "cequel/record/tasks"
|
28
41
|
end
|
@@ -1,86 +1,379 @@
|
|
1
1
|
module Cequel
|
2
|
-
|
3
2
|
module Record
|
4
|
-
|
3
|
+
#
|
4
|
+
# This class represents a subset of records from a particular table. Record
|
5
|
+
# sets encapsulate a CQL query, and are constructed using a chained builder
|
6
|
+
# interface.
|
7
|
+
#
|
8
|
+
# The primary mechanism for specifying which rows should be returned by a
|
9
|
+
# CQL query is by specifying values for one or more primary key columns. A
|
10
|
+
# record set acts like a deeply-nested hash, where each primary key column
|
11
|
+
# is a level of nesting. The {#[]} method is used to narrow the result set
|
12
|
+
# by successive primary key values.
|
13
|
+
#
|
14
|
+
# If {#[]} is used successively to specify all of the columns of a primary
|
15
|
+
# key, the result will be a single {Record} or a {LazyRecordCollection},
|
16
|
+
# depending on whether multiple values were specified for one of the key
|
17
|
+
# columns. In either case, the record instances will be unloaded.
|
18
|
+
#
|
19
|
+
# Certain methods have behavior that is dependent on which primary keys
|
20
|
+
# have been specified using {#[]}. In many methods, such as {#[]},
|
21
|
+
# {#values_at}, {#before}, {#after}, {#from}, {#upto}, and {#in}, the
|
22
|
+
# *first unscoped primary key column* serves as implicit context for the
|
23
|
+
# method: the value passed to those methods is an exact or bounding value
|
24
|
+
# for that column.
|
25
|
+
#
|
26
|
+
# CQL does not allow ordering by arbitrary columns; the ordering of a table
|
27
|
+
# is determined by its clustering column(s). You read records in reverse
|
28
|
+
# clustering order using {#reverse}.
|
29
|
+
#
|
30
|
+
# Record sets are enumerable collections; under the hood, results are
|
31
|
+
# paginated. This pagination can be made explicit using {#find_in_batches}.
|
32
|
+
# RecordSets do not store their records in memory; each time {#each} or an
|
33
|
+
# `Enumerable` method is called, the database is queried.
|
34
|
+
#
|
35
|
+
# All `RecordSet` methods are also exposed directly on {Record}
|
36
|
+
# classes. So, for instance, `Post.limit(10)` or `Post.select(:id, :title)`
|
37
|
+
# work as expected.
|
38
|
+
#
|
39
|
+
# Conversely, you may call any class method of a record class on a record
|
40
|
+
# set that targets that class. The class method will be executed in the
|
41
|
+
# context of the record set that the method is called on. See below for
|
42
|
+
# examples.
|
43
|
+
#
|
44
|
+
# @example Model class used for further examples
|
45
|
+
# class Post
|
46
|
+
# include Cequel::Record
|
47
|
+
#
|
48
|
+
# belongs_to :blog # defines key :blog_subdomain
|
49
|
+
# key :id, :timeuuid, auto: true
|
50
|
+
#
|
51
|
+
# column :title, :text
|
52
|
+
# column :author_id, :integer, index: true
|
53
|
+
#
|
54
|
+
# def self.for_author(author)
|
55
|
+
# where(:author_id, author.id)
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# @example A record set scoped to all posts
|
60
|
+
# Post.all # returns a record set with no scope restrictions
|
61
|
+
#
|
62
|
+
# @example The first ten posts
|
63
|
+
# # returns a ten-element array of loaded posts
|
64
|
+
# Post.first(10)
|
65
|
+
#
|
66
|
+
# # returns a record set scoped to yield the first 10 posts
|
67
|
+
# Post.limit(10)
|
68
|
+
#
|
69
|
+
# @example The posts in the "cassandra" blog
|
70
|
+
# # returns a record set where blog_subdomain = "cassandra"
|
71
|
+
# Post['cassandra']
|
72
|
+
#
|
73
|
+
# @example The post in the "cassandra" blog with id `params[:id]`
|
74
|
+
# # returns an unloaded Post instance
|
75
|
+
# Post['cassandra'][params[:id]]
|
76
|
+
#
|
77
|
+
# @example The posts in the "cassandra" blog with ids `id1, id2`
|
78
|
+
# # returns a LazyRecordCollection containing two unloaded Post instances
|
79
|
+
# Post['cassandra'].values_at('id1', 'id2')
|
80
|
+
#
|
81
|
+
# @example The posts in the "cassandra" blog in descending order of id
|
82
|
+
# # returns a LazyRecordCollection where blog_subdomain="cassandra" in
|
83
|
+
# # descending order of creation
|
84
|
+
# Post['cassandra'].reverse
|
85
|
+
#
|
86
|
+
# @example The posts in the "cassandra" blog created in the last week
|
87
|
+
# # returns a LazyRecordCollection where blog_subdomain="cassandra" and
|
88
|
+
# the timestamp encoded in the uuid is in the last week. This only works
|
89
|
+
# for timeuuid clustering columns
|
90
|
+
# Post['cassandra'].reverse.after(1.week.ago)
|
91
|
+
#
|
92
|
+
# @example 10 posts by a given author
|
93
|
+
# # Scoped to 10 posts where author_id=author.id. Results will not be in
|
94
|
+
# # a defined order because the partition key is not specified
|
95
|
+
# Post.for_author(author).limit(10)
|
96
|
+
#
|
97
|
+
# @see Scoped
|
98
|
+
# @see LazyRecordCollection
|
99
|
+
# @since 1.0.0
|
100
|
+
#
|
5
101
|
class RecordSet < SimpleDelegator
|
6
|
-
|
7
102
|
extend Forwardable
|
8
103
|
extend Cequel::Util::HashAccessors
|
9
104
|
include Enumerable
|
10
105
|
include BulkWrites
|
11
106
|
|
107
|
+
# @private
|
12
108
|
def self.default_attributes
|
13
|
-
{:
|
109
|
+
{scoped_key_values: [], select_columns: []}
|
14
110
|
end
|
15
111
|
|
112
|
+
# @return [Class] the Record class that this collection yields instances
|
113
|
+
# of
|
16
114
|
attr_reader :target_class
|
17
|
-
attr_writer :unloaded_records
|
18
115
|
|
116
|
+
#
|
117
|
+
# @param target_class [Class] the Record class that this collection
|
118
|
+
# yields instances of
|
119
|
+
# @param attributes [Hash] initial scoping attributes
|
120
|
+
#
|
121
|
+
# @api private
|
122
|
+
#
|
19
123
|
def initialize(target_class, attributes = {})
|
20
124
|
attributes = self.class.default_attributes.merge!(attributes)
|
21
125
|
@target_class, @attributes = target_class, attributes
|
22
126
|
super(target_class)
|
23
127
|
end
|
24
128
|
|
129
|
+
#
|
130
|
+
# @return [RecordSet] self
|
131
|
+
#
|
25
132
|
def all
|
26
133
|
self
|
27
134
|
end
|
28
135
|
|
136
|
+
#
|
137
|
+
# @overload select
|
138
|
+
#
|
139
|
+
# @yieldparam record [Record] each record in the record set
|
140
|
+
# @return [Array] records that pass the test given by the block
|
141
|
+
#
|
142
|
+
# @see
|
143
|
+
# http://ruby-doc.org/core-2.0.0/Enumerable.html#method-i-select
|
144
|
+
# Enumerable#select
|
145
|
+
#
|
146
|
+
# @overload select(*columns)
|
147
|
+
# Restrict which columns are selected when records are retrieved from
|
148
|
+
# the database
|
149
|
+
#
|
150
|
+
# @param columns [Symbol] column names
|
151
|
+
# @return [RecordSet] record set with the given column selections
|
152
|
+
# applied
|
153
|
+
#
|
154
|
+
# @see
|
155
|
+
# http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/cql_reference/select_r.html
|
156
|
+
# CQL SELECT documentation
|
157
|
+
#
|
158
|
+
# @return [Array,RecordSet]
|
159
|
+
#
|
29
160
|
def select(*columns)
|
30
161
|
return super if block_given?
|
31
162
|
scoped { |attributes| attributes[:select_columns].concat(columns) }
|
32
163
|
end
|
33
164
|
|
165
|
+
#
|
166
|
+
# Restrict the number of records that the RecordSet can contain.
|
167
|
+
#
|
168
|
+
# @param count [Integer] the maximum number of records to return
|
169
|
+
# @return [RecordSet] record set with limit applied
|
170
|
+
#
|
171
|
+
# @see
|
172
|
+
# http://www.datastax.com/documentation/cql/3.0/webhelp/index.html#cql/cql_reference/select_r.html
|
173
|
+
# CQL SELECT documentation
|
174
|
+
#
|
34
175
|
def limit(count)
|
35
|
-
scoped(:
|
36
|
-
end
|
37
|
-
|
176
|
+
scoped(row_limit: count)
|
177
|
+
end
|
178
|
+
|
179
|
+
#
|
180
|
+
# Filter the record set to records containing a given value in an indexed
|
181
|
+
# column
|
182
|
+
#
|
183
|
+
# @param column_name [Symbol] column for filter
|
184
|
+
# @param value value to match in given column
|
185
|
+
# @return [RecordSet] record set with filter applied
|
186
|
+
# @raise [IllegalQuery] if this record set is already filtered by an
|
187
|
+
# indexed column
|
188
|
+
# @raise [ArgumentError] if the specified column is not an data column
|
189
|
+
# with a secondary index
|
190
|
+
#
|
191
|
+
# @note This should only be used with data columns that have secondary
|
192
|
+
# indexes. To filter a record set using a primary key, use {#[]}
|
193
|
+
# @note Only one secondary index filter can be used in a given query
|
194
|
+
# @note Secondary index filters cannot be mixed with primary key filters
|
195
|
+
#
|
38
196
|
def where(column_name, value)
|
39
197
|
column = target_class.reflect_on_column(column_name)
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
198
|
+
if scoped_indexed_column
|
199
|
+
fail IllegalQuery,
|
200
|
+
"Can't scope by more than one indexed column in the same query"
|
201
|
+
end
|
202
|
+
unless column
|
203
|
+
fail ArgumentError,
|
204
|
+
"No column #{column_name} configured for #{target_class.name}"
|
205
|
+
end
|
206
|
+
unless column.data_column?
|
207
|
+
fail ArgumentError,
|
208
|
+
"Use the `at` method to restrict scope by primary key"
|
209
|
+
end
|
210
|
+
unless column.indexed?
|
211
|
+
fail ArgumentError,
|
212
|
+
"Can't scope by non-indexed column #{column_name}"
|
213
|
+
end
|
48
214
|
scoped(scoped_indexed_column: {column_name => column.cast(value)})
|
49
215
|
end
|
50
216
|
|
217
|
+
#
|
218
|
+
# @deprecated Use {#[]} instead
|
219
|
+
#
|
220
|
+
# Scope to values for one or more primary key columns
|
221
|
+
#
|
222
|
+
# @param scoped_key_values values for primary key columns
|
223
|
+
# @return (see #[])
|
224
|
+
#
|
51
225
|
def at(*scoped_key_values)
|
52
226
|
warn "`at` is deprecated. Use `[]` instead"
|
53
|
-
scoped_key_values
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
227
|
+
scoped_key_values
|
228
|
+
.reduce(self) { |record_set, key_value| record_set[key_value] }
|
229
|
+
end
|
230
|
+
|
231
|
+
#
|
232
|
+
# Restrict this record set to a given value for the next unscoped
|
233
|
+
# primary key column
|
234
|
+
#
|
235
|
+
# Record sets can be thought of like deeply-nested hashes, where each
|
236
|
+
# primary key column is a level of nesting. For instance, if a table
|
237
|
+
# consists of a single record with primary key `(blog_subdomain,
|
238
|
+
# permalink) = ("cassandra", "cequel")`, the record set can be thought of
|
239
|
+
# like so:
|
240
|
+
#
|
241
|
+
# ```ruby
|
242
|
+
# {
|
243
|
+
# "cassandra" => {
|
244
|
+
# "cequel" => #<Post blog_subdomain: "cassandra",
|
245
|
+
# permalink: "cequel", title: "Cequel">
|
246
|
+
# }
|
247
|
+
# }
|
248
|
+
# ```
|
249
|
+
#
|
250
|
+
# If `[]` is invoked enough times to specify all primary keys, then an
|
251
|
+
# unloaded `Record` instance is returned; this is the same behavior you
|
252
|
+
# would expect from a `Hash`. If only some subset of the primary keys
|
253
|
+
# have been specified, the result is still a `RecordSet`.
|
254
|
+
#
|
255
|
+
# @param primary_key_value value for the first unscoped primary key
|
256
|
+
# @return [RecordSet] record set with primary key filter applied, if not
|
257
|
+
# all primary keys are specified
|
258
|
+
# @return [Record] unloaded record, if all primary keys are specified
|
259
|
+
#
|
260
|
+
# @example Partially specified primary key
|
261
|
+
# Post['cequel'] # returns a RecordSet
|
262
|
+
#
|
263
|
+
# @example Fully specified primary key
|
264
|
+
# Post['cequel']['cassandra'] # returns an unloaded Record
|
265
|
+
#
|
266
|
+
# @note Accepting multiple arguments is deprecated behavior. Use
|
267
|
+
# {#values_at} instead.
|
268
|
+
#
|
269
|
+
def [](*primary_key_value)
|
270
|
+
if primary_key_value.many?
|
271
|
+
warn "Calling #[] with multiple arguments is deprecated. Use " \
|
272
|
+
"#values_at"
|
273
|
+
return values_at(*primary_key_value)
|
274
|
+
end
|
60
275
|
|
61
|
-
|
62
|
-
new_scoped_key_values.first if new_scoped_key_values.one?
|
276
|
+
primary_key_value = cast_range_key(primary_key_value.first)
|
63
277
|
|
64
|
-
|
65
|
-
|
278
|
+
scope_and_resolve do |attributes|
|
279
|
+
attributes[:scoped_key_values] << primary_key_value
|
280
|
+
end
|
66
281
|
end
|
282
|
+
alias_method :/, :[]
|
283
|
+
|
284
|
+
#
|
285
|
+
# Restrict the records in this record set to those containing any of a
|
286
|
+
# set of values
|
287
|
+
#
|
288
|
+
# @param primary_key_values values to match in the next unscoped primary
|
289
|
+
# key
|
290
|
+
# @return [RecordSet] record set with primary key scope applied if not
|
291
|
+
# all primary key columns are specified
|
292
|
+
# @return [LazyRecordCollection] collection of unloaded records if all
|
293
|
+
# primary key columns are specified
|
294
|
+
# @raise IllegalQuery if the scoped key column is neither the last
|
295
|
+
# partition key column nor the last clustering column
|
296
|
+
#
|
297
|
+
# @see #[]
|
298
|
+
#
|
299
|
+
def values_at(*primary_key_values)
|
300
|
+
unless next_unscoped_key_column_valid_for_in_query?
|
301
|
+
fail IllegalQuery,
|
302
|
+
"Only the last partition key column and the last clustering " \
|
303
|
+
"column can match multiple values"
|
304
|
+
end
|
67
305
|
|
68
|
-
|
69
|
-
self[*scoped_key_values].load!
|
70
|
-
end
|
306
|
+
primary_key_values = primary_key_values.map(&method(:cast_range_key))
|
71
307
|
|
72
|
-
|
73
|
-
|
308
|
+
scope_and_resolve do |attributes|
|
309
|
+
attributes[:scoped_key_values] << primary_key_values
|
310
|
+
end
|
74
311
|
end
|
75
312
|
|
313
|
+
#
|
314
|
+
# Return a loaded Record or collection of loaded Records with the
|
315
|
+
# specified primary key values
|
316
|
+
#
|
317
|
+
# @param scoped_key_values one or more values for the final primary key
|
318
|
+
# column
|
319
|
+
# @return [Record] if a single key is specified, return the loaded
|
320
|
+
# record at that key
|
321
|
+
# @return [LazyRecordCollection] if multiple keys are specified, return a
|
322
|
+
# collection of loaded records at those keys
|
323
|
+
# @raise [RecordNotFound] if not all the keys correspond to records in
|
324
|
+
# the table
|
325
|
+
# @raise [ArgumentError] if not all primary key columns have been
|
326
|
+
# specified
|
327
|
+
#
|
328
|
+
# @note This should only be called when all but the last column in the
|
329
|
+
# primary key is already specified in this record set
|
330
|
+
def find(*scoped_key_values)
|
331
|
+
(scoped_key_values.one? ?
|
332
|
+
self[scoped_key_values.first] :
|
333
|
+
values_at(*scoped_key_values)).load!
|
334
|
+
end
|
335
|
+
|
336
|
+
#
|
337
|
+
# Restrict records to ones whose value in the first unscoped primary key
|
338
|
+
# column are strictly greater than the given start_key.
|
339
|
+
#
|
340
|
+
# @param start_key the exclusive lower bound for the key column
|
341
|
+
# @return [RecordSet] record set with lower bound applied
|
342
|
+
#
|
343
|
+
# @see #from
|
344
|
+
#
|
76
345
|
def after(start_key)
|
77
346
|
scoped(lower_bound: bound(true, false, start_key))
|
78
347
|
end
|
79
348
|
|
349
|
+
#
|
350
|
+
# Restrict records to ones whose value in the first unscoped primary key
|
351
|
+
# column are strictly less than the given end_key.
|
352
|
+
#
|
353
|
+
# @param end_key the exclusive upper bound for the key column
|
354
|
+
# @return [RecordSet] record set with upper bound applied
|
355
|
+
#
|
356
|
+
# @see #upto
|
357
|
+
#
|
80
358
|
def before(end_key)
|
81
359
|
scoped(upper_bound: bound(false, false, end_key))
|
82
360
|
end
|
83
361
|
|
362
|
+
#
|
363
|
+
# Restrict records to those whose value in the first unscoped primary key
|
364
|
+
# column are in the given range. Will accept both inclusive ranges
|
365
|
+
# (`1..5`) and end-exclusive ranges (`1...5`). If you need a range with
|
366
|
+
# an exclusive start value, use {#after}, which can be combined with
|
367
|
+
# {#before} or {#from} to create a range.
|
368
|
+
#
|
369
|
+
# @param range [Range] range of values for the key column
|
370
|
+
# @return [RecordSet] record set with range restriction applied
|
371
|
+
#
|
372
|
+
# @see #after
|
373
|
+
# @see #before
|
374
|
+
# @see #from
|
375
|
+
# @see #upto
|
376
|
+
#
|
84
377
|
def in(range)
|
85
378
|
scoped(
|
86
379
|
lower_bound: bound(true, true, range.first),
|
@@ -88,53 +381,138 @@ module Cequel
|
|
88
381
|
)
|
89
382
|
end
|
90
383
|
|
384
|
+
#
|
385
|
+
# Restrict records to those whose value in the first unscoped primary key
|
386
|
+
# column are greater than or equal to the given start key.
|
387
|
+
#
|
388
|
+
# @param start_key the inclusive lower bound for values in the key column
|
389
|
+
# @return [RecordSet] record set with the lower bound applied
|
390
|
+
#
|
391
|
+
# @see #after
|
392
|
+
#
|
91
393
|
def from(start_key)
|
92
394
|
unless partition_specified?
|
93
|
-
|
94
|
-
|
395
|
+
fail IllegalQuery,
|
396
|
+
"Can't construct exclusive range on partition key " \
|
397
|
+
"#{range_key_name}"
|
95
398
|
end
|
96
399
|
scoped(lower_bound: bound(true, true, start_key))
|
97
400
|
end
|
98
401
|
|
402
|
+
#
|
403
|
+
# Restrict records to those whose value in the first unscoped primary key
|
404
|
+
# column are less than or equal to the given start key.
|
405
|
+
#
|
406
|
+
# @param end_key the inclusive upper bound for values in the key column
|
407
|
+
# @return [RecordSet] record set with the upper bound applied
|
408
|
+
#
|
409
|
+
# @see #before
|
410
|
+
#
|
99
411
|
def upto(end_key)
|
100
412
|
unless partition_specified?
|
101
|
-
|
102
|
-
|
413
|
+
fail IllegalQuery,
|
414
|
+
"Can't construct exclusive range on partition key " \
|
415
|
+
"#{range_key_name}"
|
103
416
|
end
|
104
417
|
scoped(upper_bound: bound(false, true, end_key))
|
105
418
|
end
|
106
419
|
|
420
|
+
#
|
421
|
+
# Reverse the order in which records will be returned from the record set
|
422
|
+
#
|
423
|
+
# @return [RecordSet] record set with order reversed
|
424
|
+
#
|
425
|
+
# @note This method can only be called on record sets whose partition key
|
426
|
+
# columns are fully specified. See {#[]} for a discussion of partition
|
427
|
+
# key scoping.
|
428
|
+
#
|
107
429
|
def reverse
|
108
430
|
unless partition_specified?
|
109
|
-
|
110
|
-
|
431
|
+
fail IllegalQuery,
|
432
|
+
"Can't reverse without scoping to partition key " \
|
433
|
+
"#{range_key_name}"
|
111
434
|
end
|
112
435
|
scoped(reversed: !reversed?)
|
113
436
|
end
|
114
437
|
|
438
|
+
#
|
439
|
+
# @overload first
|
440
|
+
# @return [Record] the first record in this record set
|
441
|
+
#
|
442
|
+
# @overload first(count)
|
443
|
+
# @param count [Integer] how many records to return
|
444
|
+
# @return [Array] the first `count` records of the record set
|
445
|
+
#
|
446
|
+
# @return [Record,Array]
|
447
|
+
#
|
115
448
|
def first(count = nil)
|
116
449
|
count ? limit(count).entries : limit(1).each.first
|
117
450
|
end
|
118
451
|
|
452
|
+
#
|
453
|
+
# @overload last
|
454
|
+
# @return [Record] the last record in this record set
|
455
|
+
#
|
456
|
+
# @overload last(count)
|
457
|
+
# @param count [Integer] how many records to return
|
458
|
+
# @return [Array] the last `count` records in the record set in
|
459
|
+
# ascending order
|
460
|
+
#
|
461
|
+
# @return [Record,Array]
|
462
|
+
#
|
119
463
|
def last(count = nil)
|
120
464
|
reverse.first(count).tap do |results|
|
121
465
|
results.reverse! if count
|
122
466
|
end
|
123
467
|
end
|
124
468
|
|
469
|
+
#
|
470
|
+
# @return [Integer] the total number of records in this record set
|
471
|
+
#
|
125
472
|
def count
|
126
473
|
data_set.count
|
127
474
|
end
|
128
475
|
|
476
|
+
#
|
477
|
+
# Enumerate over the records in this record set
|
478
|
+
#
|
479
|
+
# @yieldparam record [Record] each successive record in the record set
|
480
|
+
# @return [Enumerator] if no block given
|
481
|
+
# @return [void]
|
482
|
+
#
|
483
|
+
# @see find_each
|
484
|
+
#
|
129
485
|
def each(&block)
|
130
486
|
find_each(&block)
|
131
487
|
end
|
132
488
|
|
489
|
+
#
|
490
|
+
# Enumerate over the records in this record set, with control over how
|
491
|
+
# the database is queried
|
492
|
+
#
|
493
|
+
# @param (see #find_rows_in_batches)
|
494
|
+
# @yieldparam (see #each)
|
495
|
+
# @option (see #find_rows_in_batches)
|
496
|
+
# @return (see #each)
|
497
|
+
#
|
498
|
+
# @see #find_in_batches
|
499
|
+
#
|
133
500
|
def find_each(options = {})
|
134
501
|
return enum_for(:find_each, options) unless block_given?
|
135
502
|
find_each_row(options) { |row| yield target_class.hydrate(row) }
|
136
503
|
end
|
137
504
|
|
505
|
+
#
|
506
|
+
# Enumerate over the records in this record set in batches. Note that the
|
507
|
+
# given batch_size controls the maximum number of records that can be
|
508
|
+
# returned per query, but no batch is guaranteed to be exactly the given
|
509
|
+
# `batch_size`
|
510
|
+
#
|
511
|
+
# @param (see #find_rows_in_batches)
|
512
|
+
# @option (see #find_rows_in_batches)
|
513
|
+
# @yieldparam batch [Array<Record>] batch of records
|
514
|
+
# @return (see #each)
|
515
|
+
#
|
138
516
|
def find_in_batches(options = {})
|
139
517
|
return enum_for(:find_in_batches, options) unless block_given?
|
140
518
|
find_rows_in_batches(options) do |rows|
|
@@ -142,13 +520,39 @@ module Cequel
|
|
142
520
|
end
|
143
521
|
end
|
144
522
|
|
523
|
+
#
|
524
|
+
# Enumerate over the row data for each record in this record set, without
|
525
|
+
# hydrating an actual {Record} instance. Useful for operations where
|
526
|
+
# speed is at a premium.
|
527
|
+
#
|
528
|
+
# @param (see #find_rows_in_batches)
|
529
|
+
# @option (see #find_rows_in_batches)
|
530
|
+
# @yieldparam row [Hash<Symbol,Object>] a hash of column names to values
|
531
|
+
# for each row
|
532
|
+
# @return (see #each)
|
533
|
+
#
|
534
|
+
# @see #find_rows_in_batches
|
535
|
+
#
|
145
536
|
def find_each_row(options = {}, &block)
|
146
537
|
return enum_for(:find_each_row, options) unless block
|
147
538
|
find_rows_in_batches(options) { |rows| rows.each(&block) }
|
148
539
|
end
|
149
540
|
|
541
|
+
#
|
542
|
+
# Enumerate over batches of row data for the records in this record set.
|
543
|
+
#
|
544
|
+
# @param options [Options] options for querying the database
|
545
|
+
# @option options [Integer] :batch_size (1000) the maximum number of rows
|
546
|
+
# to return per batch query
|
547
|
+
# @yieldparam batch [Array<Hash<Symbol,Object>>] a batch of rows
|
548
|
+
# @return (see #each)
|
549
|
+
#
|
550
|
+
# @see #find_each_row
|
551
|
+
# @see #find_in_batches
|
552
|
+
#
|
150
553
|
def find_rows_in_batches(options = {}, &block)
|
151
554
|
return find_rows_in_single_batch(options, &block) if row_limit
|
555
|
+
options.assert_valid_keys(:batch_size)
|
152
556
|
batch_size = options.fetch(:batch_size, 1000)
|
153
557
|
batch_record_set = base_record_set = limit(batch_size)
|
154
558
|
more_results = true
|
@@ -165,26 +569,46 @@ module Cequel
|
|
165
569
|
end
|
166
570
|
end
|
167
571
|
|
572
|
+
#
|
573
|
+
# @return [Cequel::Metal::DataSet] the data set underlying this record
|
574
|
+
# set
|
575
|
+
#
|
168
576
|
def data_set
|
169
577
|
@data_set ||= construct_data_set
|
170
578
|
end
|
171
579
|
|
580
|
+
#
|
581
|
+
# @return [Hash] map of key column names to the values that have been
|
582
|
+
# specified in this record set
|
583
|
+
#
|
172
584
|
def scoped_key_attributes
|
173
585
|
Hash[scoped_key_columns.map { |col| col.name }.zip(scoped_key_values)]
|
174
586
|
end
|
175
587
|
|
588
|
+
# (see BulkWrites#delete_all)
|
589
|
+
def delete_all
|
590
|
+
if partition_specified?
|
591
|
+
data_set.delete
|
592
|
+
else
|
593
|
+
super
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
176
597
|
def_delegators :entries, :inspect
|
177
598
|
|
599
|
+
# @private
|
178
600
|
def ==(other)
|
179
601
|
entries == other.to_a
|
180
602
|
end
|
181
603
|
|
182
604
|
protected
|
605
|
+
|
183
606
|
attr_reader :attributes
|
184
|
-
hattr_reader :attributes, :select_columns, :scoped_key_values,
|
185
|
-
|
607
|
+
hattr_reader :attributes, :select_columns, :scoped_key_values,
|
608
|
+
:row_limit, :lower_bound, :upper_bound,
|
609
|
+
:scoped_indexed_column
|
186
610
|
protected :select_columns, :scoped_key_values, :row_limit, :lower_bound,
|
187
|
-
|
611
|
+
:upper_bound, :scoped_indexed_column
|
188
612
|
hattr_inquirer :attributes, :reversed
|
189
613
|
protected :reversed?
|
190
614
|
|
@@ -194,16 +618,16 @@ module Cequel
|
|
194
618
|
|
195
619
|
def find_nested_batches_from(row, options, &block)
|
196
620
|
if next_range_key_column
|
197
|
-
at(row[range_key_name])
|
198
|
-
next_batch_from(row)
|
199
|
-
find_rows_in_batches(options, &block)
|
621
|
+
at(row[range_key_name])
|
622
|
+
.next_batch_from(row)
|
623
|
+
.find_rows_in_batches(options, &block)
|
200
624
|
end
|
201
625
|
end
|
202
626
|
|
203
627
|
def find_rows_in_single_batch(options = {})
|
204
628
|
if options.key?(:batch_size)
|
205
|
-
|
206
|
-
|
629
|
+
fail ArgumentError,
|
630
|
+
"Can't pass :batch_size argument with a limit in the scope"
|
207
631
|
else
|
208
632
|
data_set.entries.tap do |batch|
|
209
633
|
yield batch if batch.any? && block_given?
|
@@ -251,10 +675,21 @@ module Cequel
|
|
251
675
|
scoped_key_values.length >= target_class.partition_key_columns.length
|
252
676
|
end
|
253
677
|
|
678
|
+
def partition_exactly_specified?
|
679
|
+
scoped_key_values.length == target_class.partition_key_columns.length
|
680
|
+
end
|
681
|
+
|
254
682
|
def multiple_records_specified?
|
255
683
|
scoped_key_values.any? { |values| values.is_a?(Array) }
|
256
684
|
end
|
257
685
|
|
686
|
+
def next_unscoped_key_column_valid_for_in_query?
|
687
|
+
next_unscoped_key_column = unscoped_key_columns.first
|
688
|
+
|
689
|
+
next_unscoped_key_column == partition_key_columns.last ||
|
690
|
+
next_unscoped_key_column == clustering_columns.last
|
691
|
+
end
|
692
|
+
|
258
693
|
def resolve_if_fully_specified
|
259
694
|
if fully_specified?
|
260
695
|
if multiple_records_specified?
|
@@ -267,27 +702,28 @@ module Cequel
|
|
267
702
|
end
|
268
703
|
end
|
269
704
|
|
270
|
-
# Try to order results by the first clustering column. Fall back to partition key if none exist.
|
271
705
|
def order_by_column
|
272
|
-
|
706
|
+
if target_class.clustering_columns.any?
|
707
|
+
target_class.clustering_columns.first.name
|
708
|
+
end
|
273
709
|
end
|
274
710
|
|
275
711
|
def selects_collection_columns?
|
276
712
|
select_columns.any? do |column_name|
|
277
|
-
target_class.reflect_on_column(column_name).
|
278
|
-
is_a?(Cequel::Schema::CollectionColumn)
|
713
|
+
target_class.reflect_on_column(column_name).collection_column?
|
279
714
|
end
|
280
715
|
end
|
281
716
|
|
282
717
|
def select_non_collection_columns!
|
283
718
|
if selects_collection_columns?
|
284
|
-
|
285
|
-
|
719
|
+
fail ArgumentError,
|
720
|
+
"Can't scope by multiple keys when selecting a collection " \
|
721
|
+
"column."
|
286
722
|
end
|
287
723
|
if select_columns.empty?
|
288
|
-
non_collection_columns = target_class.columns
|
289
|
-
reject { |column| column.
|
290
|
-
map { |column| column.name }
|
724
|
+
non_collection_columns = target_class.columns
|
725
|
+
.reject { |column| column.collection_column? }
|
726
|
+
.map { |column| column.name }
|
291
727
|
select(*non_collection_columns)
|
292
728
|
else
|
293
729
|
self
|
@@ -295,6 +731,7 @@ module Cequel
|
|
295
731
|
end
|
296
732
|
|
297
733
|
private
|
734
|
+
|
298
735
|
def_delegators :target_class, :connection
|
299
736
|
def_delegator :range_key_column, :cast, :cast_range_key
|
300
737
|
private :connection, :cast_range_key
|
@@ -304,22 +741,7 @@ module Cequel
|
|
304
741
|
end
|
305
742
|
|
306
743
|
def construct_data_set
|
307
|
-
|
308
|
-
data_set = data_set.limit(row_limit) if row_limit
|
309
|
-
data_set = data_set.select(*select_columns) if select_columns
|
310
|
-
if scoped_key_values
|
311
|
-
key_conditions = Hash[scoped_key_names.zip(scoped_key_values)]
|
312
|
-
data_set = data_set.where(key_conditions)
|
313
|
-
end
|
314
|
-
if lower_bound
|
315
|
-
data_set = data_set.where(*lower_bound.to_cql_with_bind_variables)
|
316
|
-
end
|
317
|
-
if upper_bound
|
318
|
-
data_set = data_set.where(*upper_bound.to_cql_with_bind_variables)
|
319
|
-
end
|
320
|
-
data_set = data_set.order(order_by_column => :desc) if reversed?
|
321
|
-
data_set = data_set.where(scoped_indexed_column) if scoped_indexed_column
|
322
|
-
data_set
|
744
|
+
DataSetBuilder.build_for(self)
|
323
745
|
end
|
324
746
|
|
325
747
|
def bound(gt, inclusive, value)
|
@@ -327,13 +749,19 @@ module Cequel
|
|
327
749
|
end
|
328
750
|
|
329
751
|
def cast_range_key_for_bound(value)
|
330
|
-
if range_key_column.type?(Type::Timeuuid) &&
|
752
|
+
if range_key_column.type?(Type::Timeuuid) &&
|
753
|
+
!value.is_a?(CassandraCQL::UUID)
|
754
|
+
|
331
755
|
Type::Timestamp.instance.cast(value)
|
332
756
|
else
|
333
757
|
cast_range_key(value)
|
334
758
|
end
|
335
759
|
end
|
336
760
|
|
761
|
+
def load!
|
762
|
+
fail ArgumentError, "Not all primary key columns have specified values"
|
763
|
+
end
|
764
|
+
|
337
765
|
def scoped(new_attributes = {}, &block)
|
338
766
|
attributes_copy = Marshal.load(Marshal.dump(attributes))
|
339
767
|
attributes_copy.merge!(new_attributes)
|
@@ -341,15 +769,16 @@ module Cequel
|
|
341
769
|
RecordSet.new(target_class, attributes_copy)
|
342
770
|
end
|
343
771
|
|
772
|
+
def scope_and_resolve(&block)
|
773
|
+
scoped(&block).resolve_if_fully_specified
|
774
|
+
end
|
775
|
+
|
344
776
|
def key_attributes_for_each_row
|
345
777
|
return enum_for(:key_attributes_for_each_row) unless block_given?
|
346
778
|
select(*key_column_names).find_each do |record|
|
347
779
|
yield record.key_attributes
|
348
780
|
end
|
349
781
|
end
|
350
|
-
|
351
782
|
end
|
352
|
-
|
353
783
|
end
|
354
|
-
|
355
784
|
end
|