* @since 2.0
 */
class DbCache extends Cache
{
	/**
	 * @var string the ID of the {@link Connection} application component. If not set,
	 * a SQLite3 database will be automatically created and used. The SQLite database file
	 * is protected/runtime/cache-YiiVersion.db.
	 */
	public $connectionID;
	/**
	 * @var string name of the DB table to store cache content. Defaults to 'YiiCache'.
	 * Note, if {@link autoCreateCacheTable} is false and you want to create the DB table
	 * manually by yourself, you need to make sure the DB table is of the following structure:
	 * 
	 * (id CHAR(128) PRIMARY KEY, expire INTEGER, value BLOB)
	 * 
	 * Note, some DBMS might not support BLOB type. In this case, replace 'BLOB' with a suitable
	 * binary data type (e.g. LONGBLOB in MySQL, BYTEA in PostgreSQL.)
	 * @see autoCreateCacheTable
	 */
	public $cacheTableName = 'tbl_cache';
	/**
	 * @var boolean whether the cache DB table should be created automatically if it does not exist. Defaults to true.
	 * If you already have the table created, it is recommended you set this property to be false to improve performance.
	 * @see cacheTableName
	 */
	public $autoCreateCacheTable = true;
	/**
	 * @var integer the probability (parts per million) that garbage collection (GC) should be performed
	 * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
	 * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
	 **/
	public $gcProbability = 100;
	/**
	 * @var Connection the DB connection instance
	 */
	private $_db;
	/**
	 * Initializes this application component.
	 *
	 * This method is required by the {@link IApplicationComponent} interface.
	 * It ensures the existence of the cache DB table.
	 * It also removes expired data items from the cache.
	 */
	public function init()
	{
		parent::init();
		$db = $this->getDbConnection();
		$db->setActive(true);
		if ($this->autoCreateCacheTable) {
			$sql = "DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<" . time();
			try {
				$db->createCommand($sql)->execute();
			} catch (Exception $e) {
				$this->createCacheTable($db, $this->cacheTableName);
			}
		}
	}
	/**
	 * Creates the cache DB table.
	 * @param Connection $db the database connection
	 * @param string $tableName the name of the table to be created
	 */
	protected function createCacheTable($db, $tableName)
	{
		$driver = $db->getDriverName();
		if ($driver === 'mysql') {
			$blob = 'LONGBLOB';
		} else {
			if ($driver === 'pgsql') {
				$blob = 'BYTEA';
			} else {
				$blob = 'BLOB';
			}
		}
		$sql = <<createCommand($sql)->execute();
	}
	/**
	 * Returns the DB connection instance used for caching purpose.
	 * @return Connection the DB connection instance
	 * @throws Exception if [[connectionID]] does not point to a valid application component.
	 */
	public function getDbConnection()
	{
		if ($this->_db === null) {
			if ($this->connectionID !== null && ($db = \Yii::$application->getComponent($this->connectionID)) instanceof Connection) {
				$this->_db = $db;
			} else {
				throw new Exception("DbCache.connectionID must refer to the ID of a DB connection application component.");
			}
		}
		return $this->_db;
	}
	/**
	 * Sets the DB connection used by the cache component.
	 * @param Connection $value the DB connection instance
	 */
	public function setDbConnection($value)
	{
		$this->_db = $value;
	}
	/**
	 * Retrieves a value from cache with a specified key.
	 * This is the implementation of the method declared in the parent class.
	 * @param string $key a unique key identifying the cached value
	 * @return string the value stored in cache, false if the value is not in the cache or expired.
	 */
	protected function getValue($key)
	{
		$query = new Query;
		$query->select(array('value'))
			->from($this->cacheTableName)
			->where(array('id' => $key))
			->andWhere('expire = 0 OR expire > ' . time());
		$db = $this->getDbConnection();
		if ($db->queryCachingDuration >= 0) {
			$duration = $db->queryCachingDuration;
			$db->queryCachingDuration = -1;
			$result = $query->createCommand($db)->queryScalar();
			$db->queryCachingDuration = $duration;
			return $result;
		} else {
			return $query->createCommand($db)->queryScalar();
		}
	}
	/**
	 * Retrieves multiple values from cache with the specified keys.
	 * @param array $keys a list of keys identifying the cached values
	 * @return array a list of cached values indexed by the keys
	 */
	protected function getValues($keys)
	{
		if (empty($keys)) {
			return array();
		}
		$ids = implode("','", $keys);
		$time = time();
		$sql = "SELECT id, value FROM {$this->cacheTableName} WHERE id IN ('$ids') AND (expire=0 OR expire>$time)";
		$db = $this->getDbConnection();
		if ($db->queryCachingDuration > 0) {
			$duration = $db->queryCachingDuration;
			$db->queryCachingDuration = 0;
			$rows = $db->createCommand($sql)->queryAll();
			$db->queryCachingDuration = $duration;
		} else {
			$rows = $db->createCommand($sql)->queryAll();
		}
		$results = array();
		foreach ($keys as $key) {
			$results[$key] = false;
		}
		foreach ($rows as $row) {
			$results[$row['id']] = $row['value'];
		}
		return $results;
	}
	/**
	 * Stores a value identified by a key in cache.
	 * This is the implementation of the method declared in the parent class.
	 *
	 * @param string $key the key identifying the value to be cached
	 * @param string $value the value to be cached
	 * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
	 * @return boolean true if the value is successfully stored into cache, false otherwise
	 */
	protected function setValue($key, $value, $expire)
	{
		$this->deleteValue($key);
		return $this->addValue($key, $value, $expire);
	}
	/**
	 * Stores a value identified by a key into cache if the cache does not contain this key.
	 * This is the implementation of the method declared in the parent class.
	 *
	 * @param string $key the key identifying the value to be cached
	 * @param string $value the value to be cached
	 * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
	 * @return boolean true if the value is successfully stored into cache, false otherwise
	 */
	protected function addValue($key, $value, $expire)
	{
		if (mt_rand(0, 1000000) < $this->_gcProbability) {
			$this->gc();
		}
		if ($expire > 0) {
			$expire += time();
		} else {
			$expire = 0;
		}
		$sql = "INSERT INTO {$this->cacheTableName} (id,expire,value) VALUES ('$key',$expire,:value)";
		try {
			$command = $this->getDbConnection()->createCommand($sql);
			$command->bindValue(':value', $value, PDO::PARAM_LOB);
			$command->execute();
			return true;
		} catch (Exception $e) {
			return false;
		}
	}
	/**
	 * Deletes a value with the specified key from cache
	 * This is the implementation of the method declared in the parent class.
	 * @param string $key the key of the value to be deleted
	 * @return boolean if no error happens during deletion
	 */
	protected function deleteValue($key)
	{
		$sql = "DELETE FROM {$this->cacheTableName} WHERE id='$key'";
		$this->getDbConnection()->createCommand($sql)->execute();
		return true;
	}
	/**
	 * Removes the expired data values.
	 */
	protected function gc()
	{
		$this->getDbConnection()->createCommand("DELETE FROM {$this->cacheTableName} WHERE expire>0 AND expire<" . time())->execute();
	}
	/**
	 * Deletes all values from cache.
	 * This is the implementation of the method declared in the parent class.
	 * @return boolean whether the flush operation was successful.
	 * @since 1.1.5
	 */
	protected function flushValues()
	{
		$this->getDbConnection()->createCommand("DELETE FROM {$this->cacheTableName}")->execute();
		return true;
	}
}