* @category Horde
* @package Lock
*/
class Horde_Lock_Sql extends Horde_Lock
{
/**
* Handle for the current database connection.
*
* @var Horde_Db_Adapter
*/
private $_db;
/**
* Constructor.
*
* @param array $params Parameters:
*
* 'db' - (Horde_Db_Adapter) [REQUIRED] The DB instance.
* 'table' - (string) The name of the lock table in 'database'.
* DEFAULT: 'horde_locks'
*
*
* @throws Horde_Lock_Exception
*/
public function __construct($params = array())
{
if (!isset($params['db'])) {
throw new Horde_Lock_Exception('Missing db parameter.');
}
$this->_db = $params['db'];
unset($params['db']);
$params = array_merge(array(
'table' => 'horde_locks'
), $params);
parent::__construct($params);
/* Only do garbage collection 0.1% of the time we create an object. */
if (substr(time(), -3) === '000') {
register_shutdown_function(array($this, 'doGC'));
}
}
/**
* Return an array of information about the requested lock.
*
* @see Horde_Lock_Base::getLockInfo()
*/
public function getLockInfo($lockid)
{
$now = time();
$sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, '
. 'lock_origin_timestamp, lock_update_timestamp, '
. 'lock_expiry_timestamp, lock_type FROM '
. $this->_params['table']
. ' WHERE lock_id = ? AND '
. '(lock_expiry_timestamp >= ? OR lock_expiry_timestamp = ?)';
$values = array($lockid, $now, Horde_Lock::PERMANENT);
try {
return $this->_db->selectOne($sql, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Lock_Exception($e);
}
}
/**
* Return a list of valid locks with the option to limit the results
* by principal, scope and/or type.
*
* @see Horde_Lock_Base::getLocks()
*/
public function getLocks($scope = null, $principal = null, $type = null)
{
$now = time();
$sql = 'SELECT lock_id, lock_owner, lock_scope, lock_principal, '
. 'lock_origin_timestamp, lock_update_timestamp, '
. 'lock_expiry_timestamp, lock_type FROM '
. $this->_params['table']
. ' WHERE (lock_expiry_timestamp >= ? OR lock_expiry_timestamp = ?)';
$values = array($now, Horde_Lock::PERMANENT);
// Check to see if we need to filter the results
if (!empty($principal)) {
$sql .= ' AND lock_principal = ?';
$values[] = $principal;
}
if (!empty($scope)) {
$sql .= ' AND lock_scope = ?';
$values[] = $scope;
}
if (!empty($type)) {
$sql .= ' AND lock_type = ?';
$values[] = $type;
}
try {
$result = $this->_db->select($sql, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Lock_Exception($e);
}
$locks = array();
foreach ($result as $row) {
$locks[$row['lock_id']] = $row;
}
return $locks;
}
/**
* Extend the valid lifetime of a valid lock to now + $lifetime.
*
* @see Horde_Lock_Base::resetLock()
*/
public function resetLock($lockid, $lifetime)
{
$now = time();
if (!$this->getLockInfo($lockid)) {
return false;
}
$expiration = $lifetime == Horde_Lock::PERMANENT ? Horde_Lock::PERMANENT : $now + $lifetime;
$sql = 'UPDATE ' . $this->_params['table'] . ' SET ' .
'lock_update_timestamp = ?, lock_expiry_timestamp = ? ' .
'WHERE lock_id = ? AND lock_expiry_timestamp <> ?';
$values = array($now, $expiration, $lockid, Horde_Lock::PERMANENT);
try {
$this->_db->update($sql, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Lock_Exception($e);
}
return true;
}
/**
* Sets a lock on the requested principal and returns the generated lock
* ID.
*
* @see Horde_Lock_Base::setLock()
*/
public function setLock($requestor, $scope, $principal,
$lifetime = 1, $type = Horde_Lock::TYPE_SHARED)
{
$oldlocks = $this->getLocks(
$scope, $principal,
$type == Horde_Lock::TYPE_SHARED ? Horde_Lock::TYPE_EXCLUSIVE : null);
if (count($oldlocks) != 0) {
// A lock exists. Deny the new request.
if ($this->_logger) {
$this->_logger->log(sprintf('Lock requested for %s denied due to existing lock.', $principal), 'NOTICE');
}
return false;
}
$lockid = (string)new Horde_Support_Uuid();
$now = time();
$expiration = $lifetime == Horde_Lock::PERMANENT ? Horde_Lock::PERMANENT : $now + $lifetime;
$sql = 'INSERT INTO ' . $this->_params['table'] . ' (lock_id, lock_owner, lock_scope, lock_principal, lock_origin_timestamp, lock_update_timestamp, lock_expiry_timestamp, lock_type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)';
$values = array($lockid, $requestor, $scope, $principal, $now, $now,
$expiration, $type);
try {
$this->_db->insert($sql, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Lock_Exception($e);
}
if ($this->_logger) {
$this->_logger->log(sprintf('Lock %s set successfully by %s in scope %s on "%s"', $lockid, $requestor, $scope, $principal), 'DEBUG');
}
return $lockid;
}
/**
* Removes a lock given the lock ID.
*
* @see Horde_Lock_Base::clearLock()
*/
public function clearLock($lockid)
{
if (empty($lockid)) {
throw new Horde_Lock_Exception('Must supply a valid lock ID.');
}
// Since we're trying to clear the lock we don't care
// whether it is still valid or not. Unconditionally
// remove it.
$sql = 'DELETE FROM ' . $this->_params['table'] . ' WHERE lock_id = ?';
$values = array($lockid);
try {
$this->_db->delete($sql, $values);
} catch (Horde_Db_Exception $e) {
throw new Horde_Lock_Exception($e);
}
if ($this->_logger) {
$this->_logger->log(sprintf('Lock %s cleared successfully.', $lockid), 'DEBUG');
}
return true;
}
/**
* Do garbage collection needed for the driver.
*
* @todo Rename to gc().
*/
public function doGC()
{
$now = time();
$query = 'DELETE FROM ' . $this->_params['table'] . ' WHERE ' .
'lock_expiry_timestamp < ? AND lock_expiry_timestamp != ?';
$values = array($now, Horde_Lock::PERMANENT);
try {
if ($this->_db) {
$result = $this->_db->delete($query, $values);
if ($this->_logger) {
$this->_logger->log(sprintf('Lock garbage collection cleared %d locks.', $result), 'DEBUG');
}
}
} catch (Horde_Db_Exception $e) {}
}
}