From 0a9c55bfd39e22828f2d152ab71789cef3b0897c Mon Sep 17 00:00:00 2001 From: Brad Jorsch Date: Wed, 30 Aug 2017 20:27:51 -0400 Subject: [PATCH] Deprecate IDatabase::nextSequenceValue() It's often forgotten because MySQL and Sqlite don't use it, the only users are PostgreSQL and Oracle. And when used, if inserts to multiple tables are being done it's easy to get the ordering wrong. This patch reimplements DatabasePostgres::insertId() in terms of PG's lastval() function, and adds triggers to the Oracle schema to make it work the same as the other databases. Bug: T164900 Change-Id: Ib308190c52673a9266c8495a589ae644f9fbefce --- RELEASE-NOTES-1.30 | 2 + autoload.php | 1 + includes/db/DatabaseOracle.php | 25 +-- includes/installer/OracleUpdater.php | 27 ++++ .../libs/rdbms/database/DatabasePostgres.php | 21 +-- includes/libs/rdbms/database/IDatabase.php | 19 ++- .../database/utils/NextSequenceValue.php | 12 ++ .../patch-auto_increment_triggers.sql | 144 ++++++++++++++++++ maintenance/oracle/tables.sql | 127 +++++++++++++++ 9 files changed, 334 insertions(+), 44 deletions(-) create mode 100644 includes/libs/rdbms/database/utils/NextSequenceValue.php create mode 100644 maintenance/oracle/archives/patch-auto_increment_triggers.sql diff --git a/RELEASE-NOTES-1.30 b/RELEASE-NOTES-1.30 index c4c56e833f..d38a34d2e1 100644 --- a/RELEASE-NOTES-1.30 +++ b/RELEASE-NOTES-1.30 @@ -197,6 +197,8 @@ changes to languages because of Phabricator reports. PRIMARY KEYs for increased maintainability: categorylinks, imagelinks, iwlinks, langlinks, log_search, module_deps, objectcache, pagelinks, query_cache, site_stats, templatelinks, text, transcache, user_former_groups, user_properties. +* IDatabase::nextSequenceValue() is no longer needed by any database backends + (formerly it was needed by PostgreSQL and Oracle), and is now deprecated. == Compatibility == MediaWiki 1.30 requires PHP 5.5.9 or later. There is experimental support for diff --git a/autoload.php b/autoload.php index 3a2ae10007..530e359777 100644 --- a/autoload.php +++ b/autoload.php @@ -1666,6 +1666,7 @@ $wgAutoloadLocalClasses = [ 'Wikimedia\\Rdbms\\MssqlResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/MssqlResultWrapper.php', 'Wikimedia\\Rdbms\\MySQLField' => __DIR__ . '/includes/libs/rdbms/field/MySQLField.php', 'Wikimedia\\Rdbms\\MySQLMasterPos' => __DIR__ . '/includes/libs/rdbms/database/position/MySQLMasterPos.php', + 'Wikimedia\\Rdbms\\NextSequenceValue' => __DIR__ . '/includes/libs/rdbms/database/utils/NextSequenceValue.php', 'Wikimedia\\Rdbms\\PostgresBlob' => __DIR__ . '/includes/libs/rdbms/encasing/PostgresBlob.php', 'Wikimedia\\Rdbms\\PostgresField' => __DIR__ . '/includes/libs/rdbms/field/PostgresField.php', 'Wikimedia\\Rdbms\\ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php', diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 556fe75547..e2feb1fa7c 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -37,9 +37,6 @@ class DatabaseOracle extends Database { /** @var int The number of rows affected as an integer */ protected $mAffectedRows; - /** @var int */ - private $mInsertId = null; - /** @var bool */ private $ignoreDupValOnIndex = false; @@ -319,12 +316,10 @@ class DatabaseOracle extends Database { return oci_field_name( $stmt, $n ); } - /** - * This must be called after nextSequenceVal - * @return null|int - */ function insertId() { - return $this->mInsertId; + $res = $this->query( "SELECT lastval_pkg.getLastval FROM dual" ); + $row = $this->fetchRow( $res ); + return is_null( $row[0] ) ? null : (int)$row[0]; } /** @@ -649,20 +644,6 @@ class DatabaseOracle extends Database { return preg_replace( '/.*\.(.*)/', '$1', $name ); } - /** - * Return the next in a sequence, save the value for retrieval via insertId() - * - * @param string $seqName - * @return null|int - */ - function nextSequenceValue( $seqName ) { - $res = $this->query( "SELECT $seqName.nextval FROM dual" ); - $row = $this->fetchRow( $res ); - $this->mInsertId = $row[0]; - - return $this->mInsertId; - } - /** * Return sequence_name if table has a sequence * diff --git a/includes/installer/OracleUpdater.php b/includes/installer/OracleUpdater.php index e262eda635..00b96614f8 100644 --- a/includes/installer/OracleUpdater.php +++ b/includes/installer/OracleUpdater.php @@ -123,6 +123,9 @@ class OracleUpdater extends DatabaseUpdater { [ 'addField', 'externallinks', 'el_index_60', 'patch-externallinks-el_index_60.sql' ], [ 'addField', 'user_groups', 'ug_expiry', 'patch-user_groups-ug_expiry.sql' ], + // 1.30 + [ 'doAutoIncrementTriggers' ], + // KEEP THIS AT THE BOTTOM!! [ 'doRebuildDuplicateFunction' ], @@ -273,6 +276,30 @@ class OracleUpdater extends DatabaseUpdater { $this->output( "ok\n" ); } + /** + * Add auto-increment triggers + */ + protected function doAutoIncrementTriggers() { + $this->output( "Adding auto-increment triggers ... " ); + + $meta = $this->db->query( 'SELECT trigger_name FROM user_triggers WHERE table_owner = \'' . + strtoupper( $this->db->getDBname() ) . + '\' AND trigger_name = \'' . + $this->db->tablePrefix() . + 'PAGE_DEFAULT_PAGE_ID\'' + ); + $row = $meta->fetchRow(); + if ( $row['column_name'] ) { + $this->output( "seems to be up to date.\n" ); + + return; + } + + $this->applyPatch( 'patch-auto_increment_triggers.sql', false ); + + $this->output( "ok\n" ); + } + /** * rebuilding of the function that duplicates tables for tests */ diff --git a/includes/libs/rdbms/database/DatabasePostgres.php b/includes/libs/rdbms/database/DatabasePostgres.php index fcfd93700d..ac59bd6171 100644 --- a/includes/libs/rdbms/database/DatabasePostgres.php +++ b/includes/libs/rdbms/database/DatabasePostgres.php @@ -39,8 +39,6 @@ class DatabasePostgres extends Database { /** @var int The number of rows affected as an integer */ protected $mAffectedRows = null; - /** @var int */ - private $mInsertId = null; /** @var float|string */ private $numericVersion = null; /** @var string Connect string to open a PostgreSQL connection */ @@ -352,14 +350,10 @@ class DatabasePostgres extends Database { return pg_field_name( $res, $n ); } - /** - * Return the result of the last call to nextSequenceValue(); - * This must be called after nextSequenceValue(). - * - * @return int|null - */ public function insertId() { - return $this->mInsertId; + $res = $this->query( "SELECT lastval()" ); + $row = $this->fetchRow( $res ); + return is_null( $row[0] ) ? null : (int)$row[0]; } public function dataSeek( $res, $row ) { @@ -776,12 +770,7 @@ __INDEXATTR__; } public function nextSequenceValue( $seqName ) { - $safeseq = str_replace( "'", "''", $seqName ); - $res = $this->query( "SELECT nextval('$safeseq')" ); - $row = $this->fetchRow( $res ); - $this->mInsertId = is_null( $row[0] ) ? null : (int)$row[0]; - - return $this->mInsertId; + return new NextSequenceValue; } /** @@ -1224,6 +1213,8 @@ SQL; $s = pg_escape_bytea( $conn, $s->fetch() ); } return "'$s'"; + } elseif ( $s instanceof NextSequenceValue ) { + return 'DEFAULT'; } return "'" . pg_escape_string( $conn, $s ) . "'"; diff --git a/includes/libs/rdbms/database/IDatabase.php b/includes/libs/rdbms/database/IDatabase.php index 9375efc253..736447f46c 100644 --- a/includes/libs/rdbms/database/IDatabase.php +++ b/includes/libs/rdbms/database/IDatabase.php @@ -1114,15 +1114,20 @@ interface IDatabase { public function anyString(); /** - * Returns an appropriately quoted sequence value for inserting a new row. - * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL - * subclass will return an integer, and save the value for insertId() + * Deprecated method, calls should be removed. * - * Any implementation of this function should *not* involve reusing - * sequence numbers created for rolled-back transactions. - * See https://bugs.mysql.com/bug.php?id=30767 for details. + * This was formerly used for PostgreSQL and Oracle to handle + * self::insertId() auto-incrementing fields. It is no longer necessary + * since DatabasePostgres::insertId() has been reimplemented using + * `lastval()` and Oracle has been reimplemented using triggers. + * + * Implementations should return null if inserting `NULL` into an + * auto-incrementing field works, otherwise it should return an instance of + * NextSequenceValue and filter it on calls to relevant methods. + * + * @deprecated since 1.30, no longer needed * @param string $seqName - * @return null|int + * @return null|NextSequenceValue */ public function nextSequenceValue( $seqName ); diff --git a/includes/libs/rdbms/database/utils/NextSequenceValue.php b/includes/libs/rdbms/database/utils/NextSequenceValue.php new file mode 100644 index 0000000000..44bf0ddcdd --- /dev/null +++ b/includes/libs/rdbms/database/utils/NextSequenceValue.php @@ -0,0 +1,12 @@ +