diff --git a/src/QueryReflection/BasePdoQueryReflector.php b/src/QueryReflection/BasePdoQueryReflector.php index 69ed1875..c46492a1 100644 --- a/src/QueryReflection/BasePdoQueryReflector.php +++ b/src/QueryReflection/BasePdoQueryReflector.php @@ -34,6 +34,8 @@ abstract class BasePdoQueryReflector implements QueryReflector, RecordingReflect private const MYSQL_UNKNOWN_TABLE = '42S02'; + private const MYSQL_INTEGRITY_CONSTRAINT_VIOLATION = '23000'; + private const PDO_SYNTAX_ERROR_CODES = [ self::MYSQL_SYNTAX_ERROR_CODE, self::PSQL_SYNTAX_ERROR, @@ -48,6 +50,7 @@ abstract class BasePdoQueryReflector implements QueryReflector, RecordingReflect self::MYSQL_SYNTAX_ERROR_CODE, self::MYSQL_UNKNOWN_COLUMN_IN_FIELDLIST, self::MYSQL_UNKNOWN_TABLE, + self::MYSQL_INTEGRITY_CONSTRAINT_VIOLATION, ]; protected const MAX_CACHE_SIZE = 50; diff --git a/src/QueryReflection/MysqliQueryReflector.php b/src/QueryReflection/MysqliQueryReflector.php index 663db214..e4c6039e 100644 --- a/src/QueryReflection/MysqliQueryReflector.php +++ b/src/QueryReflection/MysqliQueryReflector.php @@ -21,6 +21,8 @@ final class MysqliQueryReflector implements QueryReflector, RecordingReflector private const MYSQL_UNKNOWN_COLUMN_IN_FIELDLIST = 1054; + private const MYSQL_AMBIGUOUS_COLUMN_IN_FROM_CLAUSE = 1052; + public const MYSQL_UNKNOWN_TABLE = 1146; private const MYSQL_INCORRECT_TABLE = 1103; @@ -33,6 +35,7 @@ final class MysqliQueryReflector implements QueryReflector, RecordingReflector private const MYSQL_ERROR_CODES = [ self::MYSQL_SYNTAX_ERROR_CODE, self::MYSQL_UNKNOWN_COLUMN_IN_FIELDLIST, + self::MYSQL_AMBIGUOUS_COLUMN_IN_FROM_CLAUSE, self::MYSQL_UNKNOWN_TABLE, self::MYSQL_INCORRECT_TABLE, ]; diff --git a/tests/default/config/.phpunit-phpstan-dba-mysqli.cache b/tests/default/config/.phpunit-phpstan-dba-mysqli.cache index 6dc7269a..2912da98 100644 --- a/tests/default/config/.phpunit-phpstan-dba-mysqli.cache +++ b/tests/default/config/.phpunit-phpstan-dba-mysqli.cache @@ -73,6 +73,9 @@ FROM ada' => ), 'SELECT adaid FROM ada LIMIT 1 FOR SHARE' => array ( + 'error' => + \staabm\PHPStanDba\Valid::__set_state(array( + )), 'result' => array ( 5 => @@ -123,6 +126,9 @@ FROM ada' => ), 'SELECT adaid FROM ada LIMIT 1 OFFSET 1' => array ( + 'error' => + \staabm\PHPStanDba\Valid::__set_state(array( + )), 'result' => array ( 5 => @@ -133,6 +139,9 @@ FROM ada' => ), 'SELECT adaid FROM ada LIMIT 1 OFFSET 1 FOR UPDATE' => array ( + 'error' => + \staabm\PHPStanDba\Valid::__set_state(array( + )), 'result' => array ( 5 => @@ -223,6 +232,9 @@ FROM ada' => ), 'SELECT akid FROM ak WHERE eadavk>1.0' => array ( + 'error' => + \staabm\PHPStanDba\Valid::__set_state(array( + )), 'result' => array ( 5 => @@ -523,6 +535,9 @@ FROM ada' => ), 'SELECT email, adaid FROM ada WHERE gesperrt = \'1\'' => array ( + 'error' => + \staabm\PHPStanDba\Valid::__set_state(array( + )), 'result' => array ( 5 => @@ -561,6 +576,17 @@ FROM ada' => ), ), ), + 'SELECT first.adaid + FROM ada first + JOIN ada second ON second.adaid = first.adaid + JOIN ada third USING(adaid)' => + array ( + 'error' => + \staabm\PHPStanDba\Error::__set_state(array( + 'message' => 'Column \'adaid\' in from clause is ambiguous', + 'code' => 1052, + )), + ), 'SELECT max(adaid) FROM ada' => array ( 'result' => diff --git a/tests/rules/SyntaxErrorInPreparedStatementMethodRuleTest.php b/tests/rules/SyntaxErrorInPreparedStatementMethodRuleTest.php index 8826cfb6..962fe5cf 100644 --- a/tests/rules/SyntaxErrorInPreparedStatementMethodRuleTest.php +++ b/tests/rules/SyntaxErrorInPreparedStatementMethodRuleTest.php @@ -268,6 +268,26 @@ public function testSyntaxErrorInQueryRule(): void $this->analyse([__DIR__ . '/data/syntax-error-in-prepared-statement.php'], $expectedErrors); } + public function test_prepared_statement_method_reports_ambiguous_using_column(): void + { + if (MysqliQueryReflector::NAME === getenv('DBA_REFLECTOR')) { + if ('mariadb' === $_ENV['DBA_PLATFORM']) { + $expectedError = "Query error: Column 'adaid' in FROM is ambiguous (1052)."; + } else { + $expectedError = "Query error: Column 'adaid' in from clause is ambiguous (1052)."; + } + } elseif (PdoMysqlQueryReflector::NAME === getenv('DBA_REFLECTOR')) { + $expectedError = "Query error: SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'adaid' in from clause is ambiguous (23000)."; + } else { + self::markTestSkipped('This regression only applies to MySQL reflectors.'); + } + + $this->analyse( + [__DIR__ . '/data/ambiguous-using-column.php'], + [[$expectedError, 11]], + ); + } + public function testBug94() { if (MysqliQueryReflector::NAME === getenv('DBA_REFLECTOR')) { diff --git a/tests/rules/config/.phpunit-phpstan-dba-mysqli.cache b/tests/rules/config/.phpunit-phpstan-dba-mysqli.cache index 914c5319..8059ff4d 100644 --- a/tests/rules/config/.phpunit-phpstan-dba-mysqli.cache +++ b/tests/rules/config/.phpunit-phpstan-dba-mysqli.cache @@ -776,6 +776,17 @@ Simulated query: SELECT FROM WHERE LIMIT 0', ), ), ), + 'SELECT first.adaid + FROM ada first + JOIN ada second ON second.adaid = first.adaid + JOIN ada third USING(adaid)' => + array ( + 'error' => + \staabm\PHPStanDba\Error::__set_state(array( + 'message' => 'Column \'adaid\' in from clause is ambiguous', + 'code' => 1052, + )), + ), 'SELECT id FROM table WHERE a=\'1970-01-01\' AND err-or id IN (\'1\')' => array ( 'error' => diff --git a/tests/rules/config/.phpunit-phpstan-dba-pdo-mysql.cache b/tests/rules/config/.phpunit-phpstan-dba-pdo-mysql.cache index 42e89555..69d0c3a3 100644 --- a/tests/rules/config/.phpunit-phpstan-dba-pdo-mysql.cache +++ b/tests/rules/config/.phpunit-phpstan-dba-pdo-mysql.cache @@ -784,6 +784,17 @@ Simulated query: SELECT FROM WHERE LIMIT 0', ), ), ), + 'SELECT first.adaid + FROM ada first + JOIN ada second ON second.adaid = first.adaid + JOIN ada third USING(adaid)' => + array ( + 'error' => + \staabm\PHPStanDba\Error::__set_state(array( + 'message' => 'SQLSTATE[23000]: Integrity constraint violation: 1052 Column \'adaid\' in from clause is ambiguous', + 'code' => '23000', + )), + ), 'SELECT with syntax error GROUPY by x' => array ( 'error' => diff --git a/tests/rules/data/ambiguous-using-column.php b/tests/rules/data/ambiguous-using-column.php new file mode 100644 index 00000000..7b8ddd5f --- /dev/null +++ b/tests/rules/data/ambiguous-using-column.php @@ -0,0 +1,19 @@ +preparedQuery( + 'SELECT first.adaid + FROM ada first + JOIN ada second ON second.adaid = first.adaid + JOIN ada third USING(adaid)', + [], + ); + } +} diff --git a/tests/sqlAst/config/.phpunit-phpstan-dba-mysqli.cache b/tests/sqlAst/config/.phpunit-phpstan-dba-mysqli.cache index f0b25c33..33b7e962 100644 --- a/tests/sqlAst/config/.phpunit-phpstan-dba-mysqli.cache +++ b/tests/sqlAst/config/.phpunit-phpstan-dba-mysqli.cache @@ -164,7 +164,7 @@ array ( 5 => array ( - 'type-description' => 'array{str: string, 0: string}', + 'type-description' => 'array{str: string|null, 0: string|null}', ), ), ), @@ -204,7 +204,7 @@ array ( 5 => array ( - 'type-description' => 'array{str: string, 0: string}', + 'type-description' => 'array{str: string|null, 0: string|null}', ), ), ), @@ -744,7 +744,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -754,7 +754,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -794,7 +794,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -804,7 +804,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -1164,7 +1164,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1214,7 +1214,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1224,7 +1224,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1244,7 +1244,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1254,7 +1254,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -1274,7 +1274,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1614,7 +1614,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1624,7 +1624,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), diff --git a/tests/sqlAst/config/.phpunit-phpstan-dba-pdo-mysql.cache b/tests/sqlAst/config/.phpunit-phpstan-dba-pdo-mysql.cache index f0b25c33..33b7e962 100644 --- a/tests/sqlAst/config/.phpunit-phpstan-dba-pdo-mysql.cache +++ b/tests/sqlAst/config/.phpunit-phpstan-dba-pdo-mysql.cache @@ -164,7 +164,7 @@ array ( 5 => array ( - 'type-description' => 'array{str: string, 0: string}', + 'type-description' => 'array{str: string|null, 0: string|null}', ), ), ), @@ -204,7 +204,7 @@ array ( 5 => array ( - 'type-description' => 'array{str: string, 0: string}', + 'type-description' => 'array{str: string|null, 0: string|null}', ), ), ), @@ -744,7 +744,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -754,7 +754,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -794,7 +794,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -804,7 +804,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -1164,7 +1164,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1214,7 +1214,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1224,7 +1224,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1244,7 +1244,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1254,7 +1254,7 @@ array ( 5 => array ( - 'type-description' => 'array{col: string, 0: string}', + 'type-description' => 'array{col: string|null, 0: string|null}', ), ), ), @@ -1274,7 +1274,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1614,7 +1614,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ), @@ -1624,7 +1624,7 @@ array ( 5 => array ( - 'type-description' => 'array{field: string, 0: string}', + 'type-description' => 'array{field: string|null, 0: string|null}', ), ), ),