Я использую Rails 7.1.2 и пытаюсь понять, как заставить Rails/ActiveRecord выполнять интерполяцию переменных в строку SQL и возвращать мне полученный SQL. Вот что я пробовал:
>> User.connection.to_sql("select * from users where id = $1", [1])
=> "select * from users where id = $1"
>> User.connection.to_sql("select * from users where id = ?", [1])
=> "select * from users where id = ?"
>> User.connection.to_sql("select * from users where id = :id", { id: 1 })
=> "select * from users where id = :id"
>> User.connection.to_sql("select * from users where id = :id", [{ id: 1 }])
=> "select * from users where id = :id"
В документации этого метода неясно, следует ли интерполировать параметры привязки или нет, там просто говорится:
Преобразует AST в SQL
Я не знаю, имеют ли они в виду SQL с интерполированными параметрами или нет, когда говорят «SQL», но я не знаю, почему еще метод будет принимать параметры связывания, если он их не интерполирует.
Я ищу любой вариант параметров в запросе, который будет заменен, поэтому мне нужно, чтобы значения параметров привязки были интерполированы в результаты, например:
>> User.connection.to_sql("select * from users where id = $1", [1])
=> "select * from users where id = 1"
Привязки SQL работают не так. Вся суть в том, что вы передаете в базу данных запрос и значения отдельно. Это позволяет базе данных подготовить и повторно использовать запрос и избежать внедрения SQL. Как вы думаете, почему вам это нужно/нужно?
Что я делаю, так это то, что у меня есть сложный запрос, который я на самом деле вызываю с помощью select_all
, например User.connection.select_all("select first_name from users where id = $1", nil, [1])
. Мне бы хотелось увидеть запрос с интерполированными параметрами, но, поскольку это происходит в базе данных, возможно, в Ruby/Rails это просто невозможно сделать.
Эй, Пол! Я думаю, что чтобы увидеть это на клиенте, уведомления AS должны быть одним из последних действий перед отправкой текста запроса. Если да, то вы можете создать класс подписчика для событий «sql.active_record» и зарегистрировать его. Вот сообщение в блоге с соответствующим примером. В вашем случае вы не захотите фильтровать только медленные запросы, а регистрировать их все, возможно, включая класс подписчика по мере необходимости. kinsta.com/blog/logging-slow-queries
Вы ищете sanitze_sql и друзей из ActiveRecord::Sanitization::ClassMethods:
User.sanitize_sql(['select * from users where id = ?', 1])
# select * from users where id = 1
User.sanitize_sql(['select * from users where id = :id', id: 11])
# select * from users where id = 11
User.sanitize_sql(['select * from users where name = :name', name: "G'Kar"])
# select * from users where name = 'G''Kar'
Чего я действительно хочу на самом деле, так это получить SQL при использовании таких параметров привязки: User.connection.select_one('select * from users where id = $1', nil, [1])
Но, как указано выше, для того типа запроса, который использует подготовленные операторы под капотом, эта интерполяция не происходит в Ruby, она происходит в Postgres, так что это может быть не так важно
@pjb3 Rails передает это pg
. (После подготовки привязок) См. Здесь и PG::Connection#exec_params . Также см. функцию libpq
Хотя это и не встроено в рельсы, вы можете попробовать реализовать простую версию этого для себя.
Например:
def interpolate_sql_with_binds(sql, binds=[])
sql_binds = sql.scan(/\$\d+/)
raise ArgumentError, "incorrect number of bind values" if binds.size != sql_binds.size
quoted_binds = binds.map {|b| Arel::Nodes.build_quoted(b).to_sql}
sql.gsub(/\$\d+/,sql_binds.zip(quoted_binds).to_h)
end
interpolate_sql_with_binds("select * from users where id = $1",[1])
#=> "select * from users where id = 1"
interpolate_sql_with_binds("select * from users where id = $1 OR username = $2",[1,'engineersnmky'])
#=> "select * from users where id = 1 OR username = 'engineersmnky'"
Очевидно, что вы можете продолжать совершенствовать обработку ошибок и обработку синтаксиса (например, предложения IN()
, поддержку привязок массивов и т. д.) по мере необходимости.
Да, я понял, что в Rails нет для этого метода. Если бы я захотел это сделать, мне пришлось бы создать что-то вроде этого
User.where("id=?", 1).to_sql
илиUser.where(id: 1).to_sql