From ccc2e82aa008ad5e39039653e3ef945b7a448af8 Mon Sep 17 00:00:00 2001 From: yaojie Date: Wed, 24 Jul 2024 16:12:08 +0800 Subject: [PATCH 1/9] Concat primary key --- lib/pg_search/scope_options.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index b03d13d0..c98303d8 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -101,7 +101,12 @@ def order_within_rank end def primary_key - "#{quoted_table_name}.#{connection.quote_column_name(model.primary_key)}" + if model.primary_key.is_a?(Array) + model.primary_key.map { |part| "#{quoted_table_name}.#{connection.quote_column_name(part)}" } + .join(',') + else + "#{quoted_table_name}.#{connection.quote_column_name(model.primary_key)}" + end end def subquery_join From 00a4fd85bba44528e2c2700d78b4f2ead7373a18 Mon Sep 17 00:00:00 2001 From: yaojie Date: Wed, 14 Aug 2024 21:25:09 +0800 Subject: [PATCH 2/9] Handle rank joins on subquery --- lib/pg_search/scope_options.rb | 41 +++++++++++++++---- .../integration/composite_primary_key_spec.rb | 26 ++++++++++++ 2 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 spec/integration/composite_primary_key_spec.rb diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index c98303d8..65a14118 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -79,10 +79,15 @@ def increment_counter delegate :connection, :quoted_table_name, to: :model def subquery - model - .unscoped - .select("#{primary_key} AS pg_search_id") - .select("#{rank} AS rank") + query = model.unscoped + + if composite_primary_key? + query = query.select(model.primary_key.map.with_index(1) { |part, index| "#{quoted_column_name(part)} AS pg_search_id_#{index}" }) + else + query = query.select("#{primary_key} AS pg_search_id") + end + + query.select("#{rank} AS rank") .joins(subquery_join) .where(conditions) .limit(nil) @@ -101,11 +106,10 @@ def order_within_rank end def primary_key - if model.primary_key.is_a?(Array) - model.primary_key.map { |part| "#{quoted_table_name}.#{connection.quote_column_name(part)}" } - .join(',') + if composite_primary_key? + model.primary_key.map { |part| quoted_column_name(part) }.join(',') else - "#{quoted_table_name}.#{connection.quote_column_name(model.primary_key)}" + quoted_column_name(model.primary_key) end end @@ -147,7 +151,14 @@ def rank end def rank_join(rank_table_alias) - "INNER JOIN (#{subquery.to_sql}) AS #{rank_table_alias} ON #{primary_key} = #{rank_table_alias}.pg_search_id" + join_condition = if composite_primary_key? + model.primary_key.map.with_index(1) { |part, index| "#{quoted_column_name(part)} = #{rank_table_alias}.pg_search_id_#{index}" } + .join(" AND ") + else + "#{primary_key} = #{rank_table_alias}.pg_search_id" + end + + "INNER JOIN (#{subquery.to_sql}) AS #{rank_table_alias} ON #{join_condition}" end def include_table_aliasing_for_rank(scope) @@ -157,5 +168,17 @@ def include_table_aliasing_for_rank(scope) new_scope.instance_eval { extend PgSearchRankTableAliasing } end end + + def composite_primary_key? + model.primary_key.is_a?(Array) + end + + def composite_primary_key_length + model.primary_key.length + end + + def quoted_column_name(column) + "#{quoted_table_name}.#{connection.quote_column_name(column)}" + end end end diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb new file mode 100644 index 00000000..4650aaf1 --- /dev/null +++ b/spec/integration/composite_primary_key_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "composite_primary_key" do + with_model :CompositePrimaryKeyModel do + table primary_key: [:prefix, :postfix] do |t| + t.string :prefix + t.string :postfix + t.string :name + end + + model do + include PgSearch::Model + self.primary_key = [:prefix, :postfix] + pg_search_scope :search_name, against: :name + end + end + + before { CompositePrimaryKeyModel.create!(id: ["prefix", "postfix"], name: "bar") } + let!(:record_1) { CompositePrimaryKeyModel.create!(id: ["prefix_2", "postfix_2"], name: "foo") } + + it "searches without any issues" do + expect(CompositePrimaryKeyModel.search_name("foo")).to eq([record_1]) + end +end From c22a3c8a283d924d72434cfcd8e478158064f4c5 Mon Sep 17 00:00:00 2001 From: yaojie Date: Wed, 14 Aug 2024 21:27:47 +0800 Subject: [PATCH 3/9] Update specs, remove unuse method --- lib/pg_search/scope_options.rb | 4 --- .../integration/composite_primary_key_spec.rb | 36 ++++++++++--------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index 65a14118..22836440 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -173,10 +173,6 @@ def composite_primary_key? model.primary_key.is_a?(Array) end - def composite_primary_key_length - model.primary_key.length - end - def quoted_column_name(column) "#{quoted_table_name}.#{connection.quote_column_name(column)}" end diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index 4650aaf1..5a3d551b 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -3,24 +3,26 @@ require "spec_helper" describe "composite_primary_key" do - with_model :CompositePrimaryKeyModel do - table primary_key: [:prefix, :postfix] do |t| - t.string :prefix - t.string :postfix - t.string :name + context 'without associations' do + with_model :CompositePrimaryKeyModel do + table primary_key: [:prefix, :postfix] do |t| + t.string :prefix + t.string :postfix + t.string :name + end + + model do + include PgSearch::Model + self.primary_key = [:prefix, :postfix] + pg_search_scope :search_name, against: :name + end end - - model do - include PgSearch::Model - self.primary_key = [:prefix, :postfix] - pg_search_scope :search_name, against: :name + + before { CompositePrimaryKeyModel.create!(id: ["prefix", "postfix"], name: "bar") } + let!(:record_1) { CompositePrimaryKeyModel.create!(id: ["prefix_2", "postfix_2"], name: "foo") } + + it "searches without any issues" do + expect(CompositePrimaryKeyModel.search_name("foo")).to eq([record_1]) end end - - before { CompositePrimaryKeyModel.create!(id: ["prefix", "postfix"], name: "bar") } - let!(:record_1) { CompositePrimaryKeyModel.create!(id: ["prefix_2", "postfix_2"], name: "foo") } - - it "searches without any issues" do - expect(CompositePrimaryKeyModel.search_name("foo")).to eq([record_1]) - end end From 038625aecb4a00955201542f4511544a8d37b3fe Mon Sep 17 00:00:00 2001 From: yaojie Date: Wed, 14 Aug 2024 21:29:08 +0800 Subject: [PATCH 4/9] Cleanup --- lib/pg_search/scope_options.rb | 10 +++++----- spec/integration/composite_primary_key_spec.rb | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index 22836440..80af15c2 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -80,11 +80,11 @@ def increment_counter def subquery query = model.unscoped - - if composite_primary_key? - query = query.select(model.primary_key.map.with_index(1) { |part, index| "#{quoted_column_name(part)} AS pg_search_id_#{index}" }) + + query = if composite_primary_key? + query.select(model.primary_key.map.with_index(1) { |part, index| "#{quoted_column_name(part)} AS pg_search_id_#{index}" }) else - query = query.select("#{primary_key} AS pg_search_id") + query.select("#{primary_key} AS pg_search_id") end query.select("#{rank} AS rank") @@ -107,7 +107,7 @@ def order_within_rank def primary_key if composite_primary_key? - model.primary_key.map { |part| quoted_column_name(part) }.join(',') + model.primary_key.map { |part| quoted_column_name(part) }.join(",") else quoted_column_name(model.primary_key) end diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index 5a3d551b..a7fd8ca3 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -3,24 +3,24 @@ require "spec_helper" describe "composite_primary_key" do - context 'without associations' do + context "without associations" do with_model :CompositePrimaryKeyModel do table primary_key: [:prefix, :postfix] do |t| t.string :prefix t.string :postfix t.string :name end - + model do include PgSearch::Model self.primary_key = [:prefix, :postfix] pg_search_scope :search_name, against: :name end end - + before { CompositePrimaryKeyModel.create!(id: ["prefix", "postfix"], name: "bar") } let!(:record_1) { CompositePrimaryKeyModel.create!(id: ["prefix_2", "postfix_2"], name: "foo") } - + it "searches without any issues" do expect(CompositePrimaryKeyModel.search_name("foo")).to eq([record_1]) end From 6b61481c745e8ff3611dd59af56e437e9e394263 Mon Sep 17 00:00:00 2001 From: yaojie Date: Thu, 15 Aug 2024 18:28:09 +0800 Subject: [PATCH 5/9] Add failing test cases --- lib/pg_search/scope_options.rb | 1 + .../integration/composite_primary_key_spec.rb | 146 ++++++++++++++++-- 2 files changed, 136 insertions(+), 11 deletions(-) diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index 80af15c2..94b4e167 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -116,6 +116,7 @@ def primary_key def subquery_join if config.associations.any? config.associations.map do |association| + # TODO: handle composite primary keys in associations association.join(primary_key) end.join(" ") end diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index a7fd8ca3..3e6ee3a0 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -3,26 +3,150 @@ require "spec_helper" describe "composite_primary_key" do - context "without associations" do - with_model :CompositePrimaryKeyModel do - table primary_key: [:prefix, :postfix] do |t| - t.string :prefix - t.string :postfix - t.string :name + context 'without relations' do + with_model :Parent do + table primary_key: [:first_name, :last_name] do |t| + t.string :first_name + t.string :last_name + t.string :hobby end model do include PgSearch::Model - self.primary_key = [:prefix, :postfix] - pg_search_scope :search_name, against: :name + self.primary_key = [:first_name, :last_name] + pg_search_scope :search_hobby, against: :hobby end end - before { CompositePrimaryKeyModel.create!(id: ["prefix", "postfix"], name: "bar") } - let!(:record_1) { CompositePrimaryKeyModel.create!(id: ["prefix_2", "postfix_2"], name: "foo") } + before { Parent.create!(id: ["first_name", "last_name"], hobby: "golf") } + let!(:record_1) { Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "basketball") } it "searches without any issues" do - expect(CompositePrimaryKeyModel.search_name("foo")).to eq([record_1]) + expect(Parent.search_hobby("basketball")).to eq([record_1]) + end + end + + context "without composite_primary_key, searching against relation with a composite_primary_key" do + with_model :Parent do + table primary_key: [:first_name, :last_name] do |t| + t.string :first_name + t.string :last_name + t.string :hobby + end + + model do + include PgSearch::Model + has_many :children + self.primary_key = [:first_name, :last_name] + end + end + + with_model :Child do + table do |t| + t.string :parent_first_name + t.string :parent_last_name + end + + model do + include PgSearch::Model + belongs_to :parent, foreign_key: [:parent_first_name, :parent_last_name] + + pg_search_scope :search_parent_hobby, associated_against: { + parent: [:hobby] + } + end + end + + before do + parent = Parent.create!(id: ["first_name", "last_name"], hobby: "golf") + Child.create!(parent: parent) + end + + let!(:record_1) do + parent = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "basketball") + Child.create!(parent: parent) + end + + it "searches without any issues" do + expect(Child.search_parent_hobby("basketball")).to eq([record_1]) + end + end + + context "with composite_primary_key, searching against relation with a composite_primary_key" do + with_model :Parent do + table primary_key: [:first_name, :last_name] do |t| + t.string :first_name + t.string :last_name + t.string :hobby + end + + model do + include PgSearch::Model + has_many :children, foreign_key: [:first_name, :last_name] + self.primary_key = [:first_name, :last_name] + end + end + + with_model :Child do + table primary_key: [:first_name, :last_name] do |t| + t.string :hobby + t.string :first_name + t.string :last_name + t.string :parent_first_name + t.string :parent_last_name + end + + model do + include PgSearch::Model + belongs_to :parent, foreign_key: [:parent_first_name, :parent_last_name] + has_many :siblings, through: :parent, source: :children + + pg_search_scope :search_parent_hobby, associated_against: { + parent: [:hobby] + } + + pg_search_scope :search_sibling_hobby, associated_against: { + siblings: [:hobby] + } + + pg_search_scope :search_hobby, associated_against: { + parent: [:hobby], + siblings: [:hobby] + } + end + end + + before do + parent = Parent.create!(id: ["first_name", "last_name"], hobby: "golf") + Child.create!(id: ["first_name", "last_name"], parent: parent) + end + + it "searches direct relation without any issues" do + parent = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "basketball") + record_1 = Child.create!(id: ["first_name_2", "last_name_2"], parent: parent) + expect(Child.search_parent_hobby("basketball")).to eq([record_1]) + end + + it "searches through relation without any issues" do + parent = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "juggling") + Child.create!(id: ["first_name_2", "last_name_2"], parent: parent, hobby: "basketball") + record_1 = Child.create!(id: ["first_name_3", "last_name_3"], parent: parent, hobby: "studying") + + expect(Child.search_sibling_hobby("basketball")).to eq([record_1]) + end + + it "searches through multiple relations without any issues" do + parent_1 = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "basketball") + record_1 = Child.create!(id: ["first_name_2", "last_name_2"], parent: parent_1, hobby: "studying") # match by parent + + parent_2 = Parent.create!(id: ["first_name_3", "last_name_3"], hobby: "juggling") + record_2 = Child.create!(id: ["first_name_3", "last_name_3"], parent: parent_2, hobby: "bowling") # match by sibling + record_3 = Child.create!(id: ["first_name_4", "last_name_4"], parent: parent_2, hobby: "basketball") # match by self + + parent_3 = Parent.create!(id: ["first_name_4", "last_name_4"], hobby: "golf") + Child.create!(id: ["first_name_5", "last_name_5"], parent: parent_3, hobby: "sleeping") + + expect(Child.search_hobby("basketball")).to eq([record_1, record_2, record_3]) end end end From e1fd67de81835cb23ced32df032a26def124d788 Mon Sep 17 00:00:00 2001 From: yaojie Date: Sun, 18 Aug 2024 09:37:42 +0800 Subject: [PATCH 6/9] Remove out of scope tests --- lib/pg_search/scope_options.rb | 1 - .../integration/composite_primary_key_spec.rb | 78 ------------------- 2 files changed, 79 deletions(-) diff --git a/lib/pg_search/scope_options.rb b/lib/pg_search/scope_options.rb index 94b4e167..80af15c2 100644 --- a/lib/pg_search/scope_options.rb +++ b/lib/pg_search/scope_options.rb @@ -116,7 +116,6 @@ def primary_key def subquery_join if config.associations.any? config.associations.map do |association| - # TODO: handle composite primary keys in associations association.join(primary_key) end.join(" ") end diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index 3e6ee3a0..1369deb9 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -71,82 +71,4 @@ expect(Child.search_parent_hobby("basketball")).to eq([record_1]) end end - - context "with composite_primary_key, searching against relation with a composite_primary_key" do - with_model :Parent do - table primary_key: [:first_name, :last_name] do |t| - t.string :first_name - t.string :last_name - t.string :hobby - end - - model do - include PgSearch::Model - has_many :children, foreign_key: [:first_name, :last_name] - self.primary_key = [:first_name, :last_name] - end - end - - with_model :Child do - table primary_key: [:first_name, :last_name] do |t| - t.string :hobby - t.string :first_name - t.string :last_name - t.string :parent_first_name - t.string :parent_last_name - end - - model do - include PgSearch::Model - belongs_to :parent, foreign_key: [:parent_first_name, :parent_last_name] - has_many :siblings, through: :parent, source: :children - - pg_search_scope :search_parent_hobby, associated_against: { - parent: [:hobby] - } - - pg_search_scope :search_sibling_hobby, associated_against: { - siblings: [:hobby] - } - - pg_search_scope :search_hobby, associated_against: { - parent: [:hobby], - siblings: [:hobby] - } - end - end - - before do - parent = Parent.create!(id: ["first_name", "last_name"], hobby: "golf") - Child.create!(id: ["first_name", "last_name"], parent: parent) - end - - it "searches direct relation without any issues" do - parent = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "basketball") - record_1 = Child.create!(id: ["first_name_2", "last_name_2"], parent: parent) - expect(Child.search_parent_hobby("basketball")).to eq([record_1]) - end - - it "searches through relation without any issues" do - parent = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "juggling") - Child.create!(id: ["first_name_2", "last_name_2"], parent: parent, hobby: "basketball") - record_1 = Child.create!(id: ["first_name_3", "last_name_3"], parent: parent, hobby: "studying") - - expect(Child.search_sibling_hobby("basketball")).to eq([record_1]) - end - - it "searches through multiple relations without any issues" do - parent_1 = Parent.create!(id: ["first_name_2", "last_name_2"], hobby: "basketball") - record_1 = Child.create!(id: ["first_name_2", "last_name_2"], parent: parent_1, hobby: "studying") # match by parent - - parent_2 = Parent.create!(id: ["first_name_3", "last_name_3"], hobby: "juggling") - record_2 = Child.create!(id: ["first_name_3", "last_name_3"], parent: parent_2, hobby: "bowling") # match by sibling - record_3 = Child.create!(id: ["first_name_4", "last_name_4"], parent: parent_2, hobby: "basketball") # match by self - - parent_3 = Parent.create!(id: ["first_name_4", "last_name_4"], hobby: "golf") - Child.create!(id: ["first_name_5", "last_name_5"], parent: parent_3, hobby: "sleeping") - - expect(Child.search_hobby("basketball")).to eq([record_1, record_2, record_3]) - end - end end From 55834f99d9e60fca7104332d459848cacba2a742 Mon Sep 17 00:00:00 2001 From: yaojie Date: Sun, 18 Aug 2024 09:42:44 +0800 Subject: [PATCH 7/9] Rename specs --- spec/integration/composite_primary_key_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index 1369deb9..7aa5a85f 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe "composite_primary_key" do - context 'without relations' do + context 'without associations' do with_model :Parent do table primary_key: [:first_name, :last_name] do |t| t.string :first_name @@ -26,7 +26,7 @@ end end - context "without composite_primary_key, searching against relation with a composite_primary_key" do + context "without composite_primary_key, searching against association with a composite_primary_key" do with_model :Parent do table primary_key: [:first_name, :last_name] do |t| t.string :first_name From 9af37542926b171ec0a3d61135050d4a4d183dc6 Mon Sep 17 00:00:00 2001 From: yaojie Date: Sun, 18 Aug 2024 09:49:13 +0800 Subject: [PATCH 8/9] Update test description --- spec/integration/composite_primary_key_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index 7aa5a85f..c12d0bad 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -26,7 +26,7 @@ end end - context "without composite_primary_key, searching against association with a composite_primary_key" do + context "searching against association with a composite_primary_key" do with_model :Parent do table primary_key: [:first_name, :last_name] do |t| t.string :first_name From a757499165b376543e1fe0a96c7eb7d96cb01491 Mon Sep 17 00:00:00 2001 From: yaojie Date: Fri, 23 Aug 2024 11:15:45 +0800 Subject: [PATCH 9/9] rake standard:fix --- spec/integration/composite_primary_key_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/integration/composite_primary_key_spec.rb b/spec/integration/composite_primary_key_spec.rb index c12d0bad..a5e86af7 100644 --- a/spec/integration/composite_primary_key_spec.rb +++ b/spec/integration/composite_primary_key_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe "composite_primary_key" do - context 'without associations' do + context "without associations" do with_model :Parent do table primary_key: [:first_name, :last_name] do |t| t.string :first_name @@ -40,7 +40,7 @@ self.primary_key = [:first_name, :last_name] end end - + with_model :Child do table do |t| t.string :parent_first_name @@ -53,7 +53,7 @@ pg_search_scope :search_parent_hobby, associated_against: { parent: [:hobby] - } + } end end