php / php/php-src

PDO_Firebird returns null for non-null empty BLOBs

未关闭 适合新手
#23,758 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

Bug Status: Needs Triage
主要语言
C
星标
40.4k
派生
8.1k
平均合并
2 天 13 小时
30 天内合并 PR
96

描述

Description

Description

PDO_Firebird returns PHP null when fetching a non-NULL BLOB whose length is zero.

Expected behavior: An empty BLOB should return an empty PHP string (''), while SQL NULL should return PHP null.

Actual behavior: Both values return PHP null, although SQL expressions confirm that Firebird stores them distinctly:

  • SQL NULL: IS NULL = true, OCTET_LENGTH = NULL
  • Empty BLOB: IS NULL = false, OCTET_LENGTH = 0

This loses information during retrieval. Applications cannot distinguish an absent value from an explicitly stored empty value.

The behavior occurs with both text and binary BLOBs, after INSERT and UPDATE, using string or stream input. It reproduces with direct PDO, without Laravel or another framework.

Reproducer

Requires an existing Firebird database and PDO_Firebird. Set FB_DSN, FB_USER, and FB_PASSWORD as appropriate.

<?php

// Example: FB_DSN='firebird:dbname=localhost/3055:/path/database.fdb;charset=UTF8'
// FB_USER=sysdba FB_PASSWORD=... php pdo_firebird_empty_blob.php
$pdo = new PDO(
getenv('FB_DSN') ?: 'firebird:dbname=localhost/3055:/var/lib/firebird/data/database.fdb;charset=UTF8',
getenv('FB_USER') ?: 'sysdba',
getenv('FB_PASSWORD') ?: 'masterkey',
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL]
);
$table = 'BLOB_REPRO_'.bin2hex(random_bytes(6));
$created = false;
$stmt = null;

try {
$pdo->exec("CREATE TABLE $table (ID INTEGER, PAYLOAD BLOB SUB_TYPE BINARY)");
$created = true;
$stmt = $pdo->prepare("INSERT INTO $table (ID, PAYLOAD) VALUES (?, ?)");
foreach ([null, '', "\0"] as $id => $value) {
$stmt->bindValue(1, $id, PDO::PARAM_INT);
$stmt->bindValue(2, $value, $value === null ? PDO::PARAM_NULL : PDO::PARAM_STR);
$stmt->execute();
}
$stmt = null;

$stmt = $pdo-&gt;query("SELECT ID, CASE WHEN PAYLOAD IS NULL THEN 1 ELSE 0 END,
    OCTET_LENGTH(PAYLOAD), PAYLOAD FROM $table ORDER BY ID");
while ($row = $stmt-&gt;fetch(PDO::FETCH_NUM)) {
    $value = $row[3] === null ? 'NULL' : 'string('.strlen($row[3]).') hex='.bin2hex($row[3]);
    printf("%s: IS NULL=%s; OCTET_LENGTH=%s; PHP value=%s\n",
        ['SQL NULL', 'Empty BLOB', 'NUL byte'][(int) $row[0]],
        $row[1] ? 'true' : 'false',
        $row[2] === null ? 'NULL' : $row[2],
        $value
    );
}

} finally {
// Release prepared statements before dropping the table (Firebird metadata locks).
$stmt = null;
if ($created) {
$pdo->exec("DROP TABLE $table");
}
}

Actual output

SQL NULL: IS NULL=true; OCTET_LENGTH=NULL; PHP value=NULL
Empty BLOB: IS NULL=false; OCTET_LENGTH=0; PHP value=NULL
NUL byte: IS NULL=false; OCTET_LENGTH=1; PHP value=string(1) hex=00

Expected output

SQL NULL: IS NULL=true; OCTET_LENGTH=NULL; PHP value=NULL
Empty BLOB: IS NULL=false; OCTET_LENGTH=0; PHP value=string(0) hex=
NUL byte: IS NULL=false; OCTET_LENGTH=1; PHP value=string(1) hex=00

Tested versions

Earlier framework-free PDO diagnostics reproduced the behavior with:

PHP Firebird server Firebird client Platform
8.2.33 4.0.7 and 5.0.4 3.0.11 Linux
8.3.33 4.0.7 and 5.0.4 3.0.11 Linux
8.4.25 4.0.7 and 5.0.4 3.0.11 Linux
8.5.10 4.0.7 and 5.0.4 3.0.11 Linux

The exact minimal reproducer above was additionally verified with PHP 8.5.2, Firebird client 5.0.3, on macOS, against both server versions.

FETCH_ASSOC, FETCH_OBJ, and FETCH_NUM show the same behavior. Using bindColumn(..., PDO::PARAM_LOB) with FETCH_BOUND returns streams for nonempty BLOBs, but still returns null for empty BLOBs.

PDO::NULL_TO_STRING is not a solution because it also converts genuine SQL NULL values to empty strings.

Source analysis

In ext/pdo_firebird/firebird_statement.c, the relevant function is php_firebird_fetch_blob() in PHP 8.4/8.5, named firebird_fetch_blob() in PHP 8.2/8.3.

The function assigns a string to the result inside if (len). When the BLOB length is zero, that assignment is skipped, while the function still returns success.

PDO initializes the destination to NULL before invoking the driver's column-fetch callback in ext/pdo/pdo_stmt.c. This appears to explain the observed result.

PHP Version
PHP 8.5.2 (cli) (built: Jan 13 2026 21:40:53) (NTS)
Copyright (c) The PHP Group
Built by Homebrew
Zend Engine v4.5.2, Copyright (c) Zend Technologies
    with Zend OPcache v8.5.2, Copyright (c), by Zend Technologies
Operating System

macOS

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

调研方向

从 ext/pdo_firebird/firebird_statement.c 中的 php_firebird_fetch_blob() 开始(或在 PHP 8.2/8.3 中从 firebird_fetch_blob() 开始),然后检查 ext/pdo/pdo_stmt.c 中的目标初始化。针对 Firebird 运行提供的 PDO reproducer,并验证对于所描述的所有 fetch 模式,SQL NULL 仍为 PHP null,而空 BLOB 会变为空长度字符串。

由索引模型根据 Issue 内容生成。

评估

技术栈
c, php
领域
backend, database
Issue 类型
缺陷
难度
2/5
预计耗时
1-3 小时
活跃度
活跃
描述清晰度
描述清楚
新手友好度
76/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。