diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index 0bbe98145a3d5..c63f98308cb14 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -7,7 +7,11 @@ module OID # :nodoc: class Array < Type::Value # :nodoc: include ActiveModel::Type::Helpers::Mutable - Data = Struct.new(:encoder, :values) # :nodoc: + Data = Struct.new(:encoder, :values) do # :nodoc: + def hash + [encoder.name, encoder.delimiter, values].hash + end + end attr_reader :subtype, :delimiter delegate :type, :user_input_in_time_zone, :limit, :precision, :scale, to: :subtype diff --git a/activerecord/lib/arel/visitors/postgresql.rb b/activerecord/lib/arel/visitors/postgresql.rb index 0afd81352820d..74f3eb710db32 100644 --- a/activerecord/lib/arel/visitors/postgresql.rb +++ b/activerecord/lib/arel/visitors/postgresql.rb @@ -88,6 +88,26 @@ def visit_Arel_Nodes_NullsLast(o, collector) collector << " NULLS LAST" end + # Postgres-specific implementation that uses `col = any('{1,2}')` instead of `col IN (1,2)` + # to avoid pg_stat_statements churn + def visit_Arel_Nodes_HomogeneousIn(o, collector) + oid = ActiveRecord::ConnectionAdapters::PostgreSQL::OID + case o.attribute.type_caster + when oid::Bytea, oid::Jsonb + return super + end + + visit o.left, collector + collector << (o.type == :in ? " = any(" : " != all(") + + type_caster = oid::Array.new(o.attribute.type_caster, ",") + values = [type_caster.serialize(o.casted_values)] + proc_for_binds = -> value { ActiveModel::Attribute.with_cast_value(o.attribute.name, value, type_caster) } + collector.add_binds(values, proc_for_binds, &bind_block) + + collector << ")" + end + BIND_BLOCK = proc { |i| "$#{i}" } private_constant :BIND_BLOCK