msphpsql icon indicating copy to clipboard operation
msphpsql copied to clipboard

Warning incorrectly captured as error message when throwing PDOException

Open IMSoP opened this issue 2 years ago • 1 comments

PHP version
8.0.19 (also reproduced on 7.4.30)

PHP SQLSRV or PDO_SQLSRV version
pdo_sqlsrv 5.10.1

Microsoft ODBC Driver version
unixODBC 2.3.7 [ODBC Driver 17 for SQL Server]

SQL Server version Microsoft SQL Server 2016 (SP2) (KB4052908) - 13.0.5026.0 (X64)

Client operating system
Ubuntu Linux 18.04

Table schema
N/A

Problem description
When a PDOException is thrown from a query or stored procedure with multiple statements, if a Warning was issued before the error, this becomes the "message" of the exception object, even though it is not the cause of the exception.

This means the real error message is not visible from logs which rely on $exception->getMessage()

Note that the correct message is available in the errorInfo property of the exception, but after the warning; it appears that the driver is incorrectly assuming that the first message is the cause of the error, rather than checking the severity of the messages returned.

Expected behavior and actual behavior
The below code should output the same error message for both queries:

Warning only (does not throw exception):
Error only (correct case):
SQLSTATE[23000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot insert the value NULL into column 'NotNullable', table 'tempdb.dbo.#Foo________________________________________________________________________________________________________________0000047B16E0'; column does not allow nulls. INSERT fails.
Warning and then error (incorrect case):
SQLSTATE[23000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot insert the value NULL into column 'NotNullable', table 'tempdb.dbo.#Foo________________________________________________________________________________________________________________0000047B16E0'; column does not allow nulls. INSERT fails.

Instead it outputs a different message for the second query:

Warning only (does not throw exception):
Error only (correct case):
SQLSTATE[23000]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Cannot insert the value NULL into column 'NotNullable', table 'tempdb.dbo.#Foo________________________________________________________________________________________________________________0000047B16E0'; column does not allow nulls. INSERT fails.
Warning and then error (incorrect case):
SQLSTATE[01003]: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Warning: Null value is eliminated by an aggregate or other SET operation.

Repro code or steps to reproduce

$hostname = '...';
$username = '...';
$password = '...';

$dsn = 'sqlsrv:Server=' . $hostname . ';Database=master;MultipleActiveResultSets=false';

$pdo = new \PDO($dsn, $username, $password);

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->setAttribute(PDO::SQLSRV_ATTR_DIRECT_QUERY, true);

$pdo->query('Create Table #Foo (Nullable Int Null, NotNullable Int Not Null)');
$pdo->query('Insert Into #Foo (Nullable, NotNullable) Values (Null, 1)');

echo "Warning only (does not throw exception):\n";
try {
        $stmt = $pdo->query('
                Select Max(Nullable) as Example From #Foo
                Insert Into #Foo (Nullable, NotNullable) Values (Null, 2)
        ');
        $stmt->closeCursor();
}
catch ( PDOException $e ) {
        echo $e->getMessage(), "\n";
}

echo "Error only (correct case):\n";
try {
        $stmt = $pdo->query('
                Select 1 as Example
                Insert Into #Foo (Nullable, NotNullable) Values (Null, Null)
        ');
        $stmt->closeCursor();
}
catch ( PDOException $e ) {
        echo $e->getMessage(), "\n";
}

echo "Warning and then error (incorrect case):\n";
try {
        $stmt = $pdo->query('
                Select Max(Nullable) From #Foo
                Insert Into #Foo (Nullable, NotNullable) Values (Null, Null)
        ');
        $stmt->closeCursor();
}
catch ( PDOException $e ) {
        echo $e->getMessage(), "\n";
}

$stmt = $pdo->query('Select * From #Foo');
$stmt->closeCursor();

$pdo->query('Drop Table #Foo');

IMSoP avatar Jul 01 '22 15:07 IMSoP

Thanks for the details, we'll investigate it.

absci avatar Jul 13 '22 18:07 absci

I reviewed this issue, if $pdo->query contain multiple queries, all the warnings and errors will go to errorInfo. getMessage() is a function from PHP, the driver won't be able to change it. Looks like it only print out the first message from errorInfo. It's probably better to not rely on getMessage().

absci avatar Aug 03 '23 23:08 absci