cequel 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +2 -2
  4. data/README.md +8 -0
  5. data/lib/cequel/errors.rb +2 -0
  6. data/lib/cequel/metal/new_relic_instrumentation.rb +2 -1
  7. data/lib/cequel/record.rb +8 -0
  8. data/lib/cequel/record/schema.rb +30 -23
  9. data/lib/cequel/schema.rb +3 -2
  10. data/lib/cequel/schema/column.rb +11 -1
  11. data/lib/cequel/schema/keyspace.rb +18 -7
  12. data/lib/cequel/schema/patch.rb +152 -0
  13. data/lib/cequel/schema/table.rb +55 -137
  14. data/lib/cequel/schema/table_desc_dsl.rb +196 -0
  15. data/lib/cequel/schema/table_differ.rb +112 -0
  16. data/lib/cequel/schema/table_property.rb +14 -0
  17. data/lib/cequel/schema/table_reader.rb +81 -85
  18. data/lib/cequel/schema/table_updater.rb +0 -17
  19. data/lib/cequel/schema/table_writer.rb +10 -9
  20. data/lib/cequel/version.rb +1 -1
  21. data/spec/examples/metal/data_set_spec.rb +156 -153
  22. data/spec/examples/metal/keyspace_spec.rb +4 -4
  23. data/spec/examples/record/associations_spec.rb +6 -0
  24. data/spec/examples/record/mass_assignment_spec.rb +2 -2
  25. data/spec/examples/record/properties_spec.rb +1 -0
  26. data/spec/examples/record/record_set_spec.rb +1 -1
  27. data/spec/examples/schema/patch_spec.rb +190 -0
  28. data/spec/examples/schema/table_differ_spec.rb +280 -0
  29. data/spec/examples/schema/table_reader_spec.rb +379 -354
  30. data/spec/examples/schema/table_updater_spec.rb +0 -12
  31. data/spec/examples/spec_helper.rb +5 -5
  32. data/spec/examples/spec_support/preparation_spec.rb +4 -0
  33. data/spec/support/helpers.rb +23 -0
  34. metadata +9 -6
  35. data/lib/cequel/schema/create_table_dsl.rb +0 -88
  36. data/lib/cequel/schema/table_synchronizer.rb +0 -180
  37. data/spec/examples/schema/table_synchronizer_spec.rb +0 -200
@@ -12,19 +12,25 @@ module Cequel
12
12
 
13
13
  # @return [Symbol] the name of the table
14
14
  attr_reader :name
15
+
15
16
  # @return [Array<Column>] all columns defined on the table
16
17
  attr_reader :columns
18
+
17
19
  # @return [Array<PartitionKey>] partition key columns defined on the
18
20
  # table
19
21
  attr_reader :partition_key_columns
22
+
20
23
  # @return [Array<ClusteringColumn>] clustering columns defined on the
21
24
  # table
22
25
  attr_reader :clustering_columns
26
+
23
27
  # @return [Array<DataColumn,CollectionColumn>] data columns and
24
28
  # collection columns defined on the table
25
29
  attr_reader :data_columns
30
+
26
31
  # @return [Hash] storage properties defined on the table
27
32
  attr_reader :properties
33
+
28
34
  # @return [Boolean] `true` if this table is configured with compact
29
35
  # storage
30
36
  attr_writer :compact_storage
@@ -33,147 +39,48 @@ module Cequel
33
39
  # @param name [Symbol] the name of the table
34
40
  # @api private
35
41
  #
36
- def initialize(name)
37
- @name = name
42
+ def initialize(name, is_view=false)
43
+ @name = name.to_sym
44
+ @is_view = is_view
38
45
  @partition_key_columns, @clustering_columns, @data_columns = [], [], []
39
46
  @columns, @columns_by_name = [], {}
40
47
  @properties = ActiveSupport::HashWithIndifferentAccess.new
41
48
  end
42
49
 
43
- #
44
- # Define a key column. If this is the first key column defined, it will
45
- # be a partition key; otherwise, it will be a clustering column.
46
- #
47
- # @param name [Symbol] the name of the column
48
- # @param type [Symbol,Type] the type for the column
49
- # @param clustering_order [:asc,:desc] whether rows should be in
50
- # ascending or descending order by this column. Only meaningful for
51
- # clustering columns.
52
- # @return [void]
53
- #
54
- # @see #add_partition_key
55
- #
56
- def add_key(name, type, clustering_order = nil)
57
- if @partition_key_columns.empty?
58
- unless clustering_order.nil?
59
- fail ArgumentError,
60
- "Can't set clustering order for partition key #{name}"
61
- end
62
- add_partition_key(name, type)
63
- else
64
- add_clustering_column(name, type, clustering_order)
65
- end
66
- end
67
-
68
- #
69
- # Define a partition key for the table
70
- #
71
- # @param name [Symbol] the name of the column
72
- # @param type [Symbol,Type] the type for the column
73
- # @return [void]
74
- #
75
- def add_partition_key(name, type)
76
- PartitionKey.new(name, type(type)).tap do |column|
77
- @partition_key_columns << add_column(column)
78
- end
50
+ # @return [Boolean] `true` when this table is a materialized view
51
+ def materialized_view?
52
+ @is_view
79
53
  end
