From 7e3ce8352666af86d597e1fdce95bfe57531207e Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 19 May 2016 12:34:40 +0200 Subject: [PATCH] Add a method to lock a table --- lib/private/AppFramework/Db/Db.php | 17 +++++++++++++++- lib/private/DB/Adapter.php | 20 +++++++++++++++++++ lib/private/DB/AdapterMySQL.php | 12 +++++++++++ lib/private/DB/AdapterSqlite.php | 12 +++++++++++ lib/private/DB/Connection.php | 32 +++++++++++++++++++++++++++++- lib/public/IDBConnection.php | 19 ++++++++++++++++++ 6 files changed, 110 insertions(+), 2 deletions(-) diff --git a/lib/private/AppFramework/Db/Db.php b/lib/private/AppFramework/Db/Db.php index 0d17d7bc22..ab06d56cfd 100644 --- a/lib/private/AppFramework/Db/Db.php +++ b/lib/private/AppFramework/Db/Db.php @@ -29,6 +29,7 @@ namespace OC\AppFramework\Db; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDb; use OCP\IDBConnection; +use OCP\PreConditionNotMetException; /** * @deprecated use IDBConnection directly, will be removed in ownCloud 10 @@ -157,12 +158,26 @@ class Db implements IDb { * @param array $updatePreconditionValues ensure values match preconditions (column name => value) * @return int number of new rows * @throws \Doctrine\DBAL\DBALException - * @throws PreconditionNotMetException + * @throws PreConditionNotMetException */ public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []) { return $this->connection->setValues($table, $keys, $values, $updatePreconditionValues); } + /** + * @inheritdoc + */ + public function lockTable($tableName) { + $this->connection->lockTable($tableName); + } + + /** + * @inheritdoc + */ + public function unlockTable() { + $this->connection->unlockTable(); + } + /** * Start a transaction */ diff --git a/lib/private/DB/Adapter.php b/lib/private/DB/Adapter.php index 9522f768c8..bcced395cb 100644 --- a/lib/private/DB/Adapter.php +++ b/lib/private/DB/Adapter.php @@ -57,6 +57,26 @@ class Adapter { return $statement; } + /** + * Create an exclusive read+write lock on a table + * + * @param string $tableName + * @since 9.1.0 + */ + public function lockTable($tableName) { + $this->conn->beginTransaction(); + $this->conn->executeUpdate('LOCK TABLE `' .$tableName . '` IN EXCLUSIVE MODE'); + } + + /** + * Release a previous acquired lock again + * + * @since 9.1.0 + */ + public function unlockTable() { + $this->conn->commit(); + } + /** * Insert a row if the matching row does not exists. * diff --git a/lib/private/DB/AdapterMySQL.php b/lib/private/DB/AdapterMySQL.php index ab87c58974..8504fc74e0 100644 --- a/lib/private/DB/AdapterMySQL.php +++ b/lib/private/DB/AdapterMySQL.php @@ -24,6 +24,18 @@ namespace OC\DB; class AdapterMySQL extends Adapter { + + /** + * @param string $tableName + */ + public function lockTable($tableName) { + $this->conn->executeUpdate('LOCK TABLES `' .$tableName . '` WRITE'); + } + + public function unlockTable() { + $this->conn->executeUpdate('UNLOCK TABLES'); + } + public function fixupStatement($statement) { $statement = str_replace(' ILIKE ', ' COLLATE utf8_general_ci LIKE ', $statement); return $statement; diff --git a/lib/private/DB/AdapterSqlite.php b/lib/private/DB/AdapterSqlite.php index 3466e0e1aa..cefb06ffac 100644 --- a/lib/private/DB/AdapterSqlite.php +++ b/lib/private/DB/AdapterSqlite.php @@ -27,6 +27,18 @@ namespace OC\DB; class AdapterSqlite extends Adapter { + + /** + * @param string $tableName + */ + public function lockTable($tableName) { + $this->conn->executeUpdate('BEGIN EXCLUSIVE TRANSACTION'); + } + + public function unlockTable() { + $this->conn->executeUpdate('COMMIT TRANSACTION'); + } + public function fixupStatement($statement) { $statement = preg_replace('( I?LIKE \?)', '$0 ESCAPE \'\\\'', $statement); $statement = preg_replace('/`(\w+)` ILIKE \?/', 'LOWER($1) LIKE LOWER(?)', $statement); diff --git a/lib/private/DB/Connection.php b/lib/private/DB/Connection.php index 7cdc13a7c6..5b7026db2f 100644 --- a/lib/private/DB/Connection.php +++ b/lib/private/DB/Connection.php @@ -25,6 +25,7 @@ */ namespace OC\DB; + use Doctrine\DBAL\DBALException; use Doctrine\DBAL\Driver; use Doctrine\DBAL\Configuration; @@ -46,6 +47,8 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { */ protected $adapter; + protected $lockedTable = null; + public function connect() { try { return parent::connect(); @@ -281,7 +284,7 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { foreach ($values as $name => $value) { $updateQb->set($name, $updateQb->createNamedParameter($value, $this->getType($value))); } - $where = $updateQb->expr()->andx(); + $where = $updateQb->expr()->andX(); $whereValues = array_merge($keys, $updatePreconditionValues); foreach ($whereValues as $name => $value) { $where->add($updateQb->expr()->eq( @@ -301,6 +304,33 @@ class Connection extends \Doctrine\DBAL\Connection implements IDBConnection { } } + /** + * Create an exclusive read+write lock on a table + * + * @param string $tableName + * @throws \BadMethodCallException When trying to acquire a second lock + * @since 9.1.0 + */ + public function lockTable($tableName) { + if ($this->lockedTable !== null) { + throw new \BadMethodCallException('Can not lock a new table until the previous lock is released.'); + } + + $tableName = $this->tablePrefix . $tableName; + $this->lockedTable = $tableName; + $this->adapter->lockTable($tableName); + } + + /** + * Release a previous acquired lock again + * + * @since 9.1.0 + */ + public function unlockTable() { + $this->adapter->unlockTable(); + $this->lockedTable = null; + } + /** * returns the error code and message as a string for logging * works with DoctrineException diff --git a/lib/public/IDBConnection.php b/lib/public/IDBConnection.php index 780fcd2636..4ecf01ca27 100644 --- a/lib/public/IDBConnection.php +++ b/lib/public/IDBConnection.php @@ -124,6 +124,25 @@ interface IDBConnection { */ public function setValues($table, array $keys, array $values, array $updatePreconditionValues = []); + /** + * Create an exclusive read+write lock on a table + * + * Important Note: Due to the nature how locks work on different DBs, it is + * only possible to lock one table at a time. You should also NOT start a + * transaction while holding a lock. + * + * @param string $tableName + * @since 9.1.0 + */ + public function lockTable($tableName); + + /** + * Release a previous acquired lock again + * + * @since 9.1.0 + */ + public function unlockTable(); + /** * Start a transaction * @since 6.0.0