rdbms: fix table prefixing in "FOR UPDATE" clause generation in Postgres
Make SqlPlatform::tableNameWithAlias() include the unqualified table name as an alias if doing so is not redundant. This assures that the default alias from JoinGroupBase::addJoin(), equal to the unqualified table name, will be usable in SQL (regardless of table prefixes). Clean up use of identifier quotes for sqlite_master tables. The called methods expect unqualified names and a passthrough exception already exists for sqlite_* tables. Use "block_target.bt_user" directly in ApiQueryBlocks and BlockPager, instead of using addIdentifierQuotes(). The "block_target" alias is automatically added to the SQL by the rdbms layer when it's not clearly redundant, so it is always safe to use block_target.bt_user. Also, there is no reason for aliases to include quote characters. They are supposed to be simple alphanumerics like column names. This makes it easy for tableNameWithAlias() to avoid redundant aliases by checking tableName(). Avoid unneeded quotes around pg_catalog.* table names in the Postgres installer. The relevant documentation of methods like selectField() is that the table names be unqualified (no quotes nor dots), though dots are still supported internally for compatibility reasons and ease of querying schemas like pg_catalog and information_schema. Change-Id: Ic7d7826da31f49915141692cb3bd84ed1e872e96
This commit is contained in:
parent
fd31ca4e79
commit
4659cbcccc
9 changed files with 55 additions and 26 deletions
|
|
@ -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' )
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' ) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue