我可以执行一个原始的sql查询,利用准备好的语句,而不使用ActiveRecord::Relation::QueryAttribute吗?[英] Can I execute a raw sql query, leverage prepared statements, and not use ActiveRecord::Relation::QueryAttribute?

本文是小编为大家收集整理的关于我可以执行一个原始的sql查询,利用准备好的语句,而不使用ActiveRecord::Relation::QueryAttribute吗?的处理方法,想解了我可以执行一个原始的sql查询,利用准备好的语句,而不使用ActiveRecord::Relation::QueryAttribute吗?的问题怎么解决?我可以执行一个原始的sql查询,利用准备好的语句,而不使用ActiveRecord::Relation::QueryAttribute吗?问题的解决办法?那么可以参考本文帮助大家快速定位并解决问题。

问题描述

我想做一个upsert.Rails 还不支持这个.查询是这样的:

INSERT INTO foos (thing_id, bar_id) VALUES (1, 2)
ON CONFLICT (thing_id, bar_id) DO NOTHING

我可以使用 self.class.connection.execute 或 exec_insert 轻松做到这一点.但我也想利用准备好的语句.我以为我可以这样做:

thing_id = ActiveRecord::Relation::QueryAttribute.new("thing_id", thing.id, ActiveRecord::Type::Integer.new)
bar_id = ActiveRecord::Relation::QueryAttribute.new("bar_id", id, ActiveRecord::Type::Integer.new)

self.class.connection.exec_insert(<<-SQL, nil, [thing_id, bar_id])
  INSERT INTO foos (thing_id, bar_id) VALUES ($1, $2)
  ON CONFLICT (thing_id, bar_id) DO NOTHING
SQL

但是当我对此进行试验时,似乎没有创建准备好的语句.

我试过这种风格:

query = <<-SQL
  INSERT INTO foos (thing_id, bar_id) VALUES ($1, $2)
  ON CONFLICT (thing_id, bar_id) DO NOTHING
SQL

connection = ActiveRecord::Base.connection.raw_connection
connection.prepare('some_name', query)
st = connection.exec_prepared('some_name', [ thing.id, id ])

它确实创建了一个准备好的语句.但是,第二次运行时,postgres 抱怨创建了一个同名的准备好的语句.因此,rails 的预处理语句管理发生在高于此的级别,我无法在这里利用它.我将不得不在这里手动管理它.我不相信我能正确地做到这一点,即使我能做到,也会很冗长.

execute和朋友不接受where接受的("foo=?", 1) api.

有什么方法可以利用 Rails 对原始 SQL 的自动准备语句管理吗?

推荐答案

#exec_query 接受可选的关键字参数 prepare,默认为 false.看一看 定义(和https://github.com/rails/rails/blob/379d98dcd40f28fd959f42958d662b5416171eea/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L586fd959f42958d662b5416171eea/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb#L5859f42958d662b5416171).

给定下表定义:

CREATE TABLE foos (
    thing_id INT,
    bar_id INT,
    UNIQUE (thing_id, bar_id)
);

我测试了以下内容:

conn = ActiveRecord::Base.connection
stmt = 'INSERT INTO foos (thing_id, bar_id) VALUES ($1, $2) ' \
       'ON CONFLICT (thing_id, bar_id) DO NOTHING'

# "Old" bind parameters
binds = [[nil, 1], [nil, 2]]
conn.exec_query stmt, 'SQL', binds, prepare: true
conn.exec_query stmt, 'SQL', binds, prepare: true

# "New" bind parameters
type  = ActiveModel::Type::Integer.new limit: 4
binds = [
  ActiveRecord::Relation::QueryAttribute.new('thing_id', 1, type),
  ActiveRecord::Relation::QueryAttribute.new('bar_id',   2, type)
]
conn.exec_query stmt, 'SQL', binds, prepare: true
conn.exec_query stmt, 'SQL', binds, prepare: true

这两种绑定参数样式都适用于 ActiveRecord 5.2.0 和 pg 1.0.0.使用相同值的多个 INSERT 语句最终只会插入一行而不会引发错误.我查看了 Postgres 日志,只有一个"解析"(在第一次 INSERT 之前),所以看起来准备好的语句机制被正确使用.

本文地址:https://www.itbaoku.cn/post/1763779.html