X-Git-Url: https://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FStorage%2FRevisionStore.php;h=d2192672982d23b8678a497eac217fbf381c9813;hb=93cfa93d10b0d01228bf520378ad307cec193873;hp=576952779600c7960dc46a93245da8d89d446760;hpb=15f6eff90c305d405fe4331c8a8dc8caa842e5b3;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Storage/RevisionStore.php b/includes/Storage/RevisionStore.php index 5769527796..d219267298 100644 --- a/includes/Storage/RevisionStore.php +++ b/includes/Storage/RevisionStore.php @@ -746,6 +746,76 @@ class RevisionStore if ( !isset( $revisionRow['rev_id'] ) ) { // only if auto-increment was used $revisionRow['rev_id'] = intval( $dbw->insertId() ); + + if ( $dbw->getType() === 'mysql' ) { + // (T202032) MySQL until 8.0 and MariaDB until some version after 10.1.34 don't save the + // auto-increment value to disk, so on server restart it might reuse IDs from deleted + // revisions. We can fix that with an insert with an explicit rev_id value, if necessary. + + $maxRevId = intval( $dbw->selectField( 'archive', 'MAX(ar_rev_id)', '', __METHOD__ ) ); + $table = 'archive'; + if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) { + $maxRevId2 = intval( $dbw->selectField( 'slots', 'MAX(slot_revision_id)', '', __METHOD__ ) ); + if ( $maxRevId2 >= $maxRevId ) { + $maxRevId = $maxRevId2; + $table = 'slots'; + } + } + + if ( $maxRevId >= $revisionRow['rev_id'] ) { + $this->logger->debug( + '__METHOD__: Inserted revision {revid} but {table} has revisions up to {maxrevid}.' + . ' Trying to fix it.', + [ + 'revid' => $revisionRow['rev_id'], + 'table' => $table, + 'maxrevid' => $maxRevId, + ] + ); + + if ( !$dbw->lock( 'fix-for-T202032', __METHOD__ ) ) { + throw new MWException( 'Failed to get database lock for T202032' ); + } + $fname = __METHOD__; + $dbw->onTransactionResolution( function ( $trigger, $dbw ) use ( $fname ) { + $dbw->unlock( 'fix-for-T202032', $fname ); + } ); + + $dbw->delete( 'revision', [ 'rev_id' => $revisionRow['rev_id'] ], __METHOD__ ); + + // The locking here is mostly to make MySQL bypass the REPEATABLE-READ transaction + // isolation (weird MySQL "feature"). It does seem to block concurrent auto-incrementing + // inserts too, though, at least on MariaDB 10.1.29. + // + // Don't try to lock `revision` in this way, it'll deadlock if there are concurrent + // transactions in this code path thanks to the row lock from the original ->insert() above. + // + // And we have to use raw SQL to bypass the "aggregation used with a locking SELECT" warning + // that's for non-MySQL DBs. + $row1 = $dbw->query( + $dbw->selectSqlText( 'archive', [ 'v' => "MAX(ar_rev_id)" ], '', __METHOD__ ) . ' FOR UPDATE' + )->fetchObject(); + if ( $this->hasMcrSchemaFlags( SCHEMA_COMPAT_WRITE_NEW ) ) { + $row2 = $dbw->query( + $dbw->selectSqlText( 'slots', [ 'v' => "MAX(slot_revision_id)" ], '', __METHOD__ ) + . ' FOR UPDATE' + )->fetchObject(); + } else { + $row2 = null; + } + $maxRevId = max( + $maxRevId, + $row1 ? intval( $row1->v ) : 0, + $row2 ? intval( $row2->v ) : 0 + ); + + // If we don't have SCHEMA_COMPAT_WRITE_NEW, all except the first of any concurrent + // transactions will throw a duplicate key error here. It doesn't seem worth trying + // to avoid that. + $revisionRow['rev_id'] = $maxRevId + 1; + $dbw->insert( 'revision', $revisionRow, __METHOD__ ); + } + } } $commentCallback( $revisionRow['rev_id'] ); @@ -1477,6 +1547,10 @@ class RevisionStore $slots = []; foreach ( $res as $row ) { + // resolve role names and model names from in-memory cache, instead of joining. + $row->role_name = $this->slotRoleStore->getName( (int)$row->slot_role_id ); + $row->model_name = $this->contentModelStore->getName( (int)$row->content_model ); + $contentCallback = function ( SlotRecord $slot ) use ( $queryFlags, $row ) { return $this->loadSlotContent( $slot, null, null, null, $queryFlags ); }; @@ -2174,7 +2248,9 @@ class RevisionStore // NOTE: even when this class is set to not read from the old schema, callers // should still be able to join against the text table, as long as we are still // writing the old schema for compatibility. - wfDeprecated( __METHOD__ . ' with `text` option', '1.32' ); + // TODO: This should trigger a deprecation warning eventually (T200918), but not + // before all known usages are removed (see T198341 and T201164). + // wfDeprecated( __METHOD__ . ' with `text` option', '1.32' ); } $ret['tables'][] = 'text'; @@ -2196,6 +2272,9 @@ class RevisionStore * * @param array $options Any combination of the following strings * - 'content': Join with the content table, and select content meta-data fields + * - 'model': Join with the content_models table, and select the model_name field. + * Only applicable if 'content' is also set. + * - 'role': Join with the slot_roles table, and select the role_name field * * @return array With three keys: * - tables: (string[]) to include in the `$table` to `IDatabase->select()` @@ -2232,26 +2311,39 @@ class RevisionStore } } else { $ret['tables'][] = 'slots'; - $ret['tables'][] = 'slot_roles'; $ret['fields'] = array_merge( $ret['fields'], [ 'slot_revision_id', 'slot_content_id', 'slot_origin', - 'role_name' + 'slot_role_id', ] ); - $ret['joins']['slot_roles'] = [ 'INNER JOIN', [ 'slot_role_id = role_id' ] ]; + + if ( in_array( 'role', $options, true ) ) { + // Use left join to attach role name, so we still find the revision row even + // if the role name is missing. This triggers a more obvious failure mode. + $ret['tables'][] = 'slot_roles'; + $ret['joins']['slot_roles'] = [ 'LEFT JOIN', [ 'slot_role_id = role_id' ] ]; + $ret['fields'][] = 'role_name'; + } if ( in_array( 'content', $options, true ) ) { $ret['tables'][] = 'content'; - $ret['tables'][] = 'content_models'; $ret['fields'] = array_merge( $ret['fields'], [ 'content_size', 'content_sha1', 'content_address', - 'model_name' + 'content_model', ] ); $ret['joins']['content'] = [ 'INNER JOIN', [ 'slot_content_id = content_id' ] ]; - $ret['joins']['content_models'] = [ 'INNER JOIN', [ 'content_model = model_id' ] ]; + + if ( in_array( 'model', $options, true ) ) { + // Use left join to attach model name, so we still find the revision row even + // if the model name is missing. This triggers a more obvious failure mode. + $ret['tables'][] = 'content_models'; + $ret['joins']['content_models'] = [ 'LEFT JOIN', [ 'content_model = model_id' ] ]; + $ret['fields'][] = 'model_name'; + } + } }