PDO_Firebird returns null for non-null empty BLOBs
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 76/100
Rechercherichtung
Beginne in ext/pdo_firebird/firebird_statement.c bei php_firebird_fetch_blob() (oder bei firebird_fetch_blob() unter PHP 8.2/8.3) und untersuche anschließend die Initialisierung des Ziels in ext/pdo/pdo_stmt.c. Führe den bereitgestellten PDO-Reproducer mit Firebird aus und verifiziere, dass SQL NULL als PHP null erhalten bleibt, während ein leerer BLOB für alle beschriebenen Fetch-Modi zu einer Zeichenkette der Länge null wird.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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->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->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=00Expected 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=00Tested 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
- Vorherrschende Sprache
- C
- Sterne
- 40.4k
- Forks
- 8.2k
- Ø Merge
- 2 T. 14 Std.
- Gemergte PRs (30 T.)
- 112
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus php/php-src
-
Bug SAPI: cli_server Status: Verified
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
-
Bug Status: Needs Triage
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 90/100
-
Bug Status: Needs Triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
-
Flaky hrtime.phpt test OffenBug Category: Tests Status: Verified
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
-
Bug SAPI: fpm Status: Needs Triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
Ähnliche Issues
-
level/task module/gcp type/bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 86/100
hapostgres/pg_auto_failover#1190 ·
-
docs
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 85/100
-
P3 sonic-vpp
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
sonic-net/sonic-buildimage#29662 ·
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 94/100
spack/spack-packages#6586 ·