简体   繁体   English

PDOstatement (MySQL):将值 0 插入 bit(1) 字段导致 1 写入表

[英]PDOstatement (MySQL): inserting value 0 into a bit(1) field results in 1 written in table

I'm using a bit(1) field to store boolean values and writing into the table using PDO prepared statements.我正在使用一个 bit(1) 字段来存储 boolean 个值并使用 PDO 准备好的语句写入表中。

This is the test table:这是测试表:

CREATE TABLE IF NOT EXISTS `test` (
  `SomeText` varchar(255) NOT NULL,
  `TestBool` bit(1) NOT NULL DEFAULT b'0'
) ENGINE=MEMORY DEFAULT CHARSET=latin1;

This is the test code:这是测试代码:

$pdo = new PDO("connection string etc") ;
$statement = $pdo->prepare('INSERT INTO `test` (SomeText,TestBool) VALUES (?,?)') ;
$statement->execute(array("TEST",0)) ;

Running that code gives me a row with value 1 under TestBool.运行该代码会在 TestBool 下给我一行值为 1 的行。 And the same thing using bindValue() and bindParm().使用 bindValue() 和 bindParm() 也是一样。 I also tried named placeholders (instead of?) with the same result.我还尝试了命名占位符(而不是?),结果相同。

Then I tried:然后我尝试了:

$statement = $pdo->prepare('INSERT INTO `test` (SomeText,TestBool) VALUES ("TEST",0)') ;
$statement->execute() ;

Which worked properly (TestBool has value 0).哪个工作正常(TestBool 的值为 0)。 Punching in the SQL directly into MySQL also works.将 SQL 直接打入 MySQL 也可以。

Note that inserting 1 always works.请注意,插入 1 始终有效。

So why would placeholders fail to insert the value 0?那么为什么占位符无法插入值 0 呢? (and how do I actually do it?) (我该怎么做?)

BIT column is a binary type in mysql (though it's documented as numeric type - that's not precisely true) and I advise to avoid it due to problems with client libraries (which PDO issue proves). BIT 列是 mysql 中的二进制类型(尽管它被记录为数字类型 - 这并不完全正确)并且由于客户端库的问题(PDO 问题证明了这一点),我建议避免它。 You will spare yourself a lot of trouble if you modify type of column to TINYINT(1)如果您将列的类型修改为 TINYINT(1),您会省去很多麻烦

TINYINT(1) will of course consume full byte of storage for every row, but according to mysql docs BIT(1) will do as well. TINYINT(1) 当然会为每一行消耗完整字节的存储空间,但根据 mysql docs BIT(1) 也会这样做。

from: http://dev.mysql.com/doc/refman/5.1/en/storage-requirements.html来自: http : //dev.mysql.com/doc/refman/5.1/en/storage-requirements.html

bit storage requirement is: approximately (M+7)/8 bytes which suggests that BIT(M) column is also byte-aligned.位存储要求是:大约 (M+7)/8 个字节,这表明 BIT(M) 列也是字节对齐的。

Also I found this: https://bugs.php.net/bug.php?id=50757我也发现了这个: https : //bugs.php.net/bug.php?id=50757

So you could check if following code works as you expect:因此,您可以检查以下代码是否按预期工作:

$pdo = new PDO("connection string etc") ;
$statement = $pdo->prepare('INSERT INTO `test` (SomeText,TestBool) VALUES (:someText,:testBool)') ;
$statement->bindValue(':someText', "TEST");
$statement->bindValue(':testBool', 0, PDO::PARAM_INT);
$statement->execute();

You may also try with different type hints than PARAM_INT, still even if you make it work I advice to change to TINYINT.您也可以尝试使用与 PARAM_INT 不同的类型提示,即使您使它工作,我仍然建议更改为 TINYINT。

pdo by default doesnt use prepared statements for the mysql driver, it emulates them by creating dynamic sql behind the scenes for you.默认情况下,pdo 不为 mysql 驱动程序使用准备好的语句,它通过在幕后为您创建动态 sql 来模拟它们。 The sql sent to mysql ends up being a single quoted 0 like '0', which mysql interprets as a string, not a number.发送到 mysql 的 sql 最终是一个单引号 0,如“0”,mysql 将其解释为字符串,而不是数字。

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

