and can contain any PHP statements. It usually sets global variables that are
used for configuration, and includes files used by any extensions.
-Distributors can easily change the installer behavior, including LocalSettings
-generated, by placing their overrides into mw-config/overrides directory. Doing
-that is highly preferred to modifying MediaWiki code directly. See
-mw-config/overrides/README for more details and examples.
+Distributors can easily change the default settings by creating
+includes/PlatformSettings.php with overrides/additions to the default settings.
+The installer will automatically include the platform defaults when generating
+the user's LocalSettings.php file.
+
+Furthermore, distributors can change the installer behavior, by placing their
+overrides into mw-config/overrides directory. Doing that is highly preferred
+to modifying MediaWiki code directly. See mw-config/overrides/README for more
+details and examples.
There's a new maintenance/install.php script which could be used for performing
an install through the command line.
$authManager = AuthManager::singleton();
$linkRenderer = $services->getLinkRendererFactory()->create();
$config = $services->getMainConfig();
- return new DefaultPreferencesFactory( $config, $wgContLang, $authManager, $linkRenderer );
+ $factory = new DefaultPreferencesFactory( $config, $wgContLang, $authManager, $linkRenderer );
+ $factory->setLogger( LoggerFactory::getInstance( 'preferences' ) );
+
+ return $factory;
},
'HttpRequestFactory' => function ( MediaWikiServices $services ) {
}
$mcservers = $this->buildMemcachedServerList();
+ if ( file_exists( dirname( __DIR__ ) . '/PlatformSettings.php' ) ) {
+ $platformSettings = "\n## Include platform/distribution defaults";
+ $platformSettings .= "\nrequire_once \"\$IP/includes/PlatformSettings.php\";";
+ } else {
+ $platformSettings = '';
+ }
return "<?php
# This file was automatically generated by the MediaWiki {$GLOBALS['wgVersion']}
if ( !defined( 'MEDIAWIKI' ) ) {
exit;
}
+{$platformSettings}
## Uncomment this to disable output compression
# \$wgDisableOutputCompression = true;
'recentchanges',
'rc_namespace_title_timestamp', '( rc_namespace, rc_title, rc_timestamp )'
],
+ [ 'setSequenceOwner', 'mwuser', 'user_id', 'user_user_id_seq' ],
+ [ 'setSequenceOwner', 'actor', 'actor_id', 'actor_actor_id_seq' ],
+ [ 'setSequenceOwner', 'page', 'page_id', 'page_page_id_seq' ],
+ [ 'setSequenceOwner', 'revision', 'rev_id', 'revision_rev_id_seq' ],
+ [ 'setSequenceOwner', 'ip_changes', 'ipc_rev_id', 'ip_changes_ipc_rev_id_seq' ],
+ [ 'setSequenceOwner', 'pagecontent', 'old_id', 'text_old_id_seq' ],
+ [ 'setSequenceOwner', 'comment', 'comment_id', 'comment_comment_id_seq' ],
+ [ 'setSequenceOwner', 'page_restrictions', 'pr_id', 'page_restrictions_pr_id_seq' ],
+ [ 'setSequenceOwner', 'archive', 'ar_id', 'archive_ar_id_seq' ],
+ [ 'setSequenceOwner', 'content', 'content_id', 'content_content_id_seq' ],
+ [ 'setSequenceOwner', 'slot_roles', 'role_id', 'slot_roles_role_id_seq' ],
+ [ 'setSequenceOwner', 'content_models', 'model_id', 'content_models_model_id_seq' ],
+ [ 'setSequenceOwner', 'externallinks', 'el_id', 'externallinks_el_id_seq' ],
+ [ 'setSequenceOwner', 'ipblocks', 'ipb_id', 'ipblocks_ipb_id_seq' ],
+ [ 'setSequenceOwner', 'filearchive', 'fa_id', 'filearchive_fa_id_seq' ],
+ [ 'setSequenceOwner', 'uploadstash', 'us_id', 'uploadstash_us_id_seq' ],
+ [ 'setSequenceOwner', 'recentchanges', 'rc_id', 'recentchanges_rc_id_seq' ],
+ [ 'setSequenceOwner', 'watchlist', 'wl_id', 'watchlist_wl_id_seq' ],
+ [ 'setSequenceOwner', 'logging', 'log_id', 'logging_log_id_seq' ],
+ [ 'setSequenceOwner', 'job', 'job_id', 'job_job_id_seq' ],
+ [ 'setSequenceOwner', 'category', 'cat_id', 'category_cat_id_seq' ],
+ [ 'setSequenceOwner', 'change_tag', 'ct_id', 'change_tag_ct_id_seq' ],
+ [ 'setSequenceOwner', 'tag_summary', 'ts_id', 'tag_summary_ts_id_seq' ],
+ [ 'setSequenceOwner', 'sites', 'site_id', 'sites_site_id_seq' ],
];
}
protected function addSequence( $table, $pkey, $ns ) {
if ( !$this->db->sequenceExists( $ns ) ) {
$this->output( "Creating sequence $ns\n" );
- $this->db->query( "CREATE SEQUENCE $ns" );
if ( $pkey !== false ) {
+ $this->db->query( "CREATE SEQUENCE $ns OWNED BY $table.$pkey" );
$this->setDefault( $table, $pkey, '"nextval"(\'"' . $ns . '"\'::"regclass")' );
+ } else {
+ $this->db->query( "CREATE SEQUENCE $ns" );
}
}
}
}
}
+ protected function setSequenceOwner( $table, $pkey, $seq ) {
+ if ( $this->db->sequenceExists( $seq ) ) {
+ $this->output( "Setting sequence $seq owner to $table.$pkey\n" );
+ $this->db->query( "ALTER SEQUENCE $seq OWNED BY $table.$pkey" );
+ }
+ }
+
protected function renameTable( $old, $new, $patch = false ) {
if ( $this->db->tableExists( $old ) ) {
$this->output( "Renaming table $old to $new\n" );
* @var Exception|null The last error that caused the status to become STATUS_TRX_ERROR
*/
protected $trxStatusCause;
+ /**
+ * @var array|null If wasKnownStatementRollbackError() prevented trxStatus from being set,
+ * the relevant details are stored here.
+ */
+ protected $trxStatusIgnoredCause;
/**
* Either 1 if a transaction is active or 0 otherwise.
* The other Trx fields may not be meaningfull if this is 0.
}
if ( $ret === false ) {
- if ( $this->trxLevel && !$this->wasKnownStatementRollbackError() ) {
- # Either the query was aborted or all queries after BEGIN where aborted.
- if ( $this->explicitTrxActive() || $priorWritesPending ) {
- # In the first case, the only options going forward are (a) ROLLBACK, or
- # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only
- # option is ROLLBACK, since the snapshots would have been released.
- $this->trxStatus = self::STATUS_TRX_ERROR;
- $this->trxStatusCause =
- $this->makeQueryException( $lastError, $lastErrno, $sql, $fname );
- $tempIgnore = false; // cannot recover
+ if ( $this->trxLevel ) {
+ if ( !$this->wasKnownStatementRollbackError() ) {
+ # Either the query was aborted or all queries after BEGIN where aborted.
+ if ( $this->explicitTrxActive() || $priorWritesPending ) {
+ # In the first case, the only options going forward are (a) ROLLBACK, or
+ # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only
+ # option is ROLLBACK, since the snapshots would have been released.
+ $this->trxStatus = self::STATUS_TRX_ERROR;
+ $this->trxStatusCause =
+ $this->makeQueryException( $lastError, $lastErrno, $sql, $fname );
+ $tempIgnore = false; // cannot recover
+ } else {
+ # Nothing prior was there to lose from the transaction,
+ # so just roll it back.
+ $this->doRollback( __METHOD__ . " ($fname)" );
+ $this->trxStatus = self::STATUS_TRX_OK;
+ }
+ $this->trxStatusIgnoredCause = null;
} else {
- # Nothing prior was there to lose from the transaction,
- # so just roll it back.
- $this->doRollback( __METHOD__ . " ($fname)" );
- $this->trxStatus = self::STATUS_TRX_OK;
+ # We're ignoring an error that caused just the current query to be aborted.
+ # But log the cause so we can log a deprecation notice if a
+ # caller actually does ignore it.
+ $this->trxStatusIgnoredCause = [ $lastError, $lastErrno, $fname ];
}
}
* @throws DBTransactionStateError
*/
private function assertTransactionStatus( $sql, $fname ) {
- if (
- $this->trxStatus < self::STATUS_TRX_OK &&
- $this->getQueryVerb( $sql ) !== 'ROLLBACK' // transaction/savepoint
- ) {
+ if ( $this->getQueryVerb( $sql ) === 'ROLLBACK' ) { // transaction/savepoint
+ return;
+ }
+
+ if ( $this->trxStatus < self::STATUS_TRX_OK ) {
throw new DBTransactionStateError(
$this,
"Cannot execute query from $fname while transaction status is ERROR. ",
[],
$this->trxStatusCause
);
+ } elseif ( $this->trxStatus === self::STATUS_TRX_OK && $this->trxStatusIgnoredCause ) {
+ list( $iLastError, $iLastErrno, $iFname ) = $this->trxStatusIgnoredCause;
+ call_user_func( $this->deprecationLogger,
+ "Caller from $fname ignored an error originally raised from $iFname: " .
+ "[$iLastErrno] $iLastError"
+ );
+ $this->trxStatusIgnoredCause = null;
}
}
} elseif ( $savepointId !== 'n/a' ) {
$this->doRollbackToSavepoint( $savepointId, $fname );
$this->trxStatus = self::STATUS_TRX_OK; // no exception; recovered
+ $this->trxStatusIgnoredCause = null;
}
$this->affectedRowCount = 0; // for the sake of consistency
$this->doBegin( $fname );
$this->trxStatus = self::STATUS_TRX_OK;
+ $this->trxStatusIgnoredCause = null;
$this->trxAtomicCounter = 0;
$this->trxTimestamp = microtime( true );
$this->trxFname = $fname;
$newNameE = $this->addIdentifierQuotes( $newName );
$oldNameE = $this->addIdentifierQuotes( $oldName );
- $ret = $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newNameE " .
+ $temporary = $temporary ? 'TEMPORARY' : '';
+
+ $ret = $this->query( "CREATE $temporary TABLE $newNameE " .
"(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)", $fname );
if ( !$ret ) {
return $ret;
$fieldE = $this->addIdentifierQuotes( $field );
$newSeqE = $this->addIdentifierQuotes( $newSeq );
$newSeqQ = $this->addQuotes( $newSeq );
- $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " SEQUENCE $newSeqE", $fname );
+ $this->query( "CREATE $temporary SEQUENCE $newSeqE OWNED BY $newNameE.$fieldE", $fname );
$this->query(
"ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
$fname
use Parser;
use ParserOptions;
use PreferencesForm;
+use Psr\Log\LoggerAwareTrait;
+use Psr\Log\NullLogger;
use Skin;
use SpecialPage;
use Status;
* This is the default implementation of PreferencesFactory.
*/
class DefaultPreferencesFactory implements PreferencesFactory {
+ use LoggerAwareTrait;
/** @var Config */
protected $config;
$this->contLang = $contLang;
$this->authManager = $authManager;
$this->linkRenderer = $linkRenderer;
+ $this->logger = new NullLogger();
}
/**
Hooks::run( 'GetPreferences', [ $user, &$preferences ] );
$this->loadPreferenceValues( $user, $context, $preferences );
+ $this->logger->debug( "Created form descriptor for user '{$user->getName()}'" );
return $preferences;
}
"tog-watchlisthideminor": "Hide minor edits from the watchlist",
"tog-watchlisthideliu": "Hide edits by logged in users from the watchlist",
"tog-watchlistreloadautomatically": "Reload the watchlist automatically whenever a filter is changed (JavaScript required)",
- "tog-watchlistunwatchlinks": "Add direct unwatch/watch links to watchlist entries (JavaScript required for toggle functionality)",
+ "tog-watchlistunwatchlinks": "Add direct unwatch/watch markers ({{int:Watchlist-unwatch}}/{{int:Watchlist-unwatch-undo}}) to watched pages with changes (JavaScript required for toggle functionality)",
"tog-watchlisthideanons": "Hide edits by anonymous users from the watchlist",
"tog-watchlisthidepatrolled": "Hide patrolled edits from the watchlist",
"tog-watchlisthidecategorization": "Hide categorization of pages",
user_editcount INTEGER,
user_password_expires TIMESTAMPTZ NULL
);
+ALTER SEQUENCE user_user_id_seq OWNED BY mwuser.user_id;
CREATE INDEX user_email_token_idx ON mwuser (user_email_token);
-- Create a dummy user to satisfy fk contraints especially with revisions
actor_user INTEGER,
actor_name TEXT NOT NULL
);
+ALTER SEQUENCE actor_actor_id_seq OWNED BY actor.actor_id;
CREATE UNIQUE INDEX actor_user ON actor (actor_user);
CREATE UNIQUE INDEX actor_name ON actor (actor_name);
page_content_model TEXT,
page_lang TEXT DEFAULT NULL
);
+ALTER SEQUENCE page_page_id_seq OWNED BY page.page_id;
CREATE UNIQUE INDEX page_unique_name ON page (page_namespace, page_title);
CREATE INDEX page_main_title ON page (page_title text_pattern_ops) WHERE page_namespace = 0;
CREATE INDEX page_talk_title ON page (page_title text_pattern_ops) WHERE page_namespace = 1;
rev_content_model TEXT,
rev_content_format TEXT
);
+ALTER SEQUENCE revision_rev_id_seq OWNED BY revision.rev_id;
CREATE UNIQUE INDEX revision_unique ON revision (rev_page, rev_id);
CREATE INDEX rev_text_id_idx ON revision (rev_text_id);
CREATE INDEX rev_timestamp_idx ON revision (rev_timestamp);
CREATE INDEX rev_page_actor_timestamp ON revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
CREATE SEQUENCE ip_changes_ipc_rev_id_seq;
-
CREATE TABLE ip_changes (
ipc_rev_id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('ip_changes_ipc_rev_id_seq'),
ipc_rev_timestamp TIMESTAMPTZ NOT NULL,
ipc_hex BYTEA NOT NULL DEFAULT ''
);
-
+ALTER SEQUENCE ip_changes_ipc_rev_id_seq OWNED BY ip_changes.ipc_rev_id;
CREATE INDEX ipc_rev_timestamp ON ip_changes (ipc_rev_timestamp);
CREATE INDEX ipc_hex_time ON ip_changes (ipc_hex,ipc_rev_timestamp);
old_text TEXT,
old_flags TEXT
);
+ALTER SEQUENCE text_old_id_seq OWNED BY pagecontent.old_id;
CREATE SEQUENCE comment_comment_id_seq;
comment_text TEXT NOT NULL,
comment_data TEXT
);
+ALTER SEQUENCE comment_comment_id_seq OWNED BY comment.comment_id;
CREATE INDEX comment_hash ON comment (comment_hash);
pr_user INTEGER NULL,
pr_expiry TIMESTAMPTZ NULL
);
+ALTER SEQUENCE page_restrictions_pr_id_seq OWNED BY page_restrictions.pr_id;
ALTER TABLE page_restrictions ADD CONSTRAINT page_restrictions_pk PRIMARY KEY (pr_page,pr_type);
CREATE TABLE page_props (
ar_content_model TEXT,
ar_content_format TEXT
);
+ALTER SEQUENCE archive_ar_id_seq OWNED BY archive.ar_id;
CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
CREATE INDEX archive_user_text ON archive (ar_user_text);
CREATE INDEX archive_actor ON archive (ar_actor);
content_model SMALLINT NOT NULL,
content_address TEXT NOT NULL
);
+ALTER SEQUENCE content_content_id_seq OWNED BY content.content_id;
CREATE SEQUENCE slot_roles_role_id_seq;
role_id SMALLINT NOT NULL PRIMARY KEY DEFAULT nextval('slot_roles_role_id_seq'),
role_name TEXT NOT NULL
);
+ALTER SEQUENCE slot_roles_role_id_seq OWNED BY slot_roles.role_id;
CREATE UNIQUE INDEX role_name ON slot_roles (role_name);
model_id SMALLINT NOT NULL PRIMARY KEY DEFAULT nextval('content_models_model_id_seq'),
model_name TEXT NOT NULL
);
+ALTER SEQUENCE content_models_model_id_seq OWNED BY content_models.model_id;
CREATE UNIQUE INDEX model_name ON content_models (model_name);
el_index TEXT NOT NULL,
el_index_60 BYTEA NOT NULL DEFAULT ''
);
+ALTER SEQUENCE externallinks_el_id_seq OWNED BY externallinks.el_id;
CREATE INDEX externallinks_from_to ON externallinks (el_from,el_to);
CREATE INDEX externallinks_index ON externallinks (el_index);
CREATE INDEX el_index_60 ON externallinks (el_index_60, el_id);
ipb_block_email SMALLINT NOT NULL DEFAULT 0,
ipb_allow_usertalk SMALLINT NOT NULL DEFAULT 0,
ipb_parent_block_id INTEGER NULL REFERENCES ipblocks(ipb_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED
-
);
+ALTER SEQUENCE ipblocks_ipb_id_seq OWNED BY ipblocks.ipb_id;
CREATE UNIQUE INDEX ipb_address_unique ON ipblocks (ipb_address,ipb_user,ipb_auto,ipb_anon_only);
CREATE INDEX ipb_user ON ipblocks (ipb_user);
CREATE INDEX ipb_range ON ipblocks (ipb_range_start,ipb_range_end);
fa_deleted SMALLINT NOT NULL DEFAULT 0,
fa_sha1 TEXT NOT NULL DEFAULT ''
);
+ALTER SEQUENCE filearchive_fa_id_seq OWNED BY filearchive.fa_id;
CREATE INDEX fa_name_time ON filearchive (fa_name, fa_timestamp);
CREATE INDEX fa_dupe ON filearchive (fa_storage_group, fa_storage_key);
CREATE INDEX fa_notime ON filearchive (fa_deleted_timestamp);
CREATE SEQUENCE uploadstash_us_id_seq;
CREATE TYPE media_type AS ENUM ('UNKNOWN','BITMAP','DRAWING','AUDIO','VIDEO','MULTIMEDIA','OFFICE','TEXT','EXECUTABLE','ARCHIVE','3D');
-
CREATE TABLE uploadstash (
us_id INTEGER PRIMARY KEY NOT NULL DEFAULT nextval('uploadstash_us_id_seq'),
us_user INTEGER,
us_image_height INTEGER,
us_image_bits SMALLINT
);
+ALTER SEQUENCE uploadstash_us_id_seq OWNED BY uploadstash.us_id;
CREATE INDEX us_user_idx ON uploadstash (us_user);
CREATE UNIQUE INDEX us_key_idx ON uploadstash (us_key);
rc_log_action TEXT,
rc_params TEXT
);
+ALTER SEQUENCE recentchanges_rc_id_seq OWNED BY recentchanges.rc_id;
CREATE INDEX rc_timestamp ON recentchanges (rc_timestamp);
CREATE INDEX rc_timestamp_bot ON recentchanges (rc_timestamp) WHERE rc_bot = 0;
CREATE INDEX rc_namespace_title_timestamp ON recentchanges (rc_namespace, rc_title, rc_timestamp);
wl_title TEXT NOT NULL,
wl_notificationtimestamp TIMESTAMPTZ
);
+ALTER SEQUENCE watchlist_wl_id_seq OWNED BY watchlist.wl_id;
CREATE UNIQUE INDEX wl_user_namespace_title ON watchlist (wl_namespace, wl_title, wl_user);
CREATE INDEX wl_user ON watchlist (wl_user);
CREATE INDEX wl_user_notificationtimestamp ON watchlist (wl_user, wl_notificationtimestamp);
log_user_text TEXT NOT NULL DEFAULT '',
log_page INTEGER
);
+ALTER SEQUENCE logging_log_id_seq OWNED BY logging.log_id;
CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
CREATE INDEX logging_actor_time_backwards ON logging (log_timestamp, log_actor);
job_token_timestamp TIMESTAMPTZ,
job_sha1 TEXT NOT NULL DEFAULT ''
);
+ALTER SEQUENCE job_job_id_seq OWNED BY job.job_id;
CREATE INDEX job_sha1 ON job (job_sha1);
CREATE INDEX job_cmd_token ON job (job_cmd, job_token, job_random);
CREATE INDEX job_cmd_token_id ON job (job_cmd, job_token, job_id);
cat_files INTEGER NOT NULL DEFAULT 0,
cat_hidden SMALLINT NOT NULL DEFAULT 0
);
+ALTER SEQUENCE category_cat_id_seq OWNED BY category.cat_id;
CREATE UNIQUE INDEX category_title ON category(cat_title);
CREATE INDEX category_pages ON category(cat_pages);
ct_tag TEXT NOT NULL,
ct_params TEXT NULL
);
+ALTER SEQUENCE change_tag_ct_id_seq OWNED BY change_tag.ct_id;
CREATE UNIQUE INDEX change_tag_rc_tag ON change_tag(ct_rc_id,ct_tag);
CREATE UNIQUE INDEX change_tag_log_tag ON change_tag(ct_log_id,ct_tag);
CREATE UNIQUE INDEX change_tag_rev_tag ON change_tag(ct_rev_id,ct_tag);
ts_rev_id INTEGER NULL,
ts_tags TEXT NOT NULL
);
+ALTER SEQUENCE tag_summary_ts_id_seq OWNED BY tag_summary.ts_id;
CREATE UNIQUE INDEX tag_summary_rc_id ON tag_summary(ts_rc_id);
CREATE UNIQUE INDEX tag_summary_log_id ON tag_summary(ts_log_id);
CREATE UNIQUE INDEX tag_summary_rev_id ON tag_summary(ts_rev_id);
site_forward SMALLINT NOT NULL,
site_config TEXT NOT NULL
);
+ALTER SEQUENCE sites_site_id_seq OWNED BY sites.site_id;
CREATE UNIQUE INDEX site_global_key ON sites (site_global_key);
CREATE INDEX site_type ON sites (site_type);
CREATE INDEX site_group ON sites (site_group);
"karma-mocha-reporter": "2.2.5",
"karma-qunit": "1.2.1",
"mwbot": "1.0.10",
- "postcss-less": "1.1.3",
+ "postcss-less": "1.1.5",
"qunitjs": "2.4.1",
"stylelint": "9.2.0",
"stylelint-config-wikimedia": "0.4.3",
$this->errorLogger = function ( Exception $e ) {
wfWarn( get_class( $e ) . ": {$e->getMessage()}" );
};
+ $this->deprecationLogger = function ( $msg ) {
+ wfWarn( $msg );
+ };
$this->currentDomain = DatabaseDomain::newUnspecified();
$this->open( 'localhost', 'testuser', 'password', 'testdb' );
}
* @covers \Wikimedia\Rdbms\Database::query
*/
public function testImplicitTransactionRollback() {
- $doError = function ( $wasKnown = true ) {
+ $doError = function () {
$this->database->forceNextQueryError( 666, 'Evilness' );
try {
$this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
// Implicit transaction gets silently rolled back
$this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
- call_user_func( $doError, false );
+ call_user_func( $doError );
$this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
$this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
// phpcs:ignore
// ... unless there were prior writes
$this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
$this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
- call_user_func( $doError, false );
+ call_user_func( $doError );
try {
$this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
$this->fail( 'Expected exception not thrown' );
$this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; DELETE FROM error WHERE 1; ROLLBACK' );
}
+ /**
+ * @covers \Wikimedia\Rdbms\Database::query
+ */
+ public function testTransactionStatementRollbackIgnoring() {
+ $wrapper = TestingAccessWrapper::newFromObject( $this->database );
+ $warning = [];
+ $wrapper->deprecationLogger = function ( $msg ) use ( &$warning ) {
+ $warning[] = $msg;
+ };
+
+ $doError = function () {
+ $this->database->forceNextQueryError( 666, 'Evilness', [
+ 'wasKnownStatementRollbackError' => true,
+ ] );
+ try {
+ $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBError $e ) {
+ $this->assertSame( 666, $e->errno );
+ }
+ };
+ $expectWarning = 'Caller from ' . __METHOD__ .
+ ' ignored an error originally raised from ' . __CLASS__ . '::SomeCaller: [666] Evilness';
+
+ // Rollback doesn't raise a warning
+ $warning = [];
+ $this->database->startAtomic( __METHOD__ );
+ call_user_func( $doError );
+ $this->database->rollback( __METHOD__ );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->assertSame( [], $warning );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; DELETE FROM x WHERE field = \'1\'' );
+
+ // cancelAtomic() doesn't raise a warning
+ $warning = [];
+ $this->database->begin( __METHOD__ );
+ $this->database->startAtomic( __METHOD__, Database::ATOMIC_CANCELABLE );
+ call_user_func( $doError );
+ $this->database->cancelAtomic( __METHOD__ );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ $this->assertSame( [], $warning );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM error WHERE 1; ROLLBACK TO SAVEPOINT wikimedia_rdbms_atomic1; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+
+ // Commit does raise a warning
+ $warning = [];
+ $this->database->begin( __METHOD__ );
+ call_user_func( $doError );
+ $this->database->commit( __METHOD__ );
+ $this->assertSame( [ $expectWarning ], $warning );
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; COMMIT' );
+
+ // Deprecation only gets raised once
+ $warning = [];
+ $this->database->begin( __METHOD__ );
+ call_user_func( $doError );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->commit( __METHOD__ );
+ $this->assertSame( [ $expectWarning ], $warning );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+ }
+
/**
* @covers \Wikimedia\Rdbms\Database::close
*/