【问题标题】:'PDOException' with message 'SQLSTATE[22001]: String data, right truncated: 0带有消息“SQLSTATE [22001]”的“PDOException”:字符串数据,右截断:0
【发布时间】:2016-11-10 08:57:49
【问题描述】:

注意:我已将此问题范围缩小到具体的 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 服务器正在 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,如this answer 所示:

$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());

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

字符串数据,右截断

【问题讨论】:

  • 您是否启用了 ATTR_EMULATE_PREPARES?如果你改变它会有什么不同吗?如果您使用像 SELECT :param as col 这样的命名参数并绑定 [':param' => 'test'] 会有所不同吗?我不希望改变......
  • @MichaelBerkowski 我只是尝试了这两个都无济于事-同样的错误。
  • “this”文件是什么类型的文件?
  • @zajonc 这是一个字符串文字,因此其他人无需数据集即可轻松复制。我的实际用例涉及 NVARCHAR(255),但我无法让它适用于任何数据类型。
  • 我想这完全是 DBMS 问题,与 PDO 关系不大。出于好奇,您也可以使用全静态查询进行重现吗?我的意思是'this' = 'this'?

标签: php sql-server pdo odbc prepared-statement


【解决方案1】:

很遗憾,

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

幸运的是,

It has a patch 首次包含在 5.6 版本中:

这个错误也被引用 #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;

我们看到唯一改变的是SDWORD(16 位有符号整数),它被替换为新的 ODBC 类型SQLULEN,即64 bits in a 64-bit ODBC application and 32 bits in a 32-bit ODBC application。

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

我现在该怎么办?

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

【讨论】:

  • Wow 64824 是我遇到的错误。我觊觎你的 Google-Fu,因为我从来没有接近过找到它。恐怕该补丁是为 61777 中引用的一个稍微不同的错误构建的,但我仍然渴望尝试。我可能需要几天时间来启动一个测试虚拟机以使用该补丁构建自定义 PDO,但我很高兴谢谢!
  • 我将该补丁与 PHP 7.0 相关的源文件进行了比较,可以说你会做对。 @JeffPuckettII
  • 今天我用php7在vm上安装了一个全新的ubuntu 16.04,并确认不存在这个问题。我编辑了您的答案以包含此选项。明天我会用php5启动另一个vm并用补丁编译以完全确认你的答案,如果它成功了,那么我很乐意接受你的答案。
  • 我没有将Upgrade your PHP 添加到列表中,因为我猜你没有考虑这个选项。所以如果你能做到,那你完全没问题。 PHP 版本 > 5.5.x 不会出现此问题。 @JeffPuckettII
  • 确认。我克隆了 php 源代码,检查了 5.5.9 版,编译并安装以验证错误。然后我挑选了那个提交,重新编译并安装并完全验证了修复了这个错误。
【解决方案2】:

这可能不是你想听到的,但它具有 PHP 的 PDO ODBC 驱动程序中的一个错误的所有特征(由于 PHP 程序员倾向于偏爱 MySQL/SQLite/Postgres 等开源数据库而不被大量使用商业产品),或在底层 SQL 服务器驱动程序中(由于类似原因,在 Linux 中支持不佳),但如果 odbc_* 工作,那么它可能不是底层驱动程序。

如果您尝试执行完全相同的任务,除了使用 "sqlite::memory:" 作为 DSN 之外,您的所有示例都有效。这使得您不太可能做错任何事情(除非 MS Server 有一些我不知道的非常奇怪的不符合标准的 SQL 语法)。当ATTR_EMULATE_PREPARES 被启用和禁用时,你的例子对我来说很好用。

我认为您实际上可以做的就是file a bug report 并希望有人接受它。不过,您可能要等很长时间。

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

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

编辑:我发现another question on Stack Overflow 提问者似乎有类似的问题。他的解决方案是放弃“官方”MS 驱动程序,转而使用 FreeTDS。这可能是在黑暗中拍摄的东西,但值得一试。

【讨论】:

    猜你喜欢
    • 2013-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-08
    • 2013-12-24
    • 2014-11-15
    • 2012-06-29
    • 1970-01-01
    相关资源
    最近更新 更多