<?php

if (!defined('BB_ROOT')) die(basename(__FILE__));

class cache_sqlite extends cache_common
{
	var $engine = 'SQLite';
	var $used   = true;
	var $db     = null;
	var $prefix = null;
	var $prefix_len = null;
	var $prefix_sql = null;
	var $cfg    = array(
		'db_file_path' => '/path/to/cache.db.sqlite',
		'table_name'   => 'cache',
		'table_schema' => 'CREATE TABLE cache (
	                cache_name        VARCHAR(255),
	                cache_expire_time INT,
	                cache_value       TEXT,
	                PRIMARY KEY (cache_name)
	        )',
		'pconnect'     => true,
		'con_required' => true,
		'log_name'     => 'CACHE',
	);

	function cache_sqlite ($cfg, $prefix = null)
	{
		if (!$this->is_installed())
		{
			die("Error: {$this->db->engine} extension not installed");
		}

		$this->cfg = array_merge($this->cfg, $cfg);
		$this->db = new sqlite_common($this->cfg);
		$this->prefix = $prefix;
	}

	function get ($name, $get_miss_key_callback = '', $ttl = 604800)
	{
		if (empty($name))
		{
			return is_array($name) ? array() : false;
		}
		$this->db->shard($name);
		$cached_items = array();
		$this->prefix_len = strlen($this->prefix);
		$this->prefix_sql = sqlite3_escape_string($this->prefix);

		$name_ary = $name_sql = (array) $name;
		array_deep($name_sql, 'sqlite3_escape_string');

		// get available items
		$rowset = $this->db->fetch_rowset("
			SELECT cache_name, cache_value
			FROM ". $this->cfg['table_name'] ."
			WHERE cache_name IN('$this->prefix_sql". join("','$this->prefix_sql", $name_sql) ."') AND cache_expire_time > ". TIMENOW ."
			LIMIT ". count($name) ."
		");

		$this->db->debug('start', 'unserialize()');
		foreach ($rowset as $row)
		{
			$cached_items[substr($row['cache_name'], $this->prefix_len)] = unserialize($row['cache_value']);
		}
		$this->db->debug('stop');

		// get miss items
		if ($get_miss_key_callback AND $miss_key = array_diff($name_ary, array_keys($cached_items)))
		{
			foreach ($get_miss_key_callback($miss_key) as $k => $v)
			{
				$this->set($this->prefix . $k, $v, $ttl);
				$cached_items[$k] = $v;
			}
		}
		// return
		if (is_array($this->prefix . $name))
		{
			return $cached_items;
		}
		else
		{
			return isset($cached_items[$name]) ? $cached_items[$name] : false;
		}
	}

	function set ($name, $value, $ttl = 604800)
	{
		$this->db->shard($this->prefix . $name);
		$name_sql  = sqlite3_escape_string($this->prefix . $name);
		$expire    = TIMENOW + $ttl;
		$value_sql = sqlite3_escape_string(serialize($value));

		$result = $this->db->query("REPLACE INTO ". $this->cfg['table_name'] ." (cache_name, cache_expire_time, cache_value) VALUES ('$name_sql', $expire, '$value_sql')");
		return (bool) $result;
	}

	function rm ($name = '')
	{
		if ($name)
		{
			$this->db->shard($this->prefix . $name);
			$result = $this->db->query("DELETE FROM ". $this->cfg['table_name'] ." WHERE cache_name = '". sqlite3_escape_string($this->prefix . $name) ."'");
		}
		else
		{
			$result = $this->db->query("DELETE FROM ". $this->cfg['table_name']);
		}
		return (bool) $result;
	}

	function gc ($expire_time = TIMENOW)
	{
		$result = $this->db->query("DELETE FROM ". $this->cfg['table_name'] ." WHERE cache_expire_time < $expire_time");
		return ($result) ? $this->db->changes() : 0;
	}

	function is_installed ()
	{
		return class_exists('SQLite3');
	}
}

class sqlite_common extends cache_common
{
	var $cfg = array(
		'db_file_path' => 'sqlite.db',
		'table_name'   => 'table_name',
		'table_schema' => 'CREATE TABLE table_name (...)',
		'pconnect'     => true,
		'con_required' => true,
		'log_name'     => 'SQLite',
		'shard_type'   => 'none',     #  none, string, int (тип перевичного ключа для шардинга)
		'shard_val'    => 0,          #  для string - кол. начальных символов, для int - делитель (будет использован остаток от деления)
	);
	var $engine    = 'SQLite';
	var $dbh       = null;
	var $connected = false;
	var $shard_val = false;

