| : | Javascript | : | PHP Index | : | MySQL | : |
| : | Source | : | : | Explanation | : | : | Example | : | : | Todo | : | : | Feedback | : |
Easy and flexible class for storing database data into cached arrays
Added: 2009-05-07 19:39:23
<?
/******
* You may use and/or modify this script as long as you:
* 1. Keep my name & webpage mentioned
* 2. Don't use it for commercial purposes
*
* If you want to use this script without complying to the rules above, please contact me first at: marty@excudo.net
*
* Author: Martijn Korse
* Website: http://devshed.excudo.net
*
* Date: 2009-05-07 19:39:23
***/
/**
* The source shown here contains the code of 3 classes.
* If you download the zip file, the source of these 3 classes will be split into three seperate files
*/
/***** DbArray.php *****/
require_once 'DbArray/Exception.php';
/**
* Purpose of this class is to provide an easy interface for retrieving data from
* the database and cache it as a php-array in a file on the server.
* The class implements the singleton pattern.
* It has three public functions which you can use:
* 1. get(), for retrieving an Array
* 2. exists(), for testing whether an array, or subvalues of the array, exists
* 3. cache(), to write data to the cached files.
*
* For every array of data, you'll need to create a separate class that holds the
* information about how to retrieve this data from the database and how to structure
* it. Writing these classes is relatively simple as it comes with an abstract
* class that provides almost all default values (which can be overriden)
*
* NOTE: this class assumes a database connection already exists!
*/
class DbArray
{
/*****
* The following constants act as configuration.
* Please modify them when installing this class
***/
/**
* When calling the static get method and the array has to be created, the class
* with the query needs to be included. This constant is used for looking up that filename.
* %s will be replaced with the $key argument with which get() was called
*/
const FILE_FORMAT = "db_%s.php";
/**
* See above; The class-name within this file will have the format of this constant.
* %s will be replaced with the $key argument with which get() was called
*/
const CLASS_FORMAT = "DbArray_Query_%s";
/**
* When an array is loaded and is going to be written to a cache-file, the array
* needs to have a name in that file. This is the format of that name.
* %s will be replaced with the $key argument with which get() was called
*/
const ARRAY_FORMAT = "dbArray_%s";
/**
* Path to the folder where the classes with the queries are stored. This is
* relative to the root folder of you project and will therefor be combined
* with the one below.
* If null (the default), it will expect the class to be in ./DbArray/Query/
* @see CLASS_DIR_ROOT
*/
const CLASS_DIR = null;
/**
* Path to the root folder of your project
* @see CLASS_DIR
*/
const CLASS_DIR_ROOT = $_SERVER['DOCUMENT_ROOT'];
/**
* Path to the folder where the loaded arrays are stored (cached). This is
* relative to the root folder of you project and will therefor be combined
* with the one below.
* @see CACHE_DIR_ROOT
*/
const CACHE_DIR = "tmp/array";
/**
* Path to the root folder of your project
* @see CACHE_DIR
*/
const CACHE_DIR_ROOT = $_SERVER['DOCUMENT_ROOT'];
/**
* This value will be used for the chmod-command on the cached file, once the
* array has been written to it.
*/
const CHMOD = 0755;
/**
* The newline that is used in the cached file.
*/
const NEWLINE = "\r\n";
/**
* The name of the function we need to call in case an error has occured during
* the getting of the array. It can also be the name of the method of a class:
* in this case you'll also need to define CALLBACK_CLASS_IF_ERROR
* @see reportError()
*/
const CALLBACK_METHOD_IF_ERROR = null;
/**
* The name of the class which holds the method defined in CALLBACK_METHOD_IF_ERROR
* @see CALLBACK_METHOD_IF_ERROR
*/
const CALLBACK_CLASS_IF_ERROR = null;
/**
* Singleton instance
*
* @var DbArray
*/
protected static $_instance = null;
/**
* This array is used to store all the 'database arrays'
*
* @var array
*/
protected static $_data = array();
/**
* This variable is used to toggle the behaviour of throwing exceptions from the get-method
* Normally we don't want exceptions, but just an empty array (and the error logged). However, when
* we are using the get-method internally, we do want to know about exceptions; in that case we quickly
* switch on (set to True) this variable and then switch it back off (set to False) again once we're done.
*
* @var boolean
*/
protected static $_throwExceptions = False;
/**
* Singleton pattern implementation makes "new" unavailable
*
* @return void
*/
private function __construct()
{}
/**
* Singleton pattern implementation makes "clone" unavailable
*
* @return void
*/
private function __clone()
{}
/**
* creates a singleton instance
*
* @return DbArray
*/
public static function getInstance()
{
if (null === self::$_instance)
{
self::$_instance = new self();
}
return self::$_instance;
}
/**
* Retrieves an array.
* When cache-file doesn't exist yet, it will be created.
*
* @param $key String This is the key :
* - by which the array is stored internally,
* - by which the array can be retrieved using this function
* - that is used in config constants FILE_FORMAT, CLASS_FORMAT
* & ARRAY_FORMAT and subsequent functions that make use of them
* @param NOTE: you can pass any number of parameters > 1 to this function.
* The first is explained above. The 2nd, 3rd, etc will be used to
* retrieve specific values in the array that is stored under $key. Example:
* - DbArray::get('categories', 1) will return $_data['categories'][1]
* - DbArray::get('categories', 2, 7) will return $_data['categories'][2][7]
*
* @return Array
*/
public static function get($key)
{
$arguments = func_get_args();
if (count($arguments) == 1)
{
if (self::$_throwExceptions)
{
return self::getContent($key);
}
else
{
try {
return self::getContent($key);
} catch (DbArray_Exception $exc) {
self::reportError("Exception: ".$exc->getMessage()." with DbArray, ".
"line: ".$exc->getLine().", file: ".$exc->getFile().", ".
"trace: ".$exc->getTraceAsString());
return array();
}
}
}
elseif (count($arguments) > 1)
{
$arr = self::get($key);
$returnArr = $arr;
for ($x=1; $x<count($arguments); $x++)
{
$returnArr = $returnArr[$arguments[$x]];
}
return $returnArr;
}
}
/**
* Core that should have been part of the get() method, but has been isolated here
* to allow for more flexible exception throwing. This way, depending on the value
* of self::$_throwExceptions, get() can catch or deliberately not-catch any
* exception that might be the result of this function call.
* The value of self::$_throwExceptions is toggled by exists() before calling get()
*
* @param $key String see get()
*
* @see get()
*
* @return Array
*/
private static function getContent($key)
{
if (isset(self::$_data[$key]))
{
return self::$_data[$key];
}
// else
$cachedFilePath = self::CACHE_DIR_ROOT."/".self::CACHE_DIR."/".sprintf(self::FILE_FORMAT, $key);
if (file_exists($cachedFilePath))
{
require_once $cachedFilePath;
self::$_data[$key] = ${sprintf(self::ARRAY_FORMAT, $key)};
return self::$_data[$key];
}
// else
self::$_data[$key] = self::getDbArray($key);
if (self::$_throwExceptions)
{
self::write2cache($key, self::$_data[$key]);
}
else
{
try {
self::write2cache($key, self::$_data[$key]);
} catch (DbArray_Exception $exc) {
self::reportError("Exception: ".$exc->getMessage()." with DbArray, ".
"line: ".$exc->getLine().", file: ".$exc->getFile().", ".
"trace: ".$exc->getTraceAsString());
}
}
return self::$_data[$key];
}
/**
* This method works similar to the native isset()
* It requires at least 1 parameter, but can take any number of parameters.
* It allows you to ask the class whether a database-array exists, or a key
* within the database-array.
*
* @param $key String This is the key by which the array should be stored
* internally (in self::$_data)
* @param NOTE: you can pass any number of parameters > 1 to this function.
* The first is explained above. The 2nd, 3rd, etc will
* be used to test specific values in the array that is stored under $key. Example:
* - DbArray::exists('categories', 1) will test if $_data['categories'][1] exists
* - DbArray::exists('categories', 2, 7) will test if $_data['categories'][2][7] exists
*
* @return Boolean
*/
public static function exists($key)
{
$arguments = func_get_args();
if (count($arguments) > 1)
{
try {
self::$_throwExceptions = True;
$arr = self::get($key);
self::$_throwExceptions = False;
} catch (DbArray_Exception $e) {
self::$_throwExceptions = False;
if ($e->getCode() == DbArray_Exception::PATH_INVALID_CODE)
{
return False;
}
else
{
throw new DbArray_Excpetion($e->getMessage()." in ".$e->getFile()." ".
"on line ".$e->getLine(), $e->getCode());
}
}
$returnArr = $arr;
for ($x=1; $x<count($arguments); $x++)
{
if (!isset($returnArr[$arguments[$x]]))
{
return False;
}
else
{
$returnArr = $returnArr[$arguments[$x]];
}
}
return True;
}
else
{
trigger_error("Missing argument 2 for ".__CLASS__."::".__METHOD__."()", E_USER_WARNING);
}
}
/**
* Writes an array to a cache-file, given the key
* Using this key, the array will first be retrieved from the database and
* then written to the cache
*
* @param $key String See explanation under get()
*
* @throws DbArray_Exception
*
* @see getDbArray()
* @see write2cache()
*
* @return void
*/
public static function cache($key)
{
self::write2cache($key, self::getDbArray($key));
}
/**
* Writes an array to a cache-file.
*
* @param $key String See explanation under get()
* @param $arr Array The array that we want to cache
*
* @throws DbArray_Exception
*
* @return void
*/
protected static function write2cache($key, $arr)
{
$fullCacheDir = self::getFullCachePath();
$cacheDir = self::sanitizePath(self::CACHE_DIR);
$cacheDirRoot = self::sanitizePath(self::CACHE_DIR_ROOT);
if (!file_exists($fullCacheDir) || !is_dir($fullCacheDir))
{
if (strpos($cacheDir, DIRECTORY_SEPARATOR) !== False)
{
$parts = explode(DIRECTORY_SEPARATOR, $cacheDir);
}
else
{
$parts = array($cacheDir);
}
$partPath = $cacheDirRoot;
foreach ($parts AS $part)
{
$partPath .= DIRECTORY_SEPARATOR.$part;
if (!file_exists($partPath) || !is_dir($partPath))
{
if (@!mkdir($partPath, self::CHMOD))
{
throw new DbArray_Exception(
sprintf(DbArray_Exception::PATH_COULDNT_CREATE_MSG, $partPath),
DbArray_Exception::PATH_COULDNT_CREATE_CODE);
}
}
}
}
$fullPath = $fullCacheDir.DIRECTORY_SEPARATOR.sprintf(self::FILE_FORMAT, $key);
if (is_null($arr))
{
if (!isset(self::$_data[$key]))
{
throw new DbArray_Exception(
sprintf(DbArray_Exception::NO_ARRAY_PASSED_MSG, __CLASS__, __METHOD__, $key),
DbArray_Exception::NO_ARRAY_PASSED_CODE);
}
$arr = self::$_data[$key];
}
if ($fp = fopen($fullPath, "w"))
{
if (@!fputs($fp, self::arr2text($key, $arr)))
{
throw new DbArray_Exception(
sprintf(DbArray_Exception::PATH_COULDNT_WRITE_MSG, $fullPath),
DbArray_Exception::PATH_COULDNT_WRITE_CODE);
}
fclose($fp);
}
else
{
throw new DbArray_Exception(
sprintf(DbArray_Exception::PATH_COULDNT_OPEN_MSG, $fullPath),
DbArray_Exception::PATH_COULDNT_OPEN_CODE);
}
}
/**
* Recursive function that converts an array to a string of PHP code that can
* create that string. Including the PHP opening and closing tag
*
* @param $key String See get() for explanation
* @param $arr Array The array we want to parse. When recursing, this will be a sup-array
* @param $depth Integer Used during recursion to keep track of how deep we are in the array (sub-array-wise)
*
* @return String
*/
protected static function arr2text($key, $arr, $depth = 0)
{
$x = 0;
$indent = "";
while ($x <= $depth)
{
$indent .= "\t";
$x++;
}
if ($depth == 0)
{
$text = "<?php".self::NEWLINE."\$".sprintf(self::ARRAY_FORMAT, $key)." = array(".self::NEWLINE;
}
foreach ($arr AS $k => $v)
{
if (!is_int($k))
{
$k = "\"".str_replace('"', '\"', $k)."\"";
}
if (is_array($v))
{
$text .= $indent.$k." => array(".self::NEWLINE;
$text .= self::arr2text($key, $v, ($depth+1));
$text .= $indent."\t),".self::NEWLINE;
}
else
{
if (is_bool($v))
{
$v = $v ? "true" : "false";
$text .= $indent.$k." => ".$v.",".self::NEWLINE;
}
else
{
$v = str_replace('"', '\"', $v);
$text .= $indent.$k." => \"".$v."\",".self::NEWLINE;
}
}
}
if ($depth == 0)
{
$text .= $indent.");".self::NEWLINE."?>";
}
return $text;
}
/**
* Retrieves data from a database as an array and returns it
*
* @param $key String See explanation under get()
*
* @see createArray()
*
* @return Array
*/
protected static function getDbArray($key)
{
if (!is_null(self::CLASS_DIR))
$file = self::CLASS_DIR_ROOT.DIRECTORY_SEPARATOR.self::CLASS_DIR.DIRECTORY_SEPARATOR.sprintf(self::FILE_FORMAT, $key);
else
$file = dirname(realpath(__FILE__)).DIRECTORY_SEPARATOR."DbArray".DIRECTORY_SEPARATOR."Query".DIRECTORY_SEPARATOR.sprintf(self::FILE_FORMAT, $key);
$class = sprintf(self::CLASS_FORMAT, $key);
if (file_exists($file))
{
require_once $file;
$obj = new $class;
return self::createArray($obj);
}
else
{
throw new DbArray_Exception(
sprintf(DbArray_Exception::PATH_INVALID_MSG, $file, $key),
DbArray_Exception::PATH_INVALID_CODE
);
}
}
/**
* This function holds all the logic for creating the array, based on the
* DbArray_Query object that is passed to it.
* If a callback function has been defined in the object, it will be
* called before returning the constructed array
*
* @param $obj DbArray_Query_Abstract Object that holds the query and configuration for the final array
*
* @throws DbArray_Exception
*
* @return Array
*/
protected static function createArray(DbArray_Query_Abstract &$obj)
{
// creating the first option of the array (or not)
if (False === $obj->getFirstOption())
{
$outputArr = array();
}
elseif (is_array($obj->getFirstOption()))
{
$outputArr = $obj->getFirstOption();
}
else
{
$outputArr = array("" => $obj->getFirstOption());
}
$firstOption = $outputArr;
// getting the records from the database as array
$records = self::getDbData($obj);
// if nothing was found, then act accordingly
if (count($records) == 0)
{
return $obj->getIfNoResults();
}
// there were records, so we're gonna construct the array
foreach ($records AS $rowAss)
{
// values of the current record
$row = array_values($rowAss);
// mapper of the assoc-keys to numerical indexes
$rowMap = array_keys($rowAss);
// total values, which corresponds to the amount of fields in the select query
$totalValues = count($row);
switch ($totalValues)
{
// if there was only one field, we give the key the same value as the corresponding value
case 1 :
$row[1] = $row[0];
// break omitted
// we add it to the output-array (but only if this key doesn't match the key of the first-option)
case 2 :
if (!array_key_exists($row[0], $firstOption))
$outputArr[$row[0]] = $row[1];
break;
default :
// 3 or more fields. we're gonna loop through them and construct the array
if (!array_key_exists($row[0], $firstOption))
{
// creating a reference to the output array.
// we will update this reference to the current subarray while we loop
$tempArr =& $outputArr;
for ($x=0; $x<$totalValues; $x++)
{
if (False === $obj->getDepth() || $x < $obj->getDepth())
{
if (!isset($tempArr[$row[$x]]))
$tempArr[$row[$x]] = array();
if ($x == $totalValues-2)
{
// we construct the last leave
$tempArr[$row[($totalValues-2)]] = $row[($totalValues-1)];
break;
}
else
{
// updating reference with new sub-array
$tempArr =& $tempArr[$row[$x]];
}
}
elseif (False !== $obj->getDepth())
{
$tempArr[$rowMap[$x]] = $rowAss[$rowMap[$x]];
}
}
}
break;
}
}
if (method_exists($obj, "callbackConvert"))
{
// we update our array with some callback intelligence that has been defined.
$obj->callBackConvert($outputArr);
}
return $outputArr;
}
/**
* Execute (select) query on a database and return data
*
* @param $obj DbArray_Query_Abstract Object that holds the query
*
* @throws DbArray_Exception
*
* @return Array
*/
protected static function getDbData(DbArray_Query_Abstract &$obj)
{
switch ($obj->getDbType())
{
case "PostgreSQL" :
return self::getDbDataFromPostgresql($obj);
break;
case "MySQL" :
default :
return self::getDbDataFromMysql($obj);
}
}
/**
* Execute query on mysql database
*
* @param $obj DbArray_Query_Abstract
*
* @throws DbArray_Exception
*
* @see getDbData()
*
* @return Array
*/
protected static function getDbDataFromMysql(DbArray_Query_Abstract &$obj)
{
$records = array();
if (!$result = mysql_query($obj->getQuery()))
{
throw new DbArray_Exception(mysql_error()." on line ".__LINE__." in ".__FILE__, MYSQL_ERROR_CODE);
}
else
{
if (mysql_num_rows($result) > 0)
{
while ($rowAss = mysql_fetch_assoc($result))
{
$records[] = $rowAss;
}
}
}
return $records;
}
/**
* Execute query on postgresql database
*
* @param $obj DbArray_Query_Abstract
*
* @throws DbArray_Exception
*
* @see getDbData()
*
* @return Array
*/
protected static function getDbDataFromPostgresql(DbArray_Query_Abstract &$obj)
{
$records = array();
if (!$result = pg_query($obj->getQuery()))
{
throw new DbArray_Exception(mysql_error()." on line ".__LINE__." in ".__FILE__, POSTGRESQL_ERROR_CODE);
}
else
{
if (pg_num_rows($result) > 0)
{
while ($rowAss = pg_fetch_assoc($result))
{
$records[] = $rowAss;
}
}
}
return $records;
}
/**
* Makes it easy to get the full path to the cache-dir quickly in a uniform way
*
* @see sanitizePath()
*
* @return String The full path to the cache-directory
*/
public static function getFullCachePath()
{
// creating full path to cache dir
return self::sanitizePath(self::CACHE_DIR_ROOT).DIRECTORY_SEPARATOR.self::sanitizePath(self::CACHE_DIR);
}
/**
* Sanitizes (part of) a certain path. By sanitizing we mean:
* 1. convert all directory seperators that are non-native for the current OS to native ones
* (in other words, converting them into php's DIRECTORY_SEPARATOR constant)
* 2. removing any trailing (back)slashes
* Example: on windows environment temp/array/ will be converted to temp\array
*
* @param String $path The (part of the) path we want to sanitize
*
* @return String
*/
public static function sanitizePath($path)
{
// sanitizing directory separator
$contraSeparator = DIRECTORY_SEPARATOR == '/' ? '\\' : '/';
$newPath = str_replace($contraSeparator, DIRECTORY_SEPARATOR, $path);
// removing unwanted separators at the end
if (substr($newPath, -1) == DIRECTORY_SEPARATOR)
{
$newPath = substr($newPath, 0, -1);
}
return $newPath;
}
/**
* This class can warn the developer of errors at a few points in the code.
* This function uses a user defined function/method to warn the developer of the error.
* This function/method can be defined using the constants self::CALLBACK_METHOD_IF_ERROR
* and possibly CALLBACK_CLASS_IF_ERROR. When both are null, the error will be
* sent through trigger_error as an E_USER_WARNING.
*
* @param String $str String with information about the error that occured
*
* @see CALLBACK_METHOD_IF_ERROR
* @see CALLBACK_CLASS_IF_ERROR
*
* @return void
*/
protected static function reportError($str)
{
if (!is_null(self::CALLBACK_CLASS_IF_ERROR))
{
call_user_func(array(self::CALLBACK_CLASS_IF_ERROR, self::CALLBACK_METHOD_IF_ERROR), $str);
}
elseif (!is_null(self::CALLBACK_METHOD_IF_ERROR))
{
call_user_func(self::CALLBACK_METHOD_IF_ERROR, $str);
}
else
{
trigger_error($str, E_USER_WARNING);
}
}
}
/***** DbArray/Exception.php *****/
/**
* Exception class that is used in DbArray.
*/
class DbArray_Exception extends Exception
{
/**
* This one is thrown when the path to the file that should hold the
* DbArray_Query_[...] class appears not to exist.
*/
const PATH_INVALID_CODE = 101;
const PATH_INVALID_MSG = "The path [%s] doesn't exist. [%s] couldn't be resolved";
/**
* This one is thrown when the cache-file couldn't be created
*/
const PATH_COULDNT_CREATE_CODE = 102;
const PATH_COULDNT_CREATE_MSG = "Couldn't create path [%s]";
/**
* This one is thrown when the cache-file exists, but couldn't be opened
*/
const PATH_COULDNT_OPEN_CODE = 103;
const PATH_COULDNT_OPEN_MSG = "Couldn't open path [%s]";
/**
* This one is thrown when the cache-file exists but we can't write to it
*/
const PATH_COULDNT_WRITE_CODE = 104;
const PATH_COULDNT_WRITE_MSG = "Coulnd't write to [%s]";
/**
* This one is thrown when we want to write something to the cache. This can
* be done by passing the Array directly or by providing a String that is
* the key to an internal data-array. When a String is passed, but the
* internal data array couldn't be found, this exception is thrown
*/
const NO_ARRAY_PASSED_CODE = 111;
const NO_ARRAY_PASSED_MSG = "No array was passed to %s::%s and [%s] doesn't exist";
/**
* Some errors that indicate a database error (any kind)
*/
const DB_ERROR_CODE = 120;
const MYSQL_ERROR_CODE = 121;
const POSTGRESQL_ERROR_CODE = 122;
}
/***** DbArray/Query/Abstract.php *****/
/**
* This Abstract class has to be used when creating classes that can be used by
* DbArray for retrieving data from the database The only function you 'have to'
* write an implementation for is getQuery, which should return the database
* query (as a string) that selects the data. The others act as defaults, but
* can be overriden.
*/
abstract class DbArray_Query_Abstract
{
/**
* Select what type of database you are using by modifying the value of this constant.
* Valid values:
* - MySQL
* - PostgreSQL
*
* If you wish to add support for other database, please make the following modifications:
* - add a protected function 'getDbDataFrom...'
* - update the switch statement in getDbData() to use this function with the right value of DB_TYPE
*/
const DB_TYPE_MYSQL = "MySQL";
const DB_TYPE_POSTGRESQL = "PostgreSQL";
/**
* This function should return a database query, which selects the data that
* should be cached as an array.
*
* @return String
*/
abstract public function getQuery();
/***********
* Override the following functions in your implementation of this abstract
* class, to change the behaviour of that particular data:
*
* - getFirstOption()
* - getIfNoResults()
* - getDepth()
*
* Note: If a lot of your implementation need the same behaviour, it might
* be better to extend this Abstract class to some kind of (abstract) base
* class which overrides one or more of these functions
****/
/**
* In some cases you'll want the first element of the array to be something
* that is not in the database, for example when using the data for a
* drop-down box where the first option displays something like
* '- please select -'
*
* @return mixed, boolean False : means you don't want to use a first option
* String : This string will be the first option value. an empty value
* will be used as key (equivalent of (array("" => "value"))
* Array : (a flat array with only 1 element) Same as String, but here
* you'll be able to define your own key.
*/
public function getFirstOption()
{
return False;
}
/**
* This function will be called in case no database data was found.
* It allows you to have control over what will be cached in this case.
*
* @return Array
*/
public function getIfNoResults()
{
return array();
}
/**
* This function allows you to control the 'depth' of the array. Normally,
* the depth is determined by the amount of fields in the SELECT query:
* - 2 fields will result in a flat array
* - 3 fields in an array with max. 1 (sub)array per key -> this is one deep (depth = 1)
* - 4 fields in an array with max. 2 (sub)array per key -> this is one deep (depth = 2)
* etc.
* By overriding this function you can modify this automatic behaviour and
* limit the depth. Example:
* query:
* SELECT groupId, contactId, name, age FROM ...
* depth:
* 2
* array:
* array(
* 1 => array(
* 1 => array(
* "name" => "some name",
* "age" => "20",
* ),
* 2 => array(
* "name" => "some other name",
* "age" => "30",
* ),
* ),
* 2 => array(
* 3 => array(
* "name" => "yet another name",
* "age" => "40",
* ),
* ),
* );
*
* @return Integer
*/
public function getDepth()
{
return False;
}
/**
* This determines from what kind of database the data is retrieved.
* Either change the return value here, so the default will be different for
* all your implementations, or change it in the implemented classes
*
* @return String
*/
public function getDbType()
{
return self::DB_TYPE_MYSQL;
}
}?>