如何正确参数化我的 postgresql 查询

How to properly parameterize my postgresql query

我正在尝试参数化我的 postgresql 查询,以防止 SQL 在 rails 应用程序的 ruby 中注入。 SQL 查询将根据输入在我的 table 中求和一个不同的值。

这是我的函数的简化版本:

def self.calculate_value(value)
    calculated_value = ""
    if value == "quantity"
        calculated_value = "COALESCE(sum(amount), 0)"
    elsif value == "retail"
        calculated_value = "COALESCE(sum(amount * price), 0)"
    elsif value == "wholesale"
        calculated_value = "COALESCE(sum(amount * cost), 0)"
    end
    
    query = <<-SQL
        select CAST(? AS DOUBLE PRECISION) as ? from table1
    SQL
    return Table1.find_by_sql([query, calculated_value, value])
end

如果我调用 calculate_value("retail"),它将执行如下查询:

select location, CAST('COALESCE(sum(amount * price), 0)' AS DOUBLE PRECISION) as 'retail' from table1 group by location

这会导致错误。我希望它在没有这样的引号的情况下执行:

select location, CAST(COALESCE(sum(amount * price), 0) AS DOUBLE PRECISION) as retail from table1 group by location

我知道添加引号可以防止 sql 注入,但在这种情况下我该如何防止呢?处理这种情况的最佳方法是什么?

编辑: 我添加了一个要从 table 中获取的额外列,以强调我不能使用 pick 来获取一个值。

find_by_sql 用于用单行文字 SQL 填充对象。但是我们可以使用 ActiveRecord 来完成其中的大部分,我们只需要一个列。要制作对象,请使用 select. If you just want results use pluck.

当您从一组固定的字符串中进行选择时,此代码中没有 SQL 注入的风险。使用 Arel.sql 传递一个你知道是安全的 SQL 文字。

def self.calculate_value(result_name)
  sum_sql = case result_name
    when "quantity"
      "sum(amount)"
    when "retail"
      "sum(amount * price)"
    when "wholesale"
      "sum(amount * cost)"
    end

  sum_sql = Arel.sql(
    "coalesce(cast(#{sum_sql} as double precision), 0) as #{result_name}"
  )

  return Table1
    .group(:location)
    # replace pluck with select to get Table1 objects
    .pluck(:location, sum_sql)
end