From 5dc650aa9bb3cbb9d78a731b5c9788d96cc32ea4 Mon Sep 17 00:00:00 2001 From: Daniel Gollahon Date: Sat, 3 Jun 2023 08:01:26 -0700 Subject: [PATCH] Relation#union fails on virtual columns - When merging relations with virtual columns the qualified projection (added in #334) causes the virtual column to be moved into the outer select. This means that column will always be hardcoded to `NULL` instead of selecting from the result of the union. This is a [not-that-esoteric technique with unions](https://stackoverflow.com/a/39766372). This also worked in ROM prior to #334. This PR is really an issue/bug report just in the form of a failing test. I don't have a good idea as to what the right solutin is but I figured it would be helpful to at least have a very concrete demonstration of the issue. --- spec/unit/relation/union_spec.rb | 88 ++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/spec/unit/relation/union_spec.rb b/spec/unit/relation/union_spec.rb index d1307c63..dcf021c0 100644 --- a/spec/unit/relation/union_spec.rb +++ b/spec/unit/relation/union_spec.rb @@ -72,6 +72,94 @@ end end + context "when the relations unioned have different names and virtual columns" do + let(:relation1) { relation.where(id: 1).select(:id, :name).select_append { [`NULL`.as(:title)] } } + let(:relation2) { tasks.select(:id).select_append { [`NULL`.as(:name)] }.select_append(:title) } + + it "qualifies the table as the concatenated relation names" do + # this produces SQL like + # SELECT + # `users__tasks`.`id`, + # `users__tasks`.`name`, + # NULL AS 'title' + # FROM + # ( + # SELECT + # * + # FROM + # ( + # SELECT + # `users`.`id`, + # `users`.`name`, + # NULL AS 'title' + # FROM + # `users` + # WHERE + # (`id` = 1) + # ORDER BY + # `users`.`id` + # ) AS 't1' + # UNION + # SELECT + # * + # FROM + # ( + # SELECT + # `tasks`.`id`, + # NULL AS 'name', + # `tasks`.`title` + # FROM + # `tasks` + # ORDER BY + # `tasks`.`id` + # ) AS 't1' + # ) AS 'users__tasks' + result = relation1.union(relation2) + # But in older versions of ROM and if using Sequel directly it produces: + # SELECT + # * + # FROM + # ( + # SELECT + # * + # FROM + # ( + # SELECT + # `users`.`id`, + # `users`.`name`, + # NULL AS 'title' + # FROM + # `users` + # WHERE + # (`id` = 1) + # ORDER BY + # `users`.`id` + # ) AS 't1' + # UNION + # SELECT + # * + # FROM + # ( + # SELECT + # `tasks`.`id`, + # NULL AS 'name', + # `tasks`.`title` + # FROM + # `tasks` + # ORDER BY + # `tasks`.`id` + # ) AS 't1' + # ) AS 'users__tasks' + expect_to_have_qualified_name(result, :users__tasks) + end + + it "has non-nil titles from the tasks relation" do + titles = relation1.union(relation2).to_a.map { |row| row.fetch(:title) }.compact + expect(titles).not_to be_empty + end + end + + def expect_to_have_qualified_name(rel, name) metas = rel.schema.map(&:meta) expect(metas).to all(include(qualified: name))