class SlotRecord {
/**
- * @var object database result row, as a raw object
+ * @var object database result row, as a raw object. Callbacks are supported for field values,
+ * to enable on-demand emulation of these values. This is primarily intended for use
+ * during schema migration.
*/
private $row;
/**
* Constructs a complete SlotRecord for a newly saved revision, based on the incomplete
* proto-slot. This adds information that has only become available during saving,
- * particularly the revision ID and content address.
+ * particularly the revision ID, content ID and content address.
*
* @param int $revisionId the revision the slot is to be associated with (field slot_revision_id).
* If $protoSlot already has a revision, it must be the same.
- * @param int $contentId the ID of the row in the content table describing the content
+ * @param int|null $contentId the ID of the row in the content table describing the content
* referenced by $contentAddress (field slot_content_id).
* If $protoSlot already has a content ID, it must be the same.
* @param string $contentAddress the slot's content address (field content_address).
SlotRecord $protoSlot
) {
Assert::parameterType( 'integer', $revisionId, '$revisionId' );
- Assert::parameterType( 'integer', $contentId, '$contentId' );
+ // TODO once migration is over $contentId must be an integer
+ Assert::parameterType( 'integer|null', $contentId, '$contentId' );
Assert::parameterType( 'string', $contentAddress, '$contentAddress' );
if ( $protoSlot->hasRevision() && $protoSlot->getRevision() !== $revisionId ) {
);
}
- if ( $protoSlot->hasAddress() && $protoSlot->getContentId() !== $contentId ) {
+ if ( $protoSlot->hasContentId() && $protoSlot->getContentId() !== $contentId ) {
throw new LogicException(
"Mismatching content ID $contentId: "
. "The slot already has content row {$protoSlot->getContentId()} associated."
Assert::parameterType( 'object', $row, '$row' );
Assert::parameterType( 'Content|callable', $content, '$content' );
- Assert::parameter(
- property_exists( $row, 'slot_id' ),
- '$row->slot_id',
- 'must exist'
- );
Assert::parameter(
property_exists( $row, 'slot_revision_id' ),
'$row->slot_revision_id',
* @return bool whether this record contains the given field
*/
private function hasField( $name ) {
+ if ( isset( $this->row->$name ) ) {
+ // if the field is a callback, resolve first, then re-check
+ if ( !is_string( $this->row->$name ) && is_callable( $this->row->$name ) ) {
+ $this->getField( $name );
+ }
+ }
+
return isset( $this->row->$name );
}
return $this->hasField( 'content_address' );
}
+ /**
+ * Whether this slot has an origin (revision ID that originated the slot's content.
+ *
+ * @since 1.32
+ *
+ * @return bool
+ */
+ public function hasOrigin() {
+ return $this->hasField( 'slot_origin' );
+ }
+
+ /**
+ * Whether this slot has a content ID. Slots will have a content ID if their
+ * content has been stored in the content table. While building a new revision,
+ * SlotRecords will not have an ID associated.
+ *
+ * Also, during schema migration, hasContentId() may return false when encountering an
+ * un-migrated database entry in SCHEMA_COMPAT_WRITE_BOTH mode.
+ * It will however always return true for saved revisions on SCHEMA_COMPAT_READ_NEW mode,
+ * or without SCHEMA_COMPAT_WRITE_NEW mode. In the latter case, an emulated content ID
+ * is used, derived from the revision's text ID.
+ *
+ * Note that hasContentId() returning false while hasRevision() returns true always
+ * indicates an unmigrated row in SCHEMA_COMPAT_WRITE_BOTH mode, as described above.
+ * For an unsaved slot, both these methods would return false.
+ *
+ * @since 1.32
+ *
+ * @return bool
+ */
+ public function hasContentId() {
+ return $this->hasField( 'slot_content_id' );
+ }
+
/**
* Whether this slot has revision ID associated. Slots will have a revision ID associated
* only if they were loaded as part of an existing revision. While building a new revision,
* This information should be irrelevant to application logic, it is here to allow
* the construction of a full row for the revision table.
*
+ * Note that this method may return an emulated value during schema migration in
+ * SCHEMA_COMPAT_WRITE_OLD mode. See RevisionStore::emulateContentId for more information.
+ *
* @return int
*/
public function getContentId() {
return \Wikimedia\base_convert( sha1( $blob ), 16, 36, 31 );
}
+ /**
+ * Returns true if $other has the same content as this slot.
+ * The check is performed based on the model, address size, and hash.
+ * Two slots can have the same content if they use different content addresses,
+ * but if they have the same address and the same model, they have the same content.
+ * Two slots can have the same content if they belong to different
+ * revisions or pages.
+ *
+ * Note that hasSameContent() may return false even if Content::equals returns true for
+ * the content of two slots. This may happen if the two slots have different serializations
+ * representing equivalent Content. Such false negatives are considered acceptable. Code
+ * that has to be absolutely sure the Content is really not the same if hasSameContent()
+ * returns false should call getContent() and compare the Content objects directly.
+ *
+ * @since 1.32
+ *
+ * @param SlotRecord $other
+ * @return bool
+ */
+ public function hasSameContent( SlotRecord $other ) {
+ if ( $other === $this ) {
+ return true;
+ }
+
+ if ( $this->getModel() !== $other->getModel() ) {
+ return false;
+ }
+
+ if ( $this->hasAddress()
+ && $other->hasAddress()
+ && $this->getAddress() == $other->getAddress()
+ ) {
+ return true;
+ }
+
+ if ( $this->getSize() !== $other->getSize() ) {
+ return false;
+ }
+
+ if ( $this->getSha1() !== $other->getSha1() ) {
+ return false;
+ }
+
+ return true;
+ }
+
}