1: <?php
2: if(!class_exists('QAbstractCacheProvider')){
3: include_once __QCUBED_CORE__ . '/framework/QAbstractCacheProvider.class.php';
4: }
5:
6: /**
7: * The interface for cache actions to be queued and replayed.
8: */
9: interface ICacheAction {
10: /**
11: * Executes action on the given cache object
12: * @param QAbstractCacheProvider $objCache The cache object to apply this action to.
13: */
14: public function Execute(QAbstractCacheProvider $objCache);
15: }
16: /**
17: * The Set cache action to be queued and replayed.
18: */
19: class QCacheSetAction implements ICacheAction {
20: /**
21: * @var string the key to use for the object
22: */
23: protected $strKey;
24: /**
25: * @var object the object to put in the cache
26: */
27: protected $objValue;
28:
29: /**
30: * Construct the new QCacheSetAction object.
31: *
32: * @param string $strKey the key to use for the object
33: * @param object $objValue the object to put in the cache
34: */
35: public function __construct($strKey, $objValue) {
36: $this->strKey = $strKey;
37: $this->objValue = $objValue;
38: }
39:
40: /**
41: * Executes action on the given cache object
42: * @param QAbstractCacheProvider $objCache The cache object to apply this action to.
43: */
44: public function Execute(QAbstractCacheProvider $objCache) {
45: $objCache->Set($this->strKey, $this->objValue);
46: }
47: }
48: /**
49: * The Delete cache action to be queued and replayed.
50: */
51: class QCacheDeleteAction implements ICacheAction {
52: /**
53: * @var string the key to use for the object
54: */
55: protected $strKey;
56:
57: /**
58: * Construct the new QCacheDeleteAction object.
59: * @param string $strKey the key to use for the object
60: */
61: public function __construct($strKey) {
62: $this->strKey = $strKey;
63: }
64:
65: /**
66: * Executes action on the given cache object
67: * @param QAbstractCacheProvider $objCache The cache object to apply this action to.
68: */
69: public function Execute(QAbstractCacheProvider $objCache) {
70: $objCache->Delete($this->strKey);
71: }
72: }
73: /**
74: * The DeleteAll cache action to be queued and replayed.
75: */
76: class QCacheDeleteAllAction implements ICacheAction {
77: /**
78: * Construct the new QCacheDeleteAction object.
79: */
80: public function __construct() {
81: }
82:
83: /**
84: * Executes action on the given cache object
85: * @param QAbstractCacheProvider $objCache The cache object to apply this action to.
86: */
87: public function Execute(QAbstractCacheProvider $objCache) {
88: $objCache->DeleteAll();
89: }
90: }
91: /**
92: * Cache provider that records all additions and removals from the cache,
93: * and provides an interface to replay them on another instance of an QAbstractCacheProvider
94: */
95: class QCacheProviderProxy extends QAbstractCacheProvider {
96: /**
97: * @var array Additions to cache
98: */
99: protected $arrLocalCacheAdditions;
100:
101: /**
102: * @var array Removals from cache
103: */
104: protected $arrLocalCacheRemovals;
105:
106: /**
107: * @var QAbstractCacheProvider The super cache to query values from.
108: */
109: protected $objSuperCache;
110:
111: /**
112: * @var ICacheAction[] The queue of actions performed on this cache.
113: */
114: protected $objCacheActionQueue;
115:
116: /**
117: * @param QAbstractCacheProvider $objSuperCache The super cache to query values from.
118: */
119: public function __construct($objSuperCache) {
120: $this->objSuperCache = $objSuperCache;
121: $this->objCacheActionQueue = array();
122: $this->arrLocalCacheAdditions = array();
123: $this->arrLocalCacheRemovals = array();
124: }
125:
126: /**
127: * Apply changes to the cache object supplyed.
128: *
129: * @param QAbstractCacheProvider $objAbstractCacheProvider The cache object to apply changes.
130: */
131: public function Replay($objAbstractCacheProvider) {
132: foreach ($this->objCacheActionQueue as $objCacheAction) {
133: $objCacheAction->Execute($objAbstractCacheProvider);
134: }
135: $this->objCacheActionQueue = array();
136: }
137:
138: public function Get($strKey) {
139: if (isset($this->arrLocalCacheAdditions[$strKey])) {
140: return $this->arrLocalCacheAdditions[$strKey];
141: }
142: if (!isset($this->arrLocalCacheRemovals[$strKey])) {
143: if ($this->objSuperCache) {
144: return $this->objSuperCache->Get($strKey);
145: }
146: }
147: return false;
148: }
149:
150: public function Set($strKey, $objValue) {
151: $this->arrLocalCacheAdditions[$strKey] = $objValue;
152: if (isset($this->arrLocalCacheRemovals[$strKey])) {
153: unset($this->arrLocalCacheRemovals[$strKey]);
154: }
155: $this->objCacheActionQueue[] = new QCacheSetAction($strKey, $objValue);
156: }
157:
158: public function Delete($strKey) {
159: if (isset($this->arrLocalCacheAdditions[$strKey])) {
160: unset($this->arrLocalCacheAdditions[$strKey]);
161: }
162: $this->arrLocalCacheRemovals[$strKey] = true;
163: $this->objCacheActionQueue[] = new QCacheDeleteAction($strKey);
164: }
165:
166: public function DeleteAll() {
167: $this->arrLocalCacheAdditions = array();
168: $this->arrLocalCacheRemovals = array();
169: $this->objCacheActionQueue[] = new QCacheDeleteAllAction;
170: }
171: }
172:
173: /**
174: * Every database adapter must implement the following 5 classes (all of which are abstract):
175: * * DatabaseBase
176: * * DatabaseFieldBase
177: * * DatabaseResultBase
178: * * DatabaseRowBase
179: * * DatabaseExceptionBase
180: * This Database library also has the following classes already defined, and
181: * Database adapters are assumed to use them internally:
182: * * DatabaseIndex
183: * * DatabaseForeignKey
184: * * DatabaseFieldType (which is an abstract class that solely contains constants)
185: *
186: * @property-read string $EscapeIdentifierBegin
187: * @property-read string $EscapeIdentifierEnd
188: * @property-read boolean $EnableProfiling
189: * @property-read int $AffectedRows
190: * @property-read string $Profile
191: * @property-read int $DatabaseIndex
192: * @property-read int $Adapter
193: * @property-read string $Server
194: * @property-read string $Port
195: * @property-read string $Database
196: * @property-read string $Service
197: * @property-read string $Protocol
198: * @property-read string $Host
199: * @property-read string $Username
200: * @property-read string $Password
201: * @property boolean $Caching if true objects loaded from this database will be kept in cache (assuming a cache provider is also configured)
202: * @property-read string $DateFormat
203: * @property-read boolean $OnlyFullGroupBy database adapter sub-classes can override and set this property to true
204: * to prevent the behavior of automatically adding all the columns to the select clause when the query has
205: * an aggregation clause.
206: * @package DatabaseAdapters
207: */
208: abstract class QDatabaseBase extends QBaseClass {
209: // Must be updated for all Adapters
210: /** Adapter name */
211: const Adapter = 'Generic Database Adapter (Abstract)';
212:
213: // Protected Member Variables for ALL Database Adapters
214: /** @var int Database Index according to the configuration file */
215: protected $intDatabaseIndex;
216: /** @var bool Has the profiling been enabled? */
217: protected $blnEnableProfiling;
218: protected $strProfileArray;
219:
220: protected $objConfigArray;
221: protected $blnConnectedFlag = false;
222:
223: /** @var string The beginning part of characters which can escape identifiers in a SQL query for the database */
224: protected $strEscapeIdentifierBegin = '"';
225: /** @var string The ending part of characters which can escape identifiers in a SQL query for the database */
226: protected $strEscapeIdentifierEnd = '"';
227: protected $blnOnlyFullGroupBy = false; // should be set in sub-classes as appropriate
228:
229: /**
230: * @var int The transaction depth value.
231: * It is incremented on a transaction begin,
232: * decremented on a transaction commit, and reset to zero on a roll back.
233: * It is used to implement the recursive transaction functionality.
234: */
235: protected $intTransactionDepth = 0;
236:
237: /**
238: * @var QStack The stack of cache providers.
239: * It is populated with cache providers from different databases,
240: * if there are transaction of one DB in the middle of the transaction of another DB.
241: */
242: protected static $objCacheProviderStack;
243:
244: // Abstract Methods that ALL Database Adapters MUST implement
245: /**
246: * Connects to the database
247: */
248: abstract public function Connect();
249: // these are protected - externally, the "Query/NonQuery" wrappers are meant to be called
250: /**
251: * Sends a SQL query for execution to the database
252: * In this regard, a query is a 'SELECT' statement
253: *
254: * @param string $strQuery The Query to be executed
255: *
256: * @return mixed Result that the database returns after running the query.
257: */
258: abstract protected function ExecuteQuery($strQuery);
259:
260: /**
261: * Sends a non-SELECT query (such as INSERT, UPDATE, DELETE, TRUNCATE) to DB server.
262: * In most cases, the results of this function are not used and you should not send
263: * 'SELECT' queries using this method because a result is not guaranteed to be returned
264: *
265: * If there was an error, it would most probably be caught as an exception.
266: *
267: * @param string $strNonQuery The Query to be executed
268: *
269: * @return mixed Result that the database returns after running the query
270: */
271: abstract protected function ExecuteNonQuery($strNonQuery);
272:
273: /**
274: * Returns the list of tables in the database (as string)
275: *
276: * @return mixed|string[] List of tables
277: */
278: abstract public function GetTables();
279:
280: /**
281: * Returns the ID to be inserted in a table column (normally it an autoincrement column)
282: *
283: * @param null|string $strTableName Table name where the ID has to be inserted
284: * @param null|string $strColumnName Column name where the ID has to be inserted
285: *
286: * @return mixed
287: */
288: abstract public function InsertId($strTableName = null, $strColumnName = null);
289:
290: /**
291: * Get the list of columns/fields for a given table
292: *
293: * @param string $strTableName Name of table whose fields we have to get
294: *
295: * @return mixed
296: */
297: abstract public function GetFieldsForTable($strTableName);
298:
299: /**
300: * Get list of indexes for a table
301: *
302: * @param string $strTableName Name of table whose column indexes we have to get
303: *
304: * @return mixed
305: */
306: abstract public function GetIndexesForTable($strTableName);
307:
308: /**
309: * Get list of foreign keys for a table
310: *
311: * @param string $strTableName Name of table whose foreign keys we are trying to get
312: *
313: * @return mixed
314: */
315: abstract public function GetForeignKeysForTable($strTableName);
316:
317: /**
318: * This function actually begins the database transaction.
319: * Must be implemented in all subclasses.
320: * The "TransactionBegin" wrapper are meant to be called by end-user code
321: *
322: * @return void Nothing
323: */
324: abstract protected function ExecuteTransactionBegin();
325:
326: /**
327: * This function actually commits the database transaction.
328: * Must be implemented in all subclasses.
329: * The "TransactionCommit" wrapper are meant to be called by end-user code
330: * @return void Nothing
331: */
332: abstract protected function ExecuteTransactionCommit();
333:
334: /**
335: * This function actually rolls back the database transaction.
336: * Must be implemented in all subclasses.
337: * The "TransactionRollBack" wrapper are meant to be called by end-user code
338: *
339: * @return void Nothing
340: */
341: abstract protected function ExecuteTransactionRollBack();
342:
343: /**
344: * Template for executing stored procedures. Optional, for those database drivers that support it.
345: * @param $strProcName
346: * @param null $params
347: * @return mixed
348: */
349: public function ExecuteProcedure($strProcName, $params = null) {}
350:
351: /**
352: * This function begins the database transaction.
353: *
354: * @return void Nothing
355: */
356: public final function TransactionBegin() {
357: if (0 == $this->intTransactionDepth) {
358: $this->ExecuteTransactionBegin();
359: $objCacheProvider = QApplication::$objCacheProvider;
360: if ($objCacheProvider && $this->Caching && !($objCacheProvider instanceof QCacheProviderNoCache)) {
361: if (!self::$objCacheProviderStack) {
362: self::$objCacheProviderStack = new QStack;
363: }
364: self::$objCacheProviderStack->Push($objCacheProvider);
365: QApplication::$objCacheProvider = new QCacheProviderProxy($objCacheProvider);
366: }
367: }
368: $this->intTransactionDepth++;
369: }
370:
371: /**
372: * This function commits the database transaction.
373: *
374: * @throws QCallerException
375: * @return void Nothing
376: */
377: public final function TransactionCommit() {
378: if (1 == $this->intTransactionDepth) {
379: $this->ExecuteTransactionCommit();
380: $this->transactionCacheFlush();
381: $this->transactionCacheRestore();
382: }
383: if ($this->intTransactionDepth <= 0) {
384: throw new QCallerException("The transaction commit call is called before the transaction begin was called.");
385: }
386: $this->intTransactionDepth--;
387: }
388:
389: /**
390: * This function rolls back the database transaction.
391: *
392: * @return void Nothing
393: */
394: public final function TransactionRollBack() {
395: $this->ExecuteTransactionRollBack();
396: $this->intTransactionDepth = 0;
397: $this->transactionCacheRestore();
398: }
399:
400: /**
401: * Flushes all objects from the local cache to the actual one.
402: */
403: protected final function transactionCacheFlush() {
404: if (!self::$objCacheProviderStack || self::$objCacheProviderStack->IsEmpty()) {
405: return;
406: }
407: $objCacheProvider = self::$objCacheProviderStack->PeekLast();
408: QApplication::$objCacheProvider->Replay($objCacheProvider);
409: }
410:
411: /**
412: * Restores the actual cache to the QApplication variable.
413: */
414: protected final function transactionCacheRestore() {
415: if (!self::$objCacheProviderStack || self::$objCacheProviderStack->IsEmpty()) {
416: return;
417: }
418: $objCacheProvider = self::$objCacheProviderStack->Pop();
419: // restore the actual cache to the QApplication variable.
420: QApplication::$objCacheProvider = $objCacheProvider;
421: }
422:
423: abstract public function SqlLimitVariablePrefix($strLimitInfo);
424: abstract public function SqlLimitVariableSuffix($strLimitInfo);
425: abstract public function SqlSortByVariable($strSortByInfo);
426:
427: /**
428: * Closes the database connection
429: *
430: * @return mixed
431: */
432: abstract public function Close();
433:
434: /**
435: * Given an identifier for a SQL query, this method returns the escaped identifier
436: *
437: * @param string $strIdentifier Identifier to be escaped
438: *
439: * @return string Escaped identifier string
440: */
441: public function EscapeIdentifier($strIdentifier) {
442: return $this->strEscapeIdentifierBegin . $strIdentifier . $this->strEscapeIdentifierEnd;
443: }
444:
445: /**
446: * Given an array of identifiers, this method returns array of escaped identifiers
447: * For corner case handling, if a single identifier is supplied, a single escaped identifier is returned
448: *
449: * @param array|string $mixIdentifiers Array of escaped identifiers (array) or one unescaped identifier (string)
450: *
451: * @return array|string Array of escaped identifiers (array) or one escaped identifier (string)
452: */
453: public function EscapeIdentifiers($mixIdentifiers) {
454: if (is_array($mixIdentifiers)) {
455: return array_map(array($this, 'EscapeIdentifier'), $mixIdentifiers);
456: } else {
457: return $this->EscapeIdentifier($mixIdentifiers);
458: }
459: }
460:
461: /**
462: * Escapes values (or single value) which we can then send to the database
463: *
464: * @param array|mixed $mixValues Array of values (or a single value) to be escaped
465: *
466: * @return array|string Array of (or a single) escaped value(s)
467: */
468: public function EscapeValues($mixValues) {
469: if (is_array($mixValues)) {
470: return array_map(array($this, 'SqlVariable'), $mixValues);
471: } else {
472: return $this->SqlVariable($mixValues);
473: }
474: }
475:
476: /**
477: * Escapes both column and values when supplied as an array
478: *
479: * @param array $mixColumnsAndValuesArray Array with column=>value format with both (column and value) sides unescaped
480: *
481: * @return array Array with column=>value format data with both column and value escaped
482: */
483: public function EscapeIdentifiersAndValues($mixColumnsAndValuesArray) {
484: $result = array();
485: foreach ($mixColumnsAndValuesArray as $strColumn => $mixValue) {
486: $result[$this->EscapeIdentifier($strColumn)] = $this->SqlVariable($mixValue);
487: }
488: return $result;
489: }
490:
491: /**
492: * INSERTs or UPDATEs a table
493: *
494: * @param string $strTable Table name
495: * @param array $mixColumnsAndValuesArray column=>value array
496: * (they are given to 'EscapeIdentifiersAndValues' method)
497: * @param null|string|array $strPKNames Name(s) of primary key column(s) (expressed as string or array)
498: */
499: public function InsertOrUpdate($strTable, $mixColumnsAndValuesArray, $strPKNames = null) {
500: $strEscapedArray = $this->EscapeIdentifiersAndValues($mixColumnsAndValuesArray);
501: $strColumns = array_keys($strEscapedArray);
502: $strUpdateStatement = '';
503: foreach ($strEscapedArray as $strColumn => $strValue) {
504: if ($strUpdateStatement) $strUpdateStatement .= ', ';
505: $strUpdateStatement .= $strColumn . ' = ' . $strValue;
506: }
507: if (is_null($strPKNames)) {
508: $strMatchCondition = 'target_.'.$strColumns[0].' = source_.'.$strColumns[0];
509: } else if (is_array($strPKNames)) {
510: $strMatchCondition = '';
511: foreach ($strPKNames as $strPKName) {
512: if ($strMatchCondition) $strMatchCondition .= ' AND ';
513: $strMatchCondition .= 'target_.'.$this->EscapeIdentifier($strPKName).' = source_.'.$this->EscapeIdentifier($strPKName);
514: }
515: } else {
516: $strMatchCondition = 'target_.'.$this->EscapeIdentifier($strPKNames).' = source_.'.$this->EscapeIdentifier($strPKNames);
517: }
518: $strTable = $this->EscapeIdentifierBegin . $strTable . $this->EscapeIdentifierEnd;
519: $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)',
520: $strTable, $strTable,
521: $strMatchCondition, $strUpdateStatement,
522: implode(', ', $strColumns),
523: implode(', ', array_values($strEscapedArray))
524: );
525: $this->ExecuteNonQuery($strSql);
526: }
527:
528: /**
529: * Sends the 'SELECT' query to the database and returns the result
530: *
531: * @param string $strQuery query string
532: *
533: * @return QDatabaseResultBase
534: */
535: public final function Query($strQuery) {
536: $timerName = null;
537: if (!$this->blnConnectedFlag) {
538: $this->Connect();
539: }
540:
541:
542: if ($this->blnEnableProfiling) {
543: $timerName = 'queryExec' . mt_rand() ;
544: QTimer::Start($timerName);
545: }
546:
547: $result = $this->ExecuteQuery($strQuery);
548:
549: if ($this->blnEnableProfiling) {
550: $dblQueryTime = QTimer::Stop($timerName);
551: QTimer::Reset($timerName);
552:
553: // Log Query (for Profiling, if applicable)
554: $this->LogQuery($strQuery, $dblQueryTime);
555: }
556:
557: return $result;
558: }
559:
560: /**
561: * This is basically the same as 'Query' but is used when SQL statements other than 'SELECT'
562: * @param string $strNonQuery The SQL to be sent
563: *
564: * @return mixed
565: * @throws QCallerException
566: */
567: public final function NonQuery($strNonQuery) {
568: if (!$this->blnConnectedFlag) {
569: $this->Connect();
570: }
571: $timerName = '';
572: if ($this->blnEnableProfiling) {
573: $timerName = 'queryExec' . mt_rand() ;
574: QTimer::Start($timerName);
575: }
576:
577: $result = $this->ExecuteNonQuery($strNonQuery);
578:
579: if ($this->blnEnableProfiling) {
580: $dblQueryTime = QTimer::Stop($timerName);
581: QTimer::Reset($timerName);
582:
583: // Log Query (for Profiling, if applicable)
584: $this->LogQuery($strNonQuery, $dblQueryTime);
585: }
586:
587: return $result;
588: }
589:
590: /**
591: * PHP magic method
592: * @param string $strName Property name
593: *
594: * @return mixed
595: * @throws Exception|QCallerException
596: */
597: public function __get($strName) {
598: switch ($strName) {
599: case 'EscapeIdentifierBegin':
600: return $this->strEscapeIdentifierBegin;
601: case 'EscapeIdentifierEnd':
602: return $this->strEscapeIdentifierEnd;
603: case 'EnableProfiling':
604: return $this->blnEnableProfiling;
605: case 'AffectedRows':
606: return -1;
607: case 'Profile':
608: return $this->strProfileArray;
609: case 'DatabaseIndex':
610: return $this->intDatabaseIndex;
611: case 'Adapter':
612: $strConstantName = get_class($this) . '::Adapter';
613: return constant($strConstantName) . ' (' . $this->objConfigArray['adapter'] . ')';
614: case 'Server':
615: case 'Port':
616: case 'Database':
617: // Informix naming
618: case 'Service':
619: case 'Protocol':
620: case 'Host':
621:
622: case 'Username':
623: case 'Password':
624: case 'Caching':
625: return $this->objConfigArray[strtolower($strName)];
626: case 'DateFormat':
627: return (is_null($this->objConfigArray[strtolower($strName)])) ? (QDateTime::FormatIso) : ($this->objConfigArray[strtolower($strName)]);
628: case 'OnlyFullGroupBy':
629: return (!isset($this->objConfigArray[strtolower($strName)])) ? $this->blnOnlyFullGroupBy : $this->objConfigArray[strtolower($strName)];
630:
631: default:
632: try {
633: return parent::__get($strName);
634: } catch (QCallerException $objExc) {
635: $objExc->IncrementOffset();
636: throw $objExc;
637: }
638: }
639: }
640:
641: /**
642: * PHP magic method to set class properties
643: * @param string $strName Property name
644: * @param string $mixValue Property value
645: *
646: * @return mixed|void
647: * @throws Exception|QCallerException
648: */
649: public function __set($strName, $mixValue) {
650: switch ($strName) {
651: case 'Caching':
652: $this->objConfigArray[strtolower($strName)] = $mixValue;
653: break;
654:
655: default:
656: try {
657: parent::__set($strName, $mixValue);
658: } catch (QCallerException $objExc) {
659: $objExc->IncrementOffset();
660: throw $objExc;
661: }
662: }
663: }
664:
665: /**
666: * Constructs a Database Adapter based on the database index and the configuration array of properties
667: * for this particular adapter. Sets up the base-level configuration properties for this database,
668: * namely DB Profiling and Database Index
669: *
670: * @param integer $intDatabaseIndex
671: * @param string[] $objConfigArray configuration array as passed in to the constructor
672: * by QApplicationBase::InitializeDatabaseConnections();
673: *
674: * @throws Exception|QCallerException|QInvalidCastException
675: * @return QDatabaseBase
676: */
677: public function __construct($intDatabaseIndex, $objConfigArray) {
678: // Setup DatabaseIndex
679: $this->intDatabaseIndex = $intDatabaseIndex;
680:
681: // Save the ConfigArray
682: $this->objConfigArray = $objConfigArray;
683:
684: // Setup Profiling Array (if applicable)
685: $this->blnEnableProfiling = QType::Cast($objConfigArray['profiling'], QType::Boolean);
686: if ($this->blnEnableProfiling)
687: $this->strProfileArray = array();
688: }
689:
690: /**
691: * Allows for the enabling of DB profiling while in middle of the script
692: *
693: * @return void
694: */
695: public function EnableProfiling() {
696: // Only perform profiling initialization if profiling is not yet enabled
697: if (!$this->blnEnableProfiling) {
698: $this->blnEnableProfiling = true;
699: $this->strProfileArray = array();
700: }
701: }
702:
703: /**
704: * If EnableProfiling is on, then log the query to the profile array
705: *
706: * @param string $strQuery
707: * @param double $dblQueryTime query execution time in milliseconds
708: * @return void
709: */
710: private function LogQuery($strQuery, $dblQueryTime) {
711: if ($this->blnEnableProfiling) {
712: // Dereference-ize Backtrace Information
713: $objDebugBacktrace = debug_backtrace();
714:
715: // get rid of unnecessary backtrace info in case of:
716: // query
717: if ((count($objDebugBacktrace) > 3) &&
718: (array_key_exists('function', $objDebugBacktrace[2])) &&
719: (($objDebugBacktrace[2]['function'] == 'QueryArray') ||
720: ($objDebugBacktrace[2]['function'] == 'QuerySingle') ||
721: ($objDebugBacktrace[2]['function'] == 'QueryCount')))
722: $objBacktrace = $objDebugBacktrace[3];
723: else
724: if (isset($objDebugBacktrace[2]))
725: // non query
726: $objBacktrace = $objDebugBacktrace[2];
727: else
728: // ad hoc query
729: $objBacktrace = $objDebugBacktrace[1];
730:
731: // get rid of reference to current object in backtrace array
732: if( isset($objBacktrace['object']))
733: $objBacktrace['object'] = null;
734:
735: for ($intIndex = 0, $intMax = count($objBacktrace['args']); $intIndex < $intMax; $intIndex++) {
736: $obj = $objBacktrace['args'][$intIndex];
737: if (($obj instanceof QQClause) || ($obj instanceof QQCondition))
738: $obj = sprintf("[%s]", $obj->__toString());
739: else if (is_null($obj))
740: $obj = 'null';
741: else if (gettype($obj) == 'integer') {}
742: else if (gettype($obj) == 'object')
743: $obj = 'Object';
744: else if (is_array($obj))
745: $obj = 'Array';
746: else
747: $obj = sprintf("'%s'", $obj);
748: $objBacktrace['args'][$intIndex] = $obj;
749: }
750:
751: // Push it onto the profiling information array
752: $arrProfile = array(
753: 'objBacktrace' => $objBacktrace,
754: 'strQuery' => $strQuery,
755: 'dblTimeInfo' => $dblQueryTime);
756:
757: array_push( $this->strProfileArray, $arrProfile);
758: }
759: }
760:
761: /**
762: * Properly escapes $mixData to be used as a SQL query parameter.
763: * If IncludeEquality is set (usually not), then include an equality operator.
764: * So for most data, it would just be "=". But, for example,
765: * if $mixData is NULL, then most RDBMS's require the use of "IS".
766: *
767: * @param mixed $mixData
768: * @param boolean $blnIncludeEquality whether or not to include an equality operator
769: * @param boolean $blnReverseEquality whether the included equality operator should be a "NOT EQUAL", e.g. "!="
770: * @return string the properly formatted SQL variable
771: */
772: public function SqlVariable($mixData, $blnIncludeEquality = false, $blnReverseEquality = false) {
773: // Are we SqlVariabling a BOOLEAN value?
774: if (is_bool($mixData)) {
775: // Yes
776: if ($blnIncludeEquality) {
777: // We must include the inequality
778:
779: if ($blnReverseEquality) {
780: // Do a "Reverse Equality"
781:
782: // Check against NULL, True then False
783: if (is_null($mixData))
784: return 'IS NOT NULL';
785: else if ($mixData)
786: return '= 0';
787: else
788: return '!= 0';
789: } else {
790: // Check against NULL, True then False
791: if (is_null($mixData))
792: return 'IS NULL';
793: else if ($mixData)
794: return '!= 0';
795: else
796: return '= 0';
797: }
798: } else {
799: // Check against NULL, True then False
800: if (is_null($mixData))
801: return 'NULL';
802: else if ($mixData)
803: return '1';
804: else
805: return '0';
806: }
807: }
808:
809: // Check for Equality Inclusion
810: if ($blnIncludeEquality) {
811: if ($blnReverseEquality) {
812: if (is_null($mixData))
813: $strToReturn = 'IS NOT ';
814: else
815: $strToReturn = '!= ';
816: } else {
817: if (is_null($mixData))
818: $strToReturn = 'IS ';
819: else
820: $strToReturn = '= ';
821: }
822: } else
823: $strToReturn = '';
824:
825: // Check for NULL Value
826: if (is_null($mixData))
827: return $strToReturn . 'NULL';
828:
829: // Check for NUMERIC Value
830: if (is_integer($mixData) || is_float($mixData))
831: return $strToReturn . sprintf('%s', $mixData);
832:
833: // Check for DATE Value
834: if ($mixData instanceof QDateTime) {
835: /** @var QDateTime $mixData */
836: if ($mixData->IsTimeNull()) {
837: if ($mixData->IsDateNull()) {
838: return $strToReturn . 'NULL'; // null date and time is a null value
839: }
840: return $strToReturn . sprintf("'%s'", $mixData->qFormat('YYYY-MM-DD'));
841: }
842: elseif ($mixData->IsDateNull()) {
843: return $strToReturn . sprintf("'%s'", $mixData->qFormat('hhhh:mm:ss'));
844: }
845: return $strToReturn . sprintf("'%s'", $mixData->qFormat(QDateTime::FormatIso));
846: }
847:
848: // an array. Assume we are using it in an array context, like an IN clause
849: if (is_array($mixData)) {
850: $items = [];
851: foreach ($mixData as $item) {
852: $items[] = $this->SqlVariable($item); // recurse
853: }
854: return '(' . implode(',', $items) . ')';
855: }
856:
857: // Assume it's some kind of string value
858: return $strToReturn . sprintf("'%s'", addslashes($mixData));
859: }
860:
861: public function PrepareStatement($strQuery, $mixParameterArray) {
862: foreach ($mixParameterArray as $strKey => $mixValue) {
863: if (is_array($mixValue)) {
864: $strParameters = array();
865: foreach ($mixValue as $mixParameter)
866: array_push($strParameters, $this->SqlVariable($mixParameter));
867: $strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{' . $strKey . '}', implode(',', $strParameters) . ')', $strQuery);
868: } else {
869: $strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{=' . $strKey . '=}', $this->SqlVariable($mixValue, true, false), $strQuery);
870: $strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{!' . $strKey . '!}', $this->SqlVariable($mixValue, true, true), $strQuery);
871: $strQuery = str_replace(chr(QQNamedValue::DelimiterCode) . '{' . $strKey . '}', $this->SqlVariable($mixValue), $strQuery);
872: }
873: }
874:
875: return $strQuery;
876: }
877:
878: /**
879: * Displays the OutputProfiling results, plus a link which will popup the details of the profiling.
880: *
881: * @param bool $blnPrintOutput
882: * @return null|string
883: */
884: public function OutputProfiling($blnPrintOutput = true) {
885:
886: $strOut = '<div class="qDbProfile">';
887: if ($this->blnEnableProfiling) {
888: $strOut .= sprintf('<form method="post" id="frmDbProfile%s" action="%s/profile.php"><div>',
889: $this->intDatabaseIndex, __VIRTUAL_DIRECTORY__ . __PHP_ASSETS__);
890: $strOut.= sprintf('<input type="hidden" name="strProfileData" value="%s" />',
891: base64_encode(serialize($this->strProfileArray)));
892: $strOut .= sprintf('<input type="hidden" name="intDatabaseIndex" value="%s" />', $this->intDatabaseIndex);
893: $strOut .= sprintf('<input type="hidden" name="strReferrer" value="%s" /></div></form>', QApplication::HtmlEntities(QApplication::$RequestUri));
894:
895: $intCount = round(count($this->strProfileArray));
896: if ($intCount == 0)
897: $strQueryString = 'No queries';
898: else if ($intCount == 1)
899: $strQueryString = '1 query';
900: else
901: $strQueryString = $intCount . ' queries';
902:
903: $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 />',
904: $this->intDatabaseIndex, $strQueryString, $this->intDatabaseIndex);
905: } else {
906: $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.';
907: }
908: $strOut .= '</div>';
909:
910: $strOut .= '<script>$j(function() {$j(".qDbProfile").draggable();});</script>'; // make it draggable so you can move it out of the way if needed.
911:
912: if ($blnPrintOutput) {
913: print ($strOut);
914: return null;
915: }
916: else {
917: return $strOut;
918: }
919: }
920:
921: /**
922: * Executes the explain statement for a given query and returns the output without any transformation.
923: * If the database adapter does not support EXPLAIN statements, returns null.
924: *
925: * @param $strSql
926: *
927: * @return null
928: */
929: public function ExplainStatement($strSql) {
930: return null;
931: }
932:
933:
934: /**
935: * Utility function to extract the json embedded options structure from the comments.
936: *
937: * Usage:
938: * <code>
939: * list($strComment, $options) = QDatabaseFieldBase::ExtractCommentOptions($strComment);
940: * </code>
941: *
942: * @param string $strComment The comment to analyze
943: * @return array A two item array, with first item the comment with the options removed, and 2nd item the options array.
944: *
945: */
946: public static function ExtractCommentOptions($strComment) {
947: $ret[0] = null; // comment string without options
948: $ret[1] = null; // the options array
949: if (($strComment) &&
950: ($pos1 = strpos ($strComment, '{')) !== false &&
951: ($pos2 = strrpos ($strComment, '}', $pos1))) {
952:
953: $strJson = substr ($strComment, $pos1, $pos2 - $pos1 + 1);
954: $a = json_decode($strJson, true);
955:
956: if ($a) {
957: $ret[0] = substr ($strComment, 0, $pos1) . substr ($strComment, $pos2 + 1); // return comment without options
958: $ret[1] = $a;
959: } else {
960: $ret[0] = $strComment;
961: }
962: }
963:
964: return $ret;
965: }
966:
967: }
968:
969: abstract class QDatabaseFieldBase extends QBaseClass {
970: protected $strName;
971: protected $strOriginalName;
972: protected $strTable;
973: protected $strOriginalTable;
974: protected $strDefault;
975: protected $intMaxLength;
976: protected $strComment;
977:
978: // Bool
979: protected $blnIdentity;
980: protected $blnNotNull;
981: protected $blnPrimaryKey;
982: protected $blnUnique;
983: protected $blnTimestamp;
984:
985: protected $strType;
986:
987: /**
988: * PHP magic method
989: *
990: * @param string $strName Property name
991: *
992: * @return mixed
993: * @throws Exception|QCallerException
994: */
995: public function __get($strName) {
996: switch ($strName) {
997: case "Name":
998: return $this->strName;
999: case "OriginalName":
1000: return $this->strOriginalName;
1001: case "Table":
1002: return $this->strTable;
1003: case "OriginalTable":
1004: return $this->strOriginalTable;
1005: case "Default":
1006: return $this->strDefault;
1007: case "MaxLength":
1008: return $this->intMaxLength;
1009: case "Identity":
1010: return $this->blnIdentity;
1011: case "NotNull":
1012: return $this->blnNotNull;
1013: case "PrimaryKey":
1014: return $this->blnPrimaryKey;
1015: case "Unique":
1016: return $this->blnUnique;
1017: case "Timestamp":
1018: return $this->blnTimestamp;
1019: case "Type":
1020: return $this->strType;
1021: case "Comment":
1022: return $this->strComment;
1023: default:
1024: try {
1025: return parent::__get($strName);
1026: } catch (QCallerException $objExc) {
1027: $objExc->IncrementOffset();
1028: throw $objExc;
1029: }
1030: }
1031: }
1032: }
1033:
1034: /**
1035: * Class to handle results sent by database upon querying
1036: * @property QQueryBuilder $QueryBuilder
1037: */
1038: abstract class QDatabaseResultBase extends QBaseClass {
1039: // Allow to attach a QQueryBuilder object to use the result object as cursor resource for cursor queries.
1040: /** @var QQueryBuilder Query builder object */
1041: protected $objQueryBuilder;
1042:
1043: /**
1044: * Fetches one row as indexed (column=>value style) array from the result set
1045: * @abstract
1046: * @return mixed
1047: */
1048: abstract public function FetchArray();
1049:
1050: /**
1051: * Fetches one row as enumerated (with numerical indexes) array from the result set
1052: * @abstract
1053: * @return mixed
1054: */
1055: abstract public function FetchRow();
1056:
1057: abstract public function FetchField();
1058: abstract public function FetchFields();
1059: abstract public function CountRows();
1060: abstract public function CountFields();
1061:
1062: abstract public function GetNextRow();
1063: abstract public function GetRows();
1064:
1065: abstract public function Close();
1066:
1067: /**
1068: * PHP magic method
1069: *
1070: * @param string $strName Property name
1071: *
1072: * @return mixed
1073: * @throws Exception|QCallerException
1074: */
1075: public function __get($strName) {
1076: switch ($strName) {
1077: case 'QueryBuilder':
1078: return $this->objQueryBuilder;
1079: default:
1080: try {
1081: return parent::__get($strName);
1082: } catch (QCallerException $objExc) {
1083: $objExc->IncrementOffset();
1084: throw $objExc;
1085: }
1086: }
1087: }
1088:
1089: public function __set($strName, $mixValue) {
1090: switch ($strName) {
1091: case 'QueryBuilder':
1092: try {
1093: return ($this->objQueryBuilder = QType::Cast($mixValue, 'QQueryBuilder'));
1094: } catch (QInvalidCastException $objExc) {
1095: $objExc->IncrementOffset();
1096: throw $objExc;
1097: }
1098: default:
1099: try {
1100: return parent::__set($strName, $mixValue);
1101: } catch (QCallerException $objExc) {
1102: $objExc->IncrementOffset();
1103: throw $objExc;
1104: }
1105: }
1106: }
1107: }
1108:
1109: /**
1110: * Base class for all Database rows. Implemented by Database adapters
1111: * @package DatabaseAdapters
1112: */
1113: abstract class QDatabaseRowBase extends QBaseClass {
1114: /**
1115: * Gets the value of a column from a result row returned by the database
1116: *
1117: * @param string $strColumnName Name of the column
1118: * @param null|string $strColumnType Data type
1119: *
1120: * @return mixed
1121: */
1122: abstract public function GetColumn($strColumnName, $strColumnType = null);
1123: /**
1124: * Tells whether a particular column exists in a returned database row
1125: *
1126: * @param string $strColumnName Name of te column
1127: *
1128: * @return bool
1129: */
1130: abstract public function ColumnExists($strColumnName);
1131: abstract public function GetColumnNameArray();
1132:
1133: /**
1134: * Returns the boolean value corresponding to whatever a boolean column returns. Some database types
1135: * return strings that represent the boolean values. Default is to use a PHP cast.
1136: * @param $mixValue Value of the BIT column
1137: * @return bool
1138: */
1139: public function ResolveBooleanValue ($mixValue) {
1140: if ($mixValue === null) {
1141: return null;
1142: }
1143: return ((bool)$mixValue);
1144: }
1145:
1146: }
1147:
1148: /**
1149: * Class to handle exceptions related to database querying
1150: * @property-read int $ErrorNumber The number of error provided by the SQL server
1151: * @property-read string $Query The query caused the error
1152: * @package DatabaseAdapters
1153: */
1154: abstract class QDatabaseExceptionBase extends QCallerException {
1155: /** @var int Error number */
1156: protected $intErrorNumber;
1157: /** @var string Query which produced the error */
1158: protected $strQuery;
1159:
1160: /**
1161: * PHP magic function to get property values
1162: * @param string $strName
1163: *
1164: * @return array|int|mixed
1165: */
1166: public function __get($strName) {
1167: switch ($strName) {
1168: case "ErrorNumber":
1169: return $this->intErrorNumber;
1170: case "Query";
1171: return $this->strQuery;
1172: default:
1173: return parent::__get($strName);
1174: }
1175: }
1176: }
1177:
1178: /**
1179: *
1180: * @package DatabaseAdapters
1181: */
1182: class QDatabaseForeignKey extends QBaseClass {
1183: protected $strKeyName;
1184: protected $strColumnNameArray;
1185: protected $strReferenceTableName;
1186: protected $strReferenceColumnNameArray;
1187:
1188: public function __construct($strKeyName, $strColumnNameArray, $strReferenceTableName, $strReferenceColumnNameArray) {
1189: $this->strKeyName = $strKeyName;
1190: $this->strColumnNameArray = $strColumnNameArray;
1191: $this->strReferenceTableName = $strReferenceTableName;
1192: $this->strReferenceColumnNameArray = $strReferenceColumnNameArray;
1193: }
1194:
1195: /**
1196: * PHP magic method
1197: *
1198: * @param string $strName Property name
1199: *
1200: * @return mixed
1201: * @throws Exception|QCallerException
1202: */
1203: public function __get($strName) {
1204: switch ($strName) {
1205: case "KeyName":
1206: return $this->strKeyName;
1207: case "ColumnNameArray":
1208: return $this->strColumnNameArray;
1209: case "ReferenceTableName":
1210: return $this->strReferenceTableName;
1211: case "ReferenceColumnNameArray":
1212: return $this->strReferenceColumnNameArray;
1213: default:
1214: try {
1215: return parent::__get($strName);
1216: } catch (QCallerException $objExc) {
1217: $objExc->IncrementOffset();
1218: throw $objExc;
1219: }
1220: }
1221: }
1222: }
1223:
1224: /**
1225: * To handle index in a table in database
1226: * @package DatabaseAdapters
1227: */
1228: class QDatabaseIndex extends QBaseClass {
1229: /** @var string Name of the index */
1230: protected $strKeyName;
1231: /** @var bool Is the Index a primary key index? */
1232: protected $blnPrimaryKey;
1233: /** @var bool Is this a Unique index? */
1234: protected $blnUnique;
1235: /** @var array Array of column names on which this index is defined */
1236: protected $strColumnNameArray;
1237:
1238: /**
1239: * @param string $strKeyName Name of the index
1240: * @param string $blnPrimaryKey Is this index a Primary key index?
1241: * @param string $blnUnique Is this index unique?
1242: * @param array $strColumnNameArray Columns on which this index is defined
1243: */
1244: public function __construct($strKeyName, $blnPrimaryKey, $blnUnique, $strColumnNameArray) {
1245: $this->strKeyName = $strKeyName;
1246: $this->blnPrimaryKey = $blnPrimaryKey;
1247: $this->blnUnique = $blnUnique;
1248: $this->strColumnNameArray = $strColumnNameArray;
1249: }
1250:
1251: /**
1252: * PHP magic function
1253: * @param string $strName
1254: *
1255: * @return mixed
1256: * @throws Exception|QCallerException
1257: */
1258: public function __get($strName) {
1259: switch ($strName) {
1260: case "KeyName":
1261: return $this->strKeyName;
1262: case "PrimaryKey":
1263: return $this->blnPrimaryKey;
1264: case "Unique":
1265: return $this->blnUnique;
1266: case "ColumnNameArray":
1267: return $this->strColumnNameArray;
1268: default:
1269: try {
1270: return parent::__get($strName);
1271: } catch (QCallerException $objExc) {
1272: $objExc->IncrementOffset();
1273: throw $objExc;
1274: }
1275: }
1276: }
1277: }
1278:
1279: /**
1280: * Data types in a database
1281: * @package DatabaseAdapters
1282: */
1283: abstract class QDatabaseFieldType {
1284: /** Binary Data (Binary Large OBjects/BLOBs) */
1285: const Blob = "Blob";
1286: /** Character sequence - variable length */
1287: const VarChar = "VarChar";
1288: /** Character sequence - fixed length */
1289: const Char = "Char";
1290: /** Integers */
1291: const Integer = "Integer";
1292: /** Date and Time together */
1293: const DateTime = "DateTime";
1294: /** Date only */
1295: const Date = "Date";
1296: /** Time only */
1297: const Time = "Time";
1298: /** Float, Double and real (postgresql) */
1299: const Float = "Float";
1300: /** Boolean */
1301: const Bit = "Bit";
1302: /** New JSON type */
1303: const Json = "Json";
1304: }