partitioned 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. data/Gemfile +17 -0
  2. data/LICENSE +30 -0
  3. data/PARTITIONING_EXPLAINED.txt +351 -0
  4. data/README +111 -0
  5. data/Rakefile +27 -0
  6. data/examples/README +23 -0
  7. data/examples/company_id.rb +417 -0
  8. data/examples/company_id_and_created_at.rb +689 -0
  9. data/examples/created_at.rb +590 -0
  10. data/examples/created_at_referencing_awards.rb +1000 -0
  11. data/examples/id.rb +475 -0
  12. data/examples/lib/by_company_id.rb +11 -0
  13. data/examples/lib/command_line_tool_mixin.rb +71 -0
  14. data/examples/lib/company.rb +29 -0
  15. data/examples/lib/get_options.rb +44 -0
  16. data/examples/lib/roman.rb +41 -0
  17. data/examples/start_date.rb +621 -0
  18. data/init.rb +1 -0
  19. data/lib/monkey_patch_activerecord.rb +92 -0
  20. data/lib/monkey_patch_postgres.rb +73 -0
  21. data/lib/partitioned.rb +26 -0
  22. data/lib/partitioned/active_record_overrides.rb +34 -0
  23. data/lib/partitioned/bulk_methods_mixin.rb +288 -0
  24. data/lib/partitioned/by_created_at.rb +13 -0
  25. data/lib/partitioned/by_foreign_key.rb +21 -0
  26. data/lib/partitioned/by_id.rb +35 -0
  27. data/lib/partitioned/by_integer_field.rb +32 -0
  28. data/lib/partitioned/by_monthly_time_field.rb +23 -0
  29. data/lib/partitioned/by_time_field.rb +65 -0
  30. data/lib/partitioned/by_weekly_time_field.rb +30 -0
  31. data/lib/partitioned/multi_level.rb +20 -0
  32. data/lib/partitioned/multi_level/configurator/data.rb +14 -0
  33. data/lib/partitioned/multi_level/configurator/dsl.rb +32 -0
  34. data/lib/partitioned/multi_level/configurator/reader.rb +162 -0
  35. data/lib/partitioned/multi_level/partition_manager.rb +47 -0
  36. data/lib/partitioned/partitioned_base.rb +354 -0
  37. data/lib/partitioned/partitioned_base/configurator.rb +6 -0
  38. data/lib/partitioned/partitioned_base/configurator/data.rb +62 -0
  39. data/lib/partitioned/partitioned_base/configurator/dsl.rb +628 -0
  40. data/lib/partitioned/partitioned_base/configurator/reader.rb +209 -0
  41. data/lib/partitioned/partitioned_base/partition_manager.rb +138 -0
  42. data/lib/partitioned/partitioned_base/sql_adapter.rb +286 -0
  43. data/lib/partitioned/version.rb +3 -0
  44. data/lib/tasks/desirable_tasks.rake +4 -0
  45. data/partitioned.gemspec +21 -0
  46. data/spec/dummy/.rspec +1 -0
  47. data/spec/dummy/README.rdoc +261 -0
  48. data/spec/dummy/Rakefile +7 -0
  49. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  50. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  51. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  52. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  53. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  54. data/spec/dummy/config.ru +4 -0
  55. data/spec/dummy/config/application.rb +51 -0
  56. data/spec/dummy/config/boot.rb +10 -0
  57. data/spec/dummy/config/database.yml +32 -0
  58. data/spec/dummy/config/environment.rb +5 -0
  59. data/spec/dummy/config/environments/development.rb +30 -0
  60. data/spec/dummy/config/environments/production.rb +60 -0
  61. data/spec/dummy/config/environments/test.rb +39 -0
  62. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  63. data/spec/dummy/config/initializers/inflections.rb +10 -0
  64. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  65. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  66. data/spec/dummy/config/initializers/session_store.rb +8 -0
  67. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  68. data/spec/dummy/config/locales/en.yml +5 -0
  69. data/spec/dummy/config/routes.rb +58 -0
  70. data/spec/dummy/public/404.html +26 -0
  71. data/spec/dummy/public/422.html +26 -0
  72. data/spec/dummy/public/500.html +26 -0
  73. data/spec/dummy/public/favicon.ico +0 -0
  74. data/spec/dummy/script/rails +6 -0
  75. data/spec/dummy/spec/spec_helper.rb +27 -0
  76. data/spec/monkey_patch_posgres_spec.rb +176 -0
  77. data/spec/partitioned/bulk_methods_mixin_spec.rb +512 -0
  78. data/spec/partitioned/by_created_at_spec.rb +62 -0
  79. data/spec/partitioned/by_foreign_key_spec.rb +95 -0
  80. data/spec/partitioned/by_id_spec.rb +97 -0
  81. data/spec/partitioned/by_integer_field_spec.rb +143 -0
  82. data/spec/partitioned/by_monthly_time_field_spec.rb +100 -0
  83. data/spec/partitioned/by_time_field_spec.rb +182 -0
  84. data/spec/partitioned/by_weekly_time_field_spec.rb +100 -0
  85. data/spec/partitioned/multi_level/configurator/dsl_spec.rb +88 -0
  86. data/spec/partitioned/multi_level/configurator/reader_spec.rb +147 -0
  87. data/spec/partitioned/partitioned_base/configurator/dsl_spec.rb +459 -0
  88. data/spec/partitioned/partitioned_base/configurator/reader_spec.rb +513 -0
  89. data/spec/partitioned/partitioned_base/sql_adapter_spec.rb +204 -0
  90. data/spec/partitioned/partitioned_base_spec.rb +173 -0
  91. data/spec/spec_helper.rb +32 -0
  92. data/spec/support/shared_example_spec_helper_for_integer_key.rb +137 -0
  93. data/spec/support/shared_example_spec_helper_for_time_key.rb +147 -0
  94. data/spec/support/tables_spec_helper.rb +47 -0
  95. 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