partitioned 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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