-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pg_eager_any_typed_array plugin, automatically transform eager lo…
…ads to use = ANY(array_expr::type[]) instead of IN (value_list) This makes it easier to use the pg_auto_parameterize_in_array extension with the :treat_string_list_as_text_array option, by automatically using = ANY with the correct array type (instead of Sequel assuming the text[] type, which may not be correct).
- Loading branch information
1 parent
1d94f19
commit 5336fb6
Showing
6 changed files
with
122 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
# frozen-string-literal: true | ||
|
||
module Sequel | ||
module Plugins | ||
# The pg_eager_any_typed_array plugin automatically converts | ||
# the predicate expressions used for eager loading from: | ||
# | ||
# table.column IN (value_list) | ||
# | ||
# to: | ||
# | ||
# table.column = ANY(array_expr::type[]) | ||
# | ||
# This makes it easier to use the pg_auto_parameterize_in_array | ||
# extension with the :treat_string_list_as_text_array option, | ||
# when using foreign keys with non-text database types that are represented | ||
# by Ruby strings, such as enum and uuid types. | ||
# | ||
# To avoid this behavior for particular associations, set the | ||
# :eager_loading_predicate_transform association option to nil/false. | ||
# | ||
# This plugin loads the pg_array extension into the model's Database. | ||
module PgEagerAnyTypedArray | ||
# Add the pg_array extension to the database | ||
def self.apply(model) | ||
model.db.extension(:pg_array) | ||
end | ||
|
||
module ClassMethods | ||
TRANSFORM = proc do |values, ref| | ||
type = ref.send(:cached_fetch, :_pg_eager_any_typed_array_type) do | ||
key = ref.predicate_key | ||
next if key.is_a?(Array) | ||
|
||
while key.is_a?(SQL::QualifiedIdentifier) | ||
key = key.column | ||
end | ||
|
||
if (sch = ref.associated_class.db_schema[key]) | ||
sch[:db_type] | ||
end | ||
end | ||
|
||
if type | ||
Sequel.function(:ANY, Sequel.pg_array(values, type)) | ||
else | ||
values | ||
end | ||
end | ||
|
||
# If the association does not use a composite predicate key, | ||
# and does not already have the :eager_loading_predicate_transform | ||
# option set, set the option so that eager loading | ||
def associate(type, name, opts = OPTS, &block) | ||
res = super | ||
|
||
unless res.has_key?(:eager_loading_predicate_transform) | ||
res[:eager_loading_predicate_transform] = TRANSFORM | ||
end | ||
|
||
res | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper") | ||
|
||
describe "pg_eager_any_typed_array plugin" do | ||
before do | ||
@db = Sequel.connect('mock://postgres') | ||
@c = Class.new(Sequel::Model(@db[:items])) | ||
def @c.name; "C" end | ||
@c.columns :id, :c_id | ||
@c.plugin :pg_eager_any_typed_array | ||
@c.dataset = @c.dataset.with_fetch([[{:id=>1, :c_id=>2}, {:id=>3, :c_id=>4}]]) | ||
@c.db_schema[:id][:db_type] = "int4" | ||
@c.db_schema[:c_id][:db_type] = "int8" | ||
@c.one_to_many :cs, :class=>@c | ||
@c.many_to_one :c, :class=>@c | ||
@c.many_to_one :cc, :class=>@c, :key=>[:id, :c_id], :primary_key=>[:id, :c_id] | ||
@db.sqls | ||
end | ||
|
||
it "should automatically use column = ANY() for eager loads using scaler keys with known type" do | ||
@c.eager(:c, :cs).all | ||
@db.sqls.must_equal ['SELECT * FROM "items"', | ||
'SELECT * FROM "items" WHERE ("items"."id" = ANY(ARRAY[2,4]::int4[]))', | ||
'SELECT * FROM "items" WHERE ("items"."c_id" = ANY(ARRAY[1,3]::int8[]))'] | ||
end | ||
|
||
it "should automatically use (column1, column2) IN (value_list) for eager loads using composite keys" do | ||
@c.eager(:cc).all | ||
@db.sqls.must_equal ['SELECT * FROM "items"', 'SELECT * FROM "items" WHERE (("items"."id", "items"."c_id") IN ((1, 2), (3, 4)))'] | ||
end | ||
|
||
it "should automatically use column IN (value_list) for eager loads using scalar keys with unknown type" do | ||
@c.db_schema[:id].delete(:db_type) | ||
@c.db_schema[:c_id].delete(:db_type) | ||
@c.eager(:c, :cs).all | ||
@db.sqls.must_equal ['SELECT * FROM "items"', | ||
'SELECT * FROM "items" WHERE ("items"."id" IN (2, 4))', | ||
'SELECT * FROM "items" WHERE ("items"."c_id" IN (1, 3))'] | ||
end | ||
|
||
it "should automatically use column IN (value_list) for eager loads when explicitly disabled" do | ||
@c.one_to_many :cs, :class=>@c, :eager_loading_predicate_transform=>nil | ||
@c.many_to_one :c, :class=>@c, :eager_loading_predicate_transform=>nil | ||
@c.eager(:c, :cs).all | ||
@db.sqls.must_equal ['SELECT * FROM "items"', | ||
'SELECT * FROM "items" WHERE ("items"."id" IN (2, 4))', | ||
'SELECT * FROM "items" WHERE ("items"."c_id" IN (1, 3))'] | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters