partitioned 0.8.0 → 1.0.1
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.
- data/README +85 -36
- data/Rakefile +3 -0
- data/examples/README +46 -18
- data/lib/monkey_patch_activerecord.rb +14 -8
- data/lib/monkey_patch_postgres.rb +46 -13
- data/lib/partitioned/active_record_overrides.rb +13 -5
- data/lib/partitioned/bulk_methods_mixin.rb +91 -146
- data/lib/partitioned/by_created_at.rb +3 -1
- data/lib/partitioned/by_foreign_key.rb +5 -0
- data/lib/partitioned/by_id.rb +10 -4
- data/lib/partitioned/by_integer_field.rb +9 -0
- data/lib/partitioned/by_monthly_time_field.rb +8 -1
- data/lib/partitioned/by_time_field.rb +16 -8
- data/lib/partitioned/by_weekly_time_field.rb +6 -3
- data/lib/partitioned/multi_level/configurator/data.rb +1 -0
- data/lib/partitioned/multi_level/configurator/dsl.rb +11 -0
- data/lib/partitioned/multi_level/configurator/reader.rb +18 -0
- data/lib/partitioned/multi_level/partition_manager.rb +13 -4
- data/lib/partitioned/multi_level.rb +3 -1
- data/lib/partitioned/partitioned_base/configurator/data.rb +10 -1
- data/lib/partitioned/partitioned_base/configurator/dsl.rb +20 -15
- data/lib/partitioned/partitioned_base/configurator/reader.rb +3 -0
- data/lib/partitioned/partitioned_base/configurator.rb +4 -0
- data/lib/partitioned/partitioned_base/partition_manager.rb +17 -15
- data/lib/partitioned/partitioned_base/sql_adapter.rb +25 -23
- data/lib/partitioned/partitioned_base.rb +112 -41
- data/lib/partitioned/version.rb +2 -1
- data/partitioned.gemspec +3 -2
- metadata +68 -73
@@ -1,77 +1,56 @@
|
|
1
1
|
module Partitioned
|
2
|
+
#
|
3
|
+
# MixIn used to extend ActiveRecord::Base classes implementing bulk insert and update operations
|
4
|
+
# through {#create_many} and {#update_many}. {Partitioned::PartitionedBase} classes are extended
|
5
|
+
# with these methods by default.
|
6
|
+
#
|
7
|
+
# @example to use in non-{Partitioned::PartitionedBase} class:
|
8
|
+
# class Company < ActiveRecord::Base
|
9
|
+
# extend Partitioned::BulkMethodsMixin
|
10
|
+
# end
|
11
|
+
#
|
2
12
|
module BulkMethodsMixin
|
13
|
+
# exception thrown when row data structures are inconsistent between rows in single call to {#create_many} or {#update_many}
|
3
14
|
class BulkUploadDataInconsistent < StandardError
|
4
15
|
def initialize(model, table_name, expected_columns, found_columns, while_doing)
|
5
16
|
super("#{model.name}: for table: #{table_name}; #{expected_columns} != #{found_columns}; #{while_doing}")
|
6
17
|
end
|
7
18
|
end
|
8
|
-
|
19
|
+
|
9
20
|
# BULK creation of many rows
|
10
21
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
# :
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# }
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# :salary => 2000,
|
44
|
-
# }]
|
45
|
-
#
|
46
|
-
# options = {
|
47
|
-
# :returning => [:id]
|
48
|
-
# }
|
49
|
-
#
|
50
|
-
# Employee.create_many(rows, options) returns [#<Employee id: 1>, #<Employee id: 2>]
|
51
|
-
#
|
52
|
-
# third example uses :slice_size option.
|
53
|
-
# Slice_size - is an integer that specifies how many
|
54
|
-
# records will be created in a single SQL query.
|
55
|
-
#
|
56
|
-
# rows = [{
|
57
|
-
# :name => 'Keith',
|
58
|
-
# :salary => 1000,
|
59
|
-
# },
|
60
|
-
# {
|
61
|
-
# :name => 'Alex',
|
62
|
-
# :salary => 2000,
|
63
|
-
# },
|
64
|
-
# {
|
65
|
-
# :name => 'Mark',
|
66
|
-
# :salary => 3000,
|
67
|
-
# }]
|
68
|
-
#
|
69
|
-
# options = {
|
70
|
-
# :slice_size => 2
|
71
|
-
# }
|
72
|
-
#
|
73
|
-
# Employee.create_many(rows, options) will generate two insert queries
|
74
|
-
#
|
22
|
+
# @example no options used
|
23
|
+
# rows = [
|
24
|
+
# { :name => 'Keith', :salary => 1000 },
|
25
|
+
# { :name => 'Alex', :salary => 2000 }
|
26
|
+
# ]
|
27
|
+
# Employee.create_many(rows)
|
28
|
+
#
|
29
|
+
# @example with :returning option to returns key value
|
30
|
+
# rows = [
|
31
|
+
# { :name => 'Keith', :salary => 1000 },
|
32
|
+
# { :name => 'Alex', :salary => 2000 }
|
33
|
+
# ]
|
34
|
+
# options = { :returning => [:id] }
|
35
|
+
# Employee.create_many(rows, options)
|
36
|
+
# [#<Employee id: 1>, #<Employee id: 2>]
|
37
|
+
#
|
38
|
+
# @example with :slice_size option (will generate two insert queries)
|
39
|
+
# rows = [
|
40
|
+
# { :name => 'Keith', :salary => 1000 },
|
41
|
+
# { :name => 'Alex', :salary => 2000 },
|
42
|
+
# { :name => 'Mark', :salary => 3000 }
|
43
|
+
# ]
|
44
|
+
# options = { :slice_size => 2 }
|
45
|
+
# Employee.create_many(rows, options)
|
46
|
+
#
|
47
|
+
# @param [Array<Hash>] rows ([]) data to be inserted into database
|
48
|
+
# @param [Hash] options ({}) options for bulk inserts
|
49
|
+
# @option options [Integer] :slice_size (1000) how many records will be created in a single SQL query
|
50
|
+
# @option options [Boolean] :check_consitency (true) ensure some modicum of sanity on the incoming dataset, specifically: does each row define the same set of key/value pairs
|
51
|
+
# @option options [Array or String] :returning (nil) list of fields to return.
|
52
|
+
# @return [Array<Hash>] rows returned from DB as option[:returning] requests
|
53
|
+
# @raise [BulkUploadDataInconsistent] raised when key/value pairs between rows are inconsistent (check disabled with option :check_consistency)
|
75
54
|
def create_many(rows, options = {})
|
76
55
|
return [] if rows.blank?
|
77
56
|
options[:slice_size] = 1000 unless options.has_key?(:slice_size)
|
@@ -127,90 +106,56 @@ module Partitioned
|
|
127
106
|
#
|
128
107
|
# BULK updates of many rows
|
129
108
|
#
|
130
|
-
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
134
|
-
# options:
|
135
|
-
#
|
136
|
-
#
|
137
|
-
#
|
138
|
-
#
|
139
|
-
#
|
140
|
-
#
|
141
|
-
#
|
142
|
-
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
# rows
|
146
|
-
# :
|
147
|
-
#
|
148
|
-
#
|
149
|
-
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
# }
|
153
|
-
#
|
154
|
-
# :
|
155
|
-
#
|
156
|
-
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
# }
|
161
|
-
#
|
162
|
-
#
|
163
|
-
#
|
164
|
-
#
|
165
|
-
#
|
166
|
-
# rows = [{
|
167
|
-
# :id => 1,
|
168
|
-
# :salary => 1000,
|
169
|
-
# :company_id => 10
|
170
|
-
# },
|
171
|
-
# {
|
172
|
-
# :id => 10,
|
173
|
-
# :salary => 2000,
|
174
|
-
# :company_id => 12
|
175
|
-
# },
|
176
|
-
# {
|
177
|
-
# :id => 23,
|
178
|
-
# :salary => 2500,
|
179
|
-
# :company_id => 5
|
180
|
-
# }]
|
181
|
-
#
|
182
|
-
# options = {
|
183
|
-
# :set_array => '"company_id = datatable.company_id"',
|
184
|
-
# :where => '"#{table_name}.salary = datatable.salary"'
|
185
|
-
# }
|
186
|
-
#
|
187
|
-
# Employee.update_many(rows, options)
|
188
|
-
#
|
189
|
-
#
|
190
|
-
# this version sets the where clause to the KEY of the hash passed in
|
191
|
-
# and the set_array is generated from the VALUES
|
192
|
-
#
|
193
|
-
# rows = {
|
194
|
-
# { :id => 1 } => {
|
195
|
-
# :salary => 100000,
|
196
|
-
# :company_id => 10
|
197
|
-
# },
|
198
|
-
# { :id => 10 } => {
|
199
|
-
# :salary => 110000,
|
200
|
-
# :company_id => 12
|
201
|
-
# },
|
202
|
-
# { :id => 23 } => {
|
203
|
-
# :salary => 90000,
|
204
|
-
# :company_id => 5
|
109
|
+
# @return [Array<Hash>] rows returned from DB as option[:returning] requests
|
110
|
+
# @raise [BulkUploadDataInconsistent] raised when key/value pairs between rows are inconsistent (check disabled with option :check_consistency)
|
111
|
+
# @param [Hash] options ({}) options for bulk inserts
|
112
|
+
# @option options [Integer] :slice_size (1000) how many records will be created in a single SQL query
|
113
|
+
# @option options [Boolean] :check_consitency (true) ensure some modicum of sanity on the incoming dataset, specifically: does each row define the same set of key/value pairs
|
114
|
+
# @option options [Array] :returning (nil) list of fields to return.
|
115
|
+
# @option options [String] :returning (nil) single field to return.
|
116
|
+
#
|
117
|
+
# @overload update_many(rows = [], options = {})
|
118
|
+
# @param [Array<Hash>] rows ([]) data to be updated
|
119
|
+
# @option options [String] :set_array (built from first row passed in) the set clause
|
120
|
+
# @option options [String] :where ('"#{table_name}.id = datatable.id"') the where clause
|
121
|
+
#
|
122
|
+
# @overload update_many(rows = {}, options = {})
|
123
|
+
# @param [Hash<Hash, Hash>] rows ({}) data to be updated
|
124
|
+
# @option options [String] :set_array (built from the values in the first key/value pair of `rows`) the set clause
|
125
|
+
# @option options [String] :where (built from the keys in the first key/value pair of `rows`) the where clause
|
126
|
+
#
|
127
|
+
# @example using "set_array" to add the value of "salary" to the specific employee's salary the default where clause matches IDs so, it works here.
|
128
|
+
# rows = [
|
129
|
+
# { :id => 1, :salary => 1000 },
|
130
|
+
# { :id => 10, :salary => 2000 },
|
131
|
+
# { :id => 23, :salary => 2500 }
|
132
|
+
# ]
|
133
|
+
# options = { :set_array => '"salary = datatable.salary"' }
|
134
|
+
# Employee.update_many(rows, options)
|
135
|
+
#
|
136
|
+
# @example using where clause to match salary.
|
137
|
+
# rows = [
|
138
|
+
# { :id => 1, :salary => 1000, :company_id => 10 },
|
139
|
+
# { :id => 10, :salary => 2000, :company_id => 12 },
|
140
|
+
# { :id => 23, :salary => 2500, :company_id => 5 }
|
141
|
+
# ]
|
142
|
+
# options = {
|
143
|
+
# :set_array => '"company_id = datatable.company_id"',
|
144
|
+
# :where => '"#{table_name}.salary = datatable.salary"'
|
205
145
|
# }
|
206
|
-
#
|
146
|
+
# Employee.update_many(rows, options)
|
207
147
|
#
|
208
|
-
#
|
148
|
+
# @example setting where clause to the KEY of the hash passed in and the set_array is generated from the VALUES
|
149
|
+
# rows = {
|
150
|
+
# { :id => 1 } => { :salary => 100000, :company_id => 10 },
|
151
|
+
# { :id => 10 } => { :salary => 110000, :company_id => 12 },
|
152
|
+
# { :id => 23 } => { :salary => 90000, :company_id => 5 }
|
153
|
+
# }
|
154
|
+
# Employee.update_many(rows)
|
209
155
|
#
|
210
|
-
# Remember that you should probably set updated_at using "updated = datatable.updated_at"
|
211
|
-
#
|
212
|
-
#
|
213
|
-
|
156
|
+
# @note Remember that you should probably set updated_at using "updated = datatable.updated_at"
|
157
|
+
# or "updated_at = now()" in the set_array if you want to follow
|
158
|
+
# the standard active record model for time columns (and you have an updated_at column)
|
214
159
|
def update_many(rows, options = {})
|
215
160
|
return [] if rows.blank?
|
216
161
|
if rows.is_a?(Hash)
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Partitioned
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# Partition tables by created_at grouping them by week, with
|
4
4
|
# a week defined as seven days starting on Monday.
|
5
5
|
#
|
6
6
|
class ByCreatedAt < ByWeeklyTimeField
|
7
7
|
self.abstract_class = true
|
8
8
|
|
9
|
+
# the field to partition on, `created_at`
|
10
|
+
# @return [Symbol] the partition field: `created_at`
|
9
11
|
def self.partition_time_field
|
10
12
|
return :created_at
|
11
13
|
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
module Partitioned
|
2
|
+
# Partitioned abstract class for all partitioned models based as a single integer field value that is used as a foreign key
|
2
3
|
class ByForeignKey < ByIntegerField
|
3
4
|
self.abstract_class = true
|
4
5
|
|
6
|
+
# the field to partition on
|
7
|
+
# @return [Integer] re-routed to {#self.partition_foreign_key}
|
5
8
|
def self.partition_integer_field
|
6
9
|
return partition_foreign_key
|
7
10
|
end
|
8
11
|
|
12
|
+
# the field to partition on
|
13
|
+
# @return [String] the name of the foreign key field
|
9
14
|
def self.partition_foreign_key
|
10
15
|
raise MethodNotImplemented.new(self, :partition_foreign_key)
|
11
16
|
end
|
data/lib/partitioned/by_id.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Partitioned
|
2
2
|
#
|
3
|
-
#
|
4
|
-
# the value of its primary key.
|
3
|
+
# Table partitioning by id. this partitioning breaks up data by
|
4
|
+
# the value of its primary key. A specific record's child table
|
5
5
|
# is determined by the number resulting from the integer math:
|
6
6
|
# ID / ById::partition_table_size * ById::partition_table_size
|
7
7
|
#
|
@@ -9,21 +9,27 @@ module Partitioned
|
|
9
9
|
self.abstract_class = true
|
10
10
|
|
11
11
|
#
|
12
|
-
#
|
12
|
+
# Specific to this partitioning, we need to prefetch the primary key (id)
|
13
13
|
# before we attempt to do the insert because the insert wants to know the
|
14
14
|
# name of the specific child table to access.
|
15
15
|
#
|
16
|
+
# @return [Boolean] true
|
16
17
|
def self.prefetch_primary_key?
|
17
18
|
return true
|
18
19
|
end
|
19
20
|
|
20
21
|
#
|
21
|
-
#
|
22
|
+
# The number of records in each child table.
|
22
23
|
#
|
24
|
+
# @return [Integer] the number of rows in a partition
|
23
25
|
def self.partition_table_size
|
24
26
|
return 10000000
|
25
27
|
end
|
26
28
|
|
29
|
+
#
|
30
|
+
# The name of the field to partition on
|
31
|
+
#
|
32
|
+
# @return [String] the name of the field to partition on
|
27
33
|
def self.partition_integer_field
|
28
34
|
return :id
|
29
35
|
end
|
@@ -1,15 +1,24 @@
|
|
1
1
|
module Partitioned
|
2
|
+
#
|
3
|
+
# Partitioned abstract class for all partitioned models based as a single integer field value.
|
4
|
+
#
|
2
5
|
class ByIntegerField < PartitionedBase
|
3
6
|
self.abstract_class = true
|
4
7
|
|
8
|
+
# the size of each table
|
9
|
+
# @return [Integer] how many different values are in each partition
|
5
10
|
def self.partition_table_size
|
6
11
|
return 1
|
7
12
|
end
|
8
13
|
|
14
|
+
# the name of the partition key field
|
15
|
+
# @return [String] the name of the field
|
9
16
|
def self.partition_integer_field
|
10
17
|
raise MethodNotImplemented.new(self, :partition_integer_field)
|
11
18
|
end
|
12
19
|
|
20
|
+
# the normalized key value for a given key value
|
21
|
+
# @return [Integer] the normalized value
|
13
22
|
def self.partition_normalize_key_value(integer_field_value)
|
14
23
|
return integer_field_value / partition_table_size * partition_table_size
|
15
24
|
end
|
@@ -1,15 +1,22 @@
|
|
1
1
|
module Partitioned
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# Partition tables by a time field grouping them by week, with
|
4
4
|
# a week defined as seven days starting on Monday.
|
5
5
|
#
|
6
6
|
class ByMonthlyTimeField < ByTimeField
|
7
7
|
self.abstract_class = true
|
8
8
|
|
9
|
+
# Normalize a partition key value by month.
|
10
|
+
#
|
11
|
+
# @param [Time] time_value the time value to normalize
|
12
|
+
# @return [Time] the value normalized
|
9
13
|
def self.partition_normalize_key_value(time_value)
|
10
14
|
return time_value.at_beginning_of_month
|
11
15
|
end
|
12
16
|
|
17
|
+
# The size of the partition table, a month
|
18
|
+
#
|
19
|
+
# @return [Integer] the size of this partition
|
13
20
|
def self.partition_table_size
|
14
21
|
return 1.month
|
15
22
|
end
|
@@ -1,16 +1,20 @@
|
|
1
1
|
module Partitioned
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# Partition tables by a time field grouping them by day.
|
4
4
|
#
|
5
5
|
class ByTimeField < PartitionedBase
|
6
6
|
self.abstract_class = true
|
7
7
|
|
8
8
|
#
|
9
|
-
#
|
10
|
-
# start_date and end_date skipping step
|
9
|
+
# Generate an enumerable that represents all the dates between
|
10
|
+
# start_date and end_date skipping step.
|
11
11
|
#
|
12
|
-
#
|
12
|
+
# This can be used to calls that take an enumerable like create_infrastructure.
|
13
13
|
#
|
14
|
+
# @param [Date] start_date the first date to generate the range from
|
15
|
+
# @param [Date] end_date the last date to generate the range from
|
16
|
+
# @param [Object] step (:default) number of values to advance (:default means use {#self.partition_table_size}).
|
17
|
+
# @return [Enumerable] the range generated
|
14
18
|
def self.partition_generate_range(start_date, end_date, step = :default)
|
15
19
|
step = partition_table_size if step == :default
|
16
20
|
current_date = partition_normalize_key_value(start_date)
|
@@ -23,23 +27,27 @@ module Partitioned
|
|
23
27
|
end
|
24
28
|
|
25
29
|
#
|
26
|
-
#
|
30
|
+
# Normalize the value to the current day.
|
27
31
|
#
|
32
|
+
# @param [Time] time_value the partitioned key value
|
33
|
+
# @return [Time] time_value normalized
|
28
34
|
def self.partition_normalize_key_value(time_value)
|
29
35
|
return time_value.to_date
|
30
36
|
end
|
31
37
|
|
32
38
|
#
|
33
|
-
#
|
39
|
+
# The size of the partition, 1.day
|
34
40
|
#
|
41
|
+
# @return [Integer] the size of the partition
|
35
42
|
def self.partition_table_size
|
36
43
|
return 1.day
|
37
44
|
end
|
38
45
|
|
39
46
|
#
|
40
|
-
#
|
41
|
-
#
|
47
|
+
# Abstract -- implement in a derived class.
|
48
|
+
# The name of the time-related field we will use to partition child tables.
|
42
49
|
#
|
50
|
+
# @raise MethodNotImplemented
|
43
51
|
def self.partition_time_field
|
44
52
|
raise MethodNotImplemented.new(self, :partition_time_field)
|
45
53
|
end
|
@@ -1,15 +1,17 @@
|
|
1
1
|
module Partitioned
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# Partition tables by a time field grouping them by week, with
|
4
4
|
# a week defined as seven days starting on Monday.
|
5
5
|
#
|
6
6
|
class ByWeeklyTimeField < ByTimeField
|
7
7
|
self.abstract_class = true
|
8
8
|
|
9
9
|
#
|
10
|
-
#
|
11
|
-
# the
|
10
|
+
# Normalize a partition key value by week. We've picked
|
11
|
+
# the beginning of the week to key on, which is Monday.
|
12
12
|
#
|
13
|
+
# @param [Time] time_value the time value to normalize
|
14
|
+
# @return [Time] the value normalized
|
13
15
|
def self.partition_normalize_key_value(time_value)
|
14
16
|
return time_value.at_beginning_of_week
|
15
17
|
end
|
@@ -17,6 +19,7 @@ module Partitioned
|
|
17
19
|
#
|
18
20
|
# The size of the partition table, 7 days (1.week)
|
19
21
|
#
|
22
|
+
# @return [Integer] the size of this partition
|
20
23
|
def self.partition_table_size
|
21
24
|
return 1.week
|
22
25
|
end
|
@@ -1,7 +1,15 @@
|
|
1
1
|
module Partitioned
|
2
2
|
class MultiLevel
|
3
|
+
#
|
4
|
+
# Configuration manager for multi-level partitioning
|
5
|
+
# it supports, the front-end UI (a Domain Specific Language) using {Dsl}
|
6
|
+
# state using {Data}
|
7
|
+
# and a parser using {Reader}
|
8
|
+
#
|
3
9
|
module Configurator
|
10
|
+
# The Domain Specific Language UI manager for multi level partitioning classes
|
4
11
|
class Dsl < Partitioned::PartitionedBase::Configurator::Dsl
|
12
|
+
# used to prevent use of invalid directives
|
5
13
|
class InvalidForMultiLevelPartitioning < StandardError
|
6
14
|
def initialize(model, dsl_key, remedy)
|
7
15
|
super("#{model.name}: '#{dsl_key}' is not valid for multi-level partitioning. #{remedy}")
|
@@ -18,10 +26,13 @@ module Partitioned
|
|
18
26
|
#
|
19
27
|
# Definition of classes which will be used at multi level partitioning.
|
20
28
|
#
|
29
|
+
# @param [*Array<Class>] classes the classes, in order, used to partition this model
|
30
|
+
# @return [optional]
|
21
31
|
def using_classes(*classes)
|
22
32
|
data.using_classes += [*classes]
|
23
33
|
end
|
24
34
|
|
35
|
+
# @raise [InvalidForMultiLevelPartitioning] always raised. `on` is not a valid DSL directive for multi-level partitioning
|
25
36
|
def on(*ignored)
|
26
37
|
raise InvalidForMultiLevelPartitioning.new(model, :on, "the partitioned keyword 'using' is used to define multi-level partitioned tables.")
|
27
38
|
end
|
@@ -1,8 +1,13 @@
|
|
1
1
|
module Partitioned
|
2
2
|
class MultiLevel
|
3
3
|
module Configurator
|
4
|
+
# coalesces and parses all {Data} objects allowing the
|
5
|
+
# {PartitionManager} to request partitioning information froma
|
6
|
+
# centralized source from multi level partitioned models
|
4
7
|
class Reader < Partitioned::PartitionedBase::Configurator::Reader
|
8
|
+
# configurator for a specific class level
|
5
9
|
UsingConfigurator = Struct.new(:model, :sliced_class, :dsl)
|
10
|
+
|
6
11
|
def initialize(most_derived_activerecord_class)
|
7
12
|
super
|
8
13
|
@using_classes = nil
|
@@ -12,6 +17,7 @@ module Partitioned
|
|
12
17
|
#
|
13
18
|
# The field used to partition child tables.
|
14
19
|
#
|
20
|
+
# @return [Array<Symbol>] fields used to partition this model
|
15
21
|
def on_fields
|
16
22
|
unless @on_fields
|
17
23
|
@on_fields = using_collect(&:on_field).map(&:to_sym)
|
@@ -72,10 +78,22 @@ module Partitioned
|
|
72
78
|
return parts.join('_')
|
73
79
|
end
|
74
80
|
|
81
|
+
# retrieve a specific configurator from an ordered list. for multi-level partitioning
|
82
|
+
# we need to find the specific configurator for the partitioning level we are interested
|
83
|
+
# in managing.
|
84
|
+
#
|
85
|
+
# @param [Integer] index the partitioning level to query
|
86
|
+
# @return [Configurator] the configurator for the specific level queried
|
75
87
|
def using_configurator(index)
|
76
88
|
return using_class(index).configurator
|
77
89
|
end
|
78
90
|
|
91
|
+
# retrieve a specific partitioning class from an ordered list. for multi-level partitioning
|
92
|
+
# we need to find the specific {Partitioned::PartitionedBase} class for the partitioning level we are interested
|
93
|
+
# in managing.
|
94
|
+
#
|
95
|
+
# @param [Integer] index the partitioning level to query
|
96
|
+
# @return [{Partitioned::PartitionedBase}] the class for the specific level queried
|
79
97
|
def using_class(index)
|
80
98
|
return using_classes[index]
|
81
99
|
end
|
@@ -1,10 +1,15 @@
|
|
1
1
|
module Partitioned
|
2
2
|
class MultiLevel
|
3
|
+
#
|
4
|
+
# the manger of partitioned requests for models partitioned multiple times
|
5
|
+
#
|
3
6
|
class PartitionManager < Partitioned::PartitionedBase::PartitionManager
|
4
7
|
#
|
5
|
-
#
|
8
|
+
# The once called function to prepare a parent table for partitioning as well
|
6
9
|
# as create the schema that the child tables will be placed in.
|
7
10
|
#
|
11
|
+
# @param [Enumerable] enumerable (Array<Array>) the key values that should be used to create the parent partition tables.
|
12
|
+
# @return [optional]
|
8
13
|
def create_infrastructure(enumerable = [[]])
|
9
14
|
super()
|
10
15
|
enumerable.each do |*partition_key_values|
|
@@ -15,11 +20,13 @@ module Partitioned
|
|
15
20
|
protected
|
16
21
|
|
17
22
|
#
|
18
|
-
#
|
23
|
+
# Create a specific child table that does not currently
|
19
24
|
# exist and whose schema (the schema that the table exists in)
|
20
25
|
# also already exists (#create_infrastructure is designed to
|
21
26
|
# create this).
|
22
27
|
#
|
28
|
+
# @param [*Array<Object>] partition_key_values all key values needed to create a partition
|
29
|
+
# @return [optional]
|
23
30
|
def create_new_partition(*partition_key_values)
|
24
31
|
create_partition_table(*partition_key_values)
|
25
32
|
if is_leaf_partition?(*partition_key_values)
|
@@ -31,14 +38,16 @@ module Partitioned
|
|
31
38
|
end
|
32
39
|
|
33
40
|
#
|
34
|
-
#
|
41
|
+
# Is the table a child table without itself having any children.
|
35
42
|
# generally leaf tables are where all indexes and foreign key
|
36
43
|
# constraints will be placed because that is where the data will be.
|
37
44
|
#
|
38
45
|
# Non leaf tables will typically have a rule placed on them
|
39
|
-
# (via add_parent_table_rules) that prevents any inserts from
|
46
|
+
# (via add_parent_table_rules) that prevents any inserts from occurring
|
40
47
|
# on them.
|
41
48
|
#
|
49
|
+
# @param [*Array<Object>] partition_key_values all key values specifying a given child table
|
50
|
+
# @return [Boolean] true if this partition should contain records
|
42
51
|
def is_leaf_partition?(*partition_key_values)
|
43
52
|
return partition_key_values.length == parent_table_class.configurator.on_fields.length
|
44
53
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Partitioned
|
2
2
|
#
|
3
|
-
#
|
3
|
+
# Table partitioning by a referenced id column which itself is partitioned
|
4
4
|
# further weekly by a date column.
|
5
5
|
#
|
6
6
|
class MultiLevel < PartitionedBase
|
@@ -9,6 +9,8 @@ module Partitioned
|
|
9
9
|
#
|
10
10
|
# Normalize the values for the each of using class.
|
11
11
|
#
|
12
|
+
# @param [Array<Object>] value the partition key values
|
13
|
+
# @return [Array<Object>] the normalized values for the key values passed in
|
12
14
|
def self.partition_normalize_key_value(values)
|
13
15
|
normalized_values = []
|
14
16
|
[*values].each_with_index do |value,index|
|
@@ -2,9 +2,10 @@ module Partitioned
|
|
2
2
|
class PartitionedBase
|
3
3
|
module Configurator
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# The state configured by the Dsl and read by Reader.
|
6
6
|
#
|
7
7
|
class Data
|
8
|
+
# represents a SQL index
|
8
9
|
class Index
|
9
10
|
attr_accessor :field, :options
|
10
11
|
def initialize(field, options = {})
|
@@ -12,6 +13,7 @@ module Partitioned
|
|
12
13
|
@options = options
|
13
14
|
end
|
14
15
|
end
|
16
|
+
# represents a SQL foreign key reference
|
15
17
|
class ForeignKey
|
16
18
|
attr_accessor :referencing_field, :referenced_table, :referenced_field
|
17
19
|
def initialize(referencing_field, referenced_table = nil, referenced_field = :id)
|
@@ -24,6 +26,13 @@ module Partitioned
|
|
24
26
|
@referenced_field = referenced_field
|
25
27
|
end
|
26
28
|
|
29
|
+
#
|
30
|
+
# Produce a table name from the name of the foreign key. in rails, this really
|
31
|
+
# means "foo_id" should be mapped to "foos", and "company_id" should be mapped to
|
32
|
+
# "companies"
|
33
|
+
#
|
34
|
+
# @param [String] foreign_key_field the name of the foreign key field
|
35
|
+
# @return [String] the name of the table associated with the foreign key
|
27
36
|
def self.foreign_key_to_foreign_table_name(foreign_key_field)
|
28
37
|
return ActiveSupport::Inflector::pluralize(foreign_key_field.to_s.sub(/_id$/,''))
|
29
38
|
end
|