80
54
 
55
+ # Add a column descriptor to this table descriptor.
81
56
  #
82
- # Define a clustering column for the table
83
- #
84
- # @param (see #add_key)
85
- # @return [void]
57
+ # column_desc - Descriptor of column to add. Can be PartitionKey,
58
+ # ClusteringColumn, DataColumn, List, Set, or Map.
86
59
  #
87
- def add_clustering_column(name, type, clustering_order = nil)
88
- ClusteringColumn.new(name, type(type), clustering_order)
89
- .tap { |column| @clustering_columns << add_column(column) }
90
- end
60
+ def add_column(column_desc)
61
+ column_flavor = case column_desc
62
+ when PartitionKey
63
+ @partition_key_columns
64
+ when ClusteringColumn
65
+ @clustering_columns
66
+ else
67
+ @data_columns
68
+ end
91
69
 
92
- #
93
- # Define a data column on the table
94
- #
95
- # @param name [Symbol] name of the column
96
- # @param type [Type] type for the column
97
- # @param options [Options] options for the column
98
- # @option options [Boolean,Symbol] :index (nil) name of a secondary index
99
- # to apply to the column, or `true` to infer an index name by
100
- # convention
101
- # @return [void]
102
- #
103
- def add_data_column(name, type, options = {})
104
- options = {index: options} unless options.is_a?(Hash)
105
- index_name = options[:index]
106
- index_name = :"#{@name}_#{name}_idx" if index_name == true
107
- if type == :enum
108
- type = :int
109
- end
110
- DataColumn.new(name, type(type), index_name)
111
- .tap { |column| @data_columns << add_column(column) }
70
+ column_flavor << column_desc
71
+ columns << column_desc
72
+ columns_by_name[column_desc.name] = column_desc
112
73
  end
113
74
 
75
+ # Add a property to this table descriptor
114
76
  #
115
- # Define a list column on the table
116
- #
117
- # @param name [Symbol] name of the list
118
- # @param type [Symbol,Type] type of the list's elements
119
- # @return [void]
120
- #
121
- # @see List
77
+ # property_desc - A `TableProperty` describing one property of this table.
122
78
  #
123
- def add_list(name, type)
124
- List.new(name, type(type)).tap do |column|
125
- @data_columns << add_column(column)
126
- end
79
+ def add_property(property_desc)
80
+ properties[property_desc.name] = property_desc
127
81
  end
128
82
 
129
83
  #
130
- # Define a set column on the table
131
- #
132
- # @param name [Symbol] name of the set
133
- # @param type [Symbol,Type] type of the set's elements
134
- # @return [void]
135
- #
136
- # @see Set
137
- #
138
- def add_set(name, type)
139
- Set.new(name, type(type)).tap do |column|
140
- @data_columns << add_column(column)
141
- end
142
- end
143
-
144
- #
145
- # Define a map column on the table
146
- #
147
- # @param name [Symbol] name of the set
148
- # @param key_type [Symbol,Type] type of the map's keys
149
- # @param value_type [Symbol,Type] type of the map's values
150
- # @return [void]
151
- #
152
- # @see Map
153
- #
154
- def add_map(name, key_type, value_type)
155
- Map.new(name, type(key_type), type(value_type)).tap do |column|
156
- @data_columns << add_column(column)
157
- end
158
- end
159
-
160
- #
161
- # Define a storage property for the table
162
- #
163
- # @param name [Symbol] name of the property
164
- # @param value value for the property
165
- # @return [void]
166
- #
167
- # @see STORAGE_PROPERTIES List of storage property names
168
- # @see http://cassandra.apache.org/doc/cql3/CQL.html#createTableOptions
169
- # list of CQL3 table storage properties
170
- #
171
- def add_property(name, value)
172
- TableProperty.build(name, value).tap do |property|
173
- @properties[name] = property
174
- end
175
- end
176
-
177
84
  #
178
85
  # @param name [Symbol] name of column to look up
179
86
  # @return [Column] column defined on table with given name
@@ -182,17 +89,23 @@ module Cequel
182
89
  columns_by_name[name.to_sym]
183
90
  end
184
91
 