It should work now, and you also will be actually using real prepared statements.它现在应该可以工作了,而且您还将实际使用真正的准备好的语句。

Because prepare adds ' to your parameter, You have only to add b before parameter name因为prepare添加'到您的参数,您只需在参数名称前添加b

$statement = $pdo->prepare('INSERT INTO `test` (SomeText,TestBool) VALUES (?, b?)');
$statement->execute(array("TEST", 1 /* or TRUE */));

Note : you can use 1, 0 or TRUE, FALSE .注意:您可以使用1, 0TRUE, FALSE

you could try this without parameter你可以不带参数试试这个

if($_POST['bool'] == 1)
{
 $bool = "b'1'";
}
else
{
 $bool = "b'0'";
}
$statement = $pdo->prepare("INSERT INTO `test` (SomeText,TestBool) VALUES (?,$bool)") ;
$statement->execute(array("TEST")) ;

and no security problem并且没有安全问题

Just add (bool) before your bit variable, like this:只需在您的位变量之前添加 (bool),如下所示:

$id = 1234;
$active = 0;
$statement = $pdo->prepare('INSERT INTO `test_table` (id, active) VALUES (?, ?)');
$statement->execute([$id, (bool) $active]);

PDOStatement::execute() always converts all arguments to string as shown in the latest php 8.2.0 RC3 source: PDOStatement::execute()始终将所有 arguments 转换为字符串,如最新的 php 8.2.0 RC3 源所示:

https://github.com/php/php-src/blob/615b8006c42715b5ea9ec61f9368582eeef8467f/ext/pdo/pdo_stmt.c#L411-L425 https://github.com/php/php-src/blob/615b8006c42715b5ea9ec61f9368582eeef8467f/ext/pdo/pdo_stmt.c#L411-L425

So what actually happens, is that the whole array values are converted to string which causes the SQL statement to fail.所以实际发生的是,整个数组值都转换为字符串,这导致 SQL 语句失败。

$statement = $pdo->prepare('INSERT INTO `test` (SomeText, TestBool) VALUES (?, ?)');
$statement->execute([
    'TEST',
    false, // converted to string
]);

It can be fixed using PDOStatement::bindValue() as bind value allows to set the argument type.它可以使用PDOStatement::bindValue()修复,因为绑定值允许设置参数类型。

$statement = $pdo->prepare('INSERT INTO `test` (SomeText, TestBool) VALUES (?, ?)');

$statement->bindValue('SomeText', 'TEST', PDO::PARAM_STR);
$statement->bindValue('TestBool', false, PDO::PARAM_BOOL);

$statement->execute();

Obviously, it's more cumbersome than passing the array to execute that's why I created a quick and dirty helper to simplify the task.显然,它比传递数组来执行更麻烦,这就是为什么我创建了一个快速而肮脏的帮助程序来简化任务的原因。

Note : As mentioned in the comments, do not use it on unvalidated user input as it can cause havoc with queries like:注意:如评论中所述,请勿在未经验证的用户输入上使用它,因为它可能会对以下查询造成严重破坏:

$sql = <<<SQL
DELETE FROM table WHERE email = :email
SQL;

:email is meant to be a string, but if instead false is passed, it will delete all emails from the table. :email是一个字符串,但如果传递的是false ,它将删除表中的所有电子邮件。

$values = [
    'SomeTest' => 'TEST',
    'testBool' => false,
];

bind($query, $values)->execute();

/**
 * Variable to PDO type
 *
 * @param  mixed  $value
 *
 * @return int PDO type
 */
function typeToParam(mixed $value) : int
{
    switch ($type = gettype($value)) {
        case 'boolean':
            return PDO::PARAM_BOOL;

        case 'integer':
            return PDO::PARAM_INT;

        case 'NULL':
            return PDO::PARAM_NULL;

        case 'string':
            return PDO::PARAM_STR;

        default:
            throw new Exception("unsupported type - {$type}");
    }
}

/**
 * Bind values to PDO statement
 *
 * @param  PDOStatement $statement
 * @param  array        $data
 *
 * @return PDOStatement
 */
function bind(PDOStatement $statement, array $data) : PDOStatement
{
    foreach ($data as $key => $value) {
        $statement->bindValue($key, $value, typeToParam($value));
    }

    return $statement;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM