locker
This commit is contained in:
@@ -42,6 +42,7 @@ use Espo\Core\{
|
||||
Utils\Cron\ScheduledJob as CronScheduledJob,
|
||||
Utils\Cron\Job as CronJob,
|
||||
ORM\EntityManager,
|
||||
Utils\Cron\JobTask,
|
||||
};
|
||||
|
||||
use Espo\Entities\Job as JobEntity;
|
||||
@@ -196,7 +197,7 @@ class CronManager
|
||||
/**
|
||||
* Run a portion of pending jobs.
|
||||
*/
|
||||
public function processPendingJobs($queue = null, $limit = null, $poolDisabled = false, $noLock = false)
|
||||
public function processPendingJobs($queue = null, $limit = null, bool $poolDisabled = false, bool $noLock = false)
|
||||
{
|
||||
if (is_null($limit)) {
|
||||
$limit = intval($this->config->get('jobMaxPortion', 0));
|
||||
@@ -210,51 +211,17 @@ class CronManager
|
||||
$useProcessPool = false;
|
||||
}
|
||||
|
||||
$pool = null;
|
||||
|
||||
if ($useProcessPool) {
|
||||
$pool = \Spatie\Async\Pool::create()
|
||||
$pool = AsyncPool::create()
|
||||
->autoload(getcwd() . '/vendor/autoload.php')
|
||||
->concurrency($this->config->get('jobPoolConcurrencyNumber'))
|
||||
->timeout($this->config->get('jobPeriodForActiveProcess'));
|
||||
}
|
||||
|
||||
foreach ($pendingJobList as $job) {
|
||||
$skip = false;
|
||||
if (!$noLock) $this->lockJobTable();
|
||||
if ($noLock || $this->getCronJobUtil()->isJobPending($job->id)) {
|
||||
if ($job->get('scheduledJobId')) {
|
||||
if ($this->getCronJobUtil()->isScheduledJobRunning(
|
||||
$job->get('scheduledJobId'), $job->get('targetId'), $job->get('targetType'))
|
||||
) {
|
||||
$skip = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$skip = true;
|
||||
}
|
||||
|
||||
if ($skip) {
|
||||
if (!$noLock) $this->unlockTables();
|
||||
continue;
|
||||
}
|
||||
|
||||
$job->set('startedAt', date('Y-m-d H:i:s'));
|
||||
|
||||
if ($useProcessPool) {
|
||||
$job->set('status', self::READY);
|
||||
} else {
|
||||
$job->set('status', self::RUNNING);
|
||||
$job->set('pid', \Espo\Core\Utils\System::getPid());
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($job);
|
||||
if (!$noLock) $this->unlockTables();
|
||||
|
||||
if ($useProcessPool) {
|
||||
$task = new \Espo\Core\Utils\Cron\JobTask($job->id);
|
||||
$pool->add($task);
|
||||
} else {
|
||||
$this->runJob($job);
|
||||
}
|
||||
$this->processPendingJob($job, $pool, $noLock);
|
||||
}
|
||||
|
||||
if ($useProcessPool) {
|
||||
@@ -262,14 +229,74 @@ class CronManager
|
||||
}
|
||||
}
|
||||
|
||||
protected function lockJobTable()
|
||||
protected function processPendingJob(JobEntity $job, $pool = null, bool $noLock = false)
|
||||
{
|
||||
$this->entityManager->getPDO()->query('LOCK TABLES `job` WRITE');
|
||||
}
|
||||
$useProcessPool = (bool) $pool;
|
||||
|
||||
protected function unlockTables()
|
||||
{
|
||||
$this->entityManager->getPDO()->query('UNLOCK TABLES');
|
||||
$lockTable = (bool) $job->get('scheduledJobId') && !$noLock;
|
||||
|
||||
$skip = false;
|
||||
|
||||
if (!$noLock) {
|
||||
if ($lockTable) {
|
||||
// MySQL doesn't allow to lock non-existent rows. We resort to locking an entire table.
|
||||
$this->entityManager->getLocker()->lockExclusive('Job');
|
||||
} else {
|
||||
$this->entityManager->getTransactionManager()->start();
|
||||
}
|
||||
}
|
||||
|
||||
if ($noLock || $this->getCronJobUtil()->isJobPending($job->id)) {
|
||||
if ($job->get('scheduledJobId')) {
|
||||
if ($this->getCronJobUtil()->isScheduledJobRunning(
|
||||
$job->get('scheduledJobId'), $job->get('targetId'), $job->get('targetType'))
|
||||
) {
|
||||
$skip = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$skip = true;
|
||||
}
|
||||
|
||||
if ($skip) {
|
||||
if (!$noLock) {
|
||||
if ($lockTable) {
|
||||
$this->entityManager->getLocker()->rollback();
|
||||
} else {
|
||||
$this->entityManager->getTransactionManager()->rollback();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$job->set('startedAt', date('Y-m-d H:i:s'));
|
||||
|
||||
if ($useProcessPool) {
|
||||
$job->set('status', self::READY);
|
||||
} else {
|
||||
$job->set('status', self::RUNNING);
|
||||
$job->set('pid', System::getPid());
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($job);
|
||||
|
||||
if (!$noLock) {
|
||||
if ($lockTable) {
|
||||
$this->entityManager->getLocker()->commit();
|
||||
} else {
|
||||
$this->entityManager->getTransactionManager()->commit();
|
||||
}
|
||||
}
|
||||
|
||||
if ($useProcessPool) {
|
||||
$task = new JobTask($job->id);
|
||||
$pool->add($task);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->runJob($job);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,7 +319,7 @@ class CronManager
|
||||
}
|
||||
|
||||
$job->set('status', self::RUNNING);
|
||||
$job->set('pid',System::getPid());
|
||||
$job->set('pid', System::getPid());
|
||||
$this->entityManager->saveEntity($job);
|
||||
|
||||
$this->runJob($job);
|
||||
|
||||
@@ -326,12 +326,15 @@ class Importer
|
||||
}
|
||||
|
||||
if (!$duplicate) {
|
||||
$this->lockEmailTable();
|
||||
$this->getEntityManager()->getLocker()->lockExclusive('Email');
|
||||
|
||||
if ($duplicate = $this->findDuplicate($email)) {
|
||||
$this->unlockTables();
|
||||
$this->getEntityManager()->getLocker()->rollback();
|
||||
|
||||
if ($duplicate->get('status') != 'Being Imported') {
|
||||
$duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id);
|
||||
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
|
||||
|
||||
return $duplicate;
|
||||
}
|
||||
}
|
||||
@@ -355,23 +358,26 @@ class Importer
|
||||
'fromString' => $email->get('fromString'),
|
||||
'replyToString' => $email->get('replyToString'),
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->getRepository('Email')->fillAccount($duplicate);
|
||||
|
||||
$this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList);
|
||||
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
if (!$email->get('messageId')) {
|
||||
$email->setDummyMessageId();
|
||||
}
|
||||
|
||||
$email->set('status', 'Being Imported');
|
||||
|
||||
$this->getEntityManager()->saveEntity($email, [
|
||||
'skipAll' => true,
|
||||
'keepNew' => true
|
||||
'keepNew' => true,
|
||||
]);
|
||||
|
||||
$this->unlockTables();
|
||||
$this->getEntityManager()->getLocker()->commit();
|
||||
|
||||
if ($parentFound) {
|
||||
$parentType = $email->get('parentType');
|
||||
@@ -400,7 +406,7 @@ class Importer
|
||||
$attachment->set([
|
||||
'relatedId' => $email->id,
|
||||
'relatedType' => 'Email',
|
||||
'field' => 'body'
|
||||
'field' => 'body',
|
||||
]);
|
||||
$this->getEntityManager()->saveEntity($attachment);
|
||||
}
|
||||
@@ -408,21 +414,12 @@ class Importer
|
||||
return $email;
|
||||
}
|
||||
|
||||
protected function lockEmailTable()
|
||||
{
|
||||
$this->getEntityManager()->getPdo()->query('LOCK TABLES `email` WRITE');
|
||||
}
|
||||
|
||||
protected function unlockTables()
|
||||
{
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
}
|
||||
|
||||
protected function findParent(Email $email, $emailAddress)
|
||||
{
|
||||
$contact = $this->getEntityManager()->getRepository('Contact')->where([
|
||||
'emailAddress' => $emailAddress
|
||||
])->findOne();
|
||||
|
||||
if ($contact) {
|
||||
if (!$this->getConfig()->get('b2cMode')) {
|
||||
if ($contact->get('accountId')) {
|
||||
@@ -438,6 +435,7 @@ class Importer
|
||||
$account = $this->getEntityManager()->getRepository('Account')->where([
|
||||
'emailAddress' => $emailAddress
|
||||
])->findOne();
|
||||
|
||||
if ($account) {
|
||||
$email->set('parentType', 'Account');
|
||||
$email->set('parentId', $account->id);
|
||||
@@ -507,7 +505,7 @@ class Importer
|
||||
$duplicate->setLinkMultipleColumn('users', 'folderId', $uId, $folderId);
|
||||
} else {
|
||||
$this->getEntityManager()->getRepository('Email')->updateRelation($duplicate, 'users', $uId, [
|
||||
'folderId' => $folderId
|
||||
'folderId' => $folderId,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -519,17 +517,17 @@ class Importer
|
||||
|
||||
$this->getEntityManager()->getRepository('Email')->processLinkMultipleFieldSave($duplicate, 'users', [
|
||||
'skipLinkMultipleRemove' => true,
|
||||
'skipLinkMultipleUpdate' => true
|
||||
'skipLinkMultipleUpdate' => true,
|
||||
]);
|
||||
|
||||
$this->getEntityManager()->getRepository('Email')->processLinkMultipleFieldSave($duplicate, 'assignedUsers', [
|
||||
'skipLinkMultipleRemove' => true,
|
||||
'skipLinkMultipleUpdate' => true
|
||||
'skipLinkMultipleUpdate' => true,
|
||||
]);
|
||||
|
||||
if ($notificator = $this->getNotificator()) {
|
||||
$notificator->process($duplicate, [
|
||||
'isBeingImported' => true
|
||||
'isBeingImported' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -558,7 +556,7 @@ class Importer
|
||||
'targetId' => $duplicate->id
|
||||
],
|
||||
'executeAt' => $executeAt,
|
||||
'queue' => 'q1'
|
||||
'queue' => 'q1',
|
||||
]);
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
}
|
||||
|
||||
@@ -75,12 +75,22 @@ class Job
|
||||
return $this->cronScheduledJob;
|
||||
}
|
||||
|
||||
public function isJobPending($id)
|
||||
public function isJobPending(string $id) : bool
|
||||
{
|
||||
return !!$this->getEntityManager()->getRepository('Job')->select(['id'])->where([
|
||||
'id' => $id,
|
||||
'status' => CronManager::PENDING
|
||||
])->findOne();
|
||||
$job = $this->getEntityManager()
|
||||
->getRepository('Job')
|
||||
->select(['id', 'status'])
|
||||
->where([
|
||||
'id' => $id,
|
||||
])
|
||||
->forUpdate()
|
||||
->findOne();
|
||||
|
||||
if (!$job) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $job->get('status') === CronManager::PENDING;
|
||||
}
|
||||
|
||||
public function getPendingJobList($queue = null, $limit = 0)
|
||||
@@ -113,17 +123,22 @@ class Job
|
||||
return $this->getEntityManager()->getRepository('Job')->find($selectParams);
|
||||
}
|
||||
|
||||
public function isScheduledJobRunning($scheduledJobId, $targetId = null, $targetType = null)
|
||||
public function isScheduledJobRunning(string $scheduledJobId, ?string $targetId = null, ?string $targetType = null) : bool
|
||||
{
|
||||
$where = [
|
||||
'scheduledJobId' => $scheduledJobId,
|
||||
'status' => [CronManager::RUNNING, CronManager::READY],
|
||||
];
|
||||
|
||||
if ($targetId && $targetType) {
|
||||
$where['targetId'] = $targetId;
|
||||
$where['targetType'] = $targetType;
|
||||
}
|
||||
return !!$this->getEntityManager()->getRepository('Job')->select(['id'])->where($where)->findOne();
|
||||
|
||||
return (booL) $this->getEntityManager()->getRepository('Job')
|
||||
->select(['id'])
|
||||
->where($where)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
public function getRunningScheduledJobIdList() : array
|
||||
|
||||
@@ -34,6 +34,7 @@ use Espo\ORM\{
|
||||
QueryComposer\QueryComposer,
|
||||
Repository\RepositoryFactory,
|
||||
Repository\Repository,
|
||||
Locker\Locker,
|
||||
};
|
||||
|
||||
use PDO;
|
||||
@@ -70,6 +71,8 @@ class EntityManager
|
||||
|
||||
protected $transactionManager;
|
||||
|
||||
protected $locker;
|
||||
|
||||
protected $defaultMapperName = 'RDB';
|
||||
|
||||
protected $driverPlatformMap = [
|
||||
@@ -114,6 +117,8 @@ class EntityManager
|
||||
$this->collectionFactory = new CollectionFactory($this);
|
||||
|
||||
$this->transactionManager = new TransactionManager($this->getPDO(), $this->queryComposer);
|
||||
|
||||
$this->initLocker();
|
||||
}
|
||||
|
||||
protected function initQueryComposer()
|
||||
@@ -126,12 +131,28 @@ class EntityManager
|
||||
}
|
||||
|
||||
if (!$className || !class_exists($className)) {
|
||||
throw new RuntimeException("Query composer {$name} could not be created.");
|
||||
throw new RuntimeException("Query composer could not be created.");
|
||||
}
|
||||
|
||||
$this->queryComposer = new $className($this->getPDO(), $this->entityFactory, $this->metadata);
|
||||
}
|
||||
|
||||
protected function initLocker()
|
||||
{
|
||||
$className = $this->params['lockerClassName'] ?? null;
|
||||
|
||||
if (!$className) {
|
||||
$platform = $this->params['platform'];
|
||||
$className = 'Espo\\ORM\\Locker\\' . ucfirst($platform) . 'Locker';
|
||||
}
|
||||
|
||||
if (!$className || !class_exists($className)) {
|
||||
throw new RuntimeException("Locker could not be created.");
|
||||
}
|
||||
|
||||
$this->locker = new $className($this->getPDO(), $this->queryComposer, $this->transactionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Remove in v7.0.
|
||||
* @deprecated
|
||||
@@ -151,6 +172,11 @@ class EntityManager
|
||||
return $this->transactionManager;
|
||||
}
|
||||
|
||||
public function getLocker() : Locker
|
||||
{
|
||||
return $this->locker;
|
||||
}
|
||||
|
||||
protected function getMapperClassName(string $name) : string
|
||||
{
|
||||
$className = null;
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\ORM\Locker;
|
||||
|
||||
use Espo\ORM\{
|
||||
TransactionManager,
|
||||
QueryComposer\QueryComposer,
|
||||
QueryParams\LockTableBuilder,
|
||||
};
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class BaseLocker implements Locker
|
||||
{
|
||||
protected $pdo;
|
||||
protected $queryComposer;
|
||||
protected $transactionManager;
|
||||
|
||||
protected $isLocked = false;
|
||||
|
||||
public function __construct(PDO $pdo, QueryComposer $queryComposer, TransactionManager $transactionManager)
|
||||
{
|
||||
$this->pdo = $pdo;
|
||||
$this->queryComposer = $queryComposer;
|
||||
$this->transactionManager = $transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLocked() : bool
|
||||
{
|
||||
return $this->isLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lockExclusive(string $entityType)
|
||||
{
|
||||
$this->isLocked = true;
|
||||
|
||||
$this->transactionManager->start();
|
||||
|
||||
$query = (new LockTableBuilder())
|
||||
->table($entityType)
|
||||
->inExclusiveMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->queryComposer->compose($query);
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lockShare(string $entityType)
|
||||
{
|
||||
$this->isLocked = true;
|
||||
|
||||
$this->transactionManager->start();
|
||||
|
||||
$query = (new LockTableBuilder())
|
||||
->table($entityType)
|
||||
->inShareMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->queryComposer->compose($query);
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
if (!$this->isLocked) {
|
||||
throw new RuntimeException("Can't commit, it was not locked.");
|
||||
}
|
||||
|
||||
$this->transactionManager->commit();
|
||||
|
||||
$this->isLocked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
if (!$this->isLocked) {
|
||||
throw new RuntimeException("Can't rollback, it was not locked.");
|
||||
}
|
||||
|
||||
$this->transactionManager->rollback();
|
||||
|
||||
$this->isLocked = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\ORM\Locker;
|
||||
|
||||
/**
|
||||
* Locks and unlocks tables.
|
||||
* Wraps operations between lock and unlock into a transaction.
|
||||
*/
|
||||
interface Locker
|
||||
{
|
||||
/**
|
||||
* Whether any table has been locked.
|
||||
*/
|
||||
public function isLocked();
|
||||
|
||||
/**
|
||||
* Locks a table in an exclusive mode. Starts a transaction on first call.
|
||||
*/
|
||||
public function lockExclusive(string $entityType);
|
||||
|
||||
/**
|
||||
* Locks a table in a share mode. Starts a transaction on first call.
|
||||
*/
|
||||
public function lockShare(string $entityType);
|
||||
|
||||
/**
|
||||
* Commits changes and unlocks tables.
|
||||
*/
|
||||
public function commit();
|
||||
|
||||
/**
|
||||
* Rollbacks changes and unlocks tables.
|
||||
*/
|
||||
public function rollback();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\ORM\Locker;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class MysqlLocker extends BaseLocker
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit()
|
||||
{
|
||||
parent::commit();
|
||||
|
||||
$sql = $this->queryComposer->composeUnlockTables();
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback()
|
||||
{
|
||||
parent::rollback();
|
||||
|
||||
$sql = $this->queryComposer->composeUnlockTables();
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ use Espo\ORM\{
|
||||
QueryParams\Delete as DeleteQuery,
|
||||
QueryParams\Union as UnionQuery,
|
||||
QueryParams\Selecting as SelectingQuery,
|
||||
QueryParams\LockTable as LockTableQuery,
|
||||
};
|
||||
|
||||
use PDO;
|
||||
@@ -208,28 +209,32 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $this->composeUnion($query);
|
||||
}
|
||||
|
||||
if ($query instanceof LockTableQuery) {
|
||||
return $this->composeLockTable($query);
|
||||
}
|
||||
|
||||
throw new RuntimeException("ORM Query: Unknown query type passed.");
|
||||
}
|
||||
|
||||
public function composeCreateSavepoint(string $name) : string
|
||||
public function composeCreateSavepoint(string $savepointName) : string
|
||||
{
|
||||
$name = $this->sanitize($name);
|
||||
$savepointName = $this->sanitize($savepointName);
|
||||
|
||||
return 'SAVEPOINT ' . $name;
|
||||
return 'SAVEPOINT ' . $savepointName;
|
||||
}
|
||||
|
||||
public function composeReleaseSavepoint(string $name) : string
|
||||
public function composeReleaseSavepoint(string $savepointName) : string
|
||||
{
|
||||
$name = $this->sanitize($name);
|
||||
$savepointName = $this->sanitize($savepointName);
|
||||
|
||||
return 'RELEASE SAVEPOINT ' . $name;
|
||||
return 'RELEASE SAVEPOINT ' . $savepointName;
|
||||
}
|
||||
|
||||
public function composeRollbackToSavepoint(string $name) : string
|
||||
public function composeRollbackToSavepoint(string $savepointName) : string
|
||||
{
|
||||
$name = $this->sanitize($name);
|
||||
$savepointName = $this->sanitize($savepointName);
|
||||
|
||||
return 'ROLLBACK TO SAVEPOINT ' . $name;
|
||||
return 'ROLLBACK TO SAVEPOINT ' . $savepointName;
|
||||
}
|
||||
|
||||
protected function composeSelecting(SelectingQuery $queryParams) : string
|
||||
@@ -237,35 +242,35 @@ abstract class BaseQueryComposer implements QueryComposer
|
||||
return $this->compose($queryParams);
|
||||
}
|
||||
|
||||
protected function composeSelect(SelectQuery $queryParams) : string
|
||||
public function composeSelect(SelectQuery $queryParams) : string
|
||||
{
|
||||
$params = $queryParams->getRawParams();
|
||||
|
||||
return $this->createSelectQueryInternal($params);
|
||||
}
|
||||
|
||||
protected function composeUpdate(UpdateQuery $queryParams) : string
|
||||
public function composeUpdate(UpdateQuery $queryParams) : string
|
||||
{
|
||||
$params = $queryParams->getRawParams();
|
||||
|
||||
return $this->createUpdateQuery($params);
|
||||
}
|
||||
|
||||
protected function composeDelete(DeleteQuery $queryParams) : string
|
||||
public function composeDelete(DeleteQuery $queryParams) : string
|
||||
{
|
||||
$params = $queryParams->getRawParams();
|
||||
|
||||
return $this->createDeleteQuery($params);
|
||||
}
|
||||
|
||||
protected function composeInsert(InsertQuery $queryParams) : string
|
||||
public function composeInsert(InsertQuery $queryParams) : string
|
||||
{
|
||||
$params = $queryParams->getRawParams();
|
||||
|
||||
return $this->createInsertQuery($params);
|
||||
}
|
||||
|
||||
protected function composeUnion(UnionQuery $queryParams) : string
|
||||
public function composeUnion(UnionQuery $queryParams) : string
|
||||
{
|
||||
$params = $queryParams->getRawParams();
|
||||
|
||||
|
||||
@@ -29,8 +29,45 @@
|
||||
|
||||
namespace Espo\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\{
|
||||
QueryParams\LockTable as LockTableQuery,
|
||||
};
|
||||
|
||||
class MysqlQueryComposer extends BaseQueryComposer
|
||||
{
|
||||
public function composeLockTable(LockTableQuery $queryParams) : string
|
||||
{
|
||||
$params = $queryParams->getRawParams();
|
||||
|
||||
$table = $this->toDb($this->sanitize($params['table']));
|
||||
|
||||
$mode = $params['mode'];
|
||||
|
||||
if (empty($table)) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
if (!in_array($mode, [LockTableQuery::MODE_SHARE, LockTableQuery::MODE_EXCLUSIVE])) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
$sql = "LOCK TABLES `{$table}` ";
|
||||
|
||||
$modeMap = [
|
||||
LockTableQuery::MODE_SHARE => 'READ',
|
||||
LockTableQuery::MODE_EXCLUSIVE => 'WRITE',
|
||||
];
|
||||
|
||||
$sql .= $modeMap[$mode];
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
public function composeUnlockTables() : string
|
||||
{
|
||||
return "UNLOCK TABLES";
|
||||
}
|
||||
|
||||
protected function limit(string $sql, ?int $offset = null, ?int $limit = null) : string
|
||||
{
|
||||
if (!is_null($offset) && !is_null($limit)) {
|
||||
|
||||
@@ -31,6 +31,12 @@ namespace Espo\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\{
|
||||
QueryParams\Query as Query,
|
||||
QueryParams\Select as SelectQuery,
|
||||
QueryParams\Update as UpdateQuery,
|
||||
QueryParams\Insert as InsertQuery,
|
||||
QueryParams\Delete as DeleteQuery,
|
||||
QueryParams\Union as UnionQuery,
|
||||
QueryParams\LockTable as LockTableQuery,
|
||||
};
|
||||
|
||||
interface QueryComposer
|
||||
@@ -40,9 +46,21 @@ interface QueryComposer
|
||||
*/
|
||||
public function compose(Query $query) : string;
|
||||
|
||||
public function composeCreateSavepoint(string $name) : string;
|
||||
public function composeSelect(SelectQuery $query) : string;
|
||||
|
||||
public function composeReleaseSavepoint(string $name) : string;
|
||||
public function composeUpdate(UpdateQuery $query) : string;
|
||||
|
||||
public function composeRollbackToSavepoint(string $name) : string;
|
||||
public function composeDelete(DeleteQuery $query) : string;
|
||||
|
||||
public function composeInsert(InsertQuery $query) : string;
|
||||
|
||||
public function composeUnion(UnionQuery $query) : string;
|
||||
|
||||
public function composeLockTable(LockTableQuery $query) : string;
|
||||
|
||||
public function composeCreateSavepoint(string $savepointName) : string;
|
||||
|
||||
public function composeReleaseSavepoint(string $savepointName) : string;
|
||||
|
||||
public function composeRollbackToSavepoint(string $savepointName) : string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\ORM\QueryParams;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* LOCK TABLE parameters.
|
||||
*/
|
||||
class LockTable implements Query
|
||||
{
|
||||
use BaseTrait;
|
||||
|
||||
const MODE_SHARE = 'SHARE';
|
||||
const MODE_EXCLUSIVE = 'EXCLUSIVE';
|
||||
|
||||
protected function validateRawParams(array $params)
|
||||
{
|
||||
if (empty($params['table'])) {
|
||||
throw new RuntimeException("LockTable params: No table specified.");
|
||||
}
|
||||
|
||||
if (empty($params['mode'])) {
|
||||
throw new RuntimeException("LockTable params: No mode specified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\ORM\QueryParams;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class LockTableBuilder implements Builder
|
||||
{
|
||||
use BaseBuilderTrait;
|
||||
|
||||
/**
|
||||
* Build a LOCK TABLE query.
|
||||
*/
|
||||
public function build() : LockTable
|
||||
{
|
||||
return LockTable::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(LockTable $query) : self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* What entity type to lock.
|
||||
*/
|
||||
public function table(string $entityType) : self
|
||||
{
|
||||
$this->params['table'] = $entityType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* In SHARE mode.
|
||||
*/
|
||||
public function inShareMode() : self
|
||||
{
|
||||
$this->params['mode'] = LockTable::MODE_SHARE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* In EXCLUSIVE mode.
|
||||
*/
|
||||
public function inExclusiveMode() : self
|
||||
{
|
||||
$this->params['mode'] = LockTable::MODE_EXCLUSIVE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -46,8 +46,6 @@ class RDBRepository extends Repository
|
||||
{
|
||||
protected $mapper;
|
||||
|
||||
private $isTableLocked = false;
|
||||
|
||||
protected $hookMediator;
|
||||
|
||||
protected $transactionManager;
|
||||
@@ -56,10 +54,8 @@ class RDBRepository extends Repository
|
||||
string $entityType, EntityManager $entityManager, EntityFactory $entityFactory, ?HookMediator $hookMediator = null
|
||||
) {
|
||||
$this->entityType = $entityType;
|
||||
$this->entityName = $entityType;
|
||||
|
||||
$this->entityFactory = $entityFactory;
|
||||
$this->entityManager = $entityManager;
|
||||
$this->entityFactory = $entityFactory;
|
||||
|
||||
$this->seed = $this->entityFactory->create($entityType);
|
||||
|
||||
@@ -757,35 +753,6 @@ class RDBRepository extends Repository
|
||||
return $this->entityManager->getPDO();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @todo Remove.
|
||||
*/
|
||||
protected function lockTable()
|
||||
{
|
||||
$tableName = $this->entityManager->getQueryComposer()->toDb($this->entityType);
|
||||
|
||||
// @todo Use SELECT ... FOR UPDATE with transaction.
|
||||
$this->getPDO()->query("LOCK TABLES `{$tableName}` WRITE");
|
||||
$this->isTableLocked = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @todo Remove.
|
||||
*/
|
||||
protected function unlockTable()
|
||||
{
|
||||
// @todo Use SELECT ... FOR UPDATE with transaction.
|
||||
$this->getPDO()->query("UNLOCK TABLES");
|
||||
$this->isTableLocked = false;
|
||||
}
|
||||
|
||||
protected function isTableLocked()
|
||||
{
|
||||
return $this->isTableLocked;
|
||||
}
|
||||
|
||||
protected function createSelectBuilder() : RDBSelectBuilder
|
||||
{
|
||||
$builder = new RDBSelectBuilder($this->entityManager, $this->entityType);
|
||||
|
||||
@@ -87,14 +87,18 @@ class User extends \Espo\Core\Repositories\Database implements
|
||||
throw new Error("Username can't be empty.");
|
||||
}
|
||||
|
||||
$this->lockTable();
|
||||
$this->getEntityManager()->getLocker()->lockExclusive($this->entityType);
|
||||
|
||||
$user = $this->select(['id'])->where([
|
||||
'userName' => $userName
|
||||
])->findOne();
|
||||
$user = $this
|
||||
->select(['id'])
|
||||
->where([
|
||||
'userName' => $userName,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($user) {
|
||||
$this->unlockTable();
|
||||
$this->getEntityManager()->getLocker()->rollback();
|
||||
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
} else {
|
||||
@@ -104,15 +108,19 @@ class User extends \Espo\Core\Repositories\Database implements
|
||||
throw new Error("Username can't be empty.");
|
||||
}
|
||||
|
||||
$this->lockTable();
|
||||
$this->getEntityManager()->getLocker()->lockExclusive($this->entityType);
|
||||
|
||||
$user = $this->select(['id'])->where(array(
|
||||
'userName' => $userName,
|
||||
'id!=' => $entity->id
|
||||
))->findOne();
|
||||
$user = $this
|
||||
->select(['id'])
|
||||
->where([
|
||||
'userName' => $userName,
|
||||
'id!=' => $entity->id,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($user) {
|
||||
$this->unlockTable();
|
||||
$this->getEntityManager()->getLocker()->rollback();
|
||||
|
||||
throw new Conflict(json_encode(['reason' => 'userNameExists']));
|
||||
}
|
||||
}
|
||||
@@ -121,8 +129,8 @@ class User extends \Espo\Core\Repositories\Database implements
|
||||
|
||||
protected function afterSave(Entity $entity, array $options = [])
|
||||
{
|
||||
if ($this->isTableLocked()) {
|
||||
$this->unlockTable();
|
||||
if ($this->getEntityManager()->getLocker()->isLocked()) {
|
||||
$this->getEntityManager()->getLocker()->commit();
|
||||
}
|
||||
|
||||
parent::afterSave($entity, $options);
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\ORM;
|
||||
|
||||
use Espo\ORM\{
|
||||
DB\MysqlMapper,
|
||||
DB\Query\Mysql as Query,
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\ORM;
|
||||
|
||||
use Espo\ORM\{
|
||||
Metadata,
|
||||
EntityManager,
|
||||
@@ -47,6 +49,9 @@ use Espo\Entities\{
|
||||
Job,
|
||||
};
|
||||
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
|
||||
require_once 'tests/unit/testData/DB/Entities.php';
|
||||
|
||||
class DBMapperTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\ORM;
|
||||
|
||||
use Espo\ORM\{
|
||||
Metadata,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\unit\Espo\ORM;
|
||||
|
||||
use Espo\ORM\{
|
||||
TransactionManager,
|
||||
QueryComposer\MysqlQueryComposer,
|
||||
EntityFactory,
|
||||
Metadata,
|
||||
Locker\MysqlLocker,
|
||||
};
|
||||
|
||||
use PDO;
|
||||
use PDOException;
|
||||
use RuntimeException;
|
||||
|
||||
class MysqlLockerTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected function setUp() : void
|
||||
{
|
||||
$this->pdo = $this->getMockBuilder(PDO::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$entityFactory = $this->getMockBuilder(EntityFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$metadata = $this->getMockBuilder(Metadata::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$this->transactionManager = $this->getMockBuilder(TransactionManager::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$composer = new MysqlQueryComposer($this->pdo, $entityFactory, $metadata);
|
||||
|
||||
$this->locker = new MysqlLocker($this->pdo, $composer, $this->transactionManager);
|
||||
}
|
||||
|
||||
public function testLockCommit()
|
||||
{
|
||||
$this->pdo
|
||||
->expects($this->at(0))
|
||||
->method('exec')
|
||||
->with('LOCK TABLES `account` WRITE');
|
||||
|
||||
$this->transactionManager
|
||||
->expects($this->at(0))
|
||||
->method('start');
|
||||
|
||||
$this->pdo
|
||||
->expects($this->at(1))
|
||||
->method('exec')
|
||||
->with('LOCK TABLES `contact` READ');
|
||||
|
||||
$this->transactionManager
|
||||
->expects($this->at(1))
|
||||
->method('commit');
|
||||
|
||||
$this->pdo
|
||||
->expects($this->at(2))
|
||||
->method('exec')
|
||||
->with('UNLOCK TABLES');
|
||||
|
||||
$this->locker->lockExclusive('Account');
|
||||
$this->locker->lockShare('Contact');
|
||||
|
||||
$this->assertTrue($this->locker->isLocked());
|
||||
|
||||
$this->locker->commit();
|
||||
|
||||
$this->assertFalse($this->locker->isLocked());
|
||||
}
|
||||
|
||||
public function testLockRollback()
|
||||
{
|
||||
$this->pdo
|
||||
->expects($this->at(0))
|
||||
->method('exec')
|
||||
->with('LOCK TABLES `account` WRITE');
|
||||
|
||||
$this->transactionManager
|
||||
->expects($this->at(0))
|
||||
->method('start');
|
||||
|
||||
$this->pdo
|
||||
->expects($this->at(1))
|
||||
->method('exec')
|
||||
->with('LOCK TABLES `contact` READ');
|
||||
|
||||
$this->transactionManager
|
||||
->expects($this->at(1))
|
||||
->method('rollback');
|
||||
|
||||
$this->pdo
|
||||
->expects($this->at(2))
|
||||
->method('exec')
|
||||
->with('UNLOCK TABLES');
|
||||
|
||||
$this->locker->lockExclusive('Account');
|
||||
$this->locker->lockShare('Contact');
|
||||
|
||||
$this->assertTrue($this->locker->isLocked());
|
||||
|
||||
$this->locker->commit();
|
||||
|
||||
$this->assertFalse($this->locker->isLocked());
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ use Espo\ORM\QueryParams\{
|
||||
Insert,
|
||||
Update,
|
||||
Delete,
|
||||
LockTableBuilder,
|
||||
};
|
||||
|
||||
use Espo\Entities\{
|
||||
@@ -2089,4 +2090,36 @@ class MysqlQueryComposerTest extends \PHPUnit\Framework\TestCase
|
||||
|
||||
$this->assertEquals($expectedSql, $sql);
|
||||
}
|
||||
|
||||
public function testLockTableShare()
|
||||
{
|
||||
$builder = new LockTableBuilder();
|
||||
|
||||
$query = $builder
|
||||
->table('Account')
|
||||
->inShareMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->query->compose($query);
|
||||
|
||||
$expectedSql = "LOCK TABLES `account` READ";
|
||||
|
||||
$this->assertEquals($expectedSql, $sql);
|
||||
}
|
||||
|
||||
public function testLockTableExclusive()
|
||||
{
|
||||
$builder = new LockTableBuilder();
|
||||
|
||||
$query = $builder
|
||||
->table('Account')
|
||||
->inExclusiveMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->query->compose($query);
|
||||
|
||||
$expectedSql = "LOCK TABLES `account` WRITE";
|
||||
|
||||
$this->assertEquals($expectedSql, $sql);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user