【问题标题】:Is it possible to put variables in my query not using string interpolation?是否可以在不使用字符串插值的情况下将变量放入我的查询中?
【发布时间】:2016-05-18 13:09:45
【问题描述】:

好的,我有一个使用字符串插值的 SQL 请求并且运行良好:

  ActiveRecord::Base.connection.execute("UPDATE my_table SET location = ST_SetSRID(ST_MakePoint('#{my_table.longitude}'::numeric, '#{my_table.latitude}'::numeric), 4326)::geography WHERE id=#{id}")

我想做的是使用 SQL 变量而不是字符串插值(出于安全原因(SQL 注入)),但我的查询遇到了一些麻烦:

ActiveRecord::Base.connection.execute("UPDATE my_table SET location = ST_SetSRID(ST_MakePoint(':longitude'::numeric, ':latitude'::numeric), 4326)::geography WHERE id=#{id}", longitude: my_table.longitude, latitude: my_table.latitude)

我得到的错误是:

PG::InvalidTextRepresentation:错误:类型的输入语法无效 numeric: ":longitude" (ActiveRecord::StatementInvalid)

有没有办法在最后一个查询中正确使用 SQL 变量?

【问题讨论】:

    标签: sql ruby-on-rails database postgresql


    【解决方案1】:

    这种方式的字符串插值是非常危险的,并且可以打开你的服务器到SQL injection attacks。而是使用ActiveRecord's query interface 来构建查询,如下所示:

    my_table = MyTable.find(id)
    my_table.update(location: "ST_SetSRID(ST_MakePoint('#{my_table.longitude}'::numeric, '#{my_table.latitude}'::numeric), 4326)::geography")
    

    【讨论】:

    • 谢谢,明天试试并报告我的结果:)
    • 好的,我试过了,结果出现了一些语法错误:syntax error, unexpected ',', expecting => ...:geography", my_table.longitude, my_table.latitude)
    • 啊,好像是语法错误。我更新了答案以纠正它(我无法测试它,但它看起来是正确的)。这个答案仍然是 SQL 注入安全的,因为插入的文本作为散列值传递给更新命令,这将净化查询。
    【解决方案2】:

    你为什么不使用 Active Record?

    在您的场景中,您似乎正在从记录中的其他属性中获取值来填充第三个location 属性。

    假设您的应用程序中有一个由该表支持的模型,您有几个选择:

    1. 在模型上创建一个location 方法,该方法根据坐标生成并返回位置。这里的一个优势是数据库中的数据重复较少,但代价是能够针对生成的位置进行本地搜索,并且如果生成该位置的成本很高,则可能会节省成本。
    2. 添加before_save 回调以在每次保存(创建或更新)记录时生成位置。如果生成成本很高并且您经常更新记录,这可能会导致问题......但是,可以通过在生成位置之前检查坐标是否已更改来解决此问题。

    但是,这两种解决方案都需要您在模型中编写一个方法来根据坐标生成位置,或者至少调用一些 3rd-party 代码来为您执行此操作。

    【讨论】:

      【解决方案3】:

      好的,所以我尝试改用此方法(它似乎按预期工作):

      @connection = ActiveRecord::Base.connection.raw_connection
      @connection.prepare('fill_column_location', "UPDATE my_table SET location = ST_SetSRID(ST_MakePoint($1::numeric, $2::numeric), 4326)::geography WHERE id=$3")
      fill_column_location = @connection.exec_prepared('fill_column_location', [my_table.longtmp, my_table.latitmp, id])
      

      我不知道这样做有没有危险或缺陷。

      【讨论】:

        【解决方案4】:

        使用 sanatize_sql_array 在 2020 年为我工作。我认为在这种情况下,您会执行以下操作:

        sql = ActiveRecord::Base.sanatize_sql_array([
          "UPDATE my_table SET location = ST_SetSRID(ST_MakePoint('?'::numeric, '?'::numeric), 4326)::geography WHERE id=?", 
          my_table.longitude, 
          my_table.latitude, 
          id
        ])
        
        ActiveRecord::Base.connection.execute(sql)
        

        文档:https://api.rubyonrails.org/classes/ActiveRecord/Sanitization/ClassMethods.html#method-i-sanitize_sql_array

        【讨论】:

          【解决方案5】:

          试试“?”而不是插值

          ActiveRecord::Base.connection.execute("UPDATE my_table SET location = ST_SetSRID(ST_MakePoint( ? , ? , 4326)::geography WHERE id= ? ", "#{my_table.longitude}::numeric", "#{my_table.latitude}::numeric)", id  )
          

          【讨论】:

          • 感谢您的回答,它给了我一个wrong number of arguments (4 for 1...2) (ArgumentError)
          • 或者先使用插值构建查询,然后将其传递到 ActiveRecord::Base
          • 字符串插值效果很好(如前所述),但我不想使用它。我也已经使用 ActiveRecord::Base
          猜你喜欢
          • 2016-09-13
          • 1970-01-01
          • 1970-01-01
          • 2015-06-25
          • 2012-06-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多