microsoft / msphpsql

Microsoft Drivers for PHP for SQL Server
MIT License
1.8k stars 371 forks source link

Fatal error: Invalid column number in pdo_sqlsrv_stmt_get_col_data #1466

Open kicken opened 1 year ago

kicken commented 1 year ago

PHP version 8.1.10

PHP SQLSRV or PDO_SQLSRV version PDO_SQLSRV 5.11.0

Microsoft ODBC Driver version 2018.182.02.01

SQL Server version Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) Sep 24 2019 13:48:23 Copyright (C) 2019 Microsoft Corporation Developer Edition (64-bit) on Windows 10 Pro 10.0 (Build 22621: ) (Hypervisor)

Client operating system Windows 11 22H2

Problem description
Attempting to re-use a prepared statement object that returns multiple result sets fails on subsequent executions.

In some instances, PHP exists with the fatal error:

Fatal error: Invalid column number in pdo_sqlsrv_stmt_get_col_data in

In other instances, the PHP executable hard-crashes

Faulting application name: php.exe, version: 8.1.10.0, time stamp: 0x630e529d
Faulting module name: php8.dll, version: 8.1.10.0, time stamp: 0x630e5af2
Exception code: 0xc0000005
Fault offset: 0x000000000027be4a

In yet other instances, the returned data is simply incorrect. The result of the first result set is returned with the column names of the last result set.

Expected behavior and actual behavior
No error or crash.

Repro code or steps to reproduce

<?php
var_dump(PHP_VERSION);
$db = new PDO('sqlsrv:server=localhost;Database=tempdb;TrustServerCertificate=1;Encrypt=1;Driver=ODBC Driver 18 for SQL Server');
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$sql = /** @lang TSQL */
    '
set nocount on;
declare @id int = :id;
-- Result set 1
SELECT
        @id AS r1int1
        , 2 as r1int2
        , 3 as r1int3
        , 4 as r1int4
        , N\'Test\' AS r1str1
        , N\'test\' AS r1str2
        , 5 AS r1int5
        , SYSUTCDATETIME() as r1dt1
where @id in (2,3);

-- Result set 2
select
        @id as r2int1
        , 1 AS r2int6
        , N\'/test.txt\' AS r2str3
        , N\'test.txt\' AS r2str4
        , N\'text/plain\' AS r2str5
where @id in (2,3);
';
$stmt = $db->prepare($sql);

$idList = [2, 1, 3];
foreach ($idList as $id){
    $stmt->bindValue(':id', $id);
    $stmt->execute();

    //Fetch result set 1
    foreach ($stmt as $row){
        if (!array_key_exists('r1int1', $row)){
            throw new \RuntimeException('Result set 1 row is not as expected.');
        }
    }

    //Fetch result set 2
    $stmt->nextRowset();
    foreach ($stmt as $row){
        if (!array_key_exists('r2int1', $row)){
            throw new \RuntimeException('Result set 2 row is not as expected.');
        }
    }

    $stmt->closeCursor();
}

The above code results in a fatal error or hard-crash when run on my setup.

Changing the SQL query to the following results in the corrupted result rows (triggering the RuntimeException).

$sql = /** @lang TSQL */
    '
set nocount on;
declare @id int = :id;
-- Result set 1
SELECT
        @id AS r1int1
        , 2 as r1int2
        , N\'Test\' AS r1str1
        , N\'test\' AS r1str2
where @id in (2,3);

-- Result set 2
select
        @id as r2int1
        , 1 AS r2int5
        , N\'/test.txt\' AS r2str3
        , N\'test.txt\' AS r2str4
        , N\'text/plain\' AS r2str5
where @id in (2,3);
';
v-makouz commented 1 year ago

I was able to reproduce it, looking into it now.

v-makouz commented 1 year ago

This looks like it's happening due to the code in PHP extension rather than the driver itself, but I'm looking if the there is a driver command that fix this issue.