PageRenderTime 67ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/library/database/class.sqldriver.php

https://github.com/champx/Garden
PHP | 1947 lines | 895 code | 267 blank | 785 comment | 237 complexity | debba88b58fb4cb44a5a9449676d8621 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php if (!defined('APPLICATION')) exit();
  2. /*
  3. Copyright 2008, 2009 Vanilla Forums Inc.
  4. This file is part of Garden.
  5. Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  6. Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  7. You should have received a copy of the GNU General Public License along with Garden. If not, see <http://www.gnu.org/licenses/>.
  8. Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
  9. */
  10. /**
  11. * The Gdn_DatabaseDriver class (equivalent to SqlBuilder from Vanilla 1.x) is used
  12. * by any given database driver to build and execute database queries.
  13. *
  14. * This class is HEAVILY inspired by and, in places, flat out copied from
  15. * CodeIgniter (http://www.codeigniter.com). My hat is off to them.
  16. *
  17. * @author Mark O'Sullivan
  18. * @copyright 2003 Mark O'Sullivan
  19. * @license http://www.opensource.org/licenses/gpl-2.0.php GPL
  20. * @package Garden
  21. * @version @@GARDEN-VERSION@@
  22. * @namespace Garden.Database
  23. */
  24. require_once(dirname(__FILE__).DS.'class.database.php');
  25. abstract class Gdn_SQLDriver {
  26. /// CONSTRUCTOR ///
  27. public function __construct() {
  28. $this->ClassName = get_class($this);
  29. $this->Reset();
  30. }
  31. /// PROPERTIES ///
  32. /**
  33. * An associative array of table alias => table name pairs.
  34. *
  35. * @var array
  36. */
  37. protected $_AliasMap;
  38. /**
  39. *
  40. * @var bool Whether or not to capture (not execute) DML statements.
  41. */
  42. public $CaptureModifications = FALSE;
  43. /**
  44. * The name of the class that has been instantiated.
  45. *
  46. * @var string
  47. */
  48. public $ClassName;
  49. /**
  50. * The database connection.
  51. *
  52. * @var Gdn_Database The connection and engine information for the database.
  53. */
  54. public $Database;
  55. /**
  56. * An associative array of information about the database to which the
  57. * application is connected. Values include: Engine, Version, DatabaseName.
  58. *
  59. * @var string
  60. */
  61. protected $_DatabaseInfo = array();
  62. /**
  63. * A boolean value indicating if this is a distinct query.
  64. *
  65. * @var boolean
  66. */
  67. protected $_Distinct;
  68. /**
  69. * A collection of tables from which data is being selected.
  70. *
  71. * @var array
  72. */
  73. protected $_Froms;
  74. /**
  75. * A collection of group by clauses.
  76. *
  77. * @var array
  78. */
  79. protected $_GroupBys;
  80. /**
  81. * A collection of having clauses.
  82. *
  83. * @var array
  84. */
  85. protected $_Havings;
  86. /**
  87. * A collection of tables which have been joined to.
  88. *
  89. * @var array
  90. */
  91. protected $_Joins;
  92. /**
  93. * The number of records to limit the query to. FALSE by default.
  94. *
  95. * @var int
  96. */
  97. protected $_Limit;
  98. /**
  99. * An associative array of parameter_name => parameter_value pairs to be
  100. * inserted into the prepared $this->_PDOStatement.
  101. *
  102. * @var array
  103. */
  104. protected $_NamedParameters = array();
  105. /**
  106. * Whether or not to reset the properties when a query is executed.
  107. *
  108. * @var int
  109. * 0 = The object will reset after query execution.
  110. * 1 = The object will not reset after the <b>NEXT</b> query execution.
  111. * 2 = The object will not reset after <b>ALL</b> query executions.
  112. */
  113. protected $_NoReset = FALSE;
  114. /**
  115. * The offset from which data should be returned. FALSE by default.
  116. *
  117. * @var int
  118. */
  119. protected $_Offset;
  120. /**
  121. * The number of where groups currently open.
  122. *
  123. * @var int
  124. */
  125. protected $_OpenWhereGroupCount;
  126. /**
  127. * A collection of order by statements.
  128. *
  129. * @var array
  130. */
  131. protected $_OrderBys;
  132. /**
  133. * A collection of fields that are being selected.
  134. *
  135. * @var array
  136. */
  137. protected $_Selects;
  138. /**
  139. * An associative array of Field Name => Value pairs to be saved
  140. * to the database.
  141. *
  142. * @var array
  143. */
  144. protected $_Sets;
  145. /**
  146. * The logical operator used to concatenate where clauses.
  147. *
  148. * @var string
  149. */
  150. protected $_WhereConcat;
  151. /**
  152. * The default $_WhereConcat that will be reverted back to after every where clause is appended.
  153. *
  154. * @var string
  155. */
  156. protected $_WhereConcatDefault;
  157. /**
  158. * The number of where groups to open.
  159. *
  160. * @var int
  161. */
  162. protected $_WhereGroupCount;
  163. /**
  164. * A collection of where clauses.
  165. *
  166. * @var array
  167. */
  168. protected $_Wheres;
  169. /// METHODS ///
  170. /**
  171. * Removes table aliases from an array of JOIN ($this->_Joins) and GROUP BY
  172. * ($this->_GroupBys) strings. Returns the $Statements array with prefixes
  173. * removed.
  174. *
  175. * @param array $Statements The string specification of the table. ie.
  176. * "tbl_User as u" or "user u".
  177. * @return array the array of filtered statements.
  178. */
  179. //protected function _FilterTableAliases($Statements) {
  180. // foreach ($Statements as $k => $v) {
  181. // foreach ($this->_AliasMap as $Alias => $Table) {
  182. // $Statement = preg_replace('/(\w+\.\w+)/', $this->EscapeIdentifier('$0'), $v); // Makes `table.field`
  183. // $Statement = str_replace(array($this->Database->DatabasePrefix.$Table, '.'), array($Table, $this->EscapeSql('.')), $Statement);
  184. // }
  185. // $Statements[$k] = $Statement;
  186. // }
  187. // return $Statements;
  188. //}
  189. /**
  190. * Concat the next where expression with an 'and' operator.
  191. * <b>Note</b>: Since 'and' is the default operator to begin with this method doesn't usually have to be called,
  192. * unless Gdn_DatabaseDriver::Or(FALSE) has previously been called.
  193. *
  194. * @param boolean $SetDefault Whether or not the 'and' is one time or sets the default operator.
  195. * @return Gdn_DatabaseDriver $this
  196. * @see Gdn_DatabaseDriver::OrOp()
  197. */
  198. public function AndOp($SetDefault = FALSE) {
  199. $this->_WhereConcat = 'and';
  200. if($SetDefault) {
  201. $this->_WhereConcatDefault = 'and';
  202. }
  203. return $this;
  204. }
  205. public function ApplyParameters($Sql, $Parameters) {
  206. // Sort the parameters so that we don't have clashes.
  207. krsort($Parameters);
  208. foreach ($Parameters as $Key => $Value) {
  209. if (is_null($Value))
  210. $QValue = 'null';
  211. else
  212. $QValue = $this->Database->Connection()->quote($Value);
  213. $Sql = str_replace($Key, $QValue, $Sql);
  214. }
  215. return $Sql;
  216. }
  217. /**
  218. * Begin bracketed group in the where clause to group logical expressions together.
  219. *
  220. * @return Gdn_DatabaseDriver $this
  221. */
  222. public function BeginWhereGroup() {
  223. $this->_WhereGroupCount++;
  224. $this->_OpenWhereGroupCount++;
  225. return $this;
  226. }
  227. /**
  228. * Returns a single Condition Expression for use in a 'where' or an 'on' clause.
  229. *
  230. * @param string $Field The name of the field on the left hand side of the expression.
  231. * If $Field ends with an operator, then it used for the comparison. Otherwise '=' will be used.
  232. * @param mixed $Value The value on the right side of the expression. This has different behaviour depending on the type.
  233. * <b>string</b>: The value will be used. If $EscapeValueSql is true then it will end up in a parameter.
  234. * <b>array</b>: DatabaseFunction => Value will be used. if DatabaseFunction contains a "%s" then sprintf will be used.
  235. * In this case Value will be assumed to be a string.
  236. *
  237. * <b>New Syntax</b>
  238. * The $Field and Value expressions can begin with special characters to do certain things.
  239. * <ul>
  240. * <li><b>=</b>: This means that the argument is a function call.
  241. * If you want to pass field reference arguments into the function then enclose them in square brackets.
  242. * ex. <code>'=LEFT([u.Name], 4)'</code> will call the LEFT database function on the u.Name column.</li>
  243. * <li><b>@</b>: This means that the argument is a literal.
  244. * This is useful for passing in literal numbers.</li>
  245. * <li><b>no prefix></b>: This will treat the argument differently depending on the argument.
  246. * - <b>$Field</b> - The argument is a column reference.
  247. * - <b>$Value</b> - The argument will become a named parameter.
  248. * </li></ul>
  249. * @return string The single expression.
  250. */
  251. public function ConditionExpr($Field, $Value, $EscapeFieldSql = TRUE, $EscapeValueSql = TRUE) {
  252. // Change some variables from the old parameter style to the new one.
  253. // THIS PART OF THE FUNCTION SHOULD EVENTUALLY BE REMOVED.
  254. if($EscapeFieldSql === FALSE) {
  255. $Field = '@' . $Field;
  256. }
  257. if(is_array($Value)) {
  258. $FunctionCall = array_keys($Value);
  259. $FunctionCall = $FunctionCall[0];
  260. $FunctionArg = $Value[$FunctionCall];
  261. if($EscapeValueSql)
  262. $FunctionArg = '[' . $FunctionArg . ']';
  263. if(stripos($FunctionCall, '%s') === FALSE)
  264. $Value = '=' . $FunctionCall . '(' . $FunctionArg . ')';
  265. else
  266. $Value = '=' . sprintf($FunctionCall, $FunctionArg);
  267. $EscapeValueSql = FALSE;
  268. } else if(!$EscapeValueSql && !is_null($Value)) {
  269. $Value = '@' . $Value;
  270. }
  271. // Check for a straight literal field expression.
  272. if(!$EscapeFieldSql && !$EscapeValueSql && is_null($Value))
  273. return substr($Field, 1); // warning: might not be portable across different drivers
  274. $Expr = ''; // final expression which is built up
  275. $Op = ''; // logical operator
  276. // Try and split an operator out of $Field.
  277. $FieldOpRegex = "/(?:\s*(=|<>|>|<|>=|<=)\s*$)|\s+(like|not\s+like)\s*$|\s+(?:(is)\s+(null)|(is\s+not)\s+(null))\s*$/i";
  278. $Split = preg_split($FieldOpRegex, $Field, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
  279. if(count($Split) > 1) {
  280. $Field = $Split[0];
  281. $Op = $Split[1];
  282. if (count($Split) > 2) {
  283. $Value = null;
  284. }
  285. } else {
  286. $Op = '=';
  287. }
  288. if($Op == '=' && is_null($Value)) {
  289. // This is a special case where the value SQL is checking for an is null operation.
  290. $Op = 'is';
  291. $Value = '@null';
  292. $EscapeValueSql = FALSE;
  293. }
  294. // Add the left hand side of the expression.
  295. $Expr .= $this->_ParseExpr($Field, NULL, $EscapeFieldSql);
  296. // Add the expression operator.
  297. $Expr .= ' '.$Op.' ';
  298. if ($Op == 'is' || $Op == 'is not' && is_null($Value)) {
  299. $Expr .= 'null';
  300. } else {
  301. // Add the right side of the expression.
  302. $Expr .= $this->_ParseExpr($Value, $Field, $EscapeValueSql);
  303. }
  304. return $Expr;
  305. }
  306. /**
  307. * Returns the name of the database currently connected to.
  308. */
  309. public function DatabaseName() {
  310. return $this->Information('DatabaseName');
  311. }
  312. /**
  313. * Builds and executes a delete from query.
  314. *
  315. * @param mixed $Table The table (or array of table names) to delete from.
  316. * @param mixed $Where The string on the left side of the where comparison, or an associative
  317. * array of Field => Value items to compare.
  318. * @param int $Limit The number of records to limit the query to.
  319. */
  320. public function Delete($Table = '', $Where = '', $Limit = FALSE) {
  321. if ($Table == '') {
  322. if (!isset($this->_Froms[0]))
  323. return FALSE;
  324. $Table = $this->_Froms[0];
  325. } elseif (is_array($Table)) {
  326. foreach ($Table as $t) {
  327. $this->Delete($t, $Where, $Limit, FALSE);
  328. }
  329. return;
  330. } else {
  331. $Table = $this->EscapeIdentifier($this->Database->DatabasePrefix.$Table);
  332. }
  333. if ($Where != '')
  334. $this->Where($Where);
  335. if ($Limit !== FALSE)
  336. $this->Limit($Limit);
  337. if (count($this->_Wheres) == 0)
  338. return FALSE;
  339. $Sql = $this->GetDelete($Table, $this->_Wheres, $this->_Limit);
  340. return $this->Query($Sql, 'delete');
  341. }
  342. /**
  343. * Specifies that the query should be run as a distinct so that duplicate
  344. * columns are grouped together. Returns this object for chaining purposes.
  345. *
  346. * @param boolean $Bool A boolean value indicating if the query should be distinct or not.
  347. */
  348. public function Distinct($Bool = TRUE) {
  349. $this->_Distinct = (is_bool($Bool)) ? $Bool : TRUE;
  350. return $this;
  351. }
  352. /**
  353. * Removes all data from a table.
  354. *
  355. * @param string $Table The table to empty.
  356. */
  357. public function EmptyTable($Table = '') {
  358. if ($Table == '') {
  359. if (!isset($this->_Froms[0]))
  360. return FALSE;
  361. $Table = $this->_Froms[0];
  362. } else {
  363. $Table = $this->EscapeIdentifier($this->Database->DatabasePrefix.$Table);
  364. }
  365. $Sql = $this->GetDelete($Table);
  366. return $this->Query($Sql, 'delete');
  367. }
  368. /**
  369. * Closes off any open elements in the query before execution.
  370. * Ideally, the programmer should have everything closed off so this method will do nothing.
  371. */
  372. protected function _EndQuery() {
  373. // Close the where groups.
  374. while($this->_WhereGroupCount > 0) {
  375. $this->EndWhereGroup();
  376. }
  377. }
  378. /**
  379. * End a bracketed group in the where clause.
  380. * <b>Note</b>: If no items where added to the group then no barackets will appear in the final statement.
  381. *
  382. * @return Gdn_DatabaseDriver $this.
  383. */
  384. public function EndWhereGroup() {
  385. if($this->_WhereGroupCount > 0) {
  386. $WhereCount = count($this->_Wheres);
  387. if($this->_OpenWhereGroupCount >= $this->_WhereGroupCount)
  388. $this->_OpenWhereGroupCount--;
  389. else if($WhereCount > 0)
  390. $this->_Wheres[$WhereCount-1] .= ')';
  391. $this->_WhereGroupCount--;
  392. }
  393. return $this;
  394. }
  395. /**
  396. * Takes a string formatted as an SQL field reference and escapes it for the defined database engine.
  397. *
  398. * @param string $RefExpr The reference expression to be escaped.
  399. * The reference should be in the form of alias.column.
  400. */
  401. protected function EscapeIdentifier($RefExpr) {
  402. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'EscapeSql'), E_USER_ERROR);
  403. }
  404. /**
  405. * Takes a string of SQL and escapes it for the defined database engine.
  406. * ie. adds backticks or any other database-specific formatting.
  407. *
  408. * @param mixed $String The string (or array of strings) of SQL to be escaped.
  409. * @param boolean $FirstWordOnly A boolean value indicating if the first word should be escaped only.
  410. */
  411. protected function EscapeSql($String, $FirstWordOnly = FALSE) {
  412. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'EscapeSql'), E_USER_ERROR);
  413. }
  414. /**
  415. * Returns a platform-specific query to fetch column data from $Table.
  416. *
  417. * @param string $Table The name of the table to fetch column data from.
  418. */
  419. public function FetchColumnSql($Table) {
  420. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchColumnSql'), E_USER_ERROR);
  421. }
  422. /**
  423. * Returns a platform-specific query to fetch table names.
  424. * @param mixed $LimitToPrefix Whether or not to limit the search to tables with the database prefix or a specific table name. The following types can be given for this parameter:
  425. * - <b>TRUE</b>: The search will be limited to the database prefix.
  426. * - <b>FALSE</b>: All tables will be fetched. Default.
  427. * - <b>string</b>: The search will be limited to a like clause. The ':_' will be replaced with the database prefix.
  428. */
  429. public function FetchTableSql($LimitToPrefix = FALSE) {
  430. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchTableSql'), E_USER_ERROR);
  431. }
  432. /**
  433. * Returns an array containing table names in the database.
  434. * @param mixed $LimitToPrefix Whether or not to limit the search to tables with the database prefix or a specific table name. The following types can be given for this parameter:
  435. * - <b>TRUE</b>: The search will be limited to the database prefix.
  436. * - <b>FALSE</b>: All tables will be fetched. Default.
  437. * - <b>string</b>: The search will be limited to a like clause. The ':_' will be replaced with the database prefix.
  438. */
  439. public function FetchTables($LimitToPrefix = FALSE) {
  440. $Sql = $this->FetchTableSql($LimitToPrefix);
  441. $Data = $this->Query($Sql);
  442. $Return = array();
  443. foreach($Data->ResultArray() as $Row) {
  444. if (isset($Row['TABLE_NAME']))
  445. $Return[] = $Row['TABLE_NAME'];
  446. else
  447. $Return[] = array_shift($Row);
  448. }
  449. return $Return;
  450. }
  451. /**
  452. * Returns an array of schema data objects for each field in the specified
  453. * table. The returned array of objects contains the following properties:
  454. * Name, PrimaryKey, Type, AllowNull, Default, Length, Enum.
  455. *
  456. * @param string $Table The name of the table to get schema data for.
  457. */
  458. public function FetchTableSchema($Table) {
  459. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchTableSchema'), E_USER_ERROR);
  460. }
  461. /**
  462. * Returns a string of SQL that retrieves the database engine version in the
  463. * fieldname "version".
  464. */
  465. public function FetchVersionSql() {
  466. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FetchVersionSql'), E_USER_ERROR);
  467. }
  468. /**
  469. * Returns an array containing column names from $Table.
  470. *
  471. * @param string $Table The name of the table to fetch column data from.
  472. */
  473. public function FetchColumns($Table) {
  474. $Sql = $this->FetchColumnSql($Table);
  475. $Data = $this->Query($Sql);
  476. $Return = array();
  477. foreach($Data->ResultArray() as $Row) {
  478. if (isset($Row['COLUMN_NAME']))
  479. $Return[] = $Row['COLUMN_NAME'];
  480. else
  481. $Return[] = current($Row);
  482. }
  483. return $Return;
  484. }
  485. /**
  486. * Takes a table name and makes sure it is formatted for this database
  487. * engine.
  488. *
  489. * @param string $Table The name of the table name to format.
  490. */
  491. public function FormatTableName($Table) {
  492. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'FormatTableName'), E_USER_ERROR);
  493. }
  494. /**
  495. * The table(s) from which to select values. Returns this object for
  496. * chaining purposes.
  497. *
  498. * @param mixed $From A string or array of table names/aliases from which to select data.
  499. * Accepted formats include:
  500. * user
  501. * user, user u2, role
  502. * array("user u", "user u2", "role")
  503. **/
  504. public function From($From) {
  505. if (!is_array($From))
  506. $From = array($From);
  507. $Count = count($From);
  508. $i = 0;
  509. for ($i = 0; $i < $Count; ++$i) {
  510. $this->_Froms[] = $this->EscapeIdentifier($this->MapAliases($From[$i]));
  511. }
  512. return $this;
  513. }
  514. /**
  515. * Returns a string of comma delimited table names to select from.
  516. *
  517. * @param mixed $Tables The name of a table (or an array of table names) to be added in the from
  518. * clause of a query.
  519. */
  520. protected function _FromTables($Tables) {
  521. return is_array($Tables) ? implode(', ', $Tables) : $Tables;
  522. }
  523. /**
  524. * Builds the select statement and runs the query, returning a result object.
  525. *
  526. * @param string $Table The table from which to select data. Adds to the $this->_Froms collection.
  527. * @param string $OrderFields A string of fields to be ordered.
  528. * @param string $OrderDirection The direction of the sort.
  529. * @param int $Limit Adds a limit to the query.
  530. * @param int $PageNumber The page of data to retrieve.
  531. * @return DataSet
  532. */
  533. public function Get($Table = '', $OrderFields = '', $OrderDirection = 'asc', $Limit = FALSE, $PageNumber = FALSE) {
  534. if ($Table != '') {
  535. //$this->MapAliases($Table);
  536. $this->From($Table);
  537. }
  538. if ($OrderFields != '')
  539. $this->OrderBy($OrderFields, $OrderDirection);
  540. if ($Limit !== FALSE) {
  541. if ($PageNumber == FALSE || $PageNumber < 1)
  542. $PageNumber = 1;
  543. $Offset = ($PageNumber - 1) * $Limit;
  544. $this->Limit($Limit, $Offset);
  545. }
  546. $Result = $this->Query($this->GetSelect());
  547. return $Result;
  548. }
  549. /**
  550. * A helper function for escaping sql identifiers.
  551. * @param string The sql containing identifiers to escape in a different language.
  552. * All identifiers requiring escaping should be enclosed in back ticks (`).
  553. * @return array All of the tokens in the sql. The tokens that require escaping will still have back ticks.
  554. */
  555. protected function _GetIdentifierTokens($Sql) {
  556. $Tokens = preg_split('/`/', $Sql, -1, PREG_SPLIT_DELIM_CAPTURE);
  557. $Result = array();
  558. $InIdent = FALSE;
  559. $CurrentToken = '';
  560. for($i = 0; $i < count($Tokens); $i++) {
  561. $Token = $Tokens[i];
  562. $Result .= $Token;
  563. if($Token == '`') {
  564. if($InIdent && $i < count($Tokens) - 1 && $Tokens[$i + 1] == '`') {
  565. // This is an escaped back tick.
  566. $i++; // skip next token
  567. } else if($InIdent) {
  568. $Result[] = $CurrentToken;
  569. $CurrentToken = $CurrentToken;
  570. $InIdent = false;
  571. } else {
  572. $InIdent = true;
  573. }
  574. } else if(!$InIdent) {
  575. $Result[] = $CurrentToken;
  576. $CurrentToken = '';
  577. }
  578. }
  579. return $Result;
  580. }
  581. /**
  582. * Returns the total number of records in the specified table.
  583. *
  584. * @param string $Table The table from which to count rows of data.
  585. * @param mixed $Where Adds to the $this->_Wheres collection using $this->Where();
  586. */
  587. public function GetCount($Table = '', $Where = FALSE) {
  588. if($Table != '') {
  589. //$this->MapAliases($Table);
  590. $this->From($Table);
  591. }
  592. if ($Where !== FALSE)
  593. $this->Where($Where);
  594. $this->Select('*', 'count', 'RowCount');
  595. $Sql = $this->GetSelect();
  596. $Result = $this->Query($Sql);
  597. $CountData = $Result->FirstRow();
  598. return $CountData->RowCount;
  599. }
  600. /**
  601. * Returns the total number of records in the specified table.
  602. *
  603. * @param string $Table The table from which to count rows of data.
  604. * @param mixed $Like Adds to the $this->_Wheres collection using $this->Like();
  605. */
  606. public function GetCountLike($Table = '', $Like = FALSE) {
  607. if ($Table != '') {
  608. $this->MapAliases($Table);
  609. $this->From($Table);
  610. }
  611. if ($Like !== FALSE)
  612. $this->Like($Like);
  613. $this->Select('*', 'count', 'RowCount');
  614. $Result = $this->Query($this->GetSelect());
  615. $CountData = $Result->FirstRow();
  616. return $CountData->RowCount;
  617. }
  618. /**
  619. * Returns a delete statement for the specified table and the supplied
  620. * conditions.
  621. *
  622. * @param string $TableName The name of the table to delete from.
  623. * @param array $Wheres An array of where conditions.
  624. */
  625. public function GetDelete($TableName, $Wheres = array()) {
  626. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetDelete'), E_USER_ERROR);
  627. }
  628. /**
  629. * Returns an insert statement for the specified $Table with the provided $Data.
  630. *
  631. * @param string $Table The name of the table to insert data into.
  632. * @param string $Data An associative array of FieldName => Value pairs that should be inserted
  633. * $Table.
  634. */
  635. public function GetInsert($Table, $Data) {
  636. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetInsert'), E_USER_ERROR);
  637. }
  638. /**
  639. * Adds a limit clause to the provided query for this database engine.
  640. *
  641. * @param string $Query The SQL string to which the limit statement should be appended.
  642. * @param int $Limit The number of records to limit the query to.
  643. * @param int $Offset The number of records to offset the query from.
  644. */
  645. public function GetLimit($Query, $Limit, $Offset) {
  646. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetLimit'), E_USER_ERROR);
  647. }
  648. /**
  649. * Builds the select statement based on the various collections in this
  650. * object. This method should not be called directly; it is called by
  651. * $this->Get() and $this->GetWhere().
  652. */
  653. public function GetSelect() {
  654. // Close off any open query elements.
  655. $this->_EndQuery();
  656. $Sql = (!$this->_Distinct) ? 'select ' : 'select distinct ';
  657. // Don't escape the field if it is numeric or an asterisk (all columns)
  658. $Selects = array();
  659. foreach($this->_Selects as $Key => $Expr) {
  660. $Field = $Expr['Field'];
  661. $Function = $Expr['Function'];
  662. $Alias = $Expr['Alias'];
  663. $CaseOptions = ArrayValue('CaseOptions', $Expr);
  664. if ($Field != '*' && !is_numeric($Field))
  665. $Field = $this->EscapeIdentifier($Field);
  666. if ($Alias == '' && $Function != '')
  667. $Alias = $Field;
  668. // if (in_array(strtolower($Function), array('max', 'min', 'avg', 'sum', 'count')))
  669. if ($Function != '') {
  670. if(strpos($Function, '%s') !== FALSE)
  671. $Field = sprintf($Function, $Field);
  672. else
  673. $Field = $Function.'('.$Field.')';
  674. }
  675. if ($CaseOptions !== FALSE)
  676. $Field = 'case ' . $Field . $CaseOptions . ' end';
  677. if ($Alias != '')
  678. $Field .= ' as '.$this->QuoteIdentifier($Alias);
  679. if ($Field != '')
  680. $Selects[] = $Field;
  681. }
  682. $Sql .= (count($Selects) == 0) ? '*' : implode(', ', $Selects);
  683. if (count($this->_Froms) > 0)
  684. $Sql .= "\nfrom ".$this->_FromTables($this->_Froms);
  685. if (count($this->_Joins) > 0) {
  686. $Sql .= "\n";
  687. $Sql .= implode("\n", $this->_Joins);
  688. }
  689. if (count($this->_Wheres) > 0)
  690. $Sql .= "\nwhere ";
  691. $Sql .= implode("\n", $this->_Wheres);
  692. // Close any where groups that were left open.
  693. for ($i = 0; $i < $this->_OpenWhereGroupCount; ++$i) {
  694. $Sql .= ')';
  695. }
  696. $this->_OpenWhereGroupCount = 0;
  697. if (count($this->_GroupBys) > 0) {
  698. $Sql .= "\ngroup by ";
  699. // special consideration for table aliases
  700. if (count($this->_AliasMap) > 0 && $this->Database->DatabasePrefix)
  701. $Sql .= implode(', ', $this->_FilterTableAliases($this->_GroupBys));
  702. else
  703. $Sql .= implode(', ', $this->_GroupBys);
  704. }
  705. if (count($this->_Havings) > 0)
  706. $Sql .= "\nhaving ".implode("\n", $this->_Havings);
  707. if (count($this->_OrderBys) > 0)
  708. $Sql .= "\norder by ".implode(', ', $this->_OrderBys);
  709. if (is_numeric($this->_Limit)) {
  710. $Sql .= "\n";
  711. $Sql = $this->GetLimit($Sql, $this->_Limit, $this->_Offset);
  712. }
  713. return $Sql;
  714. }
  715. /**
  716. * Returns a truncate statement for this database engine.
  717. *
  718. * @param string $Table The name of the table to updated data in.
  719. */
  720. public function GetTruncate($Table) {
  721. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetTruncate'), E_USER_ERROR);
  722. }
  723. /**
  724. * Returns an update statement for the specified table with the provided
  725. * $Data.
  726. *
  727. * @param array $Tables The names of the tables to updated data in.
  728. * @param array $Data An associative array of FieldName => Value pairs that should be inserted
  729. * $Table.
  730. * @param mixed $Where A where clause (or array containing multiple where clauses) to be applied
  731. * to the where portion of the update statement.
  732. */
  733. public function GetUpdate($Tables, $Data, $Where) {
  734. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'GetUpdate'), E_USER_ERROR);
  735. }
  736. /**
  737. * Builds the select statement and runs the query, returning a result
  738. * object. Allows a where clause, limit, and offset to be added directly.
  739. *
  740. * @param string $Table The table from which to select data. Adds to the $this->_Froms collection.
  741. * @param mixed $Where Adds to the $this->_Wheres collection using $this->Where();
  742. * @param string $OrderFields A string of fields to be ordered.
  743. * @param string $OrderDirection The direction of the sort.
  744. * @param int $Limit The number of records to limit the query to.
  745. * @param int $PageNumber The offset where the query results should begin.
  746. * @return Gdn_DataSet The data returned by the query.
  747. */
  748. public function GetWhere($Table = '', $Where = FALSE, $OrderFields = '', $OrderDirection = 'asc', $Limit = FALSE, $PageNumber = FALSE) {
  749. if ($Table != '') {
  750. //$this->MapAliases($Table);
  751. $this->From($Table);
  752. }
  753. if ($Where !== FALSE)
  754. $this->Where($Where);
  755. if ($OrderFields != '')
  756. $this->OrderBy($OrderFields, $OrderDirection);
  757. if ($Limit !== FALSE) {
  758. if ($PageNumber == FALSE || $PageNumber < 1)
  759. $PageNumber = 1;
  760. $Offset = ($PageNumber - 1) * $Limit;
  761. $this->Limit($Limit, $Offset);
  762. }
  763. $Result = $this->Query($this->GetSelect());
  764. return $Result;
  765. }
  766. /**
  767. * Builds the select statement and runs the query, returning a result
  768. * object. Allows a like clause, limit, and offset to be added directly.
  769. *
  770. * @param string $Table The table from which to select data. Adds to the $this->_Froms collection.
  771. * @param mixed $Like Adds to the $this->_Wheres collection using $this->Like();
  772. * @param string $OrderFields A string of fields to be ordered.
  773. * @param string $OrderDirection The direction of the sort.
  774. * @param int $Limit The number of records to limit the query to.
  775. * @param int $PageNumber The offset where the query results should begin.
  776. */
  777. public function GetWhereLike($Table = '', $Like = FALSE, $OrderFields = '', $OrderDirection = 'asc', $Limit = FALSE, $PageNumber = FALSE) {
  778. if ($Table != '') {
  779. $this->MapAliases($Table);
  780. $this->From($Table);
  781. }
  782. if ($Like !== FALSE)
  783. $this->Like($Like);
  784. if ($OrderFields != '')
  785. $this->OrderBy($OrderFields, $OrderDirection);
  786. if ($Limit !== FALSE) {
  787. if ($PageNumber == FALSE || $PageNumber < 1)
  788. $PageNumber = 1;
  789. $Offset = ($PageNumber - 1) * $Limit;
  790. $this->Limit($Limit, $Offset);
  791. }
  792. $Result = $this->Query($this->GetSelect());
  793. return $Result;
  794. }
  795. /**
  796. * Adds to the $this->_GroupBys collection.
  797. *
  798. * @param mixed $Fields An array of field names (or a comma-delimited list of field names) to be
  799. * grouped by.
  800. */
  801. public function GroupBy($Fields = NULL) {
  802. if(is_null($Fields)) {
  803. // Group by every item in the select that isn't a function.
  804. foreach($this->_Selects as $Alias => $Select) {
  805. if(ArrayValue('Function', $Select) == '') {
  806. $this->_GroupBys[] = $Select['Field'];
  807. }
  808. }
  809. return $this;
  810. }
  811. if (is_string($Fields))
  812. $Fields = explode(',', $Fields);
  813. foreach ($Fields as $Field) {
  814. $Field = trim($Field);
  815. if ($Field != '')
  816. $this->_GroupBys[] = $this->EscapeIdentifier($Field);
  817. }
  818. return $this;
  819. }
  820. /**
  821. * Adds to the $this->_Havings collection.
  822. *
  823. * This is the most basic having that adds a freeform string of text.
  824. * It should be used only in conjunction with methods that properly escape the sql.
  825. *
  826. * @param string $Sql The condition to add.
  827. * @return Gdn_DatabaseDriver $this
  828. */
  829. protected function _Having($Sql) {
  830. // Figure out the concatenation operator.
  831. $Concat = '';
  832. if(count($this->_Havings) > 0) {
  833. $Concat = ' ' . $this->_WhereConcat . ' ';
  834. }
  835. // Revert the concat back to 'and'.
  836. $this->_WhereConcat = $this->_WhereConcatDefault;
  837. $this->_Havings[] = $Concat . $Sql;
  838. return $this;
  839. }
  840. /**
  841. * Adds to the $this->_Havings collection. Called by $this->Having() and
  842. * $this->OrHaving().
  843. *
  844. * @param mixed $Field The name of the field (or array of field names) in the having clause.
  845. * @param string $Value The string on the right side of the having comparison.
  846. * @param boolean $EscapeSql A boolean value indicating if $this->EscapeSql method should be called
  847. * on $Field.
  848. * @param boolean $EscapeString A boolean value indicating if $this->EscapeString method should be called
  849. * on $Value.
  850. */
  851. function Having($Field, $Value = '', $EscapeField = TRUE, $EscapeValue = TRUE) {
  852. if (!is_array($Field))
  853. $Field = array($Field => $Value);
  854. foreach ($Field as $ChildField => $ChildValue) {
  855. $Expr = $this->ConditionExpr($ChildField, $ChildValue, $EscapeField, $EscapeValue);
  856. $this->_Having($Expr);
  857. }
  858. return $this;
  859. }
  860. public function History($UpdateFields = TRUE, $InsertFields = FALSE) {
  861. $UserID = GetValueR('User.UserID', Gdn::Session(), Gdn::Session()->UserID);
  862. if($InsertFields)
  863. $this->Set('DateInserted', Gdn_Format::ToDateTime())->Set('InsertUserID', $UserID);
  864. if($UpdateFields)
  865. $this->Set('DateUpdated', Gdn_Format::ToDateTime())->Set('UpdateUserID', $UserID);
  866. return $this;
  867. }
  868. /**
  869. * Returns the last identity to be inserted into the database at
  870. * $this->_Connection.
  871. */
  872. public function Identity() {
  873. return $this->Connection()->lastInsertId();
  874. }
  875. /**
  876. * Returns information about the database. Values include: Engine, Version,
  877. * DatabaseName.
  878. *
  879. * @param string $Request The piece of information being requested. Accepted values are: Engine,
  880. * Version, and DatabaseName.
  881. */
  882. public function Information($Request) {
  883. if (array_key_exists($Request, $this->_DatabaseInfo) === FALSE) {
  884. if ($Request == 'Version') {
  885. $this->_DatabaseInfo['Version'] = $this->Version();
  886. } else {
  887. $this->_DatabaseInfo['HostName'] = Gdn::Config('Database.Host', '');
  888. $this->_DatabaseInfo['DatabaseName'] = Gdn::Config('Database.Name', '');
  889. }
  890. }
  891. if (array_key_exists($Request, $this->_DatabaseInfo) === TRUE) {
  892. return $this->_DatabaseInfo[$Request];
  893. } else {
  894. return '';
  895. }
  896. }
  897. /**
  898. * Builds the insert statement and runs the query, returning a result
  899. * object.
  900. *
  901. * @param string $Table The table to which data should be inserted.
  902. * @param mixed $Set An associative array (or object) of FieldName => Value pairs that should
  903. * be inserted, or an array of FieldName values that should have values
  904. * inserted from $Select.
  905. * @param string $Select A select query that will fill the FieldNames specified in $Set.
  906. */
  907. public function Insert($Table = '', $Set = NULL, $Select = '') {
  908. if (count($Set) == 0 && count($this->_Sets) == 0) {
  909. return FALSE;
  910. }
  911. if (!is_null($Set) && $Select == '' && !array_key_exists(0, $Set)) {
  912. $this->Set($Set);
  913. $Set = $this->_Sets;
  914. }
  915. if ($Table == '') {
  916. if (!isset($this->_Froms[0]))
  917. return FALSE;
  918. $Table = $this->_Froms[0];
  919. }
  920. $Sql = $this->GetInsert($this->EscapeIdentifier($this->Database->DatabasePrefix.$Table), $Set, $Select);
  921. $Result = $this->Query($Sql, 'insert');
  922. return $Result;
  923. }
  924. /**
  925. * Inserts or updates values in the table depending on whether they are already there.
  926. *
  927. * @param string $Table The name of the table to insert/update.
  928. * @param array $Set The columns to update.
  929. * @param array $Where The columns to find the row to update.
  930. * If a row is not found then one is inserted and the items in this array are merged with $Set.
  931. */
  932. public function Replace($Table = '', $Set = NULL, $Where, $CheckExisting = FALSE) {
  933. if(count($this->_Sets) > 0) {
  934. foreach($this->_Sets as $Key => $Value) {
  935. if(array_key_exists($Value, $this->_NamedParameters)) {
  936. $Set[$Key] = $this->_NamedParameters[$Value];
  937. unset($this->_NamedParameters[$Value]);
  938. } else {
  939. $Set[$Key] = $Value;
  940. }
  941. }
  942. $this->_Sets = array();
  943. }
  944. // Check to see if there is a row in the table like this.
  945. if ($CheckExisting) {
  946. $Row = $this->GetWhere($Table, $Where)->FirstRow(DATASET_TYPE_ARRAY);
  947. $Update = FALSE;
  948. if ($Row) {
  949. foreach ($Set as $Key => $Value) {
  950. $Key = trim($Key, '`');
  951. if (in_array($Key, array('DateInserted', 'InsertUserID', 'DateUpdated', 'UpdateUserID')))
  952. continue;
  953. if ($Row[$Key] != $Value) {
  954. $Update = TRUE;
  955. break;
  956. }
  957. }
  958. if (!$Update)
  959. return;
  960. }
  961. } else {
  962. $Count = $this->GetCount($Table, $Where);
  963. $Update = $Count > 0;
  964. }
  965. if($Update) {
  966. // Update the table.
  967. $this->Put($Table, $Set, $Where);
  968. } else {
  969. // Insert the table.
  970. $Set = array_merge($Set, $Where);
  971. $this->Insert($Table, $Set);
  972. }
  973. }
  974. /**
  975. * The table(s) to which this query should join. Returns this object for
  976. * chaining purposes.
  977. *
  978. * @param string $TableName The name of a single table to join to.
  979. * @param string $On The conditions on which the join should occur.
  980. * ie. "user.role_id = role.id"
  981. * @param string $Join The type of join to be made. Accepted values are:
  982. * 'inner', 'outer', 'left', 'right', 'left outer', and 'right outer'.
  983. * @return Gdn_SQLDriver
  984. */
  985. public function Join($TableName, $On, $Join = '') {
  986. $Join = strtolower(trim($Join));
  987. if ($Join != '' && !in_array($Join, array('inner', 'outer', 'left', 'right', 'left outer', 'right outer'), TRUE))
  988. $Join = '';
  989. // Add the table prefix to any table specifications in the clause
  990. // echo '<div>'.$TableName.' ---> '.$this->EscapeSql($this->Database->DatabasePrefix.$TableName, TRUE).'</div>';
  991. if($this->Database->DatabasePrefix) {
  992. $TableName = $this->MapAliases($TableName);
  993. //$Aliases = array_keys($this->_AliasMap);
  994. //$Regex = '';
  995. //foreach ($Aliases as $Alias) {
  996. // $Regex .= '(?<! '.$Alias.')';
  997. //}
  998. //$Regex = '/(\w+'.$Regex.'\.)/';
  999. //$On = preg_replace($Regex, $this->Database->DatabasePrefix.'$1', ' '.$On);
  1000. }
  1001. $JoinClause = ltrim($Join . ' join ') . $this->EscapeIdentifier($TableName, TRUE) . ' on ' . $On;
  1002. $this->_Joins[] = $JoinClause;
  1003. return $this;
  1004. }
  1005. /**
  1006. * A convenience method for Gdn_DatabaseDriver::Join that makes the join type 'left.'
  1007. * @see Gdn_DatabaseDriver::Join()
  1008. */
  1009. public function LeftJoin($TableName, $On) {
  1010. return $this->Join($TableName, $On, 'left');
  1011. }
  1012. /**
  1013. * Adds to the $this->_Wheres collection. Used to generate the LIKE portion
  1014. * of a query. Called by $this->Like(), $this->NotLike()
  1015. *
  1016. * @param mixed $Field The field name (or array of field name => match values) to search in for
  1017. * a like $Match.
  1018. * @param string $Match The value to try to match using a like statement in $Field.
  1019. * @param string $Concat The concatenation operator for the items being added to the like in
  1020. * clause.
  1021. * @param string $Side A string indicating which side of the match to place asterisk operators.
  1022. * Accepted values are left, right, both, none. Default is both.
  1023. * @param string $Op Either 'like' or 'not like' clause.
  1024. * @return Gdn_SQLDriver
  1025. */
  1026. public function Like($Field, $Match = '', $Side = 'both', $Op = 'like') {
  1027. if (!is_array($Field))
  1028. $Field = array($Field => $Match);
  1029. foreach ($Field as $SubField => $SubValue) {
  1030. $SubField .= ' ' . $Op . ' ';
  1031. switch($Side) {
  1032. case 'left':
  1033. $SubValue = '%' . $SubValue;
  1034. break;
  1035. case 'right':
  1036. $SubValue .= '%';
  1037. break;
  1038. case 'both':
  1039. if(strlen($Match) == 0)
  1040. $SubValue = '%';
  1041. else
  1042. $SubValue = '%' . $SubValue . '%';
  1043. break;
  1044. }
  1045. $Expr = $this->ConditionExpr($SubField, $SubValue);
  1046. $this->_Where($Expr);
  1047. }
  1048. return $this;
  1049. }
  1050. /**
  1051. * Sets the limit (and offset optionally) for the query.
  1052. *
  1053. * @param int $Limit The number of records to limit the query to.
  1054. * @param int $Offset The offset where the query results should begin.
  1055. */
  1056. public function Limit($Limit, $Offset = FALSE) {
  1057. $this->_Limit = $Limit;
  1058. if ($Offset !== FALSE)
  1059. $this->_Offset = $Offset;
  1060. return $this;
  1061. }
  1062. /**
  1063. * Takes a provided table specification and parses out any table aliases
  1064. * provided, placing them in an alias mapping array. Returns the table
  1065. * specification with any table prefix prepended.
  1066. *
  1067. * @param string $TableString The string specification of the table. ie.
  1068. * "tbl_User as u" or "user u".
  1069. * @return string
  1070. */
  1071. public function MapAliases($TableString) {
  1072. // Make sure all tables have an alias.
  1073. if(strpos($TableString, ' ') === FALSE) {
  1074. $TableString .= ' ' . $TableString;
  1075. }
  1076. // Map the alias to the alias mapping array
  1077. $TableString = trim(preg_replace('/\s+as\s+/i', ' ', $TableString));
  1078. $Alias = strrchr($TableString, " ");
  1079. $TableName = substr($TableString, 0, strlen($TableString) - strlen($Alias));
  1080. // If no alias was specified then it will be set to the tablename.
  1081. $Alias = trim($Alias);
  1082. if(strlen($Alias) == 0) {
  1083. $Alias = $TableName;
  1084. $TableString .= ' ' . $Alias;
  1085. }
  1086. //$this->_AliasMap[$Alias] = $TableName;
  1087. // Return the string with the database table prefix prepended
  1088. return $this->Database->DatabasePrefix . $TableString;
  1089. }
  1090. /**
  1091. * A convenience method for Gdn_DatabaseDriver::Like that changes the operator to 'not like.'
  1092. * @see Gdn_DatabaseDriver::Like()
  1093. */
  1094. public function NotLike($Field, $Match = '', $Side = 'both') {
  1095. return $this->Like($Field, $Match, $Side, 'not like');
  1096. }
  1097. /**
  1098. * Takes a parameter name and makes sure it is cleaned up to be used as a
  1099. * named parameter in a pdo prepared statement.
  1100. * @param string $Name The name of the parameter to cleanup
  1101. * @param boolean $CreateNew Wether or not this is a new or existing parameter.
  1102. * @return string The cleaned up named parameter name.
  1103. */
  1104. public function NamedParameter($Name, $CreateNew = FALSE, $Value = NULL) {
  1105. // Format the parameter name so it is safe for sql
  1106. $NiceName = ':'.preg_replace('/([^\w\d_-])/', '', $Name); // Removes everything from the string except letters, numbers, dashes, and underscores
  1107. if($CreateNew) {
  1108. // Make sure that the new name doesn't already exist.
  1109. $NumberedName = $NiceName;
  1110. $i = 0;
  1111. while (array_key_exists($NumberedName, $this->_NamedParameters)) {
  1112. $NumberedName = $NiceName.$i;
  1113. ++$i;
  1114. }
  1115. $NiceName = $NumberedName;
  1116. }
  1117. if(!is_null($Value)) {
  1118. $this->_NamedParameters[$NiceName] = $Value;
  1119. }
  1120. return $NiceName;
  1121. }
  1122. /**
  1123. * Allows a query to be called without resetting the object.
  1124. * @param boolean $Reset Whether or not to reset this object when the next query executes.
  1125. * @param boolean $OneTime Whether or not this will apply for only the next query or for all subsequent queries.
  1126. */
  1127. public function NoReset($NoReset = TRUE, $OneTime = TRUE) {
  1128. $_NoReset = $NoReset ? ($OneTime ? 1 : 2) : 0;
  1129. return $this;
  1130. }
  1131. /**
  1132. * Sets the offset for the query.
  1133. *
  1134. * @param int $Offset The offset where the query results should begin.
  1135. */
  1136. public function Offset($Offset) {
  1137. $this->_Offset = $Offset;
  1138. return $this;
  1139. }
  1140. /**
  1141. * Adds to the $this->_OrderBys collection.
  1142. *
  1143. * @param string $Fields A string of fields to be ordered.
  1144. * @param string $Direction The direction of the sort.
  1145. */
  1146. public function OrderBy($Fields, $Direction = 'asc') {
  1147. if ($Direction != 'asc')
  1148. $Direction = 'desc';
  1149. $this->_OrderBys[] = $this->EscapeIdentifier($Fields, TRUE).' '.$Direction;
  1150. return $this;
  1151. }
  1152. /**
  1153. * Adds to the $this->_Havings collection. Concatenates multiple calls with OR.
  1154. *
  1155. * @param mixed $Field The name of the field (or array of field names) in the having clause.
  1156. * @param string $Value The string on the right side of the having comparison.
  1157. * @param boolean $EscapeField A boolean value indicating if $this->EscapeSql method should be called
  1158. * on $Field.
  1159. * @param boolean $EscapeValue A boolean value indicating if $this->EscapeString method should be called
  1160. * on $Value.
  1161. * @return Gdn_DatabaseDriver $this.
  1162. * @see Gdn_DatabaseDriver::Having()
  1163. */
  1164. function OrHaving($Field, $Value = '', $EscapeField = TRUE, $EscapeValue = TRUE) {
  1165. return $this->OrOp()->Having($Field, $Value, $EscapeField, $EscapeValue);
  1166. }
  1167. /**
  1168. * A convenience method that calls Gdn_DatabaseDriver::Like with concatenated with an 'or.'
  1169. * @See Gdn_DatabaseDriver::Like()
  1170. */
  1171. public function OrLike($Field, $Match = '', $Side = 'both', $Op = 'like') {
  1172. if (!is_array($Field))
  1173. $Field = array($Field => $Match);
  1174. foreach ($Field as $f => $v) {
  1175. $this->OrOp()->Like($f, $v, $Side, $Op);
  1176. }
  1177. return $this;
  1178. // return $this->OrOp()->Like($Field, $Match, $Side, $Op);
  1179. }
  1180. /** A convenience method for Gdn_DatabaseDriver::Like that changes the operator to 'not like,'
  1181. * and is concatenated with an 'or.'
  1182. * @see Gdn_DatabaseDriver::NotLike()
  1183. * @see GenricDriver::Like()
  1184. */
  1185. public function OrNotLike($Field, $Match = '', $Side = 'both') {
  1186. return $this->OrLike($Field, $Match, $Side, 'not like');
  1187. }
  1188. /**
  1189. * Concat the next where expression with an 'or' operator.
  1190. *
  1191. * @param boolean $SetDefault Whether or not the 'or' is one time, or will revert.
  1192. * @return Gdn_DatabaseDriver $this
  1193. * @see Gdn_DatabaseDriver::AndOp()
  1194. */
  1195. public function OrOp($SetDefault = FALSE) {
  1196. $this->_WhereConcat = 'or';
  1197. if($SetDefault) {
  1198. $this->_WhereConcatDefault = 'or';
  1199. }
  1200. return $this;
  1201. }
  1202. /**
  1203. * @link Gdn_DatabaseDriver::Where()
  1204. */
  1205. public function OrWhere($Field, $Value = NULL, $EscapeFieldSql = TRUE, $EscapeValueSql = TRUE) {
  1206. return $this->OrOp()->Where($Field, $Value, $EscapeFieldSql, $EscapeValueSql);
  1207. }
  1208. /**
  1209. * A convienience method for Gdn_DatabaseDriver::WhereExists() concatenates with an 'or.'
  1210. * @see Gdn_DatabaseDriver::WhereExists()
  1211. */
  1212. public function OrWhereExists($SqlDriver, $Op = 'exists') {
  1213. return $this->OrOp()->WhereExists($SqlDriver, $Op);
  1214. }
  1215. /**
  1216. * @ling Gdn_DatabaseDriver::WhereIn()
  1217. */
  1218. public function OrWhereIn($Field, $Values) {
  1219. return $this->OrOp()->WhereIn($Field, $Values);
  1220. }
  1221. /**
  1222. * A convienience method for Gdn_DatabaseDriver::WhereExists() that changes the operator to 'not exists,'
  1223. * and concatenates with an 'or.'
  1224. * @see Gdn_DatabaseDriver::WhereExists()
  1225. * @see Gdn_DatabaseDriver::WhereNotExists()
  1226. */
  1227. public function OrWhereNotExists($SqlDriver) {
  1228. return $this->OrWhereExists($SqlDriver, 'not exists');
  1229. }
  1230. /**
  1231. * A convenience method for Gdn_DatabaseDriver::WhereIn() that changes the operator to 'not in,'
  1232. * and concatenates with an 'or.'
  1233. * @see Gdn_DatabaseDriver::WhereIn()
  1234. * @see Gdn_DatabaseDriver::WhereNotIn()
  1235. */
  1236. public function OrWhereNotIn($Field, $Values) {
  1237. return $this->OrOp()->WhereNotIn($Field, $Values);
  1238. }
  1239. /**
  1240. * Parses an expression for use in where clauses.
  1241. *
  1242. * @param string $Expr The expression to parse.
  1243. * @param string $Name A name to give the parameter if $Expr becomes a named parameter.
  1244. * @return string The parsed expression.
  1245. */
  1246. protected function _ParseExpr($Expr, $Name = NULL, $EscapeExpr = FALSE) {
  1247. $Result = '';
  1248. $C = substr($Expr, 0, 1);
  1249. if($C === '=' && $EscapeExpr === FALSE) {
  1250. // This is a function call. Each parameter has to be parsed.
  1251. $FunctionArray = preg_split('/(\[[^\]]+\])/', substr($Expr, 1), -1, PREG_SPLIT_DELIM_CAPTURE);
  1252. for($i = 0; $i < count($FunctionArray); $i++) {
  1253. $Part = $FunctionArray[$i];
  1254. if(substr($Part, 1) == '[') {
  1255. // Translate the part of the function call.
  1256. $Part = $this->_FieldExpr(substr($Part, 1, strlen($Part) - 2), $Name);
  1257. $FunctionArray[$i] = $Part;
  1258. }
  1259. }
  1260. // Combine the array back to the original function call.
  1261. $Result = join($FunctionArray);
  1262. } elseif($C === '@' && $EscapeExpr === FALSE) {
  1263. // This is a literal. Don't do anything.
  1264. $Result = substr($Expr, 1);
  1265. } else {
  1266. // This is a column reference.
  1267. if(is_null($Name)) {
  1268. $Result = $this->EscapeIdentifier($Expr);
  1269. } else {
  1270. // This is a named parameter.
  1271. // Check to see if the named parameter is valid.
  1272. if(in_array(substr($Expr, 0, 1), array('=', '@'))) {
  1273. // The parameter has to be a default name.
  1274. $Result = $this->NamedParameter('Param', TRUE);
  1275. } else {
  1276. $Result = $this->NamedParameter($Name, TRUE);
  1277. }
  1278. $this->_NamedParameters[$Result] = $Expr;
  1279. }
  1280. }
  1281. return $Result;
  1282. }
  1283. /**
  1284. * Joins the query to a permission junction table and limits the results accordingly.
  1285. *
  1286. * @param mixed $Permission The permission name (or array of names) to use when limiting the query.
  1287. * @param string $ForeignAlias The alias of the table to join to (ie. Category).
  1288. * @param string $ForeignColumn The primary key column name of $JunctionTable (ie. CategoryID).
  1289. * @param string $JunctionTable
  1290. * @param string $JunctionColumn
  1291. */
  1292. public function Permission($Permission, $ForeignAlias, $ForeignColumn, $JunctionTable = '', $JunctionColumn = '') {
  1293. $PermissionModel = Gdn::PermissionModel();
  1294. $PermissionModel->SQLPermission($this, $Permission, $ForeignAlias, $ForeignColumn, $JunctionTable, $JunctionColumn);
  1295. return $this;
  1296. }
  1297. /**
  1298. * Prefixes a table with the database prefix if it is not already there.
  1299. *
  1300. * @param string $Table The table name to prefix.
  1301. */
  1302. public function PrefixTable($Table) {
  1303. $Prefix = $this->Database->DatabasePrefix;
  1304. if ($Prefix != '' && substr($Table, 0, strlen($Prefix)) != $Prefix)
  1305. $Table = $Prefix.$Table;
  1306. return $Table;
  1307. }
  1308. /**
  1309. * Builds the update statement and runs the query, returning a result object.
  1310. *
  1311. * @param string $Table The table to which data should be updated.
  1312. * @param mixed $Set An array of $FieldName => $Value pairs, or an object of $DataSet->Field
  1313. * properties containing one rowset.
  1314. * @param string $Where Adds to the $this->_Wheres collection using $this->Where();
  1315. * @param int $Limit Adds a limit to the query.
  1316. */
  1317. public function Put($Table = '', $Set = NULL, $Where = FALSE, $Limit = FALSE) {
  1318. $this->Update($Table, $Set, $Where, $Limit);
  1319. if (count($this->_Sets) == 0 || !isset($this->_Froms[0])) {
  1320. $this->Reset();
  1321. return FALSE;
  1322. }
  1323. $Sql = $this->GetUpdate($this->_Froms, $this->_Sets, $this->_Wheres, $this->_OrderBys, $this->_Limit);
  1324. $Result = $this->Query($Sql, 'update');
  1325. return $Result;
  1326. }
  1327. public function Query($Sql, $Type = 'select') {
  1328. switch ($Type) {
  1329. case 'insert': $ReturnType = 'ID'; break;
  1330. default: $ReturnType = 'DataSet'; break;
  1331. }
  1332. try {
  1333. if ($this->CaptureModifications && strtolower($Type) != 'select') {
  1334. if(!property_exists($this->Database, 'CapturedSql'))
  1335. $this->Database->CapturedSql = array();
  1336. $Sql2 = $this->ApplyParameters($Sql, $this->_NamedParameters);
  1337. $this->Database->CapturedSql[] = $Sql2;
  1338. $this->Reset();
  1339. return TRUE;
  1340. }
  1341. $Result = $this->Database->Query($Sql, $this->_NamedParameters, array('ReturnType' => $ReturnType));
  1342. } catch (Exception $Ex) {
  1343. $this->Reset();
  1344. throw $Ex;
  1345. }
  1346. $this->Reset();
  1347. return $Result;
  1348. }
  1349. public function QuoteIdentifier($String) {
  1350. return '`'.$String.'`';
  1351. }
  1352. /**
  1353. * Resets properties of this object that relate to building a select
  1354. * statement back to their default values. Called by $this->Get() and
  1355. * $this->GetWhere().
  1356. */
  1357. public function Reset() {
  1358. // Check the _NoReset flag.
  1359. switch($this->_NoReset) {
  1360. case 1:
  1361. $this->_NoReset = 0;
  1362. return;
  1363. case 2:
  1364. return;
  1365. }
  1366. $this->_Selects = array();
  1367. $this->_Froms = array();
  1368. $this->_Joins = array();
  1369. $this->_Wheres = array();
  1370. $this->_WhereConcat = 'and';
  1371. $this->_WhereConcatDefault = 'and';
  1372. $this->_WhereGroupCount = 0;
  1373. $this->_OpenWhereGroupCount = 0;
  1374. $this->_GroupBys = array();
  1375. $this->_Havings = array();
  1376. $this->_OrderBys = array();
  1377. $this->_AliasMap = array();
  1378. $this->_Distinct = FALSE;
  1379. $this->_Limit = FALSE;
  1380. $this->_Offset = FALSE;
  1381. $this->_Order = FALSE;
  1382. $this->_Sets = array();
  1383. $this->_NamedParameters = array();
  1384. }
  1385. /**
  1386. * Allows the specification of columns to be selected in a database query.
  1387. * Returns this object for chaining purposes. ie. $db->Select()->From();
  1388. *
  1389. * @param mixed $Select NotRequired "*" The field(s) being selected. It
  1390. * can be a comma delimited string, the name of a single field, or an array
  1391. * of field names.
  1392. * @param string $Function NotRequired "" The aggregate function to be used on
  1393. * the select column. Only valid if a single column name is provided.
  1394. * Accepted values are MAX, MIN, AVG, SUM.
  1395. * @param string $Alias NotRequired "" The alias to give a column name.
  1396. * @return this
  1397. */
  1398. public function Select($Select = '*', $Function = '', $Alias = '') {
  1399. if (is_string($Select)) {
  1400. if ($Function == '')
  1401. $Select = explode(',', $Select);
  1402. else
  1403. $Select = array($Select);
  1404. }
  1405. $Count = count($Select);
  1406. $i = 0;
  1407. for ($i = 0; $i < $Count; $i++) {
  1408. $Field = trim($Select[$i]);
  1409. // Try and figure out an alias for the field.
  1410. if($Alias == '' || ($Count > 1 && $i > 0)) {
  1411. if(preg_match('/^([^\s]+)\s+(?:as\s+)?`?([^`]+)`?$/', $Field, $Matches) > 0) {
  1412. // This is an explicit alias in the select clause.
  1413. $Field = $Matches[1];
  1414. $Alias = $Matches[2];
  1415. } elseif(preg_match('/^[^\.]+\.`?([^`]+)`?$/', $Field, $Matches) > 0) {
  1416. // This is an alias from the field name.
  1417. $Alias = $Matches[1];
  1418. } else {
  1419. $Alias = '';
  1420. }
  1421. // Make sure we aren't selecting * as an alias.
  1422. if($Alias == '*')
  1423. $Alias = '';
  1424. }
  1425. $Expr = array('Field' => $Field, 'Function' => $Function, 'Alias' => $Alias);
  1426. if($Alias == '')
  1427. $this->_Selects[] = $Expr;
  1428. else
  1429. $this->_Selects[$Alias] = $Expr;
  1430. }
  1431. return $this;
  1432. }
  1433. /**
  1434. * Allows the specification of a case statement in the select list.
  1435. *
  1436. * @param string $Field The field being examined in the case statement.
  1437. * @param array $Options The options and results in an associative array. A
  1438. * blank key will be the final "else" option of the case statement. eg.
  1439. * array('null' => 1, '' => 0) results in "when null then 1 else 0".
  1440. * @param string $Alias The alias to give a column name.
  1441. */
  1442. public function SelectCase($Field, $Options, $Alias) {
  1443. $CaseOptions = '';
  1444. foreach ($Options as $Key => $Val) {
  1445. if ($Key == '')
  1446. $CaseOptions .= ' else ' . $Val;
  1447. else
  1448. $CaseOptions .= ' when ' . $Key . ' then ' . $Val;
  1449. }
  1450. $Expr = array('Field' => $Field, 'Function' => '', 'Alias' => $Alias, 'CaseOptions' => $CaseOptions);
  1451. if($Alias == '')
  1452. $this->_Selects[] = $Expr;
  1453. else
  1454. $this->_Selects[$Alias] = $Expr;
  1455. return $this;
  1456. }
  1457. /**
  1458. * Adds values to the $this->_Sets collection. Allows for the inserting
  1459. * and updating of values to the db.
  1460. *
  1461. * @param mixed $Field The name of the field to save value as. Alternately this can be an array
  1462. * of $FieldName => $Value pairs, or even an object of $DataSet->Field
  1463. * properties containing one rowset.
  1464. * @param string $Value The value to be set in $Field. Ignored if $Field was an array or object.
  1465. * @param boolean $EscapeString A boolean value indicating if the $Value(s) should be escaped or not.
  1466. * @param boolean $CreateNewNamedParameter A boolean value indicating that if (a) a named parameter is being
  1467. * created, and (b) that name already exists in $this->_NamedParameters
  1468. * collection, then a new one should be created rather than overwriting the
  1469. * existing one.
  1470. */
  1471. public function Set($Field, $Value = '', $EscapeString = TRUE, $CreateNewNamedParameter = TRUE) {
  1472. $Field = Gdn_Format::ObjectAsArray($Field);
  1473. if (!is_array($Field))
  1474. $Field = array($Field => $Value);
  1475. foreach ($Field as $f => $v) {
  1476. if (!is_object($v)) {
  1477. if (!is_array($v))
  1478. $v = array($v);
  1479. foreach($v as $FunctionName => $Val) {
  1480. if ($EscapeString === FALSE) {
  1481. if (is_string($FunctionName) !== FALSE) {
  1482. $this->_Sets[$this->EscapeIdentifier($f)] = $FunctionName.'('.$Val.')';
  1483. } else {
  1484. $this->_Sets[$this->EscapeIdentifier($f)] = $Val;
  1485. }
  1486. } else {
  1487. $NamedParameter = $this->NamedParameter($f, $CreateNewNamedParameter);
  1488. $this->_NamedParameters[$NamedParameter] = $Val;
  1489. $this->_Sets[$this->EscapeIdentifier($f)] = is_string($FunctionName) !== FALSE ? $FunctionName.'('.$NamedParameter.')' : $NamedParameter;
  1490. }
  1491. }
  1492. }
  1493. }
  1494. return $this;
  1495. }
  1496. /**
  1497. * Sets the character encoding for this database engine.
  1498. */
  1499. public function SetEncoding($Encoding) {
  1500. trigger_error(ErrorMessage('The selected database engine does not perform the requested task.', $this->ClassName, 'SetEncoding'), E_USER_ERROR);
  1501. }
  1502. /**
  1503. * Similar to $this->Set() in every way except that if a named parameter is
  1504. * used in place of $Value, it will overwrite any existing value associated
  1505. * with that name as opposed to adding a new name/value (which is the
  1506. * default way that $this->Set() works).
  1507. *
  1508. * @param mixed $Field The name of the field to save value as. Alternately this can be an array
  1509. * of $FieldName => $Value pairs, or even an object of $DataSet->Field
  1510. * properties containing one rowset.
  1511. * @param string $Value The value to be set in $Field. Ignored if $Field was an array or object.
  1512. * @param boolean $EscapeString A boolean value indicating if the $Value(s) should be escaped or not.
  1513. */
  1514. public function SetOverwrite($Field, $Value = '', $EscapeString = TRUE) {
  1515. return $this->Set($Field, $Value, $EscapeString, FALSE);
  1516. }
  1517. /**
  1518. * Truncates all data from a table (will delete from the table if database
  1519. * does not support truncate).
  1520. *
  1521. * @param string $Table The table to truncate.
  1522. */
  1523. public function Truncate($Table = '') {
  1524. if ($Table == '') {
  1525. if (!isset($this->_Froms[0]))
  1526. return FALSE;
  1527. $Table = $this->_Froms[0];
  1528. } else {
  1529. $Table = $this->EscapeIdentifier($this->Database->DatabasePrefix . $Table);
  1530. }
  1531. $Sql = $this->GetTruncate($Table);
  1532. $Result = $this->Query($Sql, 'truncate');
  1533. return $Result;
  1534. }
  1535. /**
  1536. * Allows the specification of a table to be updated in a database query.
  1537. * Returns this object for chaining purposes. ie. $db->Update()->Join()->Set()->Where();
  1538. *
  1539. * @param string $Table The table to which data should be updated.
  1540. * @param mixed $Set An array of $FieldName => $Value pairs, or an object of $DataSet->Field
  1541. * properties containing one rowset.
  1542. * @param string $Where Adds to the $this->_Wheres collection using $this->Where();
  1543. * @param int $Limit Adds a limit to the query.
  1544. */
  1545. public function Update($Table, $Set = NULL, $Where = FALSE, $Limit = FALSE) {
  1546. if($Table != '')
  1547. $this->From($Table);
  1548. if (!is_null($Set))
  1549. $this->Set($Set);
  1550. if ($Where !== FALSE)
  1551. $this->Where($Where);
  1552. if ($Limit !== FALSE)
  1553. $this->Limit($Limit);
  1554. return $this;
  1555. }
  1556. /**
  1557. * Returns a plain-english string containing the version of the database engine.
  1558. */
  1559. public function Version() {
  1560. $Query = $this->Query($this->FetchVersionSql());
  1561. return $Query->Value('version');
  1562. }
  1563. /**
  1564. * Adds to the $this->_Wheres collection. This is the most basic where that adds a freeform string of text.
  1565. * It should be used only in conjunction with methods that properly escape the sql.
  1566. * @param string $Sql The condition to add.
  1567. * @return GenricDriver $this
  1568. */
  1569. protected function _Where($Sql) {
  1570. // Figure out the concatenation operator.
  1571. $Concat = '';
  1572. if(count($this->_Wheres) > 0) {
  1573. $Concat = str_repeat(' ', $this->_WhereGroupCount + 1) . $this->_WhereConcat . ' ';
  1574. }
  1575. // Open the group(s) if necessary.
  1576. while($this->_OpenWhereGroupCount > 0) {
  1577. $Concat .= '(';
  1578. $this->_OpenWhereGroupCount--;
  1579. }
  1580. // Revert the concat back to 'and'.
  1581. $this->_WhereConcat = $this->_WhereConcatDefault;
  1582. $this->_Wheres[] = $Concat . $Sql;
  1583. return $this;
  1584. }
  1585. /**
  1586. * Adds to the $this->_Wheres collection. Called by $this->Where() and $this->OrWhere();
  1587. *
  1588. * @param mixed $Field The string on the left side of the comparison, or an associative array of
  1589. * Field => Value items to compare.
  1590. * @param mixed $Value The string on the right side of the comparison. You can optionally
  1591. * provide an array of DatabaseFunction => Value, which will be converted to
  1592. * DatabaseFunction('Value'). If DatabaseFunction contains a '%s' then sprintf will be used for to place DatabaseFunction into the value.
  1593. * @param boolean $EscapeFieldSql A boolean value indicating if $this->EscapeSql method should be called
  1594. * on $Field.
  1595. * @param boolean $EscapeValueString A boolean value indicating if $this->EscapeString method should be called
  1596. * on $Value.
  1597. */
  1598. public function Where($Field, $Value = NULL, $EscapeFieldSql = TRUE, $EscapeValueSql = TRUE) {
  1599. if (!is_array($Field))
  1600. $Field = array($Field => $Value);
  1601. foreach ($Field as $SubField => $SubValue) {
  1602. $WhereExpr = $this->ConditionExpr($SubField, $SubValue, $EscapeFieldSql, $EscapeValueSql);
  1603. if(strlen($WhereExpr) > 0) {
  1604. $this->_Where($WhereExpr);
  1605. }
  1606. }
  1607. return $this;
  1608. }
  1609. /**
  1610. * Adds to the $this->_WhereIns collection. Used to generate a "where field
  1611. * in (1,2,3)" query. Called by $this->WhereIn(), $this->OrWhereIn(),
  1612. * $this->WhereNotIn(), and $this->OrWhereNotIn().
  1613. *
  1614. * @param string $Field The field to search in for $Values.
  1615. * @param array $Values An array of values to look for in $Field.
  1616. * @param string $Op Either 'in' or 'not in' for the respective operation.
  1617. * @param string $Escape Whether or not to escape the items in $Values.
  1618. * clause.
  1619. */
  1620. public function _WhereIn($Field, $Values, $Op = 'in', $Escape = TRUE) {
  1621. if (is_null($Field) || !is_array($Values))
  1622. return;
  1623. $FieldExpr = $this->_ParseExpr($Field);
  1624. // Build up the in clause.
  1625. $In = array();
  1626. foreach ($Values as $Value) {
  1627. $ValueExpr = $this->_ParseExpr($Value, $Field, $Escape);
  1628. if(strlen($ValueExpr) > 0)
  1629. $In[] = $ValueExpr;
  1630. }
  1631. if(count($In) > 0)
  1632. $InExpr = '(' . implode(', ', $In) . ')';
  1633. else
  1634. $InExpr = '(null)';
  1635. // Set the final expression.
  1636. $Expr = $FieldExpr . ' ' . $Op . ' ' . $InExpr;
  1637. $this->_Where($Expr);
  1638. return $this;
  1639. }
  1640. /**
  1641. * Adds to the $this->_WhereIns collection. Used to generate a "where field
  1642. * in (1,2,3)" query. Concatenated with AND.
  1643. *
  1644. * @param string $Field The field to search in for $Values.
  1645. * @param array $Values An array of values to look for in $Field.
  1646. */
  1647. public function WhereIn($Field, $Values, $Escape = TRUE) {
  1648. return $this->_WhereIn($Field, $Values, 'in', $Escape);
  1649. }
  1650. /**
  1651. * A convenience method for Gdn_DatabaseDriver::WhereIn() that changes the operator to 'not in.'
  1652. * @see Gdn_DatabaseDriver::WhereIn()
  1653. */
  1654. public function WhereNotIn($Field, $Values, $Escape = TRUE) {
  1655. return $this->_WhereIn($Field, $Values, 'not in', $Escape);
  1656. }
  1657. /**
  1658. * Adds an Sql exists expression to the $this->_Wheres collection.
  1659. * @param Gdn_DatabaseDriver $SqlDriver The sql to add.
  1660. * @param string $Op Either 'exists' or 'not exists'
  1661. * @return Gdn_DatabaseDriver $this
  1662. */
  1663. public function WhereExists($SqlDriver, $Op = 'exists') {
  1664. $Sql = $Op . " (\r\n" . $SqlDriver->GetSelect() . "\n)";
  1665. // Add the inner select.
  1666. $this->_Where($Sql);
  1667. // Add the named parameters from the inner select to this statement.
  1668. foreach($SqlDriver->_NamedParameters as $Name => $Value) {
  1669. $this->_NamedParameters[$Name] = $Value;
  1670. }
  1671. return $this;
  1672. }
  1673. /**
  1674. * A convienience method for Gdn_DatabaseDriver::WhereExists() that changes the operator to 'not exists'.
  1675. * @see Gdn_DatabaseDriver::WhereExists()
  1676. */
  1677. public function WhereNotExists($SqlDriver) {
  1678. return $this->WhereExists(@SqlDriver, 'not exists');
  1679. }
  1680. }