MediaWiki 1.31 is an alpha-quality branch and is not recommended for use in
production.
+=== Important pre-upgrade notes for 1.31 ===
+* If you're using MySQL, SQLite, or MSSQL, are not using update.php to apply
+ schema changes, and cannot have downtime to run migrateArchiveText.php and
+ apply patch-drop-ar_text.sql manually, you'll have to apply a default value
+ to the ar_text and ar_flags columns of the archive table or make those
+ columns nullable before upgrading to MediaWiki 1.31.
+ maintenance/archives/patch-nullable-ar_text.sql shows how to do this for MySQL.
+
=== Configuration changes in 1.31 ===
* $wgEnableAPI and $wgEnableWriteAPI are now deprecated and will be removed in
a future version. The API is now considered to be stable, secure and
'ar_id',
'ar_page_id',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_user_text',
'ar_content_model' => 'rev_content_model',
];
- if ( empty( $archiveRow->ar_text_id ) ) {
- $fieldMap['ar_text'] = 'old_text';
- $fieldMap['ar_flags'] = 'old_flags';
- }
-
$revRow = new stdClass();
foreach ( $fieldMap as $arKey => $revKey ) {
if ( property_exists( $archiveRow, $arKey ) ) {
'ar_namespace',
'ar_title',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_minor_edit',
}
if ( $this->fetchContent ) {
- // Modern MediaWiki has the content for deleted revs in the 'text'
- // table using fields old_text and old_flags. But revisions deleted
- // pre-1.5 store the content in the 'archive' table directly using
- // fields ar_text and ar_flags, and no corresponding 'text' row. So
- // we have to LEFT JOIN and fetch all four fields.
$this->addTables( 'text' );
$this->addJoinConds(
[ 'text' => [ 'LEFT JOIN', [ 'ar_text_id=old_id' ] ] ]
);
- $this->addFields( [ 'ar_text', 'ar_flags', 'old_text', 'old_flags' ] );
+ $this->addFields( [ 'old_text', 'old_flags' ] );
// This also means stricter restrictions
$this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
}
if ( $this->fetchContent ) {
- // Modern MediaWiki has the content for deleted revs in the 'text'
- // table using fields old_text and old_flags. But revisions deleted
- // pre-1.5 store the content in the 'archive' table directly using
- // fields ar_text and ar_flags, and no corresponding 'text' row. So
- // we have to LEFT JOIN and fetch all four fields.
$this->addTables( 'text' );
$this->addJoinConds(
[ 'text' => [ 'LEFT JOIN', [ 'ar_text_id=old_id' ] ] ]
);
- $this->addFields( [ 'ar_text', 'ar_flags', 'old_text', 'old_flags' ] );
+ $this->addFields( [ 'old_text', 'old_flags' ] );
// This also means stricter restrictions
$this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
}
if ( $fld_content ) {
- // Modern MediaWiki has the content for deleted revs in the 'text'
- // table using fields old_text and old_flags. But revisions deleted
- // pre-1.5 store the content in the 'archive' table directly using
- // fields ar_text and ar_flags, and no corresponding 'text' row. So
- // we have to LEFT JOIN and fetch all four fields, plus ar_text_id
- // to be able to tell the difference.
$this->addTables( 'text' );
$this->addJoinConds(
[ 'text' => [ 'LEFT JOIN', [ 'ar_text_id=old_id' ] ] ]
);
- $this->addFields( [ 'ar_text', 'ar_flags', 'ar_text_id', 'old_text', 'old_flags' ] );
+ $this->addFields( [ 'ar_text_id', 'old_text', 'old_flags' ] );
// This also means stricter restrictions
$this->checkUserRightsAny( [ 'deletedtext', 'undelete' ] );
$anyHidden = true;
}
if ( Revision::userCanBitfield( $row->ar_deleted, Revision::DELETED_TEXT, $user ) ) {
- if ( isset( $row->ar_text ) && !$row->ar_text_id ) {
- // Pre-1.5 ar_text row (if condition from Revision::newFromArchiveRow)
- ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row, 'ar_' ) );
- } else {
- ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row ) );
- }
+ ApiResult::setContentValue( $rev, 'text', Revision::getRevisionText( $row ) );
}
}
* @since 1.31
*/
protected function migrateArchiveText() {
- $this->output( "Migrating archive ar_text to modern storage.\n" );
- $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
- $task->execute();
- $this->output( "done.\n" );
+ if ( $this->db->fieldExists( 'archive', 'ar_text', __METHOD__ ) ) {
+ $this->output( "Migrating archive ar_text to modern storage.\n" );
+ $task = $this->maintenance->runChild( MigrateArchiveText::class, 'migrateArchiveText.php' );
+ $task->setForce();
+ if ( $task->execute() ) {
+ $this->applyPatch( 'patch-drop-ar_text.sql', false,
+ 'Dropping ar_text and ar_flags columns' );
+ }
+ }
}
/**
}
/**
- * Get the text from an archive row containing ar_text, ar_flags and ar_text_id
+ * Get the text from an archive row containing ar_text_id
*
+ * @deprecated since 1.31
* @param object $row Database row
* @return string
*/
public function getTextFromRow( $row ) {
- if ( is_null( $row->ar_text_id ) ) {
- // An old row from MediaWiki 1.4 or previous.
- // Text is embedded in this row in classic compression format.
- return Revision::getRevisionText( $row, 'ar_' );
- }
-
- // New-style: keyed to the text storage backend.
$dbr = wfGetDB( DB_REPLICA );
$text = $dbr->selectRow( 'text',
[ 'old_text', 'old_flags' ],
*/
public function getLastRevisionText() {
$dbr = wfGetDB( DB_REPLICA );
- $row = $dbr->selectRow( 'archive',
- [ 'ar_text', 'ar_flags', 'ar_text_id' ],
+ $row = $dbr->selectRow(
+ [ 'archive', 'text' ],
+ [ 'old_text', 'old_flags' ],
[ 'ar_namespace' => $this->title->getNamespace(),
'ar_title' => $this->title->getDBkey() ],
__METHOD__,
- [ 'ORDER BY' => 'ar_timestamp DESC' ] );
+ [ 'ORDER BY' => 'ar_timestamp DESC, ar_id DESC' ],
+ [ 'text' => [ 'JOIN', 'old_id = ar_text_id' ] ]
+ );
if ( $row ) {
- return $this->getTextFromRow( $row );
+ return Revision::getRevisionText( $row );
}
return null;
'ar_rev_id' => $row->rev_id,
'ar_parent_id' => $row->rev_parent_id,
'ar_text_id' => $row->rev_text_id,
- 'ar_text' => '',
- 'ar_flags' => '',
'ar_len' => $row->rev_len,
'ar_page_id' => $id,
'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted,
--- /dev/null
+-- T33223: Remove obsolete ar_text and ar_flags columns
+-- (and make ar_text_id not nullable and default 0)
+
+ALTER TABLE /*_*/archive
+ DROP COLUMN ar_text,
+ DROP COLUMN ar_flags,
+ CHANGE COLUMN ar_text_id ar_text_id int unsigned NOT NULL DEFAULT 0;
--- /dev/null
+--
+-- patch-nullable-ar_text.sql
+--
+-- This patch is provided as an example for people not using update.php.
+-- You need to make a change like this before running a version of MediaWiki
+-- containing Gerrit change 5ca2d4a551, then you can run maintenance/migrateArchiveText.php
+-- and apply patch-drop-ar_text.sql at your leisure.
+--
+-- See also T33223.
+
+ALTER TABLE /*_*/archive
+ MODIFY COLUMN ar_text mediumblob NULL,
+ MODIFY COLUMN ar_flags tinyblob NULL;
--- /dev/null
+DECLARE @sql nvarchar(max),
+ @id sysname;--
+
+SET @sql = 'ALTER TABLE /*_*/archive DROP CONSTRAINT ';--
+
+SELECT @id = df.name
+FROM sys.default_constraints df
+JOIN sys.columns c
+ ON c.object_id = df.parent_object_id
+ AND c.column_id = df.parent_column_id
+WHERE
+ df.parent_object_id = OBJECT_ID('/*_*/archive')
+ AND ( c.name = 'ar_text' OR c.name = 'ar_flags' );--
+
+SET @sql = @sql + @id;--
+
+EXEC sp_executesql @sql;--
+
+ALTER TABLE /*_*/archive DROP COLUMN ar_text;
+ALTER TABLE /*_*/archive DROP COLUMN ar_flags;
+ALTER TABLE /*_*/archive ALTER COLUMN ar_text_id INT NOT NULL CONSTRAINT DF_ar_text_id DEFAULT 0;
ar_id int NOT NULL PRIMARY KEY IDENTITY,
ar_namespace SMALLINT NOT NULL DEFAULT 0,
ar_title NVARCHAR(255) NOT NULL DEFAULT '',
- ar_text NVARCHAR(MAX) NOT NULL,
ar_comment NVARCHAR(255) NOT NULL CONSTRAINT DF_ar_comment DEFAULT '',
ar_comment_id bigint unsigned NOT NULL CONSTRAINT DF_ar_comment_id DEFAULT 0 CONSTRAINT FK_ar_comment_id FOREIGN KEY REFERENCES /*_*/comment(comment_id),
ar_user INT CONSTRAINT ar_user__user_id__fk FOREIGN KEY REFERENCES /*_*/mwuser(user_id),
ar_actor bigint unsigned NOT NULL CONSTRAINT DF_ar_actor DEFAULT 0,
ar_timestamp varchar(14) NOT NULL default '',
ar_minor_edit BIT NOT NULL DEFAULT 0,
- ar_flags NVARCHAR(255) NOT NULL,
ar_rev_id INT NOT NULL, -- NOT a FK, the row gets deleted from revision and moved here
- ar_text_id INT CONSTRAINT ar_text_id__old_id__fk FOREIGN KEY REFERENCES /*_*/text(old_id) ON DELETE CASCADE,
+ ar_text_id INT NOT NULL CONSTRAINT DF_ar_text_id DEFAULT 0 CONSTRAINT ar_text_id__old_id__fk FOREIGN KEY REFERENCES /*_*/text(old_id) ON DELETE CASCADE,
ar_deleted TINYINT NOT NULL DEFAULT 0,
ar_len INT,
ar_page_id INT NULL, -- NOT a FK, the row gets deleted from page and moved here
--- /dev/null
+-- T33223: Remove obsolete ar_text and ar_flags columns
+-- (and make ar_text_id not nullable and default 0)
+
+define mw_prefix='{$wgDBprefix}';
+
+ALTER TABLE &mw_prefix.archive DROP (ar_text, ar_flags);
+ALTER TABLE &mw_prefix.archive MODIFY ar_text_id NUMBER DEFAULT 0 NOT NULL;
ar_id NUMBER NOT NULL,
ar_namespace NUMBER DEFAULT 0 NOT NULL,
ar_title VARCHAR2(255) NOT NULL,
- ar_text CLOB,
ar_comment VARCHAR2(255),
ar_comment_id NUMBER DEFAULT 0 NOT NULL,
ar_user NUMBER DEFAULT 0 NOT NULL,
ar_actor NUMBER DEFAULT 0 NOT NULL,
ar_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
ar_minor_edit CHAR(1) DEFAULT '0' NOT NULL,
- ar_flags VARCHAR2(255),
ar_rev_id NUMBER NOT NULL,
- ar_text_id NUMBER,
+ ar_text_id NUMBER DEFAULT 0 NOT NULL,
ar_deleted CHAR(1) DEFAULT '0' NOT NULL,
ar_len NUMBER,
ar_page_id NUMBER,
--- /dev/null
+-- T33223: Remove obsolete ar_text and ar_flags columns
+-- (and make ar_text_id not nullable and default 0)
+
+ALTER TABLE archive
+ DROP COLUMN ar_text,
+ DROP COLUMN ar_flags,
+ ALTER COLUMN ar_text_id SET DEFAULT 0;
+ ALTER COLUMN ar_text_id SET NOT NULL;
ar_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('archive_ar_id_seq'),
ar_namespace SMALLINT NOT NULL,
ar_title TEXT NOT NULL,
- ar_text TEXT, -- technically should be bytea, but not used anymore
ar_page_id INTEGER NULL,
ar_parent_id INTEGER NULL,
ar_sha1 TEXT NOT NULL DEFAULT '',
ar_actor INTEGER NOT NULL DEFAULT 0,
ar_timestamp TIMESTAMPTZ NOT NULL,
ar_minor_edit SMALLINT NOT NULL DEFAULT 0,
- ar_flags TEXT,
ar_rev_id INTEGER NOT NULL,
- ar_text_id INTEGER,
+ ar_text_id INTEGER NOT NULL DEFAULT 0,
ar_deleted SMALLINT NOT NULL DEFAULT 0,
ar_len INTEGER NULL,
ar_content_model TEXT,
ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
ar_namespace int NOT NULL default 0,
ar_title varchar(255) binary NOT NULL default '',
- ar_text mediumblob NOT NULL,
ar_comment varbinary(767) NOT NULL default '',
ar_comment_id bigint unsigned NOT NULL DEFAULT 0,
ar_user int unsigned NOT NULL default 0,
ar_actor bigint unsigned NOT NULL DEFAULT 0,
ar_timestamp binary(14) NOT NULL default '',
ar_minor_edit tinyint NOT NULL default 0,
- ar_flags tinyblob NOT NULL,
ar_rev_id int unsigned,
- ar_text_id int unsigned,
+ ar_text_id int unsigned NOT NULL default 0,
ar_deleted tinyint unsigned NOT NULL default 0,
ar_len int unsigned,
ar_page_id int unsigned,
) /*$wgDBTableOptions*/;
INSERT OR IGNORE INTO /*_*/archive_tmp (
- ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
- ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted,
- ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model,
- ar_content_format)
+ ar_id, ar_namespace, ar_title, ar_comment, ar_user, ar_user_text,
+ ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted, ar_len,
+ ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format)
SELECT
- ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
- ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted,
- ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model,
- ar_content_format
+ ar_id, ar_namespace, ar_title, ar_comment, ar_user, ar_user_text,
+ ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted, ar_len,
+ ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format
FROM /*_*/archive;
DROP TABLE /*_*/archive;
ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
ar_namespace int NOT NULL default 0,
ar_title varchar(255) binary NOT NULL default '',
- ar_text mediumblob NOT NULL,
ar_comment varbinary(767) NOT NULL default '',
ar_comment_id bigint unsigned NOT NULL DEFAULT 0,
ar_user int unsigned NOT NULL default 0,
ar_actor bigint unsigned NOT NULL DEFAULT 0,
ar_timestamp binary(14) NOT NULL default '',
ar_minor_edit tinyint NOT NULL default 0,
- ar_flags tinyblob NOT NULL,
ar_rev_id int unsigned NOT NULL,
- ar_text_id int unsigned,
+ ar_text_id int unsigned NOT NULL default 0,
ar_deleted tinyint unsigned NOT NULL default 0,
ar_len int unsigned,
ar_page_id int unsigned,
) /*$wgDBTableOptions*/;
INSERT OR IGNORE INTO /*_*/archive_tmp (
- ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_comment_id, ar_user,
- ar_user_text, ar_actor, ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id,
+ ar_id, ar_namespace, ar_title, ar_comment, ar_comment_id, ar_user,
+ ar_user_text, ar_actor, ar_timestamp, ar_minor_edit, ar_rev_id,
ar_text_id, ar_deleted, ar_len, ar_page_id, ar_parent_id, ar_sha1,
ar_content_model, ar_content_format)
SELECT
- ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_comment_id, ar_user,
- ar_user_text, ar_actor, ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id,
+ ar_id, ar_namespace, ar_title, ar_comment, ar_comment_id, ar_user,
+ ar_user_text, ar_actor, ar_timestamp, ar_minor_edit, ar_rev_id,
ar_text_id, ar_deleted, ar_len, ar_page_id, ar_parent_id, ar_sha1,
ar_content_model, ar_content_format
FROM /*_*/archive;
--- /dev/null
+-- T33223: Remove obsolete ar_text and ar_flags columns
+-- (and make ar_text_id not nullable and default 0)
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+CREATE TABLE /*_*/archive_tmp (
+ ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ ar_namespace int NOT NULL default 0,
+ ar_title varchar(255) binary NOT NULL default '',
+ ar_comment varbinary(767) NOT NULL default '',
+ ar_comment_id bigint unsigned NOT NULL DEFAULT 0,
+ ar_user int unsigned NOT NULL default 0,
+ ar_user_text varchar(255) binary NOT NULL,
+ ar_timestamp binary(14) NOT NULL default '',
+ ar_minor_edit tinyint NOT NULL default 0,
+ ar_rev_id int unsigned,
+ ar_text_id int unsigned NOT NULL default 0,
+ ar_deleted tinyint unsigned NOT NULL default 0,
+ ar_len int unsigned,
+ ar_page_id int unsigned,
+ ar_parent_id int unsigned default NULL,
+ ar_sha1 varbinary(32) NOT NULL default '',
+ ar_content_model varbinary(32) DEFAULT NULL,
+ ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/archive_tmp (
+ ar_id, ar_namespace, ar_title, ar_comment, ar_user, ar_user_text,
+ ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted, ar_len,
+ ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format)
+ SELECT
+ ar_id, ar_namespace, ar_title, ar_comment, ar_user, ar_user_text,
+ ar_timestamp, ar_minor_edit, ar_rev_id, ar_text_id, ar_deleted, ar_len,
+ ar_page_id, ar_parent_id, ar_sha1, ar_content_model, ar_content_format
+ FROM /*_*/archive;
+
+DROP TABLE /*_*/archive;
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+
+COMMIT;
exit( 1 );
}
- // Scan the archive table for HistoryBlobStub objects or external flags (T24624)
- $flags = $dbr->selectField( 'archive', 'ar_flags',
- 'ar_flags LIKE \'%external%\' OR (' .
- 'ar_flags LIKE \'%object%\' ' .
- 'AND LOWER(CONVERT(LEFT(ar_text,22) USING latin1)) = \'o:15:"historyblobstub"\' )',
- __METHOD__
- );
-
- if ( strpos( $flags, 'external' ) !== false ) {
- echo "Integrity check failed: found external storage pointers in your archive table.\n" .
- "Run normaliseArchiveTable.php to fix this.\n";
- exit( 1 );
- } elseif ( $flags ) {
- echo "Integrity check failed: found HistoryBlobStub objects in your archive table.\n" .
- "These objects are probably already broken, continuing would make them\n" .
- "unrecoverable. Run \"normaliseArchiveTable.php --fix-cgz-bug\" to fix this.\n";
- exit( 1 );
- }
-
echo "Integrity check OK\n";
}
-- Copied from page_title
ar_title varchar(255) binary NOT NULL default '',
- -- Copied from text.old_text, for pages deleted before MediaWiki 1.5.
- -- This row may contain the raw revision text, possibly compressed.
- -- Newer MediaWiki versions use ar_text_id instead.
- -- This field is retained for backwards compatibility, so that
- -- old archived pages will remain accessible.
- -- See migrateArchiveText.php for migrating values to text storage.
- ar_text mediumblob NOT NULL,
-
-- Basic revision stuff...
ar_comment varbinary(767) NOT NULL default '', -- Deprecated in favor of ar_comment_id
ar_comment_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ar_comment should be used)
ar_timestamp binary(14) NOT NULL default '',
ar_minor_edit tinyint NOT NULL default 0,
- -- Copied from text.old_flags, for pages deleted before MediaWiki 1.5.
- -- Otherwise empty string.
- -- See also note for ar_text.
- ar_flags tinyblob NOT NULL,
-
-- Copied from rev_id.
--
-- @since 1.5 Entries from 1.4 will be NULL here. When restoring
-- text storage. Instead, it is merely hidden from public view, by removal
-- of the page and revision entries.
--
- -- @since 1.5 Entries from 1.2-1.4 will have NULL here. When restoring
- -- archive rows without this, ar_text and ar_flags are used instead.
-- @deprecated since 1.31. If rows in the slots table with slot_revision_id = ar_rev_id
- -- exist, this field should be ignored (and may be NULL or 0) in favor of the
+ -- exist, this field should be ignored (and may be 0) in favor of the
-- corresponding data from the slots and content tables
- ar_text_id int unsigned,
+ ar_text_id int unsigned NOT NULL DEFAULT 0,
-- Copied from rev_deleted. Although this may be raised during deletion.
-- Users with the "suppressrevision" right may "archive" and "suppress"
'ar_id' => '2',
'ar_namespace' => '0',
'ar_title' => 'PageArchiveTest_thePage',
- 'ar_text' => '',
'ar_text_id' => '3',
'ar_parent_id' => '2',
],
'ar_id' => '1',
'ar_namespace' => '0',
'ar_title' => 'PageArchiveTest_thePage',
- 'ar_text' => '',
'ar_text_id' => '2',
'ar_parent_id' => '0',
],
return $f + [ 'ar_namespace', 'ar_title' ];
},
];
- yield [
- function ( $f ) {
- unset( $f['ar_text'] );
- return $f;
- },
- ];
yield [
function ( $f ) {
unset( $f['ar_text_id'] );
'ar_id',
'ar_page_id',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_user_text',
'ar_id',
'ar_page_id',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_user_text',
'ar_namespace',
'ar_title',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_minor_edit',
'ar_namespace',
'ar_title',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_minor_edit',
'ar_namespace',
'ar_title',
'ar_rev_id',
- 'ar_text',
'ar_text_id',
'ar_timestamp',
'ar_minor_edit',