92
+ # Returns true iff this table has the specified column name.
93
+ #
94
+ def has_column?(name)
95
+ columns_by_name.has_key?(name.to_sym)
96
+ end
97
+
185
98
  #
186
99
  # @return [Array<Symbol>] the names of all columns
187
100
  def column_names
188
- columns.map { |column| column.name }
101
+ columns_by_name.keys
189
102
  end
190
103
 
191
104
  #
192
105
  # @return [Array<Column>] all key columns (partition + clustering)
193
106
  #
194
107
  def key_columns
195
- @partition_key_columns + @clustering_columns
108
+ partition_key_columns + clustering_columns
196
109
  end
197
110
 
198
111
  #
@@ -224,6 +137,12 @@ module Cequel
224
137
  partition_key_columns.length
225
138
  end
226
139
 
140
+ # Returns true iff this table descriptor currently has at least one
141
+ # partition key defined.
142
+ def has_partition_key?
143
+ partition_key_columns.any?
144
+ end
145
+
227
146
  #
228
147
  # @return [Array<Symbol>] names of clustering columns
229
148
  #
@@ -243,7 +162,7 @@ module Cequel
243
162
  # @return [PartitionKey] partition key column with given name
244
163
  #
245
164
  def partition_key(name)
246
- @partition_key_columns.find { |column| column.name == name }
165
+ partition_key_columns.find { |column| column.name == name }
247
166
  end
248
167
 
249
168
  #
@@ -251,7 +170,7 @@ module Cequel
251
170
  # @return [ClusteringColumn] clustering column with given name
252
171
  #
253
172
  def clustering_column(name)
254
- @clustering_columns.find { |column| column.name == name }
173
+ clustering_columns.find { |column| column.name == name }
255
174
  end
256
175
 
257
176
  #
@@ -261,7 +180,7 @@ module Cequel
261
180
  #
262
181
  def data_column(name)
263
182
  name = name.to_sym
264
- @data_columns.find { |column| column.name == name }
183
+ data_columns.find { |column| column.name == name }
265
184
  end
266
185
 
267
186
  #
@@ -269,7 +188,7 @@ module Cequel
269
188
  # @return [TableProperty] property as defined on table
270
189
  #
271
190
  def property(name)
272
- @properties[name].try(:value)
191
+ properties.fetch(name, null_table_property).value
273
192
  end
274
193
 
275
194
  #
@@ -283,19 +202,18 @@ module Cequel
283
202
 
284
203
  attr_reader :columns_by_name
285
204
 
286
- private
287
-
288
- def add_column(column)
289
- columns << column
290
- columns_by_name[column.name] = column
291
- end
292
-
293
205
  def type(type)
294
206
  type = type.kind if type.respond_to?(:kind)
295
207
 
296
208
  ::Cequel::Type[type]
297
209
  end
298
210
 
211
+ def null_table_property
212
+ @@null_table_property ||= Class.new do
213
+ def value; nil; end
214
+ def name; nil; end
215
+ end.new
216
+ end
299
217
  end
300
218
  end
301
219
  end
