Skip to content

Instantly share code, notes, and snippets.

@benalavi
Created November 21, 2018 00:25
Show Gist options
  • Save benalavi/159110fd58907c7925fc8bd1958e3ad2 to your computer and use it in GitHub Desktop.
Save benalavi/159110fd58907c7925fc8bd1958e3ad2 to your computer and use it in GitHub Desktop.
diff --git a/lib/sequel/plugins/class_table_inheritance.rb b/lib/sequel/plugins/class_table_inheritance.rb
index 214f50bb7..40c65d784 100644
--- a/lib/sequel/plugins/class_table_inheritance.rb
+++ b/lib/sequel/plugins/class_table_inheritance.rb
@@ -198,6 +198,9 @@ module Sequel
# Overrides implicit table names.
# :ignore_subclass_columns :: Array with column names as symbols that are ignored
# on all sub-classes.
+ # :qualify_tables :: Boolean true to qualify automatically determined
+ # subclass tables with the same qualifier as their
+ # superclass.
def self.configure(model, opts = OPTS)
SingleTableInheritance.configure model, opts[:key], opts
@@ -214,6 +217,7 @@ module Sequel
source
end
@cti_ignore_subclass_columns = opts[:ignore_subclass_columns] || []
+ @cti_qualify_tables = !!opts[:qualify_tables]
end
end
@@ -244,6 +248,13 @@ module Sequel
# primary key column is always allowed to be duplicated
attr_reader :cti_ignore_subclass_columns
+ # A boolean indicating whether or not to automatically qualify tables
+ # backing subclasses with the same qualifier as their superclass, if
+ # the superclass is qualified. Specified with the :qualify_tables
+ # option to the plugin and only applied to automatically determined
+ # table names (not to the :table_map option).
+ attr_reader :cti_qualify_tables
+
# Freeze CTI information when freezing model class.
def freeze
@cti_models.freeze
@@ -251,11 +262,12 @@ module Sequel
@cti_table_columns.freeze
@cti_table_map.freeze
@cti_ignore_subclass_columns.freeze
+ @cti_qualify_tables.freeze
super
end
- Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil)
+ Plugins.inherited_instance_variables(self, :@cti_models=>nil, :@cti_tables=>nil, :@cti_table_columns=>nil, :@cti_instance_dataset=>nil, :@cti_table_map=>nil, :@cti_alias=>nil, :@cti_ignore_subclass_columns=>nil, :@cti_qualify_tables=>nil)
def inherited(subclass)
ds = sti_dataset
@@ -272,7 +284,11 @@ module Sequel
if table = cti_table_map[n.to_sym]
columns = db.from(table).columns
else
- table = subclass.implicit_table_name
+ table = if table_name.is_a?(SQL::QualifiedIdentifier) && cti_qualify_tables && schema = dataset.schema_and_table(table_name).first
+ SQL::QualifiedIdentifier.new schema, subclass.implicit_table_name
+ else
+ subclass.implicit_table_name
+ end
columns = check_non_connection_error(false){db.from(table).columns}
table = nil if !columns || columns.empty?
end
diff --git a/spec/extensions/class_table_inheritance_spec.rb b/spec/extensions/class_table_inheritance_spec.rb
index d7fa347ac..dc3390221 100644
--- a/spec/extensions/class_table_inheritance_spec.rb
+++ b/spec/extensions/class_table_inheritance_spec.rb
@@ -56,6 +56,7 @@ describe "class_table_inheritance plugin" do
Employee.cti_instance_dataset.frozen?.must_equal true
Employee.cti_table_columns.frozen?.must_equal true
Employee.cti_table_map.frozen?.must_equal true
+ Employee.cti_qualify_tables.frozen?.must_equal true
end
it "should not attempt to use prepared statements" do
@@ -586,38 +587,99 @@ describe "class_table_inheritance plugin with dataset defined with QualifiedIden
}[opts[:from] + (opts[:join] || []).map{|x| x.table}]
end
end
- ::Employee = Class.new(Sequel::Model)
- ::Employee.db = @db
- ::Employee.set_dataset(Sequel[:hr][:employees])
- class ::Employee
- def _save_refresh; @values[:id] = 1 end
- def self.columns
- dataset.columns || dataset.opts[:from].first.expression.columns
+ end
+ after do
+ [:Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s) if Object.const_defined?(s)}
+ end
+
+ describe "with table_map used to qualify subclasses" do
+ before do
+ ::Employee = Class.new(Sequel::Model)
+ ::Employee.db = @db
+ ::Employee.set_dataset(Sequel[:hr][:employees])
+ class ::Employee
+ def _save_refresh; @values[:id] = 1 end
+ def self.columns
+ dataset.columns || dataset.opts[:from].first.expression.columns
+ end
+ plugin :class_table_inheritance, :table_map=>{:Manager=>Sequel[:hr][:managers],:Staff=>Sequel[:hr][:staff]}
+ end
+ class ::Manager < Employee
+ one_to_many :staff_members, :class=>:Staff
+ end
+ class ::Staff < Employee
+ many_to_one :manager
end
- plugin :class_table_inheritance, key: :type, :table_map=>{:Manager=>Sequel[:hr][:managers], :Staff=>Sequel[:hr][:staff]}
end
- class ::Manager < Employee
- one_to_many :staff_members, :class=>:Staff
+
+ it "should handle many_to_one relationships correctly" do
+ Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E')
+ Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E')
+ @db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees WHERE (id = 3) LIMIT 1']
end
- class ::Staff < Employee
- many_to_one :manager
+
+ it "should handle one_to_many relationships correctly" do
+ Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
+ Manager.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
+ @db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees WHERE (employees.manager_id = 3)']
end
- @ds = Employee.dataset
- @db.sqls
- end
- after do
- [:Manager, :Staff, :Employee].each{|s| Object.send(:remove_const, s)}
end
- it "should handle many_to_one relationships correctly" do
- Manager.dataset = Manager.dataset.with_fetch(:id=>3, :name=>'E')
- Staff.load(:manager_id=>3).manager.must_equal Manager.load(:id=>3, :name=>'E')
- @db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees WHERE (id = 3) LIMIT 1']
+ describe "without table_map or qualify_tables set" do
+ it "should use a non-qualified subquery in subclasses" do
+ def @db.schema(table, opts={})
+ {Sequel[:hr][:employees]=>[[:id, {:primary_key=>true, :type=>:integer}], [:name, {:type=>:string}], [:kind, {:type=>:string}]],
+ :managers=>[[:id, {:type=>:integer}]],
+ }[table.is_a?(Sequel::Dataset) ? table.first_source_table : table]
+ end
+ @db.extend_datasets do
+ def columns
+ {[Sequel[:hr][:employees]]=>[:id, :name, :kind],
+ [:managers]=>[:id],
+ [Sequel[:hr][:employees], :managers]=>[:id, :name, :kind]
+ }[opts[:from] + (opts[:join] || []).map{|x| x.table}]
+ end
+ end
+ ::Employee = Class.new(Sequel::Model)
+ ::Employee.db = @db
+ ::Employee.set_dataset(Sequel[:hr][:employees])
+ class ::Employee
+ def _save_refresh; @values[:id] = 1 end
+ def self.columns
+ dataset.columns || dataset.opts[:from].first.expression.columns
+ end
+ plugin :class_table_inheritance
+ end
+ class ::Manager < ::Employee
+ end
+
+ Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN managers ON (managers.id = hr.employees.id)) AS employees'
+ end
end
- it "should handle one_to_many relationships correctly" do
- Staff.dataset = Staff.dataset.with_fetch(:id=>1, :name=>'S', :manager_id=>3)
- Manager.load(:id=>3).staff_members.must_equal [Staff.load(:id=>1, :name=>'S', :manager_id=>3)]
- @db.sqls.must_equal ['SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees WHERE (employees.manager_id = 3)']
+ describe "with qualify_tables option set" do
+ it "should use a subquery with the same qualifier in subclasses" do
+ ::Employee = Class.new(Sequel::Model)
+ ::Employee.db = @db
+ ::Employee.set_dataset(Sequel[:hr][:employees])
+ class ::Employee
+ def _save_refresh; @values[:id] = 1 end
+ def self.columns
+ dataset.columns || dataset.opts[:from].first.expression.columns
+ end
+ plugin :class_table_inheritance, :table_map=>{:Staff=>Sequel[:hr][:staff]}, qualify_tables: true
+ end
+ class ::Manager < ::Employee
+ one_to_many :staff_members, :class=>:Staff
+ end
+ class ::Staff < ::Employee
+ many_to_one :manager
+ end
+
+ Employee.dataset.sql.must_equal 'SELECT * FROM hr.employees'
+ Manager.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind FROM hr.employees INNER JOIN hr.managers ON (hr.managers.id = hr.employees.id)) AS employees'
+ Staff.dataset.sql.must_equal 'SELECT * FROM (SELECT hr.employees.id, hr.employees.name, hr.employees.kind, hr.staff.manager_id FROM hr.employees INNER JOIN hr.staff ON (hr.staff.id = hr.employees.id)) AS employees'
+ end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment