Skip to content

Commit

Permalink
EZP-28681: Legacy transaction handling (#1415)
Browse files Browse the repository at this point in the history
* EZP-29797: Legacy transaction handling: Discard draft (attribute delete)
* EZP-29798: Legacy transaction handling: Discard draft
* EZP-29799: Legacy transaction handling: Publish draft
* EZP-29800: Legacy transaction handling: DFS cache purge
* EZP-29796: Legacy transaction handling: Autosave
  • Loading branch information
glye authored Feb 15, 2019
1 parent c8ce309 commit 8e58bc6
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 11 deletions.
53 changes: 53 additions & 0 deletions kernel/classes/ezcontentobject.php
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,59 @@ static function fetch( $id, $asObject = true )
}
}

/**
* Fetches a content object by ID, using SELECT ... FOR UPDATE
*
* Ensures the table row is locked, blocking other transactions from locking it (read or write).
* Usage must be within a transaction, or it will be locked for the current session.
*
* @param int $id ID of the content object to fetch
* @param bool $asObject Return the result as an object (true) or an assoc. array (false)
*
* @return eZContentObject|mixed|null
*/
static function fetchForUpdate( $id, $asObject = true )
{
global $eZContentObjectContentObjectCache;

$db = eZDB::instance();
$id = (int) $id;

// Select for update, to lock the row
$resArray = $db->arrayQuery( "SELECT * FROM ezcontentobject WHERE id='$id' FOR UPDATE" );

if ( !is_array( $resArray ) || count( $resArray ) !== 1 )
{
eZDebug::writeError( "Object not found ($id)", __METHOD__ );
return null;
}

$objectArray = $resArray[0];
$classId = $objectArray['contentclass_id'];
$contentClassResArray = $db->arrayQuery(
"SELECT
ezcontentclass.serialized_name_list as serialized_name_list,
ezcontentclass.identifier as contentclass_identifier,
ezcontentclass.is_container as is_container
FROM
ezcontentclass
WHERE
ezcontentclass.id='$classId' AND
ezcontentclass.version=0"
);
$objectArray = array_merge($objectArray, $contentClassResArray[0]);

if ( !$asObject )
{
return $objectArray;
}

$obj = new eZContentObject( $objectArray );
$eZContentObjectContentObjectCache[$id] = $obj;

return $obj;
}