@@ -0,0 +1,196 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Cequel
3
+ module Schema
4
+ #
5
+ # Implements a DSL used to describe CQL tables.
6
+ #
7
+ # Examples
8
+ #
9
+ # TableDescDsl.new("posts").eval do
10
+ # partition_key :blog_subdomain, :text
11
+ # key :slug, :text
12
+ # column :body, :text
13
+ # set :author_names, :text
14
+ # list :comments, :text
15
+ # map :something_contrived, :text, :text
16
+ # end
17
+ #
18
+ # TableDescDsl.new("posts_view").eval do
19
+ # materialized_view
20
+ # partition_key :blog_subdomain, :text
21
+ # key :slug, :text
22
+ # column :body, :text
23
+ # end
24
+
25
+ #
26
+ class TableDescDsl < BasicObject
27
+ extend ::Cequel::Util::Forwardable
28
+
29
+ # Initialize a new instance
30
+ #
31
+ # table_name - The name of the table being described.
32
+ protected def initialize(table_name)
33
+ @table_name = table_name
34
+ @columns = []
35
+ @properties = []
36
+ @is_compact_storage = false
37
+ @is_view = false
38
+ @has_part_key = false
39
+ end
40
+
41
+ # Returns a Table object built by evaluating the provided block.
42
+ #
43
+ # Yields nothing but block is instance_evaled so it as access to
44
+ # all the methods of the instance.
45
+ def eval(&desc_block)
46
+ instance_eval(&desc_block)
47
+
48
+ table
49
+ end
50
+
51
+ # Describe (one of) the partition key(s) of the table.
52
+ #
53
+ # name - The name of the column.
54
+ # type - The type of the column. Either a `Cequel::Type` or a symbol.
55
+ # See `Cequel::Type`.
56
+ #
57
+ def partition_key(name, type)
58
+ columns << PartitionKey.new(name, type(type))
59
+ end
60
+
61
+
62
+ # Describe (one of) the key(s) of the table.
63
+ #
64
+ # name - The name of the column
65
+ # type - The type of the column. Either a `Cequel::Type` or a symbol.
66
+ # See `Cequel::Type`.
67
+ # clustering_order - `:asc` or `:desc`. Only meaningful for cluster
68
+ # keys. Leave nil for partition keys.
69
+ #
70
+ def key(name, type, clustering_order = nil)
71
+ columns << if has_partition_key?
72
+ ClusteringColumn.new(name, type(type), clustering_order)
73
+ else
74
+ (fail ArgumentError, "Can't set clustering order for partition key #{name}") if clustering_order
75
+
76
+ PartitionKey.new(name, type(type))
77
+ end
78
+ end
79
+
80
+ # Describe a column of the table
81
+ #
82
+ # name - The name of the column.
83
+ # type - The type of the column. Either a `Cequel::Type` or a symbol.
84
+ # See `Cequel::Type`.
85
+ # options
86
+ # :index - name of a secondary index to apply to the column, or
87
+ # `true` to infer an index name by convention
88
+ #
89
+ def column(name, type, options = {})
90
+ columns << DataColumn.new(name, type(type),
91
+ figure_index_name(name, options.fetch(:index, nil)))
92
+ end
93
+
94
+ # Describe a column of type list.
95
+ #
96
+ # name - The name of the column.
97
+ # type - The type of the elements of this column. Either a
98
+ # `Cequel::Type` or a symbol. See `Cequel::Type`.
99
+ #
100
+ def list(name, type)
101
+ columns << List.new(name, type(type))
102
+ end
103
+
104
+ # Describe a column of type set.
105
+ #
106
+ # name - The name of the column.
107
+ # type - The type of the members of this column. Either a
108
+ # `Cequel::Type` or a symbol. See `Cequel::Type`.
109
+ #
110
+ def set(name, type)
111
+ columns << Set.new(name, type(type))
112
+ end
113
+
114
+ # Describe a column of type map.
115
+ #
116
+ # name - The name of the column.
117
+ # key_type - The type of the keys of this column. Either a
118
+ # `Cequel::Type` or a symbol. See `Cequel::Type`.
119
+ # value_type - The type of the values of this column. Either a
120
+ # `Cequel::Type` or a symbol. See `Cequel::Type`.
121
+ def map(name, key_type, value_type)
122
+ columns << Map.new(name, type(key_type), type(value_type))
123
+ end
124
+
125
+ # Describe property of the table.
126
+ #
127
+ # name - name of property.
128
+ # value - value of property.
129
+ #
130
+ # See `STORAGE_PROPERTIES` List of storage property names
131
+ # See http://cassandra.apache.org/doc/cql3/CQL.html#createTableOptions
132
+ # list of CQL3 table storage properties
133
+ #
134
+ def with(name, value)
135
+ properties << TableProperty.build(name, value)
136
+ end
137
+
138
+ #
139
+ # Direct that this table use "compact storage". This is primarily useful
140
+ # for backwards compatibility with legacy CQL2 table schemas.
141
+ #
142
+ # @return [void]
143
+ #
144
+ def compact_storage
145
+ @is_compact_storage = true
146
+ end
147
+
148
+ #
149
+ # Indicates that this is a materialized view.
150
+ #
151
+ # @return [void]
152
+ def materialized_view
153
+ self.is_view = true
154
+ end
155
+
156
+ def table
157
+ Table.new(table_name, is_view).tap do |tab|
158
+ columns.each do |c|
159
+ tab.add_column c
160
+ end
161
+ properties.each do |p|
162
+ tab.add_property p
163
+ end
164
+ tab.compact_storage = is_compact_storage
165
+ end
166
+ end
167
+
168
+ protected
169
+
170
+ attr_reader :table_name, :columns, :properties, :is_compact_storage,
171
+ :is_view
172
+
173
+
174
+ def has_partition_key?
175
+ columns.any?{|c| c.partition_key? }
176
+ end
177
+
178
+ def type(type)
179
+ type = :int if type == :enum
180
+
181
+ ::Cequel::Type[type]
182
+ end
183
+
184
+ def figure_index_name(column_name, idx_opt)
185
+ case idx_opt
186
+ when true
187
+ :"#{table_name}_#{column_name}_idx"
188
+ when false, nil
189
+ nil
190
+ else
191
+ idx_opt
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end