partitioned 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -0
- data/LICENSE +30 -0
- data/PARTITIONING_EXPLAINED.txt +351 -0
- data/README +111 -0
- data/Rakefile +27 -0
- data/examples/README +23 -0
- data/examples/company_id.rb +417 -0
- data/examples/company_id_and_created_at.rb +689 -0
- data/examples/created_at.rb +590 -0
- data/examples/created_at_referencing_awards.rb +1000 -0
- data/examples/id.rb +475 -0
- data/examples/lib/by_company_id.rb +11 -0
- data/examples/lib/command_line_tool_mixin.rb +71 -0
- data/examples/lib/company.rb +29 -0
- data/examples/lib/get_options.rb +44 -0
- data/examples/lib/roman.rb +41 -0
- data/examples/start_date.rb +621 -0
- data/init.rb +1 -0
- data/lib/monkey_patch_activerecord.rb +92 -0
- data/lib/monkey_patch_postgres.rb +73 -0
- data/lib/partitioned.rb +26 -0
- data/lib/partitioned/active_record_overrides.rb +34 -0
- data/lib/partitioned/bulk_methods_mixin.rb +288 -0
- data/lib/partitioned/by_created_at.rb +13 -0
- data/lib/partitioned/by_foreign_key.rb +21 -0
- data/lib/partitioned/by_id.rb +35 -0
- data/lib/partitioned/by_integer_field.rb +32 -0
- data/lib/partitioned/by_monthly_time_field.rb +23 -0
- data/lib/partitioned/by_time_field.rb +65 -0
- data/lib/partitioned/by_weekly_time_field.rb +30 -0
- data/lib/partitioned/multi_level.rb +20 -0
- data/lib/partitioned/multi_level/configurator/data.rb +14 -0
- data/lib/partitioned/multi_level/configurator/dsl.rb +32 -0
- data/lib/partitioned/multi_level/configurator/reader.rb +162 -0
- data/lib/partitioned/multi_level/partition_manager.rb +47 -0
- data/lib/partitioned/partitioned_base.rb +354 -0
- data/lib/partitioned/partitioned_base/configurator.rb +6 -0
- data/lib/partitioned/partitioned_base/configurator/data.rb +62 -0
- data/lib/partitioned/partitioned_base/configurator/dsl.rb +628 -0
- data/lib/partitioned/partitioned_base/configurator/reader.rb +209 -0
- data/lib/partitioned/partitioned_base/partition_manager.rb +138 -0
- data/lib/partitioned/partitioned_base/sql_adapter.rb +286 -0
- data/lib/partitioned/version.rb +3 -0
- data/lib/tasks/desirable_tasks.rake +4 -0
- data/partitioned.gemspec +21 -0
- data/spec/dummy/.rspec +1 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +9 -0
- data/spec/dummy/app/assets/stylesheets/application.css +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +51 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +32 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +30 -0
- data/spec/dummy/config/environments/production.rb +60 -0
- data/spec/dummy/config/environments/test.rb +39 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/spec/spec_helper.rb +27 -0
- data/spec/monkey_patch_posgres_spec.rb +176 -0
- data/spec/partitioned/bulk_methods_mixin_spec.rb +512 -0
- data/spec/partitioned/by_created_at_spec.rb +62 -0
- data/spec/partitioned/by_foreign_key_spec.rb +95 -0
- data/spec/partitioned/by_id_spec.rb +97 -0
- data/spec/partitioned/by_integer_field_spec.rb +143 -0
- data/spec/partitioned/by_monthly_time_field_spec.rb +100 -0
- data/spec/partitioned/by_time_field_spec.rb +182 -0
- data/spec/partitioned/by_weekly_time_field_spec.rb +100 -0
- data/spec/partitioned/multi_level/configurator/dsl_spec.rb +88 -0
- data/spec/partitioned/multi_level/configurator/reader_spec.rb +147 -0
- data/spec/partitioned/partitioned_base/configurator/dsl_spec.rb +459 -0
- data/spec/partitioned/partitioned_base/configurator/reader_spec.rb +513 -0
- data/spec/partitioned/partitioned_base/sql_adapter_spec.rb +204 -0
- data/spec/partitioned/partitioned_base_spec.rb +173 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/support/shared_example_spec_helper_for_integer_key.rb +137 -0
- data/spec/support/shared_example_spec_helper_for_time_key.rb +147 -0
- data/spec/support/tables_spec_helper.rb +47 -0
- metadata +250 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class PartitionedBase
|
3
|
+
module Configurator
|
4
|
+
class Reader
|
5
|
+
attr_reader :model
|
6
|
+
|
7
|
+
def initialize(most_derived_activerecord_class)
|
8
|
+
@model = most_derived_activerecord_class
|
9
|
+
@configurators = nil
|
10
|
+
@on_fields = nil
|
11
|
+
@indexes = nil
|
12
|
+
@foreign_keys = nil
|
13
|
+
@check_constraint = nil
|
14
|
+
|
15
|
+
@schema_name = nil
|
16
|
+
@name_prefix = nil
|
17
|
+
@base_name = nil
|
18
|
+
@part_name = nil
|
19
|
+
|
20
|
+
@table_name = nil
|
21
|
+
|
22
|
+
@parent_table_schema_name = nil
|
23
|
+
@parent_table_name = nil
|
24
|
+
|
25
|
+
@encoded_name = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# The name of the schema that will contain all child tables.
|
30
|
+
#
|
31
|
+
def schema_name
|
32
|
+
unless @schema_name
|
33
|
+
@schema_name = collect_first(&:schema_name)
|
34
|
+
end
|
35
|
+
return @schema_name
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# The field used to partition child tables.
|
40
|
+
#
|
41
|
+
def on_fields
|
42
|
+
unless @on_fields
|
43
|
+
@on_fields = collect(&:on_field).map(&:to_sym)
|
44
|
+
end
|
45
|
+
return @on_fields
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Define an index to be created on all (leaf-) child tables.
|
50
|
+
#
|
51
|
+
def indexes(*partition_key_values)
|
52
|
+
return collect_from_collection(*partition_key_values, &:indexes).inject({}) do |bag, data_index|
|
53
|
+
bag[data_index.field] = (data_index.options || {}) unless data_index.blank?
|
54
|
+
bag
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Define a foreign key on a (leaf-) child table.
|
60
|
+
#
|
61
|
+
def foreign_keys(*partition_key_values)
|
62
|
+
return collect_from_collection(*partition_key_values, &:foreign_keys).inject(Set.new) do |set,new_items|
|
63
|
+
if new_items.is_a? Array
|
64
|
+
set += new_items
|
65
|
+
else
|
66
|
+
set += [new_items]
|
67
|
+
end
|
68
|
+
set
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Define the check constraint for a given child table.
|
74
|
+
#
|
75
|
+
def check_constraint(*partition_key_values)
|
76
|
+
return collect_first(*partition_key_values, &:check_constraint)
|
77
|
+
end
|
78
|
+
|
79
|
+
#
|
80
|
+
# The table name of the table who is the direct ancestor of a child table.
|
81
|
+
#
|
82
|
+
def parent_table_name(*partition_key_values)
|
83
|
+
return collect_first(*partition_key_values, &:parent_table_name)
|
84
|
+
end
|
85
|
+
|
86
|
+
#
|
87
|
+
# The schema name of the table who is the direct ancestor of a child table.
|
88
|
+
#
|
89
|
+
def parent_table_schema_name(*partition_key_values)
|
90
|
+
return collect_first(*partition_key_values, &:parent_table_schema_name)
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
# The full name of a child table defined by the partition key values.
|
95
|
+
#
|
96
|
+
def table_name(*partition_key_values)
|
97
|
+
return collect_first(*partition_key_values, &:table_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# The name of the child table without the schema name or name prefix.
|
102
|
+
#
|
103
|
+
def base_name(*partition_key_values)
|
104
|
+
return collect_first(*partition_key_values, &:base_name)
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# The prefix for the child table's name.
|
109
|
+
#
|
110
|
+
def name_prefix
|
111
|
+
unless @name_prefix
|
112
|
+
@name_prefix = collect_first(&:name_prefix)
|
113
|
+
end
|
114
|
+
return @name_prefix
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
# The child tables name without the schema name.
|
119
|
+
#
|
120
|
+
def part_name(*partition_key_values)
|
121
|
+
return collect_first(*partition_key_values, &:part_name)
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Define the order by clause used to list all child table names in order
|
126
|
+
# of "last to be used" to "oldest to have been used".
|
127
|
+
#
|
128
|
+
def last_partitions_order_by_clause
|
129
|
+
unless @last_partitions_order_by_clause
|
130
|
+
@last_partitions_order_by_clause = collect_first(&:last_partitions_order_by_clause)
|
131
|
+
end
|
132
|
+
return @last_partitions_order_by_clause
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
protected
|
137
|
+
|
138
|
+
def configurators
|
139
|
+
unless @configurators
|
140
|
+
@configurators = []
|
141
|
+
model.ancestors.each do |ancestor|
|
142
|
+
if ancestor.respond_to?(:configurator_dsl)
|
143
|
+
if ancestor::configurator_dsl
|
144
|
+
@configurators << ancestor::configurator_dsl
|
145
|
+
end
|
146
|
+
end
|
147
|
+
break if ancestor == Partitioned::PartitionedBase
|
148
|
+
end
|
149
|
+
end
|
150
|
+
return @configurators
|
151
|
+
end
|
152
|
+
|
153
|
+
def collect(*partition_key_values, &block)
|
154
|
+
values = []
|
155
|
+
configurators.each do |configurator|
|
156
|
+
data = configurator.data
|
157
|
+
|
158
|
+
intermediate_value = block.call(data) rescue nil
|
159
|
+
if intermediate_value.is_a? Proc
|
160
|
+
values << intermediate_value.call(model, *partition_key_values)
|
161
|
+
elsif intermediate_value.is_a? String
|
162
|
+
field_value = partition_key_values.first
|
163
|
+
values << eval("\"#{intermediate_value}\"")
|
164
|
+
else
|
165
|
+
values << intermediate_value unless intermediate_value.blank?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
return values
|
169
|
+
end
|
170
|
+
|
171
|
+
def collect_first(*partition_key_values, &block)
|
172
|
+
configurators.each do |configurator|
|
173
|
+
data = configurator.data
|
174
|
+
intermediate_value = block.call(data) rescue nil
|
175
|
+
if intermediate_value.is_a? Proc
|
176
|
+
return intermediate_value.call(model, *partition_key_values)
|
177
|
+
elsif intermediate_value.is_a? String
|
178
|
+
field_value = partition_key_values.first
|
179
|
+
return eval("\"#{intermediate_value}\"")
|
180
|
+
else
|
181
|
+
return intermediate_value unless intermediate_value.nil?
|
182
|
+
end
|
183
|
+
end
|
184
|
+
return nil
|
185
|
+
end
|
186
|
+
|
187
|
+
def collect_from_collection(*partition_key_values, &block)
|
188
|
+
values = []
|
189
|
+
configurators.each do |configurator|
|
190
|
+
data = configurator.data
|
191
|
+
intermediate_values = []
|
192
|
+
intermediate_values = block.call(data) rescue nil
|
193
|
+
[*intermediate_values].each do |intermediate_value|
|
194
|
+
if intermediate_value.is_a? Proc
|
195
|
+
values << intermediate_value.call(model, *partition_key_values)
|
196
|
+
elsif intermediate_value.is_a? String
|
197
|
+
field_value = partition_key_values.first
|
198
|
+
values << eval("\"#{intermediate_value}\"")
|
199
|
+
else
|
200
|
+
values << intermediate_value unless intermediate_value.blank?
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
return values
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class PartitionedBase
|
3
|
+
#
|
4
|
+
# PartitionManager
|
5
|
+
# interface for all requests made to build partition tables.
|
6
|
+
# these are typically delegated to us from the ActiveRecord class
|
7
|
+
# (partitioned_base.rb defines the forwarding)
|
8
|
+
class PartitionManager
|
9
|
+
attr_reader :parent_table_class
|
10
|
+
|
11
|
+
def initialize(parent_table_class)
|
12
|
+
@parent_table_class = parent_table_class
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# drop partitions that are no longer necessary.
|
17
|
+
# uses #old_partition_key_values_set as the list of
|
18
|
+
# partitions to remove.
|
19
|
+
#
|
20
|
+
def drop_old_partitions
|
21
|
+
old_partition_key_values_set.each do |*partition_key_values|
|
22
|
+
drop_old_partition(*partition_key_values)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# create partitions that are needed (probably to handle data that
|
28
|
+
# will be inserted into the database within the next few weeks).
|
29
|
+
# uses #new_partition_key_value_set to determine the key values
|
30
|
+
# for the specific child tables to create.
|
31
|
+
#
|
32
|
+
def create_new_partitions
|
33
|
+
new_partition_key_values_set.each do |*partition_key_values|
|
34
|
+
create_new_partition(*partition_key_values)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#
|
39
|
+
# create any partition tables from a list. the partition tables must
|
40
|
+
# not already exist and its schema must already exist.
|
41
|
+
#
|
42
|
+
def create_new_partition_tables(enumerable)
|
43
|
+
enumerable.each do |partition_key_values|
|
44
|
+
create_new_partition(*partition_key_values)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# the once called function to prepare a parent table for partitioning as well
|
50
|
+
# as create the schema that the child tables will be placed in.
|
51
|
+
#
|
52
|
+
def create_infrastructure
|
53
|
+
create_partition_schema
|
54
|
+
add_parent_table_rules
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
#
|
59
|
+
# an array of key values (each key value is an array of keys) that represent
|
60
|
+
# the child partitions that should be created.
|
61
|
+
#
|
62
|
+
# used by #create_new_partitions and generally called once a day to update
|
63
|
+
# the database with new soon-to-be needed child tables
|
64
|
+
#
|
65
|
+
# typically overridden by the concrete class as this is pure business logic
|
66
|
+
#
|
67
|
+
def new_partition_key_values_set
|
68
|
+
[]
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# an array of key values (each key value is an array of keys) that represent
|
73
|
+
# the child partitions that should be dropped because they are no longer needed.
|
74
|
+
#
|
75
|
+
# used by #drop_old_partitions and generally called once a day to clean up
|
76
|
+
# unneeded child tables
|
77
|
+
#
|
78
|
+
# typically overridden by the concrete class as this is pure business logic
|
79
|
+
#
|
80
|
+
def old_partition_key_values_set
|
81
|
+
[]
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# remove a specific partition from the database given
|
86
|
+
# the key value(s) of its check constraint columns
|
87
|
+
#
|
88
|
+
def drop_old_partition(*partition_key_values)
|
89
|
+
drop_partition_table(*partition_key_values)
|
90
|
+
end
|
91
|
+
|
92
|
+
#
|
93
|
+
# create a specific child table that does not currently
|
94
|
+
# exist and whose schema (the schema that the table exists in)
|
95
|
+
# also already exists (#create_infrastructure is designed to
|
96
|
+
# create this).
|
97
|
+
#
|
98
|
+
def create_new_partition(*partition_key_values)
|
99
|
+
create_partition_table(*partition_key_values)
|
100
|
+
add_partition_table_index(*partition_key_values)
|
101
|
+
add_references_to_partition_table(*partition_key_values)
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# :method: drop_partition_table
|
106
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#drop_partition_table
|
107
|
+
|
108
|
+
##
|
109
|
+
# :method: create_partition_table
|
110
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#create_partition_table
|
111
|
+
|
112
|
+
##
|
113
|
+
# :method: add_partition_table_index
|
114
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#add_partition_table_index
|
115
|
+
|
116
|
+
##
|
117
|
+
# :method: add_references_to_partition_table
|
118
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#add_references_to_partition_table
|
119
|
+
|
120
|
+
##
|
121
|
+
# :method: create_partition_schema
|
122
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#create_partition_schema
|
123
|
+
|
124
|
+
##
|
125
|
+
# :method: add_parent_table_rules
|
126
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#add_parent_table_rules
|
127
|
+
|
128
|
+
##
|
129
|
+
# :method: partition_table_name
|
130
|
+
# delegated to Partitioned::PartitionedBase::PartitionManager::SqlAdapter#partition_table_name
|
131
|
+
|
132
|
+
extend Forwardable
|
133
|
+
def_delegators :parent_table_class, :sql_adapter
|
134
|
+
def_delegators :sql_adapter, :drop_partition_table, :create_partition_table, :add_partition_table_index,
|
135
|
+
:add_references_to_partition_table, :create_partition_schema, :add_parent_table_rules, :partition_table_name
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,286 @@
|
|
1
|
+
module Partitioned
|
2
|
+
class PartitionedBase
|
3
|
+
#
|
4
|
+
# SqlAdapter
|
5
|
+
# manages requests of partitioned tables.
|
6
|
+
#
|
7
|
+
class SqlAdapter
|
8
|
+
attr_reader :parent_table_class
|
9
|
+
|
10
|
+
def initialize(parent_table_class)
|
11
|
+
@parent_table_class = parent_table_class
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# ensure our function for warning about improper partition usage is in place
|
16
|
+
#
|
17
|
+
# Name: always_fail_on_insert(text); Type: FUNCTION; Schema: public
|
18
|
+
#
|
19
|
+
# Used to raise an exception explaining why a specific insert (into a parent
|
20
|
+
# table which should never have records) should never be attempted.
|
21
|
+
#
|
22
|
+
def ensure_always_fail_on_insert_exists
|
23
|
+
sql = <<-SQL
|
24
|
+
CREATE OR REPLACE FUNCTION always_fail_on_insert(table_name text) RETURNS boolean
|
25
|
+
LANGUAGE plpgsql
|
26
|
+
AS $$
|
27
|
+
BEGIN
|
28
|
+
RAISE EXCEPTION 'partitioned table "%" does not support direct inserts, you should be inserting directly into child tables', table_name;
|
29
|
+
RETURN false;
|
30
|
+
END;
|
31
|
+
$$;
|
32
|
+
SQL
|
33
|
+
execute(sql)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Child tables whose parent table is 'foos', typically exist in a schema named foos_partitions.
|
38
|
+
#
|
39
|
+
# *partition_key_values are needed here to support the use of multiple schemas to keep tables in.
|
40
|
+
#
|
41
|
+
def create_partition_schema(*partition_key_values)
|
42
|
+
create_schema(configurator.schema_name, :unless_exists => true)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# does a specific child partition exist
|
47
|
+
#
|
48
|
+
def partition_exists?(*partition_key_values)
|
49
|
+
return find(:first,
|
50
|
+
:from => "pg_tables",
|
51
|
+
:select => "count(*) as count",
|
52
|
+
:conditions => ["schemaname = ? and tablename = ?",
|
53
|
+
configurator.schema_name,
|
54
|
+
configurator.part_name(*partition_key_values)
|
55
|
+
]).count.to_i == 1
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# returns an array of partition table names from last to first limited to
|
60
|
+
# the number of entries requested by its first parameter.
|
61
|
+
#
|
62
|
+
# the magic here is in the overridden method "last_n_partitions_order_by_clause"
|
63
|
+
# which is designed to order a list of partition table names (table names without
|
64
|
+
# their schema name) from last to first.
|
65
|
+
#
|
66
|
+
# if the child table names are the format "pYYYYMMDD" where YYYY is a four digit year, MM is
|
67
|
+
# a month number and DD is a day number, you would use the following to order from last to
|
68
|
+
# first:
|
69
|
+
# tablename desc
|
70
|
+
#
|
71
|
+
# for child table names of the format "pXXXX" where XXXX is a number, you may want something like:
|
72
|
+
# substring(tablename, 2)::integer desc
|
73
|
+
#
|
74
|
+
# for clarity, the sql executed is:
|
75
|
+
# select tablename from pg_tables where schemaname = $1 order by $2 limit $3
|
76
|
+
# where:
|
77
|
+
# $1 = the name of schema (foos_partitions)
|
78
|
+
# $2 = the order by clause that would make the greatest table name listed first
|
79
|
+
# $3 = the parameter 'how_many'
|
80
|
+
#
|
81
|
+
def last_n_partition_names(how_many = 1)
|
82
|
+
return find(:all,
|
83
|
+
:from => "pg_tables",
|
84
|
+
:select => :tablename,
|
85
|
+
:conditions => ["schemaname = ?", configurator.schema_name],
|
86
|
+
:order => last_n_partitions_order_by_clause,
|
87
|
+
:limit => how_many).map(&:tablename)
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# override this or order the tables from last (greatest value? greatest date?) to first
|
92
|
+
#
|
93
|
+
def last_n_partitions_order_by_clause
|
94
|
+
return configurator.last_n_partitions_order_by_clause
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# used to create the parent table rule to ensure
|
99
|
+
#
|
100
|
+
# this will cause an error on attempt to insert into the parent table
|
101
|
+
#
|
102
|
+
# we want all records to exist in one of the child tables so the
|
103
|
+
# query planner can optimize access to the records.
|
104
|
+
#
|
105
|
+
def add_parent_table_rules(*partition_key_values)
|
106
|
+
ensure_always_fail_on_insert_exists
|
107
|
+
|
108
|
+
insert_redirector_name = parent_table_rule_name("insert", "redirector", *partition_key_values)
|
109
|
+
sql = <<-SQL
|
110
|
+
CREATE OR REPLACE RULE #{insert_redirector_name} AS
|
111
|
+
ON INSERT TO #{configurator.parent_table_name(*partition_key_values)}
|
112
|
+
DO INSTEAD
|
113
|
+
(
|
114
|
+
SELECT always_fail_on_insert('#{configurator.parent_table_name(*partition_key_values)}')
|
115
|
+
)
|
116
|
+
SQL
|
117
|
+
execute(sql)
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# the name of the table (schemaname.childtablename) given the check constraint values.
|
122
|
+
#
|
123
|
+
def partition_table_name(*partition_key_values)
|
124
|
+
return configurator.table_name(*partition_key_values)
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# create a single child table.
|
129
|
+
#
|
130
|
+
def create_partition_table(*partition_key_values)
|
131
|
+
create_table(configurator.table_name(*partition_key_values), {
|
132
|
+
:id => false,
|
133
|
+
:options => "INHERITS (#{configurator.parent_table_name(*partition_key_values)})"
|
134
|
+
}) do |t|
|
135
|
+
t.check_constraint configurator.check_constraint(*partition_key_values)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# remove a specific single child table
|
141
|
+
#
|
142
|
+
def drop_partition_table(*partition_key_values)
|
143
|
+
drop_table(configurator.table_name(*partition_key_values))
|
144
|
+
end
|
145
|
+
|
146
|
+
#
|
147
|
+
# add indexes that must exist on child tables. Only leaf child tables
|
148
|
+
# need indexes as parent table indexes are not used in postgres.
|
149
|
+
#
|
150
|
+
def add_partition_table_index(*partition_key_values)
|
151
|
+
configurator.indexes(*partition_key_values).each do |field,options|
|
152
|
+
used_options = options.clone
|
153
|
+
unless used_options.has_key?(:name)
|
154
|
+
name = [*field].join('_')
|
155
|
+
used_options[:name] = used_options[:unique] ? unique_index_name(name, *partition_key_values) : index_name(name, *partition_key_values)
|
156
|
+
end
|
157
|
+
add_index(partition_table_name(*partition_key_values), field, used_options)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
#
|
162
|
+
# used when creating the name of a SQL rule
|
163
|
+
#
|
164
|
+
def parent_table_rule_name(name, suffix = "rule", *partition_key_values)
|
165
|
+
return "#{configurator.parent_table_name(*partition_key_values).gsub(/[.]/, '_')}_#{name}_#{suffix}"
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# used to create index names
|
170
|
+
#
|
171
|
+
def index_name(name, *partition_key_values)
|
172
|
+
return "#{configurator.part_name(*partition_key_values)}_#{name}_idx"
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# used to create index names
|
177
|
+
#
|
178
|
+
def unique_index_name(name, *partition_key_values)
|
179
|
+
return "#{configurator.part_name(*partition_key_values)}_#{name}_udx"
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# this is here for derived classes to set up references to added columns
|
184
|
+
# (or columns in the parent that need foreign key constraints).
|
185
|
+
#
|
186
|
+
# foreign keys are not inherited in postgres. So, a parent table
|
187
|
+
# of the form:
|
188
|
+
#
|
189
|
+
# -- this is the referenced table
|
190
|
+
# create table companies
|
191
|
+
# (
|
192
|
+
# id serial not null primary key,
|
193
|
+
# created_at timestamp not null default now(),
|
194
|
+
# updated_at timestamp,
|
195
|
+
# name text not null
|
196
|
+
# );
|
197
|
+
#
|
198
|
+
# -- this is the parent table
|
199
|
+
# create table employees
|
200
|
+
# (
|
201
|
+
# id serial not null primary key,
|
202
|
+
# created_at timestamp not null default now(),
|
203
|
+
# updated_at timestamp,
|
204
|
+
# name text not null,
|
205
|
+
# company_id integer not null references companies,
|
206
|
+
# supervisor_id integer not null references employees
|
207
|
+
# );
|
208
|
+
#
|
209
|
+
# -- some children
|
210
|
+
# create table employees_of_company_1 ( CHECK ( company_id = 1 ) ) INHERITS (employees);
|
211
|
+
# create table employees_of_company_2 ( CHECK ( company_id = 2 ) ) INHERITS (employees);
|
212
|
+
# create table employees_of_company_3 ( CHECK ( company_id = 3 ) ) INHERITS (employees);
|
213
|
+
#
|
214
|
+
# since postgres does not inherit referential integrity from parent tables, the following
|
215
|
+
# insert will work:
|
216
|
+
# insert into employees_of_company_1 (name, company_id, supervisor_id) values ('joe', 1, 10);
|
217
|
+
# even if there is no record in companies with id = 1 and there is no record in employees with id = 10
|
218
|
+
#
|
219
|
+
# for proper referential integrity handling you must do the following:
|
220
|
+
# ALTER TABLE employees_of_company_1 add foreign key (company_id) references companies(id)
|
221
|
+
# ALTER TABLE employees_of_company_2 add foreign key (company_id) references companies(id)
|
222
|
+
# ALTER TABLE employees_of_company_3 add foreign key (company_id) references companies(id)
|
223
|
+
#
|
224
|
+
# ALTER TABLE employees_of_company_1 add foreign key (supervisor_id) references employees_of_company_1(id)
|
225
|
+
# ALTER TABLE employees_of_company_2 add foreign key (supervisor_id) references employees_of_company_2(id)
|
226
|
+
# ALTER TABLE employees_of_company_3 add foreign key (supervisor_id) references employees_of_company_3(id)
|
227
|
+
#
|
228
|
+
# the second set of alter tables brings up a good another consideration about postgres references and partitions.
|
229
|
+
# postgres will not follow references to a child table. So, a foreign key reference to "employees" in this
|
230
|
+
# set of alter statements would not work because postgres would expect the table "employees" to have
|
231
|
+
# the specific referenced record, but the record really exists in a child of employees. So, the alter statement
|
232
|
+
# forces the reference check on the specific child table we know must contain this employees supervisor (since
|
233
|
+
# such a supervisor would have to work for the same company in our model).
|
234
|
+
#
|
235
|
+
def add_references_to_partition_table(*partition_key_values)
|
236
|
+
configurator.foreign_keys(*partition_key_values).each do |foreign_key|
|
237
|
+
add_foreign_key(partition_table_name(*partition_key_values),
|
238
|
+
foreign_key.referencing_field,
|
239
|
+
foreign_key.referenced_table,
|
240
|
+
foreign_key.referenced_field)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
##
|
245
|
+
# :method: connection
|
246
|
+
# delegated to the connection of the parent table class
|
247
|
+
|
248
|
+
##
|
249
|
+
# :method: execute
|
250
|
+
# delegated to the connection of the parent table class
|
251
|
+
|
252
|
+
##
|
253
|
+
# :method: create_schema
|
254
|
+
# delegated to the connection of the parent table class
|
255
|
+
|
256
|
+
##
|
257
|
+
# :method: drop_schema
|
258
|
+
# delegated to the connection of the parent table class
|
259
|
+
|
260
|
+
##
|
261
|
+
# :method: add_index
|
262
|
+
# delegated to the connection of the parent table class
|
263
|
+
|
264
|
+
##
|
265
|
+
# :method: remove_index
|
266
|
+
# delegated to the connection of the parent table class
|
267
|
+
|
268
|
+
##
|
269
|
+
# :method: transaction
|
270
|
+
# delegated to the connection of the parent table class
|
271
|
+
|
272
|
+
##
|
273
|
+
# :method: find_by_sql
|
274
|
+
# delegated to the connection of the parent table class
|
275
|
+
|
276
|
+
##
|
277
|
+
# :method: find
|
278
|
+
# delegated to the connection of the parent table class
|
279
|
+
|
280
|
+
extend Forwardable
|
281
|
+
def_delegators :parent_table_class, :connection, :find_by_sql, :transaction, :find, :configurator
|
282
|
+
def_delegators :connection, :execute, :add_index, :remove_index, :create_schema, :drop_schema, :add_foreign_key,
|
283
|
+
:create_table, :drop_table
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|