/**
* Returns true, if a content object with the ID $id exists, false otherwise
*
Expand Down
50 changes: 48 additions & 2 deletions kernel/classes/ezcontentobjectversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,47 @@ static function fetchVersion( $version, $contentObjectID, $asObject = true )
return isset( $ret[0] ) ? $ret[0] : false;
}

static function fetchVersionForUpdate( $version, $contentObjectID, $asObject = true )
{
global $eZContentObjectVersionCache;

$db = eZDB::instance();
$version = (int) $version;
$contentObjectID = (int) $contentObjectID;

// Select for update, to lock the row
$resArray = $db->arrayQuery(
"SELECT * FROM
ezcontentobject_version
WHERE
version='$version' AND
contentobject_id='$contentObjectID'
FOR UPDATE"
);

if ( !is_array( $resArray ) || count( $resArray ) !== 1 )
{
eZDebug::writeError( "Version '$version' for content '$contentObjectID' not found", __METHOD__ );
return null;
}

if ( !isset( $resArray[0] ) )
{
return false;
}
$versionArray = $resArray[0];

if ( !$asObject )
{
return $versionArray;
}

$versionObject = new eZContentObjectVersion( $versionArray );
$eZContentObjectVersionCache[$contentObjectID][$version] = $versionObject;

return $versionObject;
}

static function fetchUserDraft( $objectID, $userID )
{
$versions = eZPersistentObject::fetchObjectList( eZContentObjectVersion::definition(),
Expand Down Expand Up @@ -913,6 +954,13 @@ function removeThis()
$contentobjectID = $this->attribute( 'contentobject_id' );
$versionNum = $this->attribute( 'version' );

$db = eZDB::instance();
$db->begin();

// Ensure no one else deletes this version while we are doing it.
$db->query( 'SELECT * FROM ezcontentobject_version
WHERE id=' . $this->attribute( 'id' ) . ' FOR UPDATE' );

$contentObjectTranslations = $this->translations();

foreach ( $contentObjectTranslations as $contentObjectTranslation )
Expand All @@ -923,8 +971,6 @@ function removeThis()
$attribute->removeThis( $attribute->attribute( 'id' ), $versionNum );
}
}
$db = eZDB::instance();
$db->begin();
$db->query( "DELETE FROM ezcontentobject_link
WHERE from_contentobject_id=$contentobjectID AND from_contentobject_version=$versionNum" );
$db->query( "DELETE FROM eznode_assignment
Expand Down
27 changes: 24 additions & 3 deletions kernel/content/ezcontentoperationcollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,18 +127,39 @@ static public function commitTransaction()

static public function setVersionStatus( $objectID, $versionNum, $status )
{
$object = eZContentObject::fetch( $objectID );
$db = eZDB::instance();
$db->begin();

if ( !$versionNum )
{
$versionNum = $object->attribute( 'current_version' );
$objectRows = $db->arrayQuery( "SELECT * FROM ezcontentobject WHERE id = $objectID FOR UPDATE" );
if ( empty( $objectRows ) )
{
$db->commit(); // We haven't made any changes, but commit here to avoid affecting any outer transactions.
return;
}

$versionNum = $objectRows[0]['current_version'];
}
$version = $object->version( $versionNum );

$versionRows = $db->arrayQuery( "SELECT * FROM ezcontentobject_version WHERE version = $versionNum AND contentobject_id = $objectID FOR UPDATE" );
if ( empty( $versionRows ) )
{
$db->commit(); // We haven't made any changes, but commit here to avoid affecting any outer transactions.
return;
}

$version = eZContentObjectVersion::fetch( $versionRows[0]['id'] );
if ( !$version )
{
$db->commit(); // We haven't made any changes, but commit here to avoid affecting any outer transactions.
return;
}

$version->setAttribute( 'status', $status );
$version->store();

$db->commit();
}

static public function setObjectStatusPublished( $objectID, $versionNum )
Expand Down
17 changes: 16 additions & 1 deletion kernel/content/removeeditversion.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
$Module = $Params['Module'];
$http = eZHTTPTool::instance();
$db = eZDB::instance();
$objectID = (int) $http->sessionVariable( "DiscardObjectID" );
$version = (int) $http->sessionVariable( "DiscardObjectVersion" );
$editLanguage = $http->sessionVariable( "DiscardObjectLanguage" );
Expand All @@ -28,7 +29,15 @@
if ( $object === null )
return $Module->handleError( eZError::KERNEL_NOT_AVAILABLE, 'kernel' );

$versionObject = $object->version( $version );
$db->begin();

$versionRows = $db->arrayQuery( "SELECT * FROM ezcontentobject_version WHERE version = $version AND contentobject_id = $objectID FOR UPDATE" );
if ( empty( $versionRows ) )
{
$db->commit(); // We haven't made any changes, but commit here to avoid affecting any outer transactions.
return $Module->handleError( eZError::KERNEL_NOT_AVAILABLE, 'kernel' );
}
$versionObject = eZContentObjectVersion::fetch( $versionRows[0]['id'] );
if ( is_object( $versionObject ) and
in_array( $versionObject->attribute( 'status' ), array( eZContentObjectVersion::STATUS_DRAFT, eZContentObjectVersion::STATUS_INTERNAL_DRAFT ) ) )
{
Expand All @@ -46,13 +55,19 @@
$allowEdit = false;

if ( !$allowEdit )
{
$db->commit(); // We haven't made any changes, but commit here to avoid affecting any outer transactions.
return $Module->handleError( eZError::KERNEL_ACCESS_DENIED, 'kernel', array( 'AccessList' => $object->accessList( 'edit' ) ) );
}
}

$versionCount= $object->getVersionCount();
$nodeID = $versionCount == 1 ? $versionObject->attribute( 'main_parent_node_id' ) : $object->attribute( 'main_node_id' );
$versionObject->removeThis();
}

$db->commit();

$hasRedirected = false;
if ( $http->hasSessionVariable( 'RedirectIfDiscarded' ) )
{
Expand Down
22 changes: 17 additions & 5 deletions kernel/private/classes/clusterfilehandlers/dfsbackends/mysqli.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,6 @@ public function _purge( $filePath, $onlyExpired = false, $expiry = false, $fname
* @see _purge
*
* @return bool|int false if it fails, number of affected rows otherwise
* @todo This method should also remove the files from disk
*/
public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry = false, $fname = false )
{
Expand Down Expand Up @@ -392,11 +391,16 @@ public function _purgeByLike( $like, $onlyExpired = false, $limit = 50, $expiry
return $this->_fail( "Purging file metadata by like statement $like failed" );
}
$deletedDBFiles = mysqli_affected_rows( $this->db );
$this->dfsbackend->delete( $files );

$this->_commit( $fname );
$commitResult = $this->_commit( $fname );
if ( $commitResult === true || $commitResult === null )
{
$this->dfsbackend->delete( $files );

return $deletedDBFiles;
return $deletedDBFiles;
}

return false;
}

/**
Expand Down Expand Up @@ -1351,6 +1355,10 @@ protected function _begin( $fname = false )
/**
* Stops a current transaction and commits the changes by executing a COMMIT call.
* If the current transaction is a sub-transaction nothing is executed.
*
* Returns true if the commit was executed successfully, false if it failed, null if it wasn't executed.
*
* @return bool|null
*/
protected function _commit( $fname = false )
{
Expand All @@ -1360,7 +1368,11 @@ protected function _commit( $fname = false )
$fname = "_commit";
$this->transactionCount--;
if ( $this->transactionCount == 0 )
$this->_query( "COMMIT", $fname );
{
return $this->_query("COMMIT", $fname) !== false;
}

return null;
}

/**
Expand Down

0 comments on commit 8e58bc6

Please sign in to comment.