带有消息“SQLSTATE[22001]的”PDOException“:字符串数据,右截断:0

2022-08-30 17:15:46

注意:我已将此问题缩小到特定的 PDO,因为我能够使用 odbc_* 函数成功准备和执行语句。

为什么我无法将此参数绑定到 PDO 预准备语句?

这有效:

$mssqldriver = 'ODBC Driver 13 for SQL Server';
$pdoDB = new PDO("odbc:Driver=$mssqldriver;Server=$hostname;Database=$dbname", $username, $password);
$pdoDB->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

$sql = "SELECT 'value' AS col where 'this' = 'this'";
$stmt = $pdoDB->prepare($sql);
$params = [];
$stmt->execute($params);
print_r($stmt->fetch());
Array ( [col] => value [0] => value )

不起作用:

$sql = "SELECT 'value' AS col where 'this' = ?";
$stmt = $pdoDB->prepare($sql);
$params = ['this'];
$stmt->execute($params);
print_r($stmt->fetch());

Web Server 在 Linux Ubuntu 14.04 上运行 PHP 5.5.9,带有适用于 SQL Server 的 ODBC 驱动程序 13,并在 Windows Server 2012 上连接到 Microsoft SQL Server 2012

这是完整的错误:

Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[22001]:
String data, right truncated: 0
[Microsoft][ODBC Driver 13 for SQL Server]
String data, right truncation
(SQLExecute[0] at /build/buildd/php5-5.5.9+dfsg/ext/pdo_odbc/odbc_stmt.c:254)' in /var/www/scratch.php:46
Stack trace:
#0 /var/www/scratch.php(46): PDOStatement->execute(Array)
#1 {main} thrown in /var/www/scratch.php on line 46

我也尝试过设置:

$pdoDB->setAttribute( PDO::ATTR_EMULATE_PREPARES, true );

并使用命名参数:

$sql = "SELECT 'value' AS col where 'this' = :myVal";
$stmt = $pdoDB->prepare($sql);
$params = ['myVal' => 'this'];
$stmt->execute($params);
print_r($stmt->fetch());

即使使用显式冒号:

$params = [':myVal' => 'this'];

我还尝试只使用 bindParam,如以下答案所示:

$sql = "SELECT 'value' AS col where 'this' = ?";
$stmt = $pdoDB->prepare($sql);
$param = 'this';
$stmt->bindParam(1, $param);
$stmt->execute();
print_r($stmt->fetch());

以及命名参数:

$sql = "SELECT 'value' AS col where 'this' = :myVal";
$stmt = $pdoDB->prepare($sql);
$param = 'this';
$stmt->bindParam(':myVal', $param, PDO::PARAM_STR);
$stmt->execute();
print_r($stmt->fetch());

如果我尝试显式设置长度:

$stmt->bindParam(':myVal', $param, PDO::PARAM_STR, 4);

我收到一个奖金错误:

Fatal error: Uncaught exception 'PDOException' with message
'SQLSTATE[42000]: Syntax error or access violation: 102
[Microsoft][ODBC Driver 13 for SQL Server][SQL Server]
Incorrect syntax near 'OUTPUT'.

是的,所有这些都是一个没有表格的琐碎例子,所以你可以很容易地重现它,但可以肯定的是,我实际上已经用一个真正的表格尝试了这个。

CREATE TABLE myTable (
    id INT IDENTITY PRIMARY KEY,
    val NVARCHAR(255)
);
INSERT INTO myTable (val) VALUES ('hello world');

工程:

$sql = "SELECT * FROM myTable WHERE val = 'hello world'";
$stmt = $pdoDB->prepare($sql);
$params = [];
$stmt->execute($params);
print_r($stmt->fetch());
Array ( [id] => 1 [0] => 1 [val] => hello world [1] => hello world )

不起作用:

$sql = "SELECT * FROM myTable WHERE val = ?";
$stmt = $pdoDB->prepare($sql);
$params = ['hello world'];
$stmt->execute($params);
print_r($stmt->fetch());

