Created
October 10, 2020 13:09
-
-
Save estum/4ccec1dcb054f91bcc562a6a652ba67f to your computer and use it in GitHub Desktop.
Fix the array handling on ActiveRecord's prepared statements
This file contains hidden or 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
# frozen_string_literal: true | |
# Before (creates prepared statement for each varying size of given array): | |
# | |
# $ Post.where(id: [uuid1, uuid2]) | |
# => Post Load (0.6ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" IN ($1, $2) [["id", "a830f21d-a27b-4bde-8c05-6e5dd088712e"], ["id","531ee026-d60d-4a59-a9a5-d44c44578a98}"]] | |
# | |
# After (the only one statement for varying size of given array): | |
# | |
# $ Post.where(id: [uuid1, uuid2]) | |
# => Post Load (0.6ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ANY($1) [["id", "{a830f21d-a27b-4bde-8c05-6e5dd088712e,531ee026-d60d-4a59-a9a5-d44c44578a98}"]] | |
module PredicateBuilderArrayFix | |
module BuilderHook | |
def build_bind_attribute(column_name, value) | |
if value.is_a?(Array) | |
column_type = table.type(column_name.to_sym).type | |
array_type = Type.lookup(column_type, array: true) | |
attr = Relation::QueryAttribute.new(column_name, value, array_type) | |
Arel::Nodes::BindParam.new(attr) | |
else | |
super | |
end | |
end | |
end | |
module HandlerHook | |
def call(attribute, value) | |
return attribute.in([]) if value.empty? | |
values = value.map { |x| x.is_a?(Base) ? x.id : x } | |
nils, values = values.partition(&:nil?) | |
ranges, values = values.partition { |v| v.is_a?(Range) } | |
values_predicate = | |
case values.length | |
when 0 then NullPredicate | |
when 1 then predicate_builder.build(attribute, values.first) | |
else | |
values = predicate_builder.build_bind_attribute(attribute.name, values) | |
attribute.eq(Arel::Nodes::NamedFunction.new("ANY", [values])) | |
end | |
unless nils.empty? | |
values_predicate = values_predicate.or(predicate_builder.build(attribute, nil)) | |
end | |
array_predicates = ranges.map { |range| predicate_builder.build(attribute, range) } | |
array_predicates.unshift(values_predicate) | |
array_predicates.inject(&:or) | |
end | |
end | |
end | |
ActiveSupport.on_load(:active_record) do | |
ActiveRecord::PredicateBuilder.prepend(PredicateBuilderArrayFix::BuilderHook) | |
ActiveRecord::PredicateBuilder::ArrayHandler.prepend(PredicateBuilderArrayFix::HandlerHook) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Elegantly fixes rails/rails#33702