Permalink
Browse files

Added pessimistic locking to query builder via 'lock', 'lockForUpdate…

…', and 'sharedLock'.
  • Loading branch information...
taylorotwell committed Nov 27, 2013
1 parent 86d3291 commit a33143257d9bed8c81065ebc6513e3cca7d3f124
@@ -120,6 +120,13 @@ class Builder {
*/
public $unions;
/**
* Indicates whether row locking is being used.
*
* @var string|bool
*/
public $lock;
/**
* The key that should be used when caching the query.
*
@@ -1018,6 +1025,39 @@ public function unionAll($query)
return $this->union($query, true);
}
/**
* Lock the selected rows in the table.
*
* @param bool $update
* @return \Illuminate\Database\Query\Builder
*/
public function lock($value = true)
{
$this->lock = $value;
return $this;
}
/**
* Lock the selected rows in the table for updating.
*
* @return \Illuminate\Database\Query\Builder
*/
public function lockForUpdate()
{
return $this->lock(true);
}
/**
* Share lock the selected rows in the table.
*
* @return \Illuminate\Database\Query\Builder
*/
public function sharedLock()
{
return $this->lock(false);
}
/**
* Get the SQL representation of the query.
*
@@ -29,6 +29,7 @@ class Grammar extends BaseGrammar {
'limit',
'offset',
'unions',
'lock',
);
/**
@@ -641,6 +642,18 @@ public function compileTruncate(Builder $query)
return array('truncate '.$this->wrapTable($query->from) => array());
}
/**
* Compile the lock into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param bool|string $value
* @return string
*/
protected function compileLock(Builder $query, $value)
{
return is_string($value) ? $value : '';
}
/**
* Concatenate an array of segments, removing empties.
*
@@ -27,6 +27,7 @@ class MySqlGrammar extends Grammar {
'orders',
'limit',
'offset',
'lock',
);
/**
@@ -60,6 +61,20 @@ protected function compileUnion(array $union)
return $joiner.'('.$union['query']->toSql().')';
}
/**
* Compile the lock into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param bool|string $value
* @return string
*/
protected function compileLock(Builder $query, $value)
{
if (is_string($value)) return $value;
return $value ? 'for update' : 'lock in share mode';
}
/**
* Compile an update statement into SQL.
*
@@ -15,6 +15,20 @@ class PostgresGrammar extends Grammar {
'&', '|', '#', '<<', '>>',
);
/**
* Compile the lock into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @param bool|string $value
* @return string
*/
protected function compileLock(Builder $query, $value)
{
if (is_string($value)) return $value;
return $value ? 'for update' : 'for share';
}
/**
* Compile an update statement into SQL.
*
@@ -67,6 +67,29 @@ protected function compileColumns(Builder $query, $columns)
return $select.$this->columnize($columns);
}
/**
* Compile the "from" portion of the query.
*
* @param \Illuminate\Database\Query\Builder $query
* @param string $table
* @return string
*/
protected function compileFrom(Builder $query, $table)
{
$from = parent::compileFrom($query, $table);
if (is_string($query->lock)) return $from.' '.$query->lock;
if ( ! is_null($query->lock))
{
return $from.' with(rowlock,'.($query->lock ? 'updlock,' : '').'holdlock)';
}
else
{
return $from;
}
}
/**
* Create a full ANSI offset clause for the query.
*
@@ -64,7 +64,9 @@
{"message": "Cache:add now returns true when the value is actually added. False is returned otherwise.", "backport": null},
{"message": "Added merge, diff, and intersect to the Collection class.", "backport": null},
{"message": "Added fragment method to paginator.", "backport": null},
{"message": "Added 'renderSections' method to the View.", "backport": null}
{"message": "Added 'renderSections' method to the View.", "backport": null},
{"message": "Added 'renderSections' method to the View.", "backport": null},
{"message": "Added pessimistic locking to query builder via 'lock', 'lockForUpdate', and 'sharedLock'.", "backport": null}
],
"4.0.x": [
{"message": "Added implode method to query builder and Collection class.", "backport": null},
@@ -52,9 +52,9 @@ public function testSelectWithCaching()
$query = $query->remember(5);
$cache->shouldReceive('remember')
->once()
->with($query->getCacheKey(), 5, m::type('Closure'))
->andReturnUsing(function($key, $minutes, $callback) { return $callback(); });
->once()
->with($query->getCacheKey(), 5, m::type('Closure'))
->andReturnUsing(function($key, $minutes, $callback) { return $callback(); });
$this->assertEquals($query->get(), array('results'));
@@ -65,7 +65,7 @@ public function testSelectWithCachingForever()
$cache = m::mock('stdClass');
$query = $this->setupCacheTestQuery($cache);
$query = $query->rememberForever();
$cache->shouldReceive('rememberForever')
->once()
->with($query->getCacheKey(), m::type('Closure'))
@@ -862,9 +862,52 @@ public function setupCacheTestQuery($cache)
$builder = $this->getMock('Illuminate\Database\Query\Builder', array('getFresh'), array($connection, $grammar, $processor));
$builder->expects($this->once())->method('getFresh')->with($this->equalTo(array('*')))->will($this->returnValue(array('results')));
return $builder->select('*')->from('users')->where('email', 'foo@bar.com');
return $builder->select('*')->from('users')->where('email', 'foo@bar.com');
}
public function testMySqlLock()
{
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('foo')->where('bar', '=', 'baz')->lock();
$this->assertEquals('select * from `foo` where `bar` = ? for update', $builder->toSql());
$this->assertEquals(array('baz'), $builder->getBindings());
$builder = $this->getMySqlBuilder();
$builder->select('*')->from('foo')->where('bar', '=', 'baz')->lock(false);
$this->assertEquals('select * from `foo` where `bar` = ? lock in share mode', $builder->toSql());
$this->assertEquals(array('baz'), $builder->getBindings());
}
public function testPostgresLock()
{
$builder = $this->getPostgresBuilder();
$builder->select('*')->from('foo')->where('bar', '=', 'baz')->lock();
$this->assertEquals('select * from "foo" where "bar" = ? for update', $builder->toSql());
$this->assertEquals(array('baz'), $builder->getBindings());
$builder = $this->getPostgresBuilder();
$builder->select('*')->from('foo')->where('bar', '=', 'baz')->lock(false);
$this->assertEquals('select * from "foo" where "bar" = ? for share', $builder->toSql());
$this->assertEquals(array('baz'), $builder->getBindings());
}
public function testSqlServerLock()
{
$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('foo')->where('bar', '=', 'baz')->lock();
$this->assertEquals('select * from [foo] with(rowlock,updlock,holdlock) where [bar] = ?', $builder->toSql());
$this->assertEquals(array('baz'), $builder->getBindings());
$builder = $this->getSqlServerBuilder();
$builder->select('*')->from('foo')->where('bar', '=', 'baz')->lock(false);
$this->assertEquals('select * from [foo] with(rowlock,holdlock) where [bar] = ?', $builder->toSql());
$this->assertEquals(array('baz'), $builder->getBindings());
}
protected function getBuilder()
{
$grammar = new Illuminate\Database\Query\Grammars\Grammar;

0 comments on commit a331432

Please sign in to comment.