Merge "rdbms: fix table prefixing in "FOR UPDATE" clause generation in Postgres" into REL1_43

This commit is contained in:
jenkins-bot 2025-06-24 22:33:35 +00:00 committed by Gerrit Code Review
commit 787b2e4603
9 changed files with 55 additions and 26 deletions

View file

@ -235,7 +235,7 @@ class ApiQueryBlocks extends ApiQueryBase {
if ( !$this->getAuthority()->isAllowed( 'hideuser' ) ) {
$this->addWhere(
$this->hideUserUtils->getExpression( $db, $db->tableName( 'block_target' ) . '.bt_user' )
$this->hideUserUtils->getExpression( $db, 'block_target.bt_user' )
);
}

View file

@ -216,7 +216,7 @@ class PostgresInstaller extends DatabaseInstaller {
$conn = $status->getDB();
$superuser = $this->getVar( '_InstallUser' );
$row = $conn->selectRow( '"pg_catalog"."pg_roles"', '*',
$row = $conn->selectRow( 'pg_catalog.pg_roles', '*',
[ 'rolname' => $superuser ], __METHOD__ );
return $row;
@ -247,9 +247,9 @@ class PostgresInstaller extends DatabaseInstaller {
return false;
}
$conn = $status->getDB();
$installerId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
$installerId = $conn->selectField( 'pg_catalog.pg_roles', 'oid',
[ 'rolname' => $this->getVar( '_InstallUser' ) ], __METHOD__ );
$webId = $conn->selectField( '"pg_catalog"."pg_roles"', 'oid',
$webId = $conn->selectField( 'pg_catalog.pg_roles', 'oid',
[ 'rolname' => $this->getVar( 'wgDBuser' ) ], __METHOD__ );
return $this->isRoleMember( $conn, $installerId, $webId, $this->maxRoleSearchDepth );
@ -269,7 +269,7 @@ class PostgresInstaller extends DatabaseInstaller {
return true;
}
// Get all members of the given group
$res = $conn->select( '"pg_catalog"."pg_auth_members"', [ 'member' ],
$res = $conn->select( 'pg_catalog.pg_auth_members', [ 'member' ],
[ 'roleid' => $group ], __METHOD__ );
foreach ( $res as $row ) {
if ( $row->member == $targetMember ) {
@ -319,7 +319,7 @@ class PostgresInstaller extends DatabaseInstaller {
$conn = $status->getDB();
$dbName = $this->getVar( 'wgDBname' );
$exists = (bool)$conn->selectField( '"pg_catalog"."pg_database"', '1',
$exists = (bool)$conn->selectField( 'pg_catalog.pg_database', '1',
[ 'datname' => $dbName ], __METHOD__ );
if ( !$exists ) {
$safedb = $conn->addIdentifierQuotes( $dbName );

View file

@ -334,7 +334,7 @@ EOT;
$module = DatabaseSqlite::getFulltextSearchModule();
$searchIndexSql = (string)$this->db->newSelectQueryBuilder()
->select( 'sql' )
->from( $this->db->addIdentifierQuotes( 'sqlite_master' ) )
->from( 'sqlite_master' )
->where( [ 'tbl_name' => $this->db->tableName( 'searchindex', 'raw' ) ] )
->caller( __METHOD__ )->fetchField();
$fts3tTable = ( stristr( $searchIndexSql, 'fts' ) !== false );

View file

@ -85,24 +85,23 @@ class PostgresPlatform extends SQLPlatform {
reset( $toCheck );
while ( $toCheck ) {
$alias = key( $toCheck );
$name = $toCheck[$alias];
$table = $toCheck[$alias];
unset( $toCheck[$alias] );
$hasAlias = !is_numeric( $alias );
if ( !$hasAlias && is_string( $name ) ) {
$alias = $name;
if ( !is_string( $alias ) ) {
// No alias? Set it equal to the table name
$alias = $table;
}
if ( !isset( $join_conds[$alias] ) ||
!preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_conds[$alias][0] )
) {
if ( is_array( $name ) ) {
if ( is_array( $table ) ) {
// It's a parenthesized group, process all the tables inside the group.
$toCheck = array_merge( $toCheck, $name );
$toCheck = array_merge( $toCheck, $table );
} else {
// Quote alias names so $this->tableName() won't mangle them
$options['FOR UPDATE'][] = $hasAlias ?
$this->addIdentifierQuotes( $alias ) : $alias;
// If an alias is declared, then any FOR UPDATE FOR must use it
$options['FOR UPDATE'][] = $alias;
}
}
}
@ -135,8 +134,10 @@ class PostgresPlatform extends SQLPlatform {
$preLimitTail .= $this->makeOrderBy( $options );
if ( isset( $options['FOR UPDATE'] ) ) {
$postLimitTail .= ' FOR UPDATE OF ' .
implode( ', ', array_map( [ $this, 'tableName' ], $options['FOR UPDATE'] ) );
$postLimitTail .= ' FOR UPDATE OF ' . implode(
', ',
array_map( [ $this, 'addIdentifierQuotes' ], $options['FOR UPDATE'] )
);
} elseif ( isset( $noKeyOptions['FOR UPDATE'] ) ) {
$postLimitTail .= ' FOR UPDATE';
}

View file

@ -90,7 +90,7 @@ class SQLPlatform implements ISQLPlatform {
public function addIdentifierQuotes( $s ) {
if ( strcspn( $s, "\0\"`'." ) !== strlen( $s ) ) {
throw new DBLanguageError(
"Identifier must not contain quote, dot or null characters"
"Identifier must not contain quote, dot or null characters: got '$s'"
);
}
$quoteChar = $this->getIdentifierQuoteChar();
@ -1027,15 +1027,24 @@ class SQLPlatform implements ISQLPlatform {
throw new InvalidArgumentException( "Table must be a string or Subquery" );
}
if ( $alias === false || $alias === $table ) {
if ( $alias === false ) {
if ( $table instanceof Subquery ) {
throw new InvalidArgumentException( "Subquery table missing alias" );
}
return $quotedTable;
$quotedTableWithAnyAlias = $quotedTable;
} elseif (
$alias === $table &&
(
str_contains( $alias, '.' ) ||
$this->tableName( $alias, 'raw' ) === $table
)
) {
$quotedTableWithAnyAlias = $quotedTable;
} else {
return $quotedTable . ' ' . $this->addIdentifierQuotes( $alias );
$quotedTableWithAnyAlias = $quotedTable . ' ' . $this->addIdentifierQuotes( $alias );
}
return $quotedTableWithAnyAlias;
}
public function tableName( string $name, $format = 'quoted' ) {

View file

@ -38,7 +38,7 @@ class SearchSqlite extends SearchDatabase {
$dbr = $this->dbProvider->getReplicaDatabase();
$sql = (string)$dbr->newSelectQueryBuilder()
->select( 'sql' )
->from( $dbr->addIdentifierQuotes( 'sqlite_master' ) )
->from( 'sqlite_master' )
->where( [ 'tbl_name' => $dbr->tableName( 'searchindex', 'raw' ) ] )
->caller( __METHOD__ )->fetchField();

View file

@ -487,7 +487,7 @@ class BlockListPager extends TablePager {
# With multiblocks we can't just rely on bl_deleted in the row being formatted
$info['fields']['hu_deleted'] = $this->hideUserUtils->getExpression(
$db,
$db->tableName( 'block_target' ) . '.bt_user',
'block_target.bt_user',
HideUserUtils::HIDDEN_USERS );
return $info;
}

View file

@ -987,6 +987,11 @@ class RevisionStoreDbTest extends MediaWikiIntegrationTestCase {
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
$storeRecord = $store->getRevisionByPageId( $page->getId(), 0, IDBAccessObject::READ_LOCKING );
$this->assertSame( $revRecord->getId(), $storeRecord->getId() );
$this->assertTrue( $storeRecord->getSlot( SlotRecord::MAIN )->getContent()->equals( $content ) );
$this->assertSame( __METHOD__, $storeRecord->getComment()->text );
}
/**

View file

@ -541,6 +541,20 @@ class DatabaseMySQLTest extends TestCase {
$wdb->platform = new MySQLPlatform( new AddQuoterMock() );
/** @var IDatabase $db */
$db->setTableAliases( [
'meow' => [ 'dbname' => 'feline', 'schema' => null, 'prefix' => '' ]
] );
$sql = $db->newSelectQueryBuilder()
->select( 'field' )
->from( 'meow' )
->where( [ 'a' => 'x' ] )
->caller( __METHOD__ )->getSQL();
$this->assertSameSql(
"SELECT field FROM `feline`.`meow` `meow` WHERE a = 'x' ",
$sql
);
$db->setTableAliases( [
'meow' => [ 'dbname' => 'feline', 'schema' => null, 'prefix' => 'cat_' ]
] );
@ -551,7 +565,7 @@ class DatabaseMySQLTest extends TestCase {
->caller( __METHOD__ )->getSQL();
$this->assertSameSql(
"SELECT field FROM `feline`.`cat_meow` WHERE a = 'x' ",
"SELECT field FROM `feline`.`cat_meow` `meow` WHERE a = 'x' ",
$sql
);