rdbms: Map PostgreSQL boolean values to MySQL-compatible forms

Also fix callers that were checking for t/f.

In CASE and COALESCE expressions, using 't' and 'f' did actually work,
because those literals have an unknown type and the other argument is
boolean, so PG coerces them to boolean. But it seems safer and clearer
to use the strongly typed literals TRUE and FALSE.

Bug: T352229
Change-Id: Ia01b76d3d6d2e048feac8e3118d9faff63a9ac56
This commit is contained in:
Tim Starling 2023-11-29 10:40:48 +11:00
parent b23e47c20a
commit c5d182eb01
6 changed files with 76 additions and 13 deletions

View file

@ -318,12 +318,12 @@ class PostgresInstaller extends DatabaseInstaller {
protected function canCreateAccounts() {
$perms = $this->getInstallUserPermissions();
return $perms && ( $perms->rolsuper === 't' || $perms->rolcreaterole === 't' );
return $perms && $perms->rolsuper || $perms->rolcreaterole;
}
protected function isSuperUser() {
$perms = $this->getInstallUserPermissions();
return $perms && $perms->rolsuper === 't';
return $perms && $perms->rolsuper;
}
public function getSettingsForm() {

View file

@ -1054,7 +1054,7 @@ __INDEXATTR__;
$res = $this->query( $query, $method );
$row = $res->fetchObject();
return ( $row->unlocked === 't' );
return (bool)$row->unlocked;
}
public function doLock( string $lockName, string $method, int $timeout ) {
@ -1094,7 +1094,7 @@ __INDEXATTR__;
$result = $this->query( $query, $method );
$row = $result->fetchObject();
return ( $row->released === 't' );
return (bool)$row->released;
}
protected function doFlushSession( $fname ) {

View file

@ -27,13 +27,55 @@ class PostgresResultWrapper extends ResultWrapper {
}
protected function doFetchObject() {
// pg_fetch_object may raise a warning after a seek to an invalid offset
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
return @pg_fetch_object( $this->result );
$row = @pg_fetch_object( $this->result );
// Map boolean values (T352229)
if ( is_object( $row ) ) {
$numFields = pg_num_fields( $this->result );
for ( $i = 0; $i < $numFields; $i++ ) {
if ( pg_field_type( $this->result, $i ) === 'bool' ) {
$name = pg_field_name( $this->result, $i );
$row->$name = $this->convertBoolean( $row->$name );
}
}
}
return $row;
}
protected function doFetchRow() {
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
return @pg_fetch_array( $this->result );
$row = @pg_fetch_array( $this->result );
// Map boolean values (T352229)
if ( is_array( $row ) ) {
$numFields = pg_num_fields( $this->result );
for ( $i = 0; $i < $numFields; $i++ ) {
if ( pg_field_type( $this->result, $i ) === 'bool' ) {
$name = pg_field_name( $this->result, $i );
$row[$i] = $this->convertBoolean( $row[$i] );
$row[$name] = $this->convertBoolean( $row[$name] );
}
}
}
return $row;
}
/**
* Convert a boolean value from the database to the string '0' or '1' for
* compatibility with MySQL.
*
* @param mixed $value
* @return mixed
*/
private function convertBoolean( $value ) {
if ( $value === 't' ) {
return '1';
} elseif ( $value === 'f' ) {
return '0';
} else {
// Just pass through values that are not 't' or 'f'
return $value;
}
}
protected function doSeek( $pos ) {

View file

@ -18,8 +18,8 @@ SELECT
attnotnull, attlen, conname AS conname,
atthasdef,
pg_get_expr(adbin, adrelid) AS adsrc,
COALESCE(condeferred, 'f') AS deferred,
COALESCE(condeferrable, 'f') AS deferrable,
COALESCE(condeferred, FALSE) AS deferred,
COALESCE(condeferrable, FALSE) AS deferrable,
CASE WHEN typname = 'int2' THEN 'smallint'
WHEN typname = 'int4' THEN 'integer'
WHEN typname = 'int8' THEN 'bigint'
@ -52,14 +52,14 @@ SQL;
}
$n = new PostgresField;
$n->type = $row->typname;
$n->nullable = ( $row->attnotnull == 'f' );
$n->nullable = !$row->attnotnull;
$n->name = $field;
$n->tablename = $table;
$n->max_length = $row->attlen;
$n->deferrable = ( $row->deferrable == 't' );
$n->deferred = ( $row->deferred == 't' );
$n->deferrable = (bool)$row->deferrable;
$n->deferred = (bool)$row->deferred;
$n->conname = $row->conname;
$n->has_default = ( $row->atthasdef === 't' );
$n->has_default = (bool)$row->atthasdef;
$n->default = $row->adsrc;
return $n;

View file

@ -240,7 +240,7 @@ class PostgresPlatform extends SQLPlatform {
// http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
$key = $this->quoter->addQuotes( $this->bigintFromLockName( $lockName ) );
return "SELECT (CASE(pg_try_advisory_lock($key))
WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS unlocked";
WHEN FALSE THEN FALSE ELSE pg_advisory_unlock($key) END) AS unlocked";
}
public function unlockSQLText( $lockName ) {

View file

@ -101,4 +101,25 @@ class DatabaseIntegrationTest extends MediaWikiIntegrationTestCase {
"The generated schema in '$type' type has to be the same"
);
}
/**
* T352229
*/
public function testBooleanValues() {
$res = $this->db->newSelectQueryBuilder()
->select( [ 'false' => '1=0', 'true' => '1=1' ] )
->fetchResultSet();
$obj = $res->fetchObject();
$this->assertCount( 2, (array)$obj );
$this->assertSame( '0', $obj->false );
$this->assertSame( '1', $obj->true );
$res->seek( 0 );
$row = $res->fetchRow();
$this->assertCount( 4, $row );
$this->assertSame( '0', $row[0] );
$this->assertSame( '1', $row[1] );
$this->assertSame( '0', $row['false'] );
$this->assertSame( '1', $row['true'] );
}
}