Skip to content

BUG: fbird_query() stub declares Firebird\ResultSet return type but C code returns raw resource #296

Description

@satwareAG-ironMike

Description

The fbird_query() function stub in stubs/firebird-stubs.php declares:

function fbird_query(mixed $link_or_query, mixed ...$args): \Firebird\ResultSet|int|bool {}

But the C implementation in fbird_query_exec.c (PHP_FUNCTION(fbird_query), line 1011) returns a raw resource for SELECT queries, not a Firebird\ResultSet object.

This is a stub/C mismatch — the only function in the M3 migration where the declared return type doesn't match the runtime behavior.

Root Cause

PHP_FUNCTION(fbird_execute) (line 1382) correctly wraps the result in a Firebird\ResultSet object:

// fbird_query_exec.c:1430-1437 (inside PHP_FUNCTION(fbird_execute))
/* M3 Phase G: wrap le_query result in Firebird\ResultSet */
if (Z_TYPE_P(return_value) == IS_RESOURCE &&
    Z_RES_TYPE_P(return_value) == le_query) {
    zend_resource *_res = Z_RES_P(return_value);
    fbird_setup_resultset_object(return_value, _res);
}

But PHP_FUNCTION(fbird_query) (line 1011) never calls fbird_setup_resultset_object(). After _php_fbird_exec() returns (which sets return_value via RETVAL_RES() at lines 509, 677, or 877), the function does ownership transfer (lines 1190-1210) and returns — leaving return_value as a raw resource.

Verification

<?php
$conn = fbird_connect('localhost:/var/lib/firebird/data/test.fdb', 'SYSDBA', 'masterkey');

// fbird_query() — returns resource (WRONG: stub says Firebird\ResultSet)
$r1 = fbird_query($conn, 'SELECT 1 FROM RDB$DATABASE');
var_dump(gettype($r1));        // string(8) "resource"
var_dump($r1 instanceof \Firebird\ResultSet);  // bool(false)

// fbird_execute() — returns object (CORRECT)
$stmt = fbird_prepare($conn, 'SELECT 1 FROM RDB$DATABASE');
$r2 = fbird_execute($stmt);
var_dump(gettype($r2));        // string(6) "object"
var_dump($r2 instanceof \Firebird\ResultSet);  // bool(true)

fbird_close($conn);

Impact

  1. Static analysis (PHPStan/Psalm) trusts the stub and assumes fbird_query() returns Firebird\ResultSet. Code that checks is_resource() on the return value is flagged as "always false" — but at runtime it's true.

  2. doctrine-firebird-driver cannot use instanceof \Firebird\ResultSet for result validation — it must use the broader is_object() check, which also accepts unrelated objects.

  3. Consistency: fbird_execute() wraps results in objects but fbird_query() doesn't, despite both calling the same _php_fbird_exec() internally. This forces consumers to handle the two functions differently.

Suggested Fix

Add the same wrapping block that PHP_FUNCTION(fbird_execute) uses, at the end of PHP_FUNCTION(fbird_query) (after the ownership transfer block, before the final efree(args)):

// In PHP_FUNCTION(fbird_query), after the existing ownership transfer
// block (around line 1210), add:

/* M3: wrap le_query result in Firebird\ResultSet (same as fbird_execute) */
if (Z_TYPE_P(return_value) == IS_RESOURCE &&
    Z_RES_TYPE_P(return_value) == le_query) {
    zend_resource *_res = Z_RES_P(return_value);
    fbird_setup_resultset_object(return_value, _res);
}

The same block should also be added to:

  • PHP_FUNCTION(fbird_execute_query) (line 1589) — after the ownership transfer block
  • PHP_FUNCTION(fbird_query_params_tx) (line 1784) — after the ownership transfer block
  • PHP_FUNCTION(fbird_execute_auto) (line 1661) — before the ZVAL_COPY_VALUE restore

Environment

  • php-firebird v11.0.1 (tag v11.0.1, commit 58775ee)
  • PHP 8.4.22
  • Firebird 3.0.14

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions