pg_query_params因参数太多而失败

时间:2015-03-20 00:19:18

标签: php postgresql

我不确定为什么会失败,我有两个例子,它们是相同的功能,但是一个有效,另一个无效。我真的想要一个不起作用的工作,因为它可以让我把很多可能重复的代码放到一个函数中。

这会调用setItemWorkssetItemDoesNotWork

public function setitemName($itemId, $name){
    /* Add the name of the item to the database */

    $fieldName = 'name';
    $tableName = "items";
    $idName = "id";

    $result = $this->setItemWorks($itemId, $name);
    // Use comments to enable below function and disable function call above.
    //$result = $this->setItemDoesNotWork($tableName, $idName, $fieldName, $itemId, $name);

}

setItemDoesNotWork:

private function setItemDoesNotWork($table, $id, $field, $itemId, $fieldValue){

    $_1 = $itemId;
    $_2 = $fieldValue;
    $_3 = $field;
    $_4 = $table;
    $_5 = $id;

    $parameters = array($_1, $_2, $_3, $_4, $_5);

    // If the ID already exists, then update the name!
    $sql = 'update $4 set $3 = $2 where $5 = $1;';
    pg_query_params($this->database, $sql, $parameters);

    // Add ID and Name into table.
    $sql = 'insert into $4($5, $3) select $1, $2 where not exists(select 1 from $4 where $5=$1)';
    $result = pg_query_params($this->database, $sql, $parameters);

    return $result;
}

setItemWorks:

private function setItemWorks($table, $id, $field, $itemId, $fieldValue){

    $_1 = $itemId;
    $_2 = $fieldValue;

    $parameters = array($_1, $_2);

    // If the ID already exists, then update the name!
    $sql = 'update items set $name = $2 where id = $1;';
    pg_query_params($this->database, $sql, $parameters);

    // Add ID and Name into table.
    $sql = 'insert into items(id, name) select $1, $2 where not exists(select 1 from items where id=$1)';
    $result = pg_query_params($this->database, $sql, $parameters);

    return $result;
}

当我尝试将5个变量放入pg_query_params时,它似乎无法正常工作。

这是我得到的错误:

错误:

[20-Mar-2015 00:17:19 UTC] PHP Warning:  pg_query_params(): Query failed: ERROR:  syntax error at or near "$4"
LINE 1: update $4 set $3 = $2 where $5 = $1;
               ^ in /home/ubuntu/workspace/lib/ItemDatabase.php on line 139

修改:我当前给出的答案是不安全的,如何在不能使用pg_query_params的情况下保证安全?

3 个答案:

答案 0 :(得分:2)

您正在同时打开两罐蠕虫:

  • 动态SQL ,同时避免 SQL注入
  • UPSERT 在解决并发交易的竞争条件时

两者都很棘手,最好的解决方案取决于用例的细节。

PL / pgSQL函数

我建议使用服务器端plpgsql函数来封装两个问题的干净解决方案:

  • SQL注入是不可能的。
  • 并发问题已解决。

CREATE OR REPLACE FUNCTION f_dynamic_upsert
         (_tbl text, _id text, _fld text, _item_id int, _field_val text)
  RETURNS void AS
$func$
DECLARE
   _found int;
BEGIN
LOOP
   EXECUTE format('UPDATE %I SET %I = $1 WHERE %I = $2', _tbl, _fld, _id)
   USING   _field_val, _item_id;

   GET DIAGNOSTICS _found = ROW_COUNT;
   IF _found > 0 THEN RETURN; END IF;

   BEGIN
      EXECUTE format('INSERT INTO %I(%I, %I) SELECT $1, $2', _tbl, _fld, _id)
      USING   _field_val, _item_id;
      RETURN;
   EXCEPTION WHEN unique_violation THEN
      -- rarely happens, keep trying
   END;
END LOOP;
END
$func$  LANGUAGE plpgsql;

呼叫:

SELECT f_dynamic_upsert('foo', 'foo_id', 'foo_val', 3, 'new foo');

named parameter syntax

SELECT f_dynamic_upsert(_tbl := 'foo'
                      , _id := 'foo_id'
                      , _fld := 'foo_val'
                      , _item_id := 3
                      , _field_val :='new foo');

SQL Fiddle证明它有效。

有关动态SQL和SQL注入的更多信息:

正在进行Postgres UPSERT实施的工作:INSERT .. ON CONFLICT UPDATE。运气好的话,这将是下一个版本9.5。 Details in the Postgres Wiki.
有关UPSERT的更多信息:

PHP函数

看起来像这样:

private function setItem($table, $id, $field, $itemId, $fieldValue) {
   $parameters = array($table, $id, $field, $itemId, $fieldValue)
   $sql = 'SELECT f_dynamic_upsert($1, $2, $3, $4, $5)';

   $result = pg_query_params($this->database, $sql, $parameters);
   return $result;
}

答案 1 :(得分:1)

参数的数量不会导致pg_query_params()出现任何问题(不管只有5个参数),问题的根源就是您的查询。

正如错误消息所示,查询解析器不期望该位置的参数而是表名。

查询参数只能用于代替文字值(字符串,数字,NULL等)。数据库,表和字段名称,SQL关键字,函数,运算符和其他语法元素不能被参数替换。

您必须将表名和字段放在查询中。这就是您的方法setItemWorks()运行且setItemDoesNotWork()失败的原因。

有一个很好的理由说明pg_query_params()(和其他DBMS-es的类似函数)只接受参数而不是文字值:使用参数的查询是parsed by the server并且它必须是正确的SQL

准备好的查询的主要目的是在使用不同的文字值多次执行相同的查询时,只进行一次SQL解析。准备好的查询的另一个优点是防止SQL注入。由于参数的值未在查询中直接插入(引用或不引用),因此将排除SQL注入。参数值未转义发送到服务器;没有理由逃避它们,因为它们不再成为SQL查询的一部分。

答案 2 :(得分:0)

根据wildplasser's评论,我已按照以下方式组合了该功能,现在它可以正常工作:

private function setItemDoesNotWork($table, $id, $field, $itemId, $fieldValue){

    $_1 = $itemId;
    $_2 = $fieldValue;
    $_3 = $field;
    $_4 = $table;
    $_5 = $id;

    $parameters = array($_1, $_2);

    // If the ID already exists, then update the name!
    $sql = 'update ' . $_4 . ' set ' .$_3 . ' = $2 where ' . $_5 . ' = $1;';
    /*$result = */pg_query_params($this->database, $sql, $parameters);

    // Add ID and Name into table.
    $sql = 'insert into ' . $_4 . '(' . $_5 . ',' . $_3 . ') select $1, $2 where not exists(select 1 from ' . $_4 . ' where ' . $_5 . '=$1)';

    $result = pg_query_params($this->database, $sql, $parameters);

    return $result;
}

修改

所以这是不安全的,是否有一种简单的方法可以使这个函数免于SQL注入?