includes/framework/QDatabaseBase.class.php
<?php
if(!class_exists('QAbstractCacheProvider')){
include_once __QCUBED_CORE__ . '/framework/QAbstractCacheProvider.class.php';
}
/**
* The interface for cache actions to be queued and replayed.
*/
interface ICacheAction {
/**
* Executes action on the given cache object
* @param QAbstractCacheProvider $objCache The cache object to apply this action to.
*/
public function Execute(QAbstractCacheProvider $objCache);
}
/**
* The Set cache action to be queued and replayed.
*/
class QCacheSetAction implements ICacheAction {
/**
* @var string the key to use for the object
*/
protected $strKey;
/**
* @var object the object to put in the cache
*/
protected $objValue;
/**
* @var null|int Time after which the object is to be invalidated/expired
*/
protected $intExpirationTime = null;
/**
* Construct the new QCacheSetAction object.
*
* @param string $strKey the key to use for the object
* @param object $objValue the object to put in the cache
* @param null|int $intExpirationTime Number of seconds after which the value has to expire
*/
public function __construct($strKey, $objValue, $intExpirationTime = null) {
$this->strKey = $strKey;
$this->objValue = $objValue;
$this->intExpirationTime = (int)$intExpirationTime;
}
/**
* Executes action on the given cache object
* @param QAbstractCacheProvider $objCache The cache object to apply this action to.
*/
public function Execute(QAbstractCacheProvider $objCache) {
$objCache->Set($this->strKey, $this->objValue, $this->intExpirationTime);
}
}
/**
* The Delete cache action to be queued and replayed.
*/
class QCacheDeleteAction implements ICacheAction {
/**
* @var string the key to use for the object
*/
protected $strKey;
/**
* Construct the new QCacheDeleteAction object.
* @param string $strKey the key to use for the object
*/
public function __construct($strKey) {
$this->strKey = $strKey;
}
/**
* Executes action on the given cache object
* @param QAbstractCacheProvider $objCache The cache object to apply this action to.
*/
public function Execute(QAbstractCacheProvider $objCache) {
$objCache->Delete($this->strKey);
}
}
/**
* The DeleteAll cache action to be queued and replayed.
*/
class QCacheDeleteAllAction implements ICacheAction {
/**
* Construct the new QCacheDeleteAction object.
*/
public function __construct() {
}
/**
* Executes action on the given cache object
* @param QAbstractCacheProvider $objCache The cache object to apply this action to.
*/
public function Execute(QAbstractCacheProvider $objCache) {
$objCache->DeleteAll();
}
}
/**
* Cache provider that records all additions and removals from the cache,
* and provides an interface to replay them on another instance of an QAbstractCacheProvider
*/
class QCacheProviderProxy extends QAbstractCacheProvider {
/**
* @var array Additions to cache
*/
protected $arrLocalCacheAdditions;
/**
* @var array Removals from cache
*/
protected $arrLocalCacheRemovals;
/**
* @var QAbstractCacheProvider The super cache to query values from.
*/
protected $objSuperCache;
/**
* @var ICacheAction[] The queue of actions performed on this cache.
*/
protected $objCacheActionQueue;
/**
* @param QAbstractCacheProvider $objSuperCache The super cache to query values from.
*/
public function __construct($objSuperCache) {
$this->objSuperCache = $objSuperCache;
$this->objCacheActionQueue = array();
$this->arrLocalCacheAdditions = array();
$this->arrLocalCacheRemovals = array();
}
/**
* Apply changes to the cache object supplyed.
*
* @param QAbstractCacheProvider $objAbstractCacheProvider The cache object to apply changes.
*/
public function Replay($objAbstractCacheProvider) {
foreach ($this->objCacheActionQueue as $objCacheAction) {
$objCacheAction->Execute($objAbstractCacheProvider);
}
$this->objCacheActionQueue = array();
}
public function Get($strKey) {
if (isset($this->arrLocalCacheAdditions[$strKey])) {
return $this->arrLocalCacheAdditions[$strKey];
}
if (!isset($this->arrLocalCacheRemovals[$strKey])) {
if ($this->objSuperCache) {
return $this->objSuperCache->Get($strKey);
}
}
return false;
}
public function Set($strKey, $objValue, $intExpirationTime = null) {
$this->arrLocalCacheAdditions[$strKey] = $objValue;
if (isset($this->arrLocalCacheRemovals[$strKey])) {
unset($this->arrLocalCacheRemovals[$strKey]);
}
$this->objCacheActionQueue[] = new QCacheSetAction($strKey, $objValue, $intExpirationTime);
}
public function Delete($strKey) {
if (isset($this->arrLocalCacheAdditions[$strKey])) {
unset($this->arrLocalCacheAdditions[$strKey]);
}
$this->arrLocalCacheRemovals[$strKey] = true;
$this->objCacheActionQueue[] = new QCacheDeleteAction($strKey);
}
public function DeleteAll() {
$this->arrLocalCacheAdditions = array();
$this->arrLocalCacheRemovals = array();
$this->objCacheActionQueue[] = new QCacheDeleteAllAction;
}
}
/**
* Every database adapter must implement the following 5 classes (all of which are abstract):
* * DatabaseBase
* * DatabaseFieldBase
* * DatabaseResultBase
* * DatabaseRowBase
* * DatabaseExceptionBase
* This Database library also has the following classes already defined, and
* Database adapters are assumed to use them internally:
* * DatabaseIndex
* * DatabaseForeignKey
* * DatabaseFieldType (which is an abstract class that solely contains constants)
*
* @property-read string $EscapeIdentifierBegin
* @property-read string $EscapeIdentifierEnd
* @property-read boolean $EnableProfiling
* @property-read int $AffectedRows
* @property-read string $Profile
* @property-read int $DatabaseIndex
* @property-read int $Adapter
* @property-read string $Server
* @property-read string $Port
* @property-read string $Database
* @property-read string $Service
* @property-read string $Protocol
* @property-read string $Host
* @property-read string $Username
* @property-read string $Password
* @property boolean $Caching if true objects loaded from this database will be kept in cache (assuming a cache provider is also configured)
* @property-read string $DateFormat
* @property-read boolean $OnlyFullGroupBy database adapter sub-classes can override and set this property to true
* to prevent the behavior of automatically adding all the columns to the select clause when the query has
* an aggregation clause.
* @package DatabaseAdapters
*/
abstract class QDatabaseBase extends QBaseClass {
// Must be updated for all Adapters
/** Adapter name */
const Adapter = 'Generic Database Adapter (Abstract)';
// Protected Member Variables for ALL Database Adapters
/** @var int Database Index according to the configuration file */
protected $intDatabaseIndex;
/** @var bool Has the profiling been enabled? */
protected $blnEnableProfiling;
protected $strProfileArray;
protected $objConfigArray;
protected $blnConnectedFlag = false;
/** @var string The beginning part of characters which can escape identifiers in a SQL query for the database */
protected $strEscapeIdentifierBegin = '"';
/** @var string The ending part of characters which can escape identifiers in a SQL query for the database */
protected $strEscapeIdentifierEnd = '"';
protected $blnOnlyFullGroupBy = false; // should be set in sub-classes as appropriate
/**
* @var int The transaction depth value.
* It is incremented on a transaction begin,
* decremented on a transaction commit, and reset to zero on a roll back.
* It is used to implement the recursive transaction functionality.
*/
protected $intTransactionDepth = 0;
/**
* @var QStack The stack of cache providers.
* It is populated with cache providers from different databases,
* if there are transaction of one DB in the middle of the transaction of another DB.
*/
protected static $objCacheProviderStack;
// Abstract Methods that ALL Database Adapters MUST implement
/**
* Connects to the database
*/
abstract public function Connect();
// these are protected - externally, the "Query/NonQuery" wrappers are meant to be called
/**
* Sends a SQL query for execution to the database
* In this regard, a query is a 'SELECT' statement
*
* @param string $strQuery The Query to be executed
*
* @return mixed Result that the database returns after running the query.
*/
abstract protected function ExecuteQuery($strQuery);
/**
* Sends a non-SELECT query (such as INSERT, UPDATE, DELETE, TRUNCATE) to DB server.
* In most cases, the results of this function are not used and you should not send
* 'SELECT' queries using this method because a result is not guaranteed to be returned
*
* If there was an error, it would most probably be caught as an exception.
*
* @param string $strNonQuery The Query to be executed
*
* @return mixed Result that the database returns after running the query
*/
abstract protected function ExecuteNonQuery($strNonQuery);
/**
* Returns the list of tables in the database (as string)
*
* @return mixed|string[] List of tables
*/
abstract public function GetTables();
/**
* Returns the ID to be inserted in a table column (normally it an autoincrement column)
*
* @param null|string $strTableName Table name where the ID has to be inserted
* @param null|string $strColumnName Column name where the ID has to be inserted
*
* @return mixed
*/
abstract public function InsertId($strTableName = null, $strColumnName = null);
/**
* Get the list of columns/fields for a given table
*
* @param string $strTableName Name of table whose fields we have to get
*
* @return mixed
*/
abstract public function GetFieldsForTable($strTableName);
/**
* Get list of indexes for a table
*
* @param string $strTableName Name of table whose column indexes we have to get
*
* @return mixed
*/
abstract public function GetIndexesForTable($strTableName);
/**
* Get list of foreign keys for a table
*
* @param string $strTableName Name of table whose foreign keys we are trying to get
*
* @return mixed
*/
abstract public function GetForeignKeysForTable($strTableName);
/**
* This function actually begins the database transaction.
* Must be implemented in all subclasses.
* The "TransactionBegin" wrapper are meant to be called by end-user code
*
* @return void Nothing
*/
abstract protected function ExecuteTransactionBegin();
/**
* This function actually commits the database transaction.
* Must be implemented in all subclasses.
* The "TransactionCommit" wrapper are meant to be called by end-user code
* @return void Nothing
*/
abstract protected function ExecuteTransactionCommit();
/**
* This function actually rolls back the database transaction.
* Must be implemented in all subclasses.
* The "TransactionRollBack" wrapper are meant to be called by end-user code
*
* @return void Nothing
*/
abstract protected function ExecuteTransactionRollBack();
/**
* Template for executing stored procedures. Optional, for those database drivers that support it.
* @param $strProcName
* @param null $params
* @return mixed
*/
public function ExecuteProcedure($strProcName, $params = null) {}
/**
* This function begins the database transaction.
*
* @return void Nothing
*/
public final function TransactionBegin() {
if (0 == $this->intTransactionDepth) {
$this->ExecuteTransactionBegin();
$objCacheProvider = QApplication::$objCacheProvider;
if ($objCacheProvider && $this->Caching && !($objCacheProvider instanceof QCacheProviderNoCache)) {
if (!self::$objCacheProviderStack) {
self::$objCacheProviderStack = new QStack;
}
self::$objCacheProviderStack->Push($objCacheProvider);
QApplication::$objCacheProvider = new QCacheProviderProxy($objCacheProvider);
}
}
$this->intTransactionDepth++;
}
/**
* This function commits the database transaction.
*
* @throws QCallerException
* @return void Nothing
*/
public final function TransactionCommit() {
if (1 == $this->intTransactionDepth) {
$this->ExecuteTransactionCommit();
$this->transactionCacheFlush();
$this->transactionCacheRestore();
}
if ($this->intTransactionDepth <= 0) {
throw new QCallerException("The transaction commit call is called before the transaction begin was called.");
}
$this->intTransactionDepth--;
}
/**
* This function rolls back the database transaction.
*
* @return void Nothing
*/
public final function TransactionRollBack() {
$this->ExecuteTransactionRollBack();
$this->intTransactionDepth = 0;
$this->transactionCacheRestore();
}
/**
* Flushes all objects from the local cache to the actual one.
*/
protected final function transactionCacheFlush() {
if (!self::$objCacheProviderStack || self::$objCacheProviderStack->IsEmpty()) {
return;
}
$objCacheProvider = self::$objCacheProviderStack->PeekLast();
QApplication::$objCacheProvider->Replay($objCacheProvider);
}
/**
* Restores the actual cache to the QApplication variable.
*/
protected final function transactionCacheRestore() {
if (!self::$objCacheProviderStack || self::$objCacheProviderStack->IsEmpty()) {
return;
}
$objCacheProvider = self::$objCacheProviderStack->Pop();
// restore the actual cache to the QApplication variable.
QApplication::$objCacheProvider = $objCacheProvider;
}
abstract public function SqlLimitVariablePrefix($strLimitInfo);
abstract public function SqlLimitVariableSuffix($strLimitInfo);
abstract public function SqlSortByVariable($strSortByInfo);
/**
* Closes the database connection
*
* @return mixed
*/
abstract public function Close();
/**
* Given an identifier for a SQL query, this method returns the escaped identifier
*
* @param string $strIdentifier Identifier to be escaped
*
* @return string Escaped identifier string
*/
public function EscapeIdentifier($strIdentifier) {
return $this->strEscapeIdentifierBegin . $strIdentifier . $this->strEscapeIdentifierEnd;
}
/**
* Given an array of identifiers, this method returns array of escaped identifiers
* For corner case handling, if a single identifier is supplied, a single escaped identifier is returned
*
* @param array|string $mixIdentifiers Array of escaped identifiers (array) or one unescaped identifier (string)
*
* @return array|string Array of escaped identifiers (array) or one escaped identifier (string)
*/
public function EscapeIdentifiers($mixIdentifiers) {
if (is_array($mixIdentifiers)) {
return array_map(array($this, 'EscapeIdentifier'), $mixIdentifiers);
} else {
return $this->EscapeIdentifier($mixIdentifiers);
}
}
/**
* Escapes values (or single value) which we can then send to the database
*
* @param array|mixed $mixValues Array of values (or a single value) to be escaped
*
* @return array|string Array of (or a single) escaped value(s)
*/
public function EscapeValues($mixValues) {
if (is_array($mixValues)) {
return array_map(array($this, 'SqlVariable'), $mixValues);
} else {
return $this->SqlVariable($mixValues);
}
}
/**
* Escapes both column and values when supplied as an array
*
* @param array $mixColumnsAndValuesArray Array with column=>value format with both (column and value) sides unescaped
*
* @return array Array with column=>value format data with both column and value escaped
*/
public function EscapeIdentifiersAndValues($mixColumnsAndValuesArray) {
$result = array();
foreach ($mixColumnsAndValuesArray as $strColumn => $mixValue) {
$result[$this->EscapeIdentifier($strColumn)] = $this->SqlVariable($mixValue);
}
return $result;
}
/**
* INSERTs or UPDATEs a table
*
* @param string $strTable Table name
* @param array $mixColumnsAndValuesArray column=>value array
* (they are given to 'EscapeIdentifiersAndValues' method)
* @param null|string|array $strPKNames Name(s) of primary key column(s) (expressed as string or array)
*/
public function InsertOrUpdate($strTable, $mixColumnsAndValuesArray, $strPKNames = null) {
$strEscapedArray = $this->EscapeIdentifiersAndValues($mixColumnsAndValuesArray);
$strColumns = array_keys($strEscapedArray);
$strUpdateStatement = '';
foreach ($strEscapedArray as $strColumn => $strValue) {
if ($strUpdateStatement) $strUpdateStatement .= ', ';
$strUpdateStatement .= $strColumn . ' = ' . $strValue;
}
if (is_null($strPKNames)) {
$strMatchCondition = 'target_.'.$strColumns[0].' = source_.'.$strColumns[0];
} else if (is_array($strPKNames)) {
$strMatchCondition = '';
foreach ($strPKNames as $strPKName) {
if ($strMatchCondition) $strMatchCondition .= ' AND ';
$strMatchCondition .= 'target_.'.$this->EscapeIdentifier($strPKName).' = source_.'.$this->EscapeIdentifier($strPKName);
}
} else {
$strMatchCondition = 'target_.'.$this->EscapeIdentifier($strPKNames).' = source_.'.$this->EscapeIdentifier($strPKNames);
}
$strTable = $this->EscapeIdentifierBegin . $strTable . $this->EscapeIdentifierEnd;
$strSql = sprintf('MERGE INTO %s AS target_ USING %s AS source_ ON %s WHEN MATCHED THEN UPDATE SET %s WHEN NOT MATCHED THEN INSERT (%s) VALUES (%s)',
$strTable, $strTable,
$strMatchCondition, $strUpdateStatement,
implode(', ', $strColumns),
implode(', ', array_values($strEscapedArray))
);
$this->ExecuteNonQuery($strSql);
}
/**
* Sends the 'SELECT' query to the database and returns the result
*
* @param string $strQuery query string
*
* @return QDatabaseResultBase
*/
public final function Query($strQuery) {
$timerName = null;
if (!$this->blnConnectedFlag) {
$this->Connect();
}
if ($this->blnEnableProfiling) {
$timerName = 'queryExec' . mt_rand() ;
QTimer::Start($timerName);
}
$result = $this->ExecuteQuery($strQuery);
if ($this->blnEnableProfiling) {
$dblQueryTime = QTimer::Stop($timerName);
QTimer::Reset($timerName);
// Log Query (for Profiling, if applicable)
$this->LogQuery($strQuery, $dblQueryTime);
}
return $result;
}
/**
* This is basically the same as 'Query' but is used when SQL statements other than 'SELECT'
* @param string $strNonQuery The SQL to be sent
*
* @return mixed
* @throws QCallerException
*/
public final function NonQuery($strNonQuery) {
if (!$this->blnConnectedFlag) {
$this->Connect();
}
$timerName = '';
if ($this->blnEnableProfiling) {
$timerName = 'queryExec' . mt_rand() ;
QTimer::Start($timerName);
}
$result = $this->ExecuteNonQuery($strNonQuery);
if ($this->blnEnableProfiling) {
$dblQueryTime = QTimer::Stop($timerName);
QTimer::Reset($timerName);
// Log Query (for Profiling, if applicable)
$this->LogQuery($strNonQuery, $dblQueryTime);
}
return $result;
}
/**
* PHP magic method
* @param string $strName Property name
*
* @return mixed
* @throws Exception|QCallerException
*/
public function __get($strName) {
switch ($strName) {
case 'EscapeIdentifierBegin':
return $this->strEscapeIdentifierBegin;
case 'EscapeIdentifierEnd':
return $this->strEscapeIdentifierEnd;
case 'EnableProfiling':
return $this->blnEnableProfiling;
case 'AffectedRows':
return -1;
case 'Profile':
return $this->strProfileArray;
case 'DatabaseIndex':
return $this->intDatabaseIndex;
case 'Adapter':
$strConstantName = get_class($this) . '::Adapter';
return constant($strConstantName) . ' (' . $this->objConfigArray['adapter'] . ')';
case 'Server':
case 'Port':
case 'Database':
// Informix naming
case 'Service':
case 'Protocol':
case 'Host':
case 'Username':
case 'Password':
case 'Caching':
return $this->objConfigArray[strtolower($strName)];
case 'DateFormat':
return (is_null($this->objConfigArray[strtolower($strName)])) ? (QDateTime::FormatIso) : ($this->objConfigArray[strtolower($strName)]);
case 'OnlyFullGroupBy':
return (!isset($this->objConfigArray[strtolower($strName)])) ? $this->blnOnlyFullGroupBy : $this->objConfigArray[strtolower($strName)];
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
/**
* PHP magic method to set class properties
* @param string $strName Property name
* @param string $mixValue Property value
*
* @return mixed|void
* @throws Exception|QCallerException
*/
public function __set($strName, $mixValue) {
switch ($strName) {
case 'Caching':
$this->objConfigArray[strtolower($strName)] = $mixValue;
break;
default:
try {
parent::__set($strName, $mixValue);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
/**
* Constructs a Database Adapter based on the database index and the configuration array of properties
* for this particular adapter. Sets up the base-level configuration properties for this database,
* namely DB Profiling and Database Index
*
* @param integer $intDatabaseIndex
* @param string[] $objConfigArray configuration array as passed in to the constructor
* by QApplicationBase::InitializeDatabaseConnections();
*
* @throws Exception|QCallerException|QInvalidCastException
* @return QDatabaseBase
*/
public function __construct($intDatabaseIndex, $objConfigArray) {
// Setup DatabaseIndex
$this->intDatabaseIndex = $intDatabaseIndex;
// Save the ConfigArray
$this->objConfigArray = $objConfigArray;
// Setup Profiling Array (if applicable)
$this->blnEnableProfiling = QType::Cast($objConfigArray['profiling'], QType::Boolean);
if ($this->blnEnableProfiling)
$this->strProfileArray = array();
}
/**
* Allows for the enabling of DB profiling while in middle of the script
*
* @return void
*/
public function EnableProfiling() {
// Only perform profiling initialization if profiling is not yet enabled
if (!$this->blnEnableProfiling) {
$this->blnEnableProfiling = true;
$this->strProfileArray = array();
}
}
/**
* If EnableProfiling is on, then log the query to the profile array
*
* @param string $strQuery
* @param double $dblQueryTime query execution time in milliseconds
* @return void
*/
private function LogQuery($strQuery, $dblQueryTime) {
if ($this->blnEnableProfiling) {
// Dereference-ize Backtrace Information
$objDebugBacktrace = debug_backtrace();
// get rid of unnecessary backtrace info in case of:
// query
if ((count($objDebugBacktrace) > 3) &&
(array_key_exists('function', $objDebugBacktrace[2])) &&
(($objDebugBacktrace[2]['function'] == 'QueryArray') ||
($objDebugBacktrace[2]['function'] == 'QuerySingle') ||
($objDebugBacktrace[2]['function'] == 'QueryCount')))
$objBacktrace = $objDebugBacktrace[3];
else
if (isset($objDebugBacktrace[2]))
// non query
$objBacktrace = $objDebugBacktrace[2];
else
// ad hoc query
$objBacktrace = $objDebugBacktrace[1];
// get rid of reference to current object in backtrace array
if( isset($objBacktrace['object']))
$objBacktrace['object'] = null;
for ($intIndex = 0, $intMax = count($objBacktrace['args']); $intIndex < $intMax; $intIndex++) {
$obj = $objBacktrace['args'][$intIndex];
if (($obj instanceof QQClause) || ($obj instanceof QQCondition))
$obj = sprintf("[%s]", $obj->__toString());
else if (is_null($obj))
$obj = 'null';
else if (gettype($obj) == 'integer') {}
else if (gettype($obj) == 'object')
$obj = 'Object';
else if (is_array($obj))
$obj = 'Array';
else
$obj = sprintf("'%s'", $obj);
$objBacktrace['args'][$intIndex] = $obj;
}
// Push it onto the profiling information array
$arrProfile = array(
'objBacktrace' => $objBacktrace,
'strQuery' => $strQuery,
'dblTimeInfo' => $dblQueryTime);
array_push( $this->strProfileArray, $arrProfile);
}
}
/**
* Properly escapes $mixData to be used as a SQL query parameter.
* If IncludeEquality is set (usually not), then include an equality operator.
* So for most data, it would just be "=". But, for example,
* if $mixData is NULL, then most RDBMS's require the use of "IS".
*
* @param mixed $mixData
* @param boolean $blnIncludeEquality whether or not to include an equality operator
* @param boolean $blnReverseEquality whether the included equality operator should be a "NOT EQUAL", e.g. "!="
* @return string the properly formatted SQL variable
*/
public function SqlVariable($mixData, $blnIncludeEquality = false, $blnReverseEquality = false) {
// Are we SqlVariabling a BOOLEAN value?
if (is_bool($mixData)) {
// Yes
if ($blnIncludeEquality) {
// We must include the inequality
if ($blnReverseEquality) {
// Do a "Reverse Equality"
// Check against NULL, True then False
if (is_null($mixData))
return 'IS NOT NULL';
else if ($mixData)
return '= 0';
else
return '!= 0';
} else {
// Check against NULL, True then False
if (is_null($mixData))
return 'IS NULL';
else if ($mixData)
return '!= 0';
else
return '= 0';
}
} else {
// Check against NULL, True then False
if (is_null($mixData))
return 'NULL';
else if ($mixData)
return '1';
else
return '0';
}
}
// Check for Equality Inclusion
if ($blnIncludeEquality) {
if ($blnReverseEquality) {
if (is_null($mixData))
$strToReturn = 'IS NOT ';
else
$strToReturn = '!= ';
} else {
if (is_null($mixData))
$strToReturn = 'IS ';
else
$strToReturn = '= ';
}
} else
$strToReturn = '';
// Check for NULL Value
if (is_null($mixData))
return $strToReturn . 'NULL';
// Check for NUMERIC Value
if (is_integer($mixData) || is_float($mixData))
return $strToReturn . sprintf('%s', $mixData);
// Check for DATE Value
if ($mixData instanceof QDateTime) {
/** @var QDateTime $mixData */
if ($mixData->IsTimeNull()) {
if ($mixData->IsDateNull()) {
return $strToReturn . 'NULL'; // null date and time is a null value
}
return $strToReturn . sprintf("'%s'", $mixData->qFormat('YYYY-MM-DD'));
}
elseif ($mixData->IsDateNull()) {
return $strToReturn . sprintf("'%s'", $mixData->qFormat('hhhh:mm:ss'));
}
return $strToReturn . sprintf("'%s'", $mixData->qFormat(QDateTime::FormatIso));
}
// an array. Assume we are using it in an array context, like an IN clause
if (is_array($mixData)) {
$items = [];
foreach ($mixData as $item) {
$items[] = $this->SqlVariable($item); // recurse
}
return '(' . implode(',', $items) . ')';
}
// Assume it's some kind of string value
return $strToReturn . sprintf("'%s'", addslashes($mixData));
}
public function PrepareStatement($strQuery, $mixParameterArray) {
foreach ($mixParameterArray as $strKey => $mixValue) {
if (is_array($mixValue)) {
$strParameters = array();
foreach ($mixValue as $mixParameter)
array_push($strParameters, $this->SqlVariable($mixParameter));
$strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{' . $strKey . '}', implode(',', $strParameters) . ')', $strQuery);
} else {
$strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{=' . $strKey . '=}', $this->SqlVariable($mixValue, true, false), $strQuery);
$strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{!' . $strKey . '!}', $this->SqlVariable($mixValue, true, true), $strQuery);
$strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{' . $strKey . '}', $this->SqlVariable($mixValue), $strQuery);
}
}
return $strQuery;
}
/**
* Displays the OutputProfiling results, plus a link which will popup the details of the profiling.
*
* @param bool $blnPrintOutput
* @return null|string
*/
public function OutputProfiling($blnPrintOutput = true) {
$strOut = '<div class="qDbProfile">';
if ($this->blnEnableProfiling) {
$strOut .= sprintf('<form method="post" id="frmDbProfile%s" action="%s/profile.php"><div>',
$this->intDatabaseIndex, __VIRTUAL_DIRECTORY__ . __PHP_ASSETS__);
$strOut.= sprintf('<input type="hidden" name="strProfileData" value="%s" />',
base64_encode(serialize($this->strProfileArray)));
$strOut .= sprintf('<input type="hidden" name="intDatabaseIndex" value="%s" />', $this->intDatabaseIndex);
$strOut .= sprintf('<input type="hidden" name="strReferrer" value="%s" /></div></form>', QApplication::HtmlEntities(QApplication::$RequestUri));
$intCount = round(count($this->strProfileArray));
if ($intCount == 0)
$strQueryString = 'No queries';
else if ($intCount == 1)
$strQueryString = '1 query';
else
$strQueryString = $intCount . ' queries';
$strOut .= sprintf('<b>PROFILING INFORMATION FOR DATABASE CONNECTION #%s</b>: %s performed. Please <a href="#" onclick="var frmDbProfile = document.getElementById(\'frmDbProfile%s\'); frmDbProfile.target = \'_blank\'; frmDbProfile.submit(); return false;">click here to view profiling detail</a><br />',
$this->intDatabaseIndex, $strQueryString, $this->intDatabaseIndex);
} else {
$strOut .= '<form></form><b>Profiling was not enabled for this database connection (#' . $this->intDatabaseIndex . ').</b> To enable, ensure that ENABLE_PROFILING is set to TRUE.';
}
$strOut .= '</div>';
$strOut .= '<script>$j(function() {$j(".qDbProfile").draggable();});</script>'; // make it draggable so you can move it out of the way if needed.
if ($blnPrintOutput) {
print ($strOut);
return null;
}
else {
return $strOut;
}
}
/**
* Executes the explain statement for a given query and returns the output without any transformation.
* If the database adapter does not support EXPLAIN statements, returns null.
*
* @param $strSql
*
* @return null
*/
public function ExplainStatement($strSql) {
return null;
}
/**
* Utility function to extract the json embedded options structure from the comments.
*
* Usage:
* <code>
* list($strComment, $options) = QDatabaseFieldBase::ExtractCommentOptions($strComment);
* </code>
*
* @param string $strComment The comment to analyze
* @return array A two item array, with first item the comment with the options removed, and 2nd item the options array.
*
*/
public static function ExtractCommentOptions($strComment) {
$ret[0] = null; // comment string without options
$ret[1] = null; // the options array
if (($strComment) &&
($pos1 = strpos ($strComment, '{')) !== false &&
($pos2 = strrpos ($strComment, '}', $pos1))) {
$strJson = substr ($strComment, $pos1, $pos2 - $pos1 + 1);
$a = json_decode($strJson, true);
if ($a) {
$ret[0] = substr ($strComment, 0, $pos1) . substr ($strComment, $pos2 + 1); // return comment without options
$ret[1] = $a;
} else {
$ret[0] = $strComment;
}
}
return $ret;
}
}
abstract class QDatabaseFieldBase extends QBaseClass {
protected $strName;
protected $strOriginalName;
protected $strTable;
protected $strOriginalTable;
protected $strDefault;
protected $intMaxLength;
protected $strComment;
// Bool
protected $blnIdentity;
protected $blnNotNull;
protected $blnPrimaryKey;
protected $blnUnique;
protected $blnTimestamp;
protected $strType;
/**
* PHP magic method
*
* @param string $strName Property name
*
* @return mixed
* @throws Exception|QCallerException
*/
public function __get($strName) {
switch ($strName) {
case "Name":
return $this->strName;
case "OriginalName":
return $this->strOriginalName;
case "Table":
return $this->strTable;
case "OriginalTable":
return $this->strOriginalTable;
case "Default":
return $this->strDefault;
case "MaxLength":
return $this->intMaxLength;
case "Identity":
return $this->blnIdentity;
case "NotNull":
return $this->blnNotNull;
case "PrimaryKey":
return $this->blnPrimaryKey;
case "Unique":
return $this->blnUnique;
case "Timestamp":
return $this->blnTimestamp;
case "Type":
return $this->strType;
case "Comment":
return $this->strComment;
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
}
/**
* Class to handle results sent by database upon querying
* @property QQueryBuilder $QueryBuilder
*/
abstract class QDatabaseResultBase extends QBaseClass {
// Allow to attach a QQueryBuilder object to use the result object as cursor resource for cursor queries.
/** @var QQueryBuilder Query builder object */
protected $objQueryBuilder;
/**
* Fetches one row as indexed (column=>value style) array from the result set
* @abstract
* @return mixed
*/
abstract public function FetchArray();
/**
* Fetches one row as enumerated (with numerical indexes) array from the result set
* @abstract
* @return mixed
*/
abstract public function FetchRow();
abstract public function FetchField();
abstract public function FetchFields();
abstract public function CountRows();
abstract public function CountFields();
abstract public function GetNextRow();
abstract public function GetRows();
abstract public function Close();
/**
* PHP magic method
*
* @param string $strName Property name
*
* @return mixed
* @throws Exception|QCallerException
*/
public function __get($strName) {
switch ($strName) {
case 'QueryBuilder':
return $this->objQueryBuilder;
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
public function __set($strName, $mixValue) {
switch ($strName) {
case 'QueryBuilder':
try {
return ($this->objQueryBuilder = QType::Cast($mixValue, 'QQueryBuilder'));
} catch (QInvalidCastException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
default:
try {
return parent::__set($strName, $mixValue);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
}
/**
* Base class for all Database rows. Implemented by Database adapters
* @package DatabaseAdapters
*/
abstract class QDatabaseRowBase extends QBaseClass {
/**
* Gets the value of a column from a result row returned by the database
*
* @param string $strColumnName Name of the column
* @param null|string $strColumnType Data type
*
* @return mixed
*/
abstract public function GetColumn($strColumnName, $strColumnType = null);
/**
* Tells whether a particular column exists in a returned database row
*
* @param string $strColumnName Name of te column
*
* @return bool
*/
abstract public function ColumnExists($strColumnName);
abstract public function GetColumnNameArray();
/**
* Returns the boolean value corresponding to whatever a boolean column returns. Some database types
* return strings that represent the boolean values. Default is to use a PHP cast.
* @param $mixValue Value of the BIT column
* @return bool
*/
public function ResolveBooleanValue ($mixValue) {
if ($mixValue === null) {
return null;
}
return ((bool)$mixValue);
}
}
/**
* Class to handle exceptions related to database querying
* @property-read int $ErrorNumber The number of error provided by the SQL server
* @property-read string $Query The query caused the error
* @package DatabaseAdapters
*/
abstract class QDatabaseExceptionBase extends QCallerException {
/** @var int Error number */
protected $intErrorNumber;
/** @var string Query which produced the error */
protected $strQuery;
/**
* PHP magic function to get property values
* @param string $strName
*
* @return array|int|mixed
*/
public function __get($strName) {
switch ($strName) {
case "ErrorNumber":
return $this->intErrorNumber;
case "Query";
return $this->strQuery;
default:
return parent::__get($strName);
}
}
}
/**
*
* @package DatabaseAdapters
*/
class QDatabaseForeignKey extends QBaseClass {
protected $strKeyName;
protected $strColumnNameArray;
protected $strReferenceTableName;
protected $strReferenceColumnNameArray;
public function __construct($strKeyName, $strColumnNameArray, $strReferenceTableName, $strReferenceColumnNameArray) {
$this->strKeyName = $strKeyName;
$this->strColumnNameArray = $strColumnNameArray;
$this->strReferenceTableName = $strReferenceTableName;
$this->strReferenceColumnNameArray = $strReferenceColumnNameArray;
}
/**
* PHP magic method
*
* @param string $strName Property name
*
* @return mixed
* @throws Exception|QCallerException
*/
public function __get($strName) {
switch ($strName) {
case "KeyName":
return $this->strKeyName;
case "ColumnNameArray":
return $this->strColumnNameArray;
case "ReferenceTableName":
return $this->strReferenceTableName;
case "ReferenceColumnNameArray":
return $this->strReferenceColumnNameArray;
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
}
/**
* To handle index in a table in database
* @package DatabaseAdapters
*/
class QDatabaseIndex extends QBaseClass {
/** @var string Name of the index */
protected $strKeyName;
/** @var bool Is the Index a primary key index? */
protected $blnPrimaryKey;
/** @var bool Is this a Unique index? */
protected $blnUnique;
/** @var array Array of column names on which this index is defined */
protected $strColumnNameArray;
/**
* @param string $strKeyName Name of the index
* @param string $blnPrimaryKey Is this index a Primary key index?
* @param string $blnUnique Is this index unique?
* @param array $strColumnNameArray Columns on which this index is defined
*/
public function __construct($strKeyName, $blnPrimaryKey, $blnUnique, $strColumnNameArray) {
$this->strKeyName = $strKeyName;
$this->blnPrimaryKey = $blnPrimaryKey;
$this->blnUnique = $blnUnique;
$this->strColumnNameArray = $strColumnNameArray;
}
/**
* PHP magic function
* @param string $strName
*
* @return mixed
* @throws Exception|QCallerException
*/
public function __get($strName) {
switch ($strName) {
case "KeyName":
return $this->strKeyName;
case "PrimaryKey":
return $this->blnPrimaryKey;
case "Unique":
return $this->blnUnique;
case "ColumnNameArray":
return $this->strColumnNameArray;
default:
try {
return parent::__get($strName);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
}
}
}
/**
* Data types in a database
* @package DatabaseAdapters
*/
abstract class QDatabaseFieldType {
/** Binary Data (Binary Large OBjects/BLOBs) */
const Blob = "Blob";
/** Character sequence - variable length */
const VarChar = "VarChar";
/** Character sequence - fixed length */
const Char = "Char";
/** Integers */
const Integer = "Integer";
/** Date and Time together */
const DateTime = "DateTime";
/** Date only */
const Date = "Date";
/** Time only */
const Time = "Time";
/** Float, Double and real (postgresql) */
const Float = "Float";
/** Boolean */
const Bit = "Bit";
/** New JSON type */
const Json = "Json";
}