	var $table_create_attempts = 0;

	function sqlite_common ($cfg)
	{
		$this->cfg = array_merge($this->cfg, $cfg);
		$this->dbg_enabled = sql_dbg_enabled();

		// Creates filecache dir
		if ($db_dir = dirname($this->cfg['db_file_path']))
		{
			if (!is_dir($db_dir))
			{
				if (!bb_mkdir($db_dir))
				{
					$create_error = "Cannot create {$this->engine} cache dir: $db_dir";

					if (DBG_LOG)
					{
						dbg_log($create_error, "{$this->engine}-CACHE-mkdir-FAIL");
					}

					die($create_error);
				}
			}
		}
	}

	function connect ()
	{
		$this->cur_query = ($this->dbg_enabled) ? 'connect to: '. $this->cfg['db_file_path'] : 'connect';
		$this->debug('start');

		if (@$this->dbh = new SQLite3($this->cfg['db_file_path']))
		{
			$this->connected = true;
		}

		if (!$this->connected && $this->cfg['con_required'])
		{
			header("HTTP/1.0 503 Service Unavailable");
			$con_error = "Could not connect to {$this->engine}";

			if (DBG_LOG)
			{
				dbg_log($con_error, "{$this->engine}-CACHE-connect-FAIL");
			}

			die($con_error);
		}

		$this->debug('stop');
		$this->cur_query = null;
	}

	function create_table ()
	{
		$this->table_create_attempts++;
		return $this->dbh->query($this->cfg['table_schema']);
	}

	function shard ($name)
	{
		$type = $this->cfg['shard_type'];

		if ($type == 'none') return;
		if (is_array($name))  trigger_error('cannot shard: $name is array', E_USER_ERROR);

		// define shard_val
		if ($type == 'string')
		{
			$shard_val = substr($name, 0, $this->cfg['shard_val']);
		}
		else
		{
			$shard_val = $name % $this->cfg['shard_val'];
		}
		// все запросы должны быть к одному и тому же шарду
		if ($this->shard_val !== false)
		{
			if ($shard_val != $this->shard_val)
			{
				trigger_error("shard cannot be reassigned. [{$this->shard_val}, $shard_val, $name]", E_USER_ERROR);
			}
			else
			{
				return;
			}
		}
		$this->shard_val = $shard_val;
		$this->cfg['db_file_path'] = str_replace('*', $shard_val, $this->cfg['db_file_path']);
	}

	function query ($query)
	{
		if (!$this->connected) $this->connect();

		$this->cur_query = $query;
		$this->debug('start');

		if (!$result = @$this->dbh->query($query))
		{
			$rowsresult = $this->dbh->query("PRAGMA table_info({$this->cfg['table_name']})");
			$rowscount = 0;
			while ($row = $rowsresult->fetchArray(SQLITE3_ASSOC))
			{
				$rowscount++;
			}
			if (!$this->table_create_attempts && !$rowscount)
			{
				if ($this->create_table())
				{
					$result = $this->dbh->query($query);
				}
			}
			if (!$result)
			{
				$this->trigger_error($this->get_error_msg());
			}
		}

		$this->debug('stop');
		$this->cur_query = null;

		$this->num_queries++;

		return $result;
	}

	function insert_id()
	{
		return $this->dbh->lastInsertRowID();
	}

	function fetch_row ($query)
	{
		$result = $this->query($query);
		$row = $result->fetchArray(SQLITE3_ASSOC);
		return is_resource($result) ? $row : false;
	}

	function fetch_rowset ($query)
	{
		$result = $this->query($query);
		$rowset = array();
		while ($row = $result->fetchArray(SQLITE3_ASSOC))
		{
			$rowset[] = $row;
		}
		return $rowset;
	}

	function changes ()
	{
		$changes = $this->dbh->changes();
		return is_resource($this->dbh) ? $changes : 0;
	}

	function escape ($str)
	{
		return SQLite3::escapeString($str);
	}

	function get_error_msg ()
	{
		return 'SQLite error #'. ($err_code = $this->dbh->lastErrorCode()) .': '. $this->dbh->lastErrorMsg();
	}

	function trigger_error ($msg = 'DB Error')
	{
		if (error_reporting()) trigger_error($msg, E_USER_ERROR);
	}
}
