cequel 2.1.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
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