所有路径都会导致相同的错误:

字符串数据,右截断


答案 1

不幸

这是一个64位不兼容问题(#61777#64824),毫无疑问,您使用的是64位构建,不允许绑定参数。PDO_ODBC

幸运

它有一个补丁,最初包含在5.6版本中:

此 bug 在 #61777 中也有引用,并且仍然存在于 5.5 分支的最新稳定版本中。我看到这个问题已经存在两个票证,我只是通过github提交这些更改,以提醒人们,对于在x64版本上使用的人来说,这都是一个严重的问题。PDO_ODBC

您的 PHP 发货有什么问题?PDO_ODBC

通过查看其中一个推荐的补丁:

diff --git a/ext/pdo_odbc/odbc_stmt.c b/ext/pdo_odbc/odbc_stmt.c
index 8b0ccf3..1d275cd 100644
--- a/ext/pdo_odbc/odbc_stmt.c
+++ b/ext/pdo_odbc/odbc_stmt.c
@@ -551,7 +551,7 @@ static int odbc_stmt_describe(pdo_stmt_t *stmt, int colno TSRMLS_DC)
    struct pdo_column_data *col = &stmt->columns[colno];
    RETCODE rc;
    SWORD   colnamelen;
-   SDWORD  colsize;
+   SQLULEN colsize;
    SQLLEN displaysize;

我们看到唯一更改的是(16 位有符号整数),它被替换为新的 ODBC 类型,该类型在 64 位 ODBC 应用程序中为 64 位,在 32 位 ODBC 应用程序中为 32 位SDWORDSQLULEN

我相信提交者不知道数据类型,因为在下一行中正确定义了。colsizeSQLLEN

我现在该怎么办?

  1. 升级到 PHP 版本 > = 5.6
  2. 坚持使用功能作为工作解决方案。odbc_*
  3. 使用提供的补丁编译 PHP v5.5.9。
  4. 按照@GordonM的建议构建自己的 PDO 包装器

答案 2

这可能不是你想听到的,但这具有PHP的PDO ODBC驱动程序(由于PHP程序员倾向于喜欢像MySQL / SQLite / Postgres这样的开源数据库而不是商业产品)或底层SQL服务器驱动程序(在Linux中支持性较差,因此没有被大量使用)的所有错误特征。 出于类似的原因),尽管如果odbc_*工作正常,那么它可能不是底层驱动程序。

如果您尝试执行完全相同的任务,除了用作DSN之外,则所有示例都可以正常工作。这使得你不太可能做错任何事情(除非MS Server有一些非常奇怪的不合规的SQL语法,我不知道)。您的示例在启用和禁用时都可以使用SQLite为我工作正常。"sqlite::memory:"ATTR_EMULATE_PREPARES

我认为你所能做的就是提交一个错误报告,并希望有人把它捡起来。不过,您可能会等待很长时间。

至于问题的实际解决方案,您的选择是a)切换到PHP支持的DBMS,或者b)诉诸SQL字符串构造而不是准备好的语句,并准备好自己承担避免SQL注入攻击的负担。不过,这应该被认为是最后的手段!使用PDO::quote()可能会有所帮助,但我也会确保你的数据也经过非常彻底的验证。我知道这不是一个理想的解决方案,但是如果你必须使用MS SQL并且迫不及待地想让PHP团队修复它,那么我真的看不出你有太多的选择。

或者有选项c)如果它们有效,则使用odbc_*函数。当然,如果你想使用OOP样式,那么你必须实现你自己的类,它将过程odbc函数包装在OO方法中,这样可能会做很多工作。

编辑:我在Stack Overflow上发现了另一个问题,其中提问者似乎有类似的问题。他的解决方案是放弃“官方”MS驱动程序,转而支持FreeTDS。这可能是在黑暗中拍摄的,但值得一试。


推荐