Prepared SQL name collision. The name implicitly is truncated by NAMEDATALEN

From: Alexey Kachalin <kachalin(dot)alexey(at)gmail(dot)com>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Subject: Prepared SQL name collision. The name implicitly is truncated by NAMEDATALEN
Date: 2023-05-23 07:00:11
Message-ID: CAF9fLqtmtPqjLD06gnxFB_oLtx7SKm-gAe+7JNzXQG5iRb=utA@mail.gmail.com
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hello,

I would like to report a bug.

# Title.
Prepared SQL name collision. The name implicitly is truncated by
NAMEDATALEN

# Description.
Prepared sql collision can occur when 2 conditions are met:
1. Prepared SQL name is longer than 63 characters.
2. Both sql names have the same first 63 characters.

This issue happens because Postgres silently truncates prepared SQL names
to 63 characters.
According to documentation the length of 63 characters is defined in
NAMEDATALEN constant.
In the second example two different SQLs return the same result. It's
almost impossible to detect a problem.
A prepared SQL can return unpredictable results based on the time which SQL
was prepared first.
Or return an error that is not related to the prepared SQL name.
Also it's possible to miss the warning message of double prepared SQL. Most
of the time errors are related to a difference between collided SQL.

The roots of this behaviour are hard detectable on application level.
Is it possible to know which value was used at compilation time from
application code?

# Desirable behaviour.
If an identifier exceeds the length defined in NAMEDATALEN an error is
thrown, neither silently truncating an identifier.

# Version info
Postgres version 15.3
Php version: 8.2.6

#Code examples on PHP scripts.
<?php

$host = '';
$db = '';
$port = '5432';
$user = '';
$pass = '';

$connectString = "host=$host port=$port dbname=$db user=$user
password=$pass";
$pg_pconnect = pg_pconnect($connectString);

$string63 =
'5c6b58ebdd4464734a57a87431ba24b38d2e49ae5c6b58ebdd4464734a57a87';
//$string63 = 'smallLengthSQL';// Uncomment for normal behaviour and
expected result.

# Uncomment to check an example #1
//$sqlPreparedNameA = $string63 . '_A';
//$sqlPreparedNameB = $string63 . '_B';
//$sqlPreparedBodyA = 'SELECT $1 as result_1' ;
//$sqlPreparedBodyB = 'SELECT $1 as result_1, $2 as result_2';
//
//$pg_prepareA = pg_prepare($pg_pconnect, $sqlPreparedNameA,
$sqlPreparedBodyA);
//$pg_prepareB = pg_prepare($pg_pconnect, $sqlPreparedNameB,
$sqlPreparedBodyB);
//
//$pg_executeA = pg_execute($pg_pconnect, $sqlPreparedNameA, array("Result
A1" ));
//$pg_executeB = pg_execute($pg_pconnect, $sqlPreparedNameB, array("Result
B1", "Result B2"));
//
//$resultA = pg_fetch_all($pg_executeA);
//$resultB = pg_fetch_all($pg_executeB);
//
//var_dump($resultA, $resultB);
//exit();
/*
*
*
* Expected output
array(1) {
[0]=>
array(1) {
["result_1"]=>
string(9) "Result A1"
}
}
array(1) {
[0]=>
array(2) {
["result_1"]=>
string(9) "Result B1"
["result_2"]=>
string(9) "Result B2"
}
}
*
*
*
* Gotten output
Warning: pg_prepare(): Query failed: ERROR: prepared statement
"5c6b58ebdd4464734a57a87431ba24b38d2e49ae5c6b58ebdd4464734a57a87_B" already
exists
Warning: pg_execute(): Query failed: ERROR: bind message supplies 2
parameters, but prepared statement
"5c6b58ebdd4464734a57a87431ba24b38d2e49ae5c6b58ebdd4464734a57a87_B"
requires 1
Fatal error: Uncaught TypeError: pg_fetch_all(): Argument #1 ($result) must
be of type PgSql\Result, bool given
*/
/*
*
*
* Expected output
array(1) {
[0]=>
array(1) {
["result_1"]=>
string(9) "Result A1"
}
}
array(1) {
[0]=>
array(2) {
["result_1"]=>
string(9) "Result B1"
["result_2"]=>
string(9) "Result B2"
}
}
*
*
*
* Gotten output
Warning: pg_prepare(): Query failed: ERROR: prepared statement
"5c6b58ebdd4464734a57a87431ba24b38d2e49ae5c6b58ebdd4464734a57a87_B" already
exists
Warning: pg_execute(): Query failed: ERROR: bind message supplies 2
parameters, but prepared statement
"5c6b58ebdd4464734a57a87431ba24b38d2e49ae5c6b58ebdd4464734a57a87_B"
requires 1
Fatal error: Uncaught TypeError: pg_fetch_all(): Argument #1 ($result) must
be of type PgSql\Result, bool given
*/

# Uncomment to check an example #2
$sqlPreparedNameA = $string63 . '_A';
$sqlPreparedNameB = $string63 . '_B';
$sqlPreparedBodyA = 'SELECT 111 as result_1';
$sqlPreparedBodyB = 'SELECT 222 as result_1';

$pg_prepareA = pg_prepare($pg_pconnect, $sqlPreparedNameA,
$sqlPreparedBodyA);
$pg_prepareB = pg_prepare($pg_pconnect, $sqlPreparedNameB,
$sqlPreparedBodyB);

$pg_executeA = pg_execute($pg_pconnect, $sqlPreparedNameA, []);
$pg_executeB = pg_execute($pg_pconnect, $sqlPreparedNameB, []);

$resultA = pg_fetch_all($pg_executeA);
$resultB = pg_fetch_all($pg_executeB);

var_dump($resultA, $resultB);
exit();
/*
*
* Expected output
array(1) {
[0]=>
array(1) {
["result_1"]=>
string(3) "111"
}
}
array(1) {
[0]=>
array(1) {
["result_1"]=>
string(3) "222"
}
}
*
*
*
* Gotten output
<br />
<b>Warning</b>: pg_prepare(): Query failed: ERROR: prepared statement
&quot;5c6b58ebdd4464734a57a87431ba24b38d2e49ae5c6b58ebdd4464734a57a87_B&quot;
already exists
array(1) {
[0]=>
array(1) {
["result_1"]=>
string(3) "111"
}
}
array(1) {
[0]=>
array(1) {
["result_1"]=>
string(3) "111"
}
}
*/

--
Best regards,
Alexey Kachalin.

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Julien Rouhaud 2023-05-23 07:04:19 Re: BUG #17939: CREATE EXTENSION pltcl; looks in the wrong folder
Previous Message PG Bug reporting form 2023-05-23 06:47:19 BUG #17939: CREATE EXTENSION pltcl; looks in the wrong folder