updateable_views_inheritance 1.1.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/.gitignore +3 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +110 -0
- data/Rakefile +35 -0
- data/doc/template/horo.rb +613 -0
- data/lib/generators/updateable_views_inheritance_migration/USAGE +8 -0
- data/lib/generators/updateable_views_inheritance_migration/templates/migration.rb +17 -0
- data/lib/generators/updateable_views_inheritance_migration/uvi_migration_generator.rb +12 -0
- data/lib/updateable_views_inheritance.rb +5 -0
- data/lib/updateable_views_inheritance/active_record.rb +16 -0
- data/lib/updateable_views_inheritance/postgresql_adapter.rb +450 -0
- data/lib/updateable_views_inheritance/version.rb +3 -0
- data/tasks/updateable_views_inheritance_tasks.rake +19 -0
- data/test/app/models/bicycle.rb +3 -0
- data/test/app/models/boat.rb +3 -0
- data/test/app/models/car.rb +3 -0
- data/test/app/models/electric_locomotive.rb +3 -0
- data/test/app/models/electric_train.rb +3 -0
- data/test/app/models/locomotive.rb +3 -0
- data/test/app/models/maglev_locomotive.rb +3 -0
- data/test/app/models/maglev_train.rb +3 -0
- data/test/app/models/rack_locomotive.rb +3 -0
- data/test/app/models/rack_train.rb +3 -0
- data/test/app/models/railed_vehicle.rb +3 -0
- data/test/app/models/steam_locomotive.rb +3 -0
- data/test/app/models/steam_train.rb +3 -0
- data/test/app/models/train.rb +3 -0
- data/test/app/models/vehicle.rb +3 -0
- data/test/app/models/wheeled_vehicle.rb +3 -0
- data/test/config/database.yml +19 -0
- data/test/config/environment.rb +14 -0
- data/test/config/routes.rb +3 -0
- data/test/config/schema.rb +3 -0
- data/test/content_test.rb +66 -0
- data/test/database.yml +7 -0
- data/test/deep_hierarchy_test.rb +64 -0
- data/test/fixtures/bicycles.yml +6 -0
- data/test/fixtures/boats.yml +6 -0
- data/test/fixtures/cars.yml +12 -0
- data/test/fixtures/electric_locomotives.yml +6 -0
- data/test/fixtures/electric_trains.yml +7 -0
- data/test/fixtures/maglev_locomotives.yml +7 -0
- data/test/fixtures/maglev_trains.yml +8 -0
- data/test/fixtures/migrations/1_add_class_table_inheritance.rb +15 -0
- data/test/fixtures/migrations/2_create_with_default_table.rb +19 -0
- data/test/fixtures/migrations/3_create_with_explicit_table.rb +11 -0
- data/test/fixtures/migrations/4_create_deeper_hierarchy.rb +11 -0
- data/test/fixtures/migrations/5_default_column_values.rb +13 -0
- data/test/fixtures/migrations/6_single_table_inheritance_view.rb +10 -0
- data/test/fixtures/migrations/7_second_deep_hierarchy.rb +70 -0
- data/test/fixtures/migrations/8_second_single_table_inheritance_view.rb +10 -0
- data/test/fixtures/rack_trains.yml +7 -0
- data/test/fixtures/steam_locomotives.yml +7 -0
- data/test/fixtures/steam_trains.yml +8 -0
- data/test/migration_test.rb +30 -0
- data/test/schema_test.rb +179 -0
- data/test/single_table_inheritance.rb +141 -0
- data/test/test_helper.rb +80 -0
- data/updateable_views_inheritance.gemspec +26 -0
- metadata +219 -0
@@ -0,0 +1,8 @@
|
|
1
|
+
Description:
|
2
|
+
Creates a migration for adding a table used by the updateable_views_inheritance gem.
|
3
|
+
|
4
|
+
The generator takes a migration name as its argument. The migration name may be
|
5
|
+
given in CamelCase or under_score.
|
6
|
+
|
7
|
+
Example:
|
8
|
+
./script/generate updateable_views_inheritance_migration AddUvi
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class <%= class_name %> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table(:updateable_views_inheritance, :id => false) do |t|
|
4
|
+
t.column :parent_relation, :string, :null => false
|
5
|
+
t.column :child_aggregate_view, :string, :null => false
|
6
|
+
t.column :child_relation, :string, :null => false
|
7
|
+
end
|
8
|
+
|
9
|
+
execute "ALTER TABLE updateable_views_inheritance ADD PRIMARY KEY (parent_relation, child_aggregate_view, child_relation)"
|
10
|
+
execute "ALTER TABLE updateable_views_inheritance ADD UNIQUE (child_aggregate_view)"
|
11
|
+
execute "ALTER TABLE updateable_views_inheritance ADD UNIQUE (parent_relation, child_aggregate_view)"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :updateable_views_inheritance
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class ClassTableInheritanceMigrationGenerator < Rails::Generator::NamedBase #:nodoc:
|
2
|
+
def initialize(runtime_args, runtime_options = {})
|
3
|
+
runtime_args << 'add_updateable_views_inheritance_migration' if runtime_args.empty?
|
4
|
+
super
|
5
|
+
end
|
6
|
+
|
7
|
+
def manifest
|
8
|
+
record do |m|
|
9
|
+
m.migration_template 'migration.rb', 'db/migrate'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
class Base #:nodoc:
|
3
|
+
class << self
|
4
|
+
private
|
5
|
+
def instantiate_with_updateable_views_inheritance_support( record )
|
6
|
+
object = instantiate_without_updateable_views_inheritance_support( record )
|
7
|
+
if object.class.name == self.name
|
8
|
+
object
|
9
|
+
else
|
10
|
+
object.class.find( object.id )
|
11
|
+
end
|
12
|
+
end
|
13
|
+
alias_method_chain :instantiate, :updateable_views_inheritance_support
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,450 @@
|
|
1
|
+
module ActiveRecord #:nodoc:
|
2
|
+
module ConnectionAdapters #:nodoc:
|
3
|
+
class PostgreSQLAdapter
|
4
|
+
# Use this in migration to create child table and view.
|
5
|
+
# Options:
|
6
|
+
# [:parent]
|
7
|
+
# parent relation
|
8
|
+
# [:child_table_name]
|
9
|
+
# default is <tt>"#{child_view}_data"</tt>
|
10
|
+
def create_child(child_view, options)
|
11
|
+
raise 'Please call me with a parent, for example: create_child(:steam_locomotives, :parent => :locomotives)' unless options[:parent]
|
12
|
+
parent_relation = options[:parent].to_s
|
13
|
+
if tables.include?(parent_relation)
|
14
|
+
parent_table = parent_relation
|
15
|
+
else # view, interpreted as inheritance chain deeper than two levels
|
16
|
+
parent_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(parent_relation)}")[0][0]
|
17
|
+
end
|
18
|
+
child_table = options[:table] || "#{child_view}_data"
|
19
|
+
child_table_pk = "#{child_view.singularize}_id"
|
20
|
+
|
21
|
+
create_table(child_table, :id => false) do |t|
|
22
|
+
t.integer child_table_pk, :null => false
|
23
|
+
yield t
|
24
|
+
end
|
25
|
+
execute "ALTER TABLE #{child_table} ADD PRIMARY KEY (#{child_table_pk})"
|
26
|
+
execute "ALTER TABLE #{child_table} ADD FOREIGN KEY (#{child_table_pk})
|
27
|
+
REFERENCES #{parent_table} ON DELETE CASCADE ON UPDATE CASCADE"
|
28
|
+
|
29
|
+
create_child_view(parent_relation, child_view, child_table)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Drop child view and table
|
33
|
+
def drop_child(child_view)
|
34
|
+
drop_view(child_view)
|
35
|
+
child_table = query("SELECT child_relation FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}")[0][0]
|
36
|
+
drop_table(child_table)
|
37
|
+
execute "DELETE FROM updateable_views_inheritance WHERE child_aggregate_view = #{quote(child_view)}"
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates aggregate updateable view of parent and child relations. The convention for naming child tables is
|
41
|
+
# <tt>"#{child_view}_data"</tt>. If you don't follow it, supply +child_table_name+ as third argument.
|
42
|
+
def create_child_view(parent_table, child_view, child_table=nil)
|
43
|
+
child_table ||= child_view.to_s + "_data"
|
44
|
+
|
45
|
+
parent_columns = columns(parent_table)
|
46
|
+
child_columns = columns(child_table)
|
47
|
+
|
48
|
+
child_column_names = child_columns.collect{|c| c.name}
|
49
|
+
parent_column_names = parent_columns.collect{|c| c.name}
|
50
|
+
|
51
|
+
child_pk = pk_and_sequence_for(child_table)[0]
|
52
|
+
child_column_names.delete(child_pk)
|
53
|
+
|
54
|
+
parent_pk, parent_pk_seq = pk_and_sequence_for(parent_table)
|
55
|
+
parent_column_names.delete(parent_pk)
|
56
|
+
|
57
|
+
do_create_child_view(parent_table, parent_column_names, parent_pk, child_view, child_column_names, child_pk, child_table)
|
58
|
+
make_child_view_updateable(parent_table, parent_column_names, parent_pk, parent_pk_seq, child_view, child_column_names, child_pk, child_table)
|
59
|
+
|
60
|
+
# assign default values for table columns on the view - it is not automatic in Postgresql 8.1
|
61
|
+
set_defaults(child_view, parent_table, parent_columns)
|
62
|
+
set_defaults(child_view, child_table, child_columns)
|
63
|
+
create_system_table_records(parent_table, child_view, child_table)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Resets sequence to the max value of the table's pk if present respecting inheritance (i.e. one sequence can be shared by many tables).
|
67
|
+
def reset_pk_sequence!(table, pk = nil, sequence = nil)
|
68
|
+
parent = parent_table(table)
|
69
|
+
if parent
|
70
|
+
reset_pk_sequence!(parent, pk, sequence)
|
71
|
+
else
|
72
|
+
unless pk and sequence
|
73
|
+
default_pk, default_sequence = pk_and_sequence_for(table)
|
74
|
+
pk ||= default_pk
|
75
|
+
sequence ||= default_sequence
|
76
|
+
end
|
77
|
+
if pk
|
78
|
+
if sequence
|
79
|
+
select_value <<-end_sql, 'Reset sequence'
|
80
|
+
SELECT setval('#{sequence}', (SELECT COALESCE(MAX(#{pk})+(SELECT increment_by FROM #{sequence}), (SELECT min_value FROM #{sequence})) FROM #{table}), false)
|
81
|
+
end_sql
|
82
|
+
else
|
83
|
+
@logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def primary_key(relation)
|
90
|
+
res = pk_and_sequence_for(relation)
|
91
|
+
res && res.first
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a relation's primary key and belonging sequence. If +relation+ is a table the result is its PK and sequence.
|
95
|
+
# When it is a view, PK and sequence of the table at the root of the inheritance chain are returned.
|
96
|
+
def pk_and_sequence_for(relation)
|
97
|
+
result = query(<<-end_sql, 'PK')[0]
|
98
|
+
SELECT attr.attname
|
99
|
+
FROM pg_attribute attr,
|
100
|
+
pg_constraint cons
|
101
|
+
WHERE cons.conrelid = attr.attrelid
|
102
|
+
AND cons.conrelid = '#{relation}'::regclass
|
103
|
+
AND cons.contype = 'p'
|
104
|
+
AND attr.attnum = ANY(cons.conkey)
|
105
|
+
end_sql
|
106
|
+
if result.nil? or result.empty?
|
107
|
+
parent = parent_table(relation)
|
108
|
+
pk_and_sequence_for(parent) if parent
|
109
|
+
else
|
110
|
+
# log(result[0], "PK for #{relation}")
|
111
|
+
[result[0], query("SELECT pg_get_serial_sequence('#{relation}', '#{result[0]}') ")[0][0]]
|
112
|
+
end
|
113
|
+
rescue
|
114
|
+
nil
|
115
|
+
end
|
116
|
+
|
117
|
+
# Drops a view from the database.
|
118
|
+
def drop_view(name)
|
119
|
+
execute "DROP VIEW #{name}"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return the list of all views in the schema search path.
|
123
|
+
def views(name=nil)
|
124
|
+
schemas = schema_search_path.split(/,/).map { |p| quote(p) }.join(',')
|
125
|
+
query(<<-SQL, name).map { |row| row[0] }
|
126
|
+
SELECT viewname
|
127
|
+
FROM pg_views
|
128
|
+
WHERE schemaname IN (#{schemas})
|
129
|
+
SQL
|
130
|
+
end
|
131
|
+
|
132
|
+
# Checks whether relation +name+ is a view.
|
133
|
+
def is_view?(name)
|
134
|
+
result = query(<<-SQL, name).map { |row| row[0] }
|
135
|
+
SELECT viewname
|
136
|
+
FROM pg_views
|
137
|
+
WHERE viewname = '#{name}'
|
138
|
+
SQL
|
139
|
+
!result.empty?
|
140
|
+
end
|
141
|
+
|
142
|
+
# Recursively delete +parent_relation+ (if it is a view) and the children views the depend on it.
|
143
|
+
def remove_parent_and_children_views(parent_relation)
|
144
|
+
children_views = query(<<-end_sql).map{|row| row[0]}
|
145
|
+
SELECT child_aggregate_view
|
146
|
+
FROM updateable_views_inheritance
|
147
|
+
WHERE parent_relation = '#{parent_relation}'
|
148
|
+
end_sql
|
149
|
+
children_views.each do |cv|
|
150
|
+
remove_parent_and_children_views(cv)
|
151
|
+
# drop the view only if it wasn't dropped beforehand in recursive call from other method.
|
152
|
+
drop_view(cv) if is_view?(cv)
|
153
|
+
end
|
154
|
+
drop_view(parent_relation) if is_view?(parent_relation)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Recreates views in the part of the hierarchy chain starting from the +parent_relation+.
|
158
|
+
def rebuild_parent_and_children_views(parent_relation)
|
159
|
+
# Current implementation is not very efficient - it can drop and recreate one and the same view in the bottom of the hierarchy many times.
|
160
|
+
remove_parent_and_children_views(parent_relation)
|
161
|
+
children = query(<<-end_sql)
|
162
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
163
|
+
FROM updateable_views_inheritance
|
164
|
+
WHERE parent_relation = '#{parent_relation}'
|
165
|
+
end_sql
|
166
|
+
|
167
|
+
#if the parent is in the middle of the inheritance chain, it's a view that should be rebuilt as well
|
168
|
+
parent = query(<<-end_sql)[0]
|
169
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
170
|
+
FROM updateable_views_inheritance
|
171
|
+
WHERE child_aggregate_view = '#{parent_relation}'
|
172
|
+
end_sql
|
173
|
+
create_child_view(parent[0], parent[1], parent[2]) if (parent && !parent.empty?)
|
174
|
+
|
175
|
+
children.each do |child|
|
176
|
+
create_child_view(child[0], child[1], child[2])
|
177
|
+
rebuild_parent_and_children_views(child[1])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Creates Single Table Inheritanche-like aggregate view called +sti_aggregate_view+
|
182
|
+
# for +parent_relation+ and all its descendants. <i>The view isn't updateable.</i>
|
183
|
+
# The order of all or just the first few columns in the aggregate view can be explicitly set
|
184
|
+
# by passing array of column names as third argument.
|
185
|
+
# If there are columns with the same name but different types in two or more relations
|
186
|
+
# they will appear as a single column of type +text+ in the view.
|
187
|
+
def create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
|
188
|
+
columns_for_view ||= []
|
189
|
+
relations_heirarchy = get_view_hierarchy_for(parent_relation)
|
190
|
+
relations = relations_heirarchy.flatten
|
191
|
+
leaves_relations = get_leaves_relations(relations_heirarchy)
|
192
|
+
all_columns = leaves_relations.map{|rel| columns(rel)}.flatten
|
193
|
+
columns_hash = {}
|
194
|
+
conflict_column_names = []
|
195
|
+
all_columns.each do |col|
|
196
|
+
c = columns_hash[col.name]
|
197
|
+
if(c && col.sql_type != c.sql_type)
|
198
|
+
conflict_column_names << col.name
|
199
|
+
else
|
200
|
+
columns_hash[col.name] = col
|
201
|
+
end
|
202
|
+
end
|
203
|
+
conflict_column_names = conflict_column_names.uniq.sort if !conflict_column_names.empty?
|
204
|
+
sorted_column_names = (columns_for_view + columns_hash.keys.sort).uniq
|
205
|
+
parent_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(parent_relation)
|
206
|
+
quoted_inheritance_column = quote_column_name(parent_klass_name.inheritance_column)
|
207
|
+
queries = relations.map{|rel| generate_single_table_inheritanche_union_clause(rel, sorted_column_names, conflict_column_names, columns_hash, quoted_inheritance_column)}
|
208
|
+
unioin_clauses = queries.join("\n UNION ")
|
209
|
+
execute <<-end_sql
|
210
|
+
CREATE VIEW #{sti_aggregate_view} AS (
|
211
|
+
#{unioin_clauses}
|
212
|
+
)
|
213
|
+
end_sql
|
214
|
+
end
|
215
|
+
|
216
|
+
# Recreates the Single_Table_Inheritanche-like aggregate view +sti_aggregate_view+
|
217
|
+
# for +parent_relation+ and all its descendants.
|
218
|
+
def rebuild_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view = nil)
|
219
|
+
drop_view(sti_aggregate_view)
|
220
|
+
create_single_table_inheritance_view(sti_aggregate_view, parent_relation, columns_for_view)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Overriden - it must return false, otherwise deleting fixtures won't work
|
224
|
+
def supports_disable_referential_integrity?
|
225
|
+
false
|
226
|
+
end
|
227
|
+
|
228
|
+
def table_exists_with_updateable_views_inheritance_support?(name)
|
229
|
+
is_view?(name) ? true : table_exists_without_updateable_views_inheritance_support?(name)
|
230
|
+
end
|
231
|
+
alias_method_chain :table_exists?, :updateable_views_inheritance_support
|
232
|
+
|
233
|
+
module Tutuf #:nodoc:
|
234
|
+
class ClassTableReflection
|
235
|
+
class << self
|
236
|
+
# Returns all models' class objects that are ActiveRecord::Base descendants
|
237
|
+
def all_db_klasses
|
238
|
+
return @@klasses if defined?(@@klasses)
|
239
|
+
@@klasses = []
|
240
|
+
# load model classes so that inheritance_column is set correctly where defined
|
241
|
+
model_filenames.collect{|m| load "#{Rails.root}/app/models/#{m}";m.match(%r{([^/]+?)\.rb$})[1].camelize.constantize }.each do |klass|
|
242
|
+
@@klasses << klass if klass < ActiveRecord::Base
|
243
|
+
end
|
244
|
+
@@klasses.uniq
|
245
|
+
end
|
246
|
+
|
247
|
+
# Returns the class object for +table_name+
|
248
|
+
def get_klass_for_table(table_name)
|
249
|
+
klass_for_tables()[table_name.to_s]
|
250
|
+
end
|
251
|
+
|
252
|
+
# Returns hash with tables and thier corresponding class.
|
253
|
+
# {table_name1 => ClassName1, ...}
|
254
|
+
def klass_for_tables
|
255
|
+
return @@tables_klasses if defined?(@@tables_klasses)
|
256
|
+
@@tables_klasses = {}
|
257
|
+
all_db_klasses.each do |klass|
|
258
|
+
@@tables_klasses[klass.table_name] = klass if klass.respond_to?(:table_name)
|
259
|
+
end
|
260
|
+
@@tables_klasses
|
261
|
+
end
|
262
|
+
|
263
|
+
# Returns filenames for models in the current Rails application
|
264
|
+
def model_filenames
|
265
|
+
Dir.chdir("#{Rails.root}/app/models"){ Dir["**/*.rb"] }
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
|
273
|
+
def do_create_child_view(parent_table, parent_columns, parent_pk, child_view, child_columns, child_pk, child_table)
|
274
|
+
view_columns = parent_columns + child_columns
|
275
|
+
execute <<-end_sql
|
276
|
+
CREATE OR REPLACE VIEW #{child_view} AS (
|
277
|
+
SELECT parent.#{parent_pk},
|
278
|
+
#{ view_columns.join(",") }
|
279
|
+
FROM #{parent_table} parent
|
280
|
+
INNER JOIN #{child_table} child
|
281
|
+
ON ( parent.#{parent_pk}=child.#{child_pk} )
|
282
|
+
)
|
283
|
+
end_sql
|
284
|
+
end
|
285
|
+
|
286
|
+
# Creates rules for +INSERT+, +UPDATE+ and +DELETE+ on the view
|
287
|
+
def make_child_view_updateable(parent_table, parent_columns, parent_pk, parent_pk_seq, child_view, child_columns, child_pk, child_table)
|
288
|
+
# insert
|
289
|
+
# NEW.#{parent_pk} can be explicitly specified and when it is null every call to it increments the sequence.
|
290
|
+
# Setting the sequence to its value (explicitly supplied or the default) covers both cases.
|
291
|
+
execute <<-end_sql
|
292
|
+
CREATE OR REPLACE RULE #{child_view}_insert AS
|
293
|
+
ON INSERT TO #{child_view} DO INSTEAD (
|
294
|
+
SELECT setval('#{parent_pk_seq}', NEW.#{parent_pk});
|
295
|
+
INSERT INTO #{parent_table}
|
296
|
+
( #{ [parent_pk, parent_columns].flatten.join(", ") } )
|
297
|
+
VALUES( currval('#{parent_pk_seq}') #{ parent_columns.empty? ? '' : ',' + parent_columns.collect{ |col| "NEW." + col}.join(",") } )
|
298
|
+
#{insert_returning_clause(child_view, parent_columns.unshift(parent_pk)) if supports_insert_with_returning?};
|
299
|
+
INSERT INTO #{child_table}
|
300
|
+
( #{ [child_pk, child_columns].flatten.join(",")} )
|
301
|
+
VALUES( currval('#{parent_pk_seq}') #{ child_columns.empty? ? '' : ',' + child_columns.collect{ |col| "NEW." + col}.join(",") } )
|
302
|
+
)
|
303
|
+
end_sql
|
304
|
+
|
305
|
+
# delete
|
306
|
+
execute <<-end_sql
|
307
|
+
CREATE OR REPLACE RULE #{child_view}_delete AS
|
308
|
+
ON DELETE TO #{child_view} DO INSTEAD
|
309
|
+
DELETE FROM #{parent_table} WHERE #{parent_pk} = OLD.#{parent_pk}
|
310
|
+
end_sql
|
311
|
+
|
312
|
+
# update
|
313
|
+
execute <<-end_sql
|
314
|
+
CREATE OR REPLACE RULE #{child_view}_update AS
|
315
|
+
ON UPDATE TO #{child_view} DO INSTEAD (
|
316
|
+
#{ parent_columns.empty? ? '':
|
317
|
+
"UPDATE #{parent_table}
|
318
|
+
SET #{ parent_columns.collect{ |col| col + "= NEW." +col }.join(", ") }
|
319
|
+
WHERE #{parent_pk} = OLD.#{parent_pk};"}
|
320
|
+
#{ child_columns.empty? ? '':
|
321
|
+
"UPDATE #{child_table}
|
322
|
+
SET #{ child_columns.collect{ |col| col + " = NEW." +col }.join(", ") }
|
323
|
+
WHERE #{child_pk} = OLD.#{parent_pk}"
|
324
|
+
}
|
325
|
+
)
|
326
|
+
end_sql
|
327
|
+
end
|
328
|
+
|
329
|
+
def insert_returning_clause(child_view,parent_columns)
|
330
|
+
"RETURNING "+
|
331
|
+
columns(child_view).map do |c|
|
332
|
+
if parent_columns.include?(c.name)
|
333
|
+
c.name
|
334
|
+
else
|
335
|
+
"CAST (NULL AS #{c.sql_type})"
|
336
|
+
end
|
337
|
+
end.join(",")
|
338
|
+
end
|
339
|
+
|
340
|
+
# Set default values from the table columns for a view
|
341
|
+
def set_defaults(view_name, table_name, columns)
|
342
|
+
columns.each do |column|
|
343
|
+
if !(default_value = get_default_value(table_name, column.name)).nil?
|
344
|
+
execute("ALTER TABLE #{view_name} ALTER #{column.name} SET DEFAULT #{default_value}")
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# ActiveRecord::ConnectionAdapters::Column objects have nil default value for serial primary key
|
350
|
+
def get_default_value(table_name, column_name)
|
351
|
+
result = query(<<-end_sql, 'Column default value')[0]
|
352
|
+
SELECT pg_catalog.pg_get_expr(d.adbin, d.adrelid) as constraint
|
353
|
+
FROM pg_catalog.pg_attrdef d, pg_catalog.pg_attribute a, pg_catalog.pg_class c
|
354
|
+
WHERE d.adrelid = a.attrelid
|
355
|
+
AND d.adnum = a.attnum
|
356
|
+
AND a.atthasdef
|
357
|
+
AND c.relname = '#{table_name}'
|
358
|
+
AND a.attrelid = c.oid
|
359
|
+
AND a.attname = '#{column_name}'
|
360
|
+
AND a.attnum > 0 AND NOT a.attisdropped
|
361
|
+
end_sql
|
362
|
+
if !result.nil? && !result.empty?
|
363
|
+
result[0]
|
364
|
+
else
|
365
|
+
nil
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def create_system_table_records(parent_relation, child_aggregate_view, child_relation)
|
370
|
+
parent_relation, child_aggregate_view, child_relation = [parent_relation, child_aggregate_view, child_relation].collect{|rel| quote(rel.to_s)}
|
371
|
+
exists = query <<-end_sql
|
372
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
373
|
+
FROM updateable_views_inheritance
|
374
|
+
WHERE parent_relation = #{parent_relation}
|
375
|
+
AND child_aggregate_view = #{child_aggregate_view}
|
376
|
+
AND child_relation = #{child_relation}
|
377
|
+
end_sql
|
378
|
+
# log "res: #{exists}"
|
379
|
+
if exists.nil? or exists.empty?
|
380
|
+
execute "INSERT INTO updateable_views_inheritance (parent_relation, child_aggregate_view, child_relation)" +
|
381
|
+
"VALUES( #{parent_relation}, #{child_aggregate_view}, #{child_relation} )"
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def parent_table(relation)
|
386
|
+
if table_exists?('updateable_views_inheritance')
|
387
|
+
res = query(<<-end_sql, 'Parent relation')[0]
|
388
|
+
SELECT parent_relation
|
389
|
+
FROM updateable_views_inheritance
|
390
|
+
WHERE child_aggregate_view = '#{relation}'
|
391
|
+
end_sql
|
392
|
+
res[0] if res
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# Single Table Inheritance Aggregate View
|
397
|
+
|
398
|
+
# Nested list for the +parent_relation+ inheritance hierarchy
|
399
|
+
# Every descendant relation is presented as an array with relation's name as first element
|
400
|
+
# and the other elements are the relation's children presented in the same way as lists.
|
401
|
+
# For example:
|
402
|
+
# [[child_view1, [grandchild11,[...]], [grandchild12]],
|
403
|
+
# [child_view2, [...]
|
404
|
+
# ]
|
405
|
+
def get_view_hierarchy_for(parent_relation)
|
406
|
+
hierarchy = []
|
407
|
+
children = query(<<-end_sql)
|
408
|
+
SELECT parent_relation, child_aggregate_view, child_relation
|
409
|
+
FROM updateable_views_inheritance
|
410
|
+
WHERE parent_relation = '#{parent_relation}'
|
411
|
+
end_sql
|
412
|
+
children.each do |child|
|
413
|
+
hierarchy << [child[1], *get_view_hierarchy_for(child[1])]
|
414
|
+
end
|
415
|
+
hierarchy
|
416
|
+
end
|
417
|
+
|
418
|
+
def get_leaves_relations(hierarchy)
|
419
|
+
return [] if hierarchy.nil? || hierarchy.empty?
|
420
|
+
head, hierarchy = hierarchy.first, hierarchy[1..(hierarchy.size)]
|
421
|
+
if(head.is_a? Array)
|
422
|
+
return (get_leaves_relations(head) + get_leaves_relations(hierarchy)).compact
|
423
|
+
elsif(hierarchy.nil? || hierarchy.empty?)
|
424
|
+
return [head]
|
425
|
+
else
|
426
|
+
return get_leaves_relations(hierarchy).compact
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
def generate_single_table_inheritanche_union_clause(rel, column_names, conflict_column_names, columns_hash, quoted_inheritance_column)
|
431
|
+
relation_columns = columns(rel).collect{|c| c.name}
|
432
|
+
columns_select = column_names.inject([]) do |arr, col_name|
|
433
|
+
sql_type = conflict_column_names.include?(col_name) ? 'text' : columns_hash[col_name].sql_type
|
434
|
+
value = "NULL::#{sql_type}"
|
435
|
+
if(relation_columns.include?(col_name))
|
436
|
+
value = col_name
|
437
|
+
value = "#{value}::text" if conflict_column_names.include?(col_name)
|
438
|
+
end
|
439
|
+
statement = " AS #{col_name}"
|
440
|
+
statement = "#{value} #{statement}"
|
441
|
+
arr << " #{statement}"
|
442
|
+
end
|
443
|
+
columns_select = columns_select.join(", ")
|
444
|
+
rel_klass_name = Tutuf::ClassTableReflection.get_klass_for_table(rel)
|
445
|
+
where_clause = " WHERE #{quoted_inheritance_column} = '#{rel_klass_name}'"
|
446
|
+
["SELECT", columns_select, "FROM #{rel} #{where_clause}"].join(" ")
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|