cequel 1.10.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +93 -65
- data/README.md +26 -5
- data/Vagrantfile +2 -2
- data/lib/cequel/errors.rb +2 -0
- data/lib/cequel/instrumentation.rb +5 -4
- data/lib/cequel/metal/batch.rb +21 -18
- data/lib/cequel/metal/data_set.rb +17 -28
- data/lib/cequel/metal/inserter.rb +3 -2
- data/lib/cequel/metal/keyspace.rb +56 -33
- data/lib/cequel/metal/request_logger.rb +22 -8
- data/lib/cequel/metal/row_specification.rb +9 -8
- data/lib/cequel/metal/statement.rb +23 -7
- data/lib/cequel/metal/updater.rb +12 -10
- data/lib/cequel/metal/writer.rb +5 -13
- data/lib/cequel/record/association_collection.rb +6 -33
- data/lib/cequel/record/collection.rb +2 -1
- data/lib/cequel/record/errors.rb +6 -0
- data/lib/cequel/record/persistence.rb +2 -2
- data/lib/cequel/record/record_set.rb +3 -4
- data/lib/cequel/record/validations.rb +5 -5
- data/lib/cequel/schema/table.rb +3 -5
- data/lib/cequel/schema/table_reader.rb +73 -111
- data/lib/cequel/schema/table_updater.rb +9 -15
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/metal/data_set_spec.rb +34 -46
- data/spec/examples/metal/keyspace_spec.rb +8 -6
- data/spec/examples/record/associations_spec.rb +8 -18
- data/spec/examples/record/persistence_spec.rb +6 -6
- data/spec/examples/record/record_set_spec.rb +39 -12
- data/spec/examples/record/timestamps_spec.rb +12 -5
- data/spec/examples/schema/keyspace_spec.rb +13 -37
- data/spec/examples/schema/table_reader_spec.rb +4 -1
- data/spec/examples/schema/table_updater_spec.rb +22 -7
- data/spec/examples/schema/table_writer_spec.rb +2 -3
- data/spec/examples/spec_helper.rb +1 -0
- data/spec/examples/spec_support/preparation_spec.rb +14 -7
- metadata +7 -8
data/lib/cequel/metal/updater.rb
CHANGED
@@ -45,7 +45,8 @@ module Cequel
|
|
45
45
|
# @see DataSet#list_prepend
|
46
46
|
#
|
47
47
|
def list_prepend(column, elements)
|
48
|
-
|
48
|
+
elements = Array(elements)
|
49
|
+
statements << "#{column} = ? + #{column}"
|
49
50
|
bind_vars << elements
|
50
51
|
end
|
51
52
|
|
@@ -59,7 +60,8 @@ module Cequel
|
|
59
60
|
# @see DataSet#list_append
|
60
61
|
#
|
61
62
|
def list_append(column, elements)
|
62
|
-
|
63
|
+
elements = Array(elements)
|
64
|
+
statements << "#{column} = #{column} + ?"
|
63
65
|
bind_vars << elements
|
64
66
|
end
|
65
67
|
|
@@ -73,7 +75,8 @@ module Cequel
|
|
73
75
|
# @see DataSet#list_remove
|
74
76
|
#
|
75
77
|
def list_remove(column, value)
|
76
|
-
|
78
|
+
value = Array(value)
|
79
|
+
statements << "#{column} = #{column} - ?"
|
77
80
|
bind_vars << value
|
78
81
|
end
|
79
82
|
|
@@ -102,8 +105,8 @@ module Cequel
|
|
102
105
|
# @see DataSet#set_add
|
103
106
|
#
|
104
107
|
def set_add(column, values)
|
105
|
-
statements << "#{column} = #{column} +
|
106
|
-
bind_vars << values
|
108
|
+
statements << "#{column} = #{column} + ?"
|
109
|
+
bind_vars << Set.new(::Kernel.Array(values))
|
107
110
|
end
|
108
111
|
|
109
112
|
#
|
@@ -116,8 +119,8 @@ module Cequel
|
|
116
119
|
# @see DataSet#set_remove
|
117
120
|
#
|
118
121
|
def set_remove(column, values)
|
119
|
-
statements << "#{column} = #{column} -
|
120
|
-
bind_vars << ::Kernel.Array(values)
|
122
|
+
statements << "#{column} = #{column} - ?"
|
123
|
+
bind_vars << Set.new(::Kernel.Array(values))
|
121
124
|
end
|
122
125
|
|
123
126
|
#
|
@@ -130,9 +133,8 @@ module Cequel
|
|
130
133
|
# @see DataSet#map_update
|
131
134
|
#
|
132
135
|
def map_update(column, updates)
|
133
|
-
|
134
|
-
|
135
|
-
bind_vars.concat(updates.flatten)
|
136
|
+
statements << "#{column} = #{column} + ?"
|
137
|
+
bind_vars << updates
|
136
138
|
end
|
137
139
|
|
138
140
|
private
|
data/lib/cequel/metal/writer.rb
CHANGED
@@ -13,6 +13,8 @@ module Cequel
|
|
13
13
|
class Writer
|
14
14
|
extend Util::Forwardable
|
15
15
|
|
16
|
+
attr_accessor :type_hints
|
17
|
+
|
16
18
|
#
|
17
19
|
# @param data_set [DataSet] data set to write to
|
18
20
|
#
|
@@ -41,8 +43,8 @@ module Cequel
|
|
41
43
|
consistency = options.fetch(:consistency, data_set.query_consistency)
|
42
44
|
write_to_statement(statement, options)
|
43
45
|
statement.append(*data_set.row_specifications_cql)
|
44
|
-
data_set.
|
45
|
-
|
46
|
+
data_set.write_with_options(statement,
|
47
|
+
consistency: consistency)
|
46
48
|
end
|
47
49
|
|
48
50
|
private
|
@@ -52,17 +54,7 @@ module Cequel
|
|
52
54
|
def_delegator :statements, :empty?
|
53
55
|
|
54
56
|
def prepare_upsert_value(value)
|
55
|
-
|
56
|
-
when ::Array
|
57
|
-
yield '[?]', value
|
58
|
-
when ::Set then
|
59
|
-
yield '{?}', value.to_a
|
60
|
-
when ::Hash then
|
61
|
-
binding_pairs = ::Array.new(value.length) { '?:?' }.join(',')
|
62
|
-
yield "{#{binding_pairs}}", *value.flatten
|
63
|
-
else
|
64
|
-
yield '?', value
|
65
|
-
end
|
57
|
+
yield '?', value
|
66
58
|
end
|
67
59
|
|
68
60
|
#
|
@@ -50,41 +50,14 @@ module Cequel
|
|
50
50
|
end
|
51
51
|
|
52
52
|
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# will always query Cassandra, even if the records are loaded in
|
56
|
-
# memory.
|
53
|
+
# @raise [DangerousQueryError] to prevent loading the entire record set
|
54
|
+
# to be counted
|
57
55
|
#
|
58
|
-
|
59
|
-
|
60
|
-
# @see #length
|
61
|
-
#
|
62
|
-
def_delegator :record_set, :count
|
63
|
-
|
64
|
-
#
|
65
|
-
# @!method length
|
66
|
-
# The number of child instances in the in-memory collection. If the
|
67
|
-
# records are not loaded in memory, they will be loaded and then
|
68
|
-
# counted.
|
69
|
-
#
|
70
|
-
# @return [Integer] length of the loaded record collection in memory
|
71
|
-
# @see #size
|
72
|
-
# @see #count
|
73
|
-
#
|
74
|
-
def_delegator :entries, :length
|
75
|
-
|
76
|
-
#
|
77
|
-
# Get the size of the child collection. If the records are loaded in
|
78
|
-
# memory from a previous operation, count the length of the array in
|
79
|
-
# memory. If the collection is unloaded, perform a `COUNT` query.
|
80
|
-
#
|
81
|
-
# @return [Integer] size of the child collection
|
82
|
-
# @see #length
|
83
|
-
# @see #count
|
84
|
-
#
|
85
|
-
def size
|
86
|
-
loaded? ? length : count
|
56
|
+
def count
|
57
|
+
raise Cequel::Record::DangerousQueryError.new
|
87
58
|
end
|
59
|
+
alias_method :length, :count
|
60
|
+
alias_method :size, :count
|
88
61
|
|
89
62
|
#
|
90
63
|
# @return [Boolean] true if this collection's records are loaded in
|
@@ -332,7 +332,8 @@ module Cequel
|
|
332
332
|
#
|
333
333
|
def unshift(*objects)
|
334
334
|
objects.map!(&method(:cast_element))
|
335
|
-
|
335
|
+
prepared = @model.class.connection.bug8733_version? ? objects.reverse : objects
|
336
|
+
to_update { updater.list_prepend(column_name, prepared) }
|
336
337
|
to_modify { super }
|
337
338
|
end
|
338
339
|
alias_method :prepend, :unshift
|
data/lib/cequel/record/errors.rb
CHANGED
@@ -44,6 +44,12 @@ module Cequel
|
|
44
44
|
#
|
45
45
|
IllegalQuery = Class.new(StandardError)
|
46
46
|
|
47
|
+
#
|
48
|
+
# Raised when attempting to perform a query that has detrimental effects.
|
49
|
+
# Typically when trying to count records.
|
50
|
+
#
|
51
|
+
DangerousQueryError = Class.new(StandardError)
|
52
|
+
|
47
53
|
#
|
48
54
|
# Raised when attempting to persist a Cequel::Record without defining all
|
49
55
|
# primary key columns
|
@@ -274,8 +274,8 @@ module Cequel
|
|
274
274
|
|
275
275
|
def create(options = {})
|
276
276
|
assert_keys_present!
|
277
|
-
|
278
|
-
|
277
|
+
attributes_for_write = attributes.reject { |attr, value| value.nil? }
|
278
|
+
metal_scope.insert(attributes_for_write, options)
|
279
279
|
loaded!
|
280
280
|
persisted!
|
281
281
|
end
|
@@ -537,11 +537,10 @@ module Cequel
|
|
537
537
|
end
|
538
538
|
end
|
539
539
|
|
540
|
-
#
|
541
|
-
#
|
542
|
-
#
|
540
|
+
# @raise [DangerousQueryError] to prevent loading the entire record set
|
541
|
+
# to be counted
|
543
542
|
def count
|
544
|
-
|
543
|
+
raise Cequel::Record::DangerousQueryError.new
|
545
544
|
end
|
546
545
|
alias_method :length, :count
|
547
546
|
alias_method :size, :count
|
@@ -26,7 +26,7 @@ module Cequel
|
|
26
26
|
included do
|
27
27
|
include ActiveModel::Validations
|
28
28
|
define_model_callbacks :validation
|
29
|
-
|
29
|
+
prepend Callback
|
30
30
|
end
|
31
31
|
|
32
32
|
#
|
@@ -80,11 +80,11 @@ module Cequel
|
|
80
80
|
self.attributes = attributes
|
81
81
|
save!
|
82
82
|
end
|
83
|
+
end
|
83
84
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
run_callbacks(:validation) { valid_without_callbacks? context }
|
85
|
+
module Callback
|
86
|
+
def valid?(context=nil)
|
87
|
+
run_callbacks(:validation) { super context }
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
data/lib/cequel/schema/table.rb
CHANGED
@@ -9,11 +9,6 @@ module Cequel
|
|
9
9
|
# @see Keyspace#read_table
|
10
10
|
#
|
11
11
|
class Table
|
12
|
-
STORAGE_PROPERTIES = %w(
|
13
|
-
bloom_filter_fp_chance caching comment compaction compression
|
14
|
-
dclocal_read_repair_chance gc_grace_seconds read_repair_chance
|
15
|
-
replicate_on_write
|
16
|
-
)
|
17
12
|
|
18
13
|
# @return [Symbol] the name of the table
|
19
14
|
attr_reader :name
|
@@ -293,8 +288,11 @@ module Cequel
|
|
293
288
|
end
|
294
289
|
|
295
290
|
def type(type)
|
291
|
+
type = type.kind if type.respond_to?(:kind)
|
292
|
+
|
296
293
|
::Cequel::Type[type]
|
297
294
|
end
|
295
|
+
|
298
296
|
end
|
299
297
|
end
|
300
298
|
end
|
@@ -54,6 +54,7 @@ module Cequel
|
|
54
54
|
read_clustering_columns
|
55
55
|
read_data_columns
|
56
56
|
read_properties
|
57
|
+
read_table_settings
|
57
58
|
table
|
58
59
|
end
|
59
60
|
end
|
@@ -64,142 +65,103 @@ module Cequel
|
|
64
65
|
|
65
66
|
private
|
66
67
|
|
67
|
-
# XXX This gets a lot easier in Cassandra 2.0: all logical columns
|
68
|
-
# (including keys) are returned from the `schema_columns` query, so
|
69
|
-
# there's no need to jump through all these hoops to figure out what the
|
70
|
-
# key columns look like.
|
71
|
-
#
|
72
|
-
# However, this approach works for both 1.2 and 2.0, so better to keep it
|
73
|
-
# for now. It will be worth refactoring this code to take advantage of
|
74
|
-
# 2.0's better interface in a future version of Cequel that targets 2.0+.
|
75
68
|
def read_partition_keys
|
76
|
-
|
77
|
-
|
78
|
-
columns = partition_columns.sort_by { |c| c['component_index'] }
|
79
|
-
.map { |c| c['column_name'] }
|
80
|
-
|
81
|
-
columns.zip(types) do |name, type|
|
82
|
-
table.add_partition_key(name.to_sym, Type.lookup_internal(type))
|
69
|
+
table_data.partition_key.each do |k|
|
70
|
+
table.add_partition_key(k.name.to_sym, k.type)
|
83
71
|
end
|
72
|
+
|
84
73
|
end
|
85
74
|
|
86
|
-
# XXX See comment on {read_partition_keys}
|
87
75
|
def read_clustering_columns
|
88
|
-
|
89
|
-
.
|
90
|
-
|
91
|
-
unless comparators
|
92
|
-
table.compact_storage = true
|
93
|
-
return unless column_data.empty?
|
94
|
-
columns << :column1 if cluster_columns.empty?
|
95
|
-
comparators = [table_data['comparator']]
|
96
|
-
end
|
97
|
-
|
98
|
-
columns.zip(comparators) do |name, type|
|
99
|
-
if REVERSED_TYPE_PATTERN =~ type
|
100
|
-
type = $1
|
101
|
-
clustering_order = :desc
|
76
|
+
table_data.clustering_columns.zip(table_data.clustering_order)
|
77
|
+
.each do |c,o|
|
78
|
+
table.add_clustering_column(c.name.to_sym, c.type, o)
|
102
79
|
end
|
103
|
-
table.add_clustering_column(
|
104
|
-
name.to_sym,
|
105
|
-
Type.lookup_internal(type),
|
106
|
-
clustering_order
|
107
|
-
)
|
108
|
-
end
|
109
80
|
end
|
110
81
|
|
111
82
|
def read_data_columns
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
83
|
+
indexes = Hash[table_data.each_index.map{|i| [i.target, i.name]}]
|
84
|
+
|
85
|
+
((table_data.each_column - table_data.partition_key) - table_data.clustering_columns)
|
86
|
+
.each do |c|
|
87
|
+
next if table.column(c.name.to_sym)
|
88
|
+
case c.type
|
89
|
+
when Cassandra::Types::Simple
|
90
|
+
opts = if indexes[c.name]
|
91
|
+
{index: indexes[c.name].to_sym}
|
92
|
+
else
|
93
|
+
{}
|
94
|
+
end
|
95
|
+
table.add_data_column(c.name.to_sym, c.type, opts)
|
96
|
+
when Cassandra::Types::List
|
97
|
+
table.add_list(c.name.to_sym, c.type.value_type)
|
98
|
+
when Cassandra::Types::Set
|
99
|
+
table.add_set(c.name.to_sym, c.type.value_type)
|
100
|
+
when Cassandra::Types::Map
|
101
|
+
table.add_map(c.name.to_sym, c.type.key_type, c.type.value_type)
|
126
102
|
else
|
127
|
-
|
128
|
-
result['column_name'].to_sym,
|
129
|
-
Type.lookup_internal(result['validator']),
|
130
|
-
result['index_name'].try(:to_sym)
|
131
|
-
)
|
103
|
+
fail "Unsupported type #{c.type.inspect}"
|
132
104
|
end
|
133
105
|
end
|
134
|
-
end
|
135
106
|
end
|
136
107
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
end
|
108
|
+
@@prop_extractors = []
|
109
|
+
def self.def_property(name,
|
110
|
+
option_method = name,
|
111
|
+
coercion = ->(val, _table_data){ val })
|
142
112
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
end
|
147
|
-
compaction = JSON.parse(table_data['compaction_strategy_options'])
|
148
|
-
.symbolize_keys
|
149
|
-
compaction[:class] = table_data['compaction_strategy_class']
|
150
|
-
table.add_property(:compaction, compaction)
|
151
|
-
compression = JSON.parse(table_data['compression_parameters'])
|
152
|
-
table.add_property(:compression, compression)
|
153
|
-
end
|
113
|
+
@@prop_extractors << ->(table, table_data) {
|
114
|
+
raw_prop_val = table_data.options.public_send(option_method)
|
115
|
+
prop_val = coercion.call(raw_prop_val,table_data)
|
154
116
|
|
155
|
-
|
156
|
-
|
157
|
-
$1.split(',')
|
158
|
-
end
|
117
|
+
table.add_property(name, prop_val)
|
118
|
+
}
|
159
119
|
end
|
160
120
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
121
|
+
def_property("bloom_filter_fp_chance")
|
122
|
+
def_property("caching")
|
123
|
+
def_property("comment")
|
124
|
+
def_property("local_read_repair_chance")
|
125
|
+
def_property("dclocal_read_repair_chance", :local_read_repair_chance)
|
126
|
+
def_property("compression", :compression,
|
127
|
+
->(comp, table_data) {
|
128
|
+
comp.clone.tap { |r|
|
129
|
+
r["chunk_length_kb"] ||= r["chunk_length_in_kb"] if r["chunk_length_in_kb"]
|
130
|
+
r["crc_check_chance"] ||= table_data.options.crc_check_chance
|
131
|
+
}
|
132
|
+
})
|
133
|
+
def_property("compaction", :compaction_strategy,
|
134
|
+
->(compaction_strategy, _table_data) {
|
135
|
+
compaction_strategy.options
|
136
|
+
.merge(class: compaction_strategy.class_name)
|
137
|
+
})
|
138
|
+
def_property("gc_grace_seconds")
|
139
|
+
def_property("read_repair_chance")
|
140
|
+
def_property("replicate_on_write", :replicate_on_write?)
|
169
141
|
|
170
|
-
def
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
SELECT * FROM system.schema_columns
|
175
|
-
WHERE keyspace_name = ? AND columnfamily_name = ?
|
176
|
-
CQL
|
177
|
-
column_query.map(&:to_hash)
|
178
|
-
end
|
142
|
+
def read_properties
|
143
|
+
@@prop_extractors.each do |extractor|
|
144
|
+
extractor.call(table, table_data)
|
145
|
+
end
|
179
146
|
end
|
180
147
|
|
181
|
-
def
|
182
|
-
|
183
|
-
column['type'] == 'compact_value'
|
184
|
-
end || {}
|
148
|
+
def read_table_settings
|
149
|
+
table.compact_storage = table_data.options.compact_storage?
|
185
150
|
end
|
186
151
|
|
187
|
-
def
|
188
|
-
@
|
189
|
-
|
190
|
-
|
191
|
-
|
152
|
+
def table_data
|
153
|
+
@table_data ||=
|
154
|
+
begin
|
155
|
+
cluster = keyspace.cluster
|
156
|
+
cluster.refresh_schema
|
192
157
|
|
193
|
-
|
194
|
-
|
195
|
-
column['type'] == 'partition_key'
|
196
|
-
end
|
197
|
-
end
|
158
|
+
fail(NoSuchKeyspaceError, "No such keyspace #{keyspace.name}") if
|
159
|
+
!cluster.has_keyspace?(keyspace.name)
|
198
160
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
161
|
+
cluster
|
162
|
+
.keyspace(keyspace.name)
|
163
|
+
.table(table_name.to_s)
|
164
|
+
end
|
203
165
|
end
|
204
166
|
end
|
205
167
|
end
|