PageRenderTime 47ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/library/database/class.mysql.driver.php

https://github.com/dkobia/Garden
PHP | 379 lines | 281 code | 21 blank | 77 comment | 21 complexity | 8e2b1da48e37c576e8904f7dd4a3fdaa MD5 | raw file
  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 MySQLDriver class is a MySQL-specific class for manipulating
  12. * database information.
  13. *
  14. * The MySQLDriver class can be treated as an interface for all database
  15. * engines. Any new database engine should have the same public and protected
  16. * properties and methods as this one so that they can all be treated the same
  17. * by the application.
  18. *
  19. * This class is HEAVILY inspired by and, in places, flat out copied from
  20. * CodeIgniter (http://www.codeigniter.com). My hat is off to them.
  21. *
  22. * @author Mark O'Sullivan
  23. * @copyright 2003 Mark O'Sullivan
  24. * @license http://www.opensource.org/licenses/gpl-2.0.php GPL
  25. * @package Garden
  26. * @version @@GARDEN-VERSION@@
  27. * @namespace Garden.Database
  28. */
  29. require_once(dirname(__FILE__).DS.'class.sqldriver.php');
  30. class Gdn_MySQLDriver extends Gdn_SQLDriver {
  31. // =============================================================================
  32. // SECTION 1. STRING SAFETY, PARSING, AND MANIPULATION.
  33. // =============================================================================
  34. /**
  35. * Takes a string of SQL and adds backticks if necessary.
  36. *
  37. * @param string|array $String The string (or array of strings) of SQL to be escaped.
  38. * @param boolean $FirstWordOnly Should the function only escape the first word?\
  39. */
  40. public function EscapeSql($String, $FirstWordOnly = FALSE) {
  41. if (is_array($String)) {
  42. $EscapedArray = array();
  43. foreach ($String as $k => $v) {
  44. $EscapedArray[$this->EscapeSql($k)] = $this->EscapeSql($v, $FirstWordOnly);
  45. }
  46. return $EscapedArray;
  47. }
  48. // echo '<div>STRING: '.$String.'</div>';
  49. // This function may get "item1 item2" as a string, and so
  50. // we may need "`item1` `item2`" and not "`item1 item2`"
  51. if (ctype_alnum($String) === FALSE) {
  52. if (strpos($String, '.') !== FALSE) {
  53. $MungedAliases = implode('.', array_keys($this->_AliasMap)).'.';
  54. $TableName = substr($String, 0, strpos($String, '.')+1);
  55. //echo '<div>STRING: '.$String.'</div>';
  56. //echo '<div>TABLENAME: '.$TableName.'</div>';
  57. //echo '<div>ALIASES: '.$MungedAliases.'</div>';
  58. // If the "TableName" isn't found in the alias list and it is a valid table name, apply the database prefix to it
  59. $String = (strpos($MungedAliases, $TableName) !== FALSE || strpos($TableName, "'") !== FALSE) ? $String : $this->Database->DatabasePrefix.$String;
  60. //echo '<div>RESULT: '.$String.'</div>';
  61. }
  62. // This function may get "field >= 1", and need it to return "`field` >= 1"
  63. $LeftBound = ($FirstWordOnly === TRUE) ? '' : '|\s|\(';
  64. $String = preg_replace('/(^'.$LeftBound.')([\w\d\-\_]+?)(\s|\)|$)/iS', '$1`$2`$3', $String);
  65. //echo '<div>STRING: '.$String.'</div>';
  66. } else {
  67. return "`{$String}`";
  68. }
  69. $Exceptions = array('as', '/', '-', '%', '+', '*');
  70. foreach ($Exceptions as $Exception) {
  71. if (stristr($String, " `{$Exception}` ") !== FALSE)
  72. $String = preg_replace('/ `('.preg_quote($Exception).')` /i', ' $1 ', $String);
  73. }
  74. return $String;
  75. }
  76. public function EscapeIdentifier($RefExpr) {
  77. // The MySql back tick syntax is the default escape sequence so nothing needs to be done.
  78. return $RefExpr;
  79. }
  80. // =============================================================================
  81. // SECTION 2. DATABASE ENGINE SPECIFIC QUERYING.
  82. // =============================================================================
  83. /**
  84. * Returns a platform-specific query to fetch column data from $Table.
  85. *
  86. * @param string $Table The name of the table to fetch column data from.
  87. */
  88. public function FetchColumnSql($Table) {
  89. return "show columns from ".$this->FormatTableName($Table);
  90. }
  91. /**
  92. * Returns a platform-specific query to fetch table names.
  93. * @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:
  94. * - <b>TRUE</b>: The search will be limited to the database prefix.
  95. * - <b>FALSE</b>: All tables will be fetched. Default.
  96. * - <b>string</b>: The search will be limited to a like clause. The ':_' will be replaced with the database prefix.
  97. */
  98. public function FetchTableSql($LimitToPrefix = FALSE) {
  99. $Sql = "show tables";
  100. if (is_bool($LimitToPrefix) && $LimitToPrefix && $this->Database->DatabasePrefix != '')
  101. $Sql .= " like ".$this->Database->Connection()->quote($this->Database->DatabasePrefix.'%');
  102. elseif (is_string($LimitToPrefix) && $LimitToPrefix)
  103. $Sql .= " like ".$this->Database->Connection()->quote(str_replace(':_', $this->Database->DatabasePrefix, $LimitToPrefix));
  104. return $Sql;
  105. echo "<pre>$Sql</pre>";
  106. }
  107. /**
  108. * Returns an array of schema data objects for each field in the specified
  109. * table. The returned array of objects contains the following properties:
  110. * Name, PrimaryKey, Type, AllowNull, Default, Length, Enum.
  111. *
  112. * @param string $Table The name of the table to get schema data for.
  113. */
  114. public function FetchTableSchema($Table) {
  115. // Format the table name.
  116. $Table = $this->EscapeSql($this->Database->DatabasePrefix.$Table);
  117. $DataSet = $this->Query($this->FetchColumnSql($Table));
  118. $Schema = array();
  119. foreach ($DataSet->Result() as $Field) {
  120. $Type = $Field->Type;
  121. $Unsigned = stripos($Type, 'unsigned') !== FALSE;
  122. $Length = '';
  123. $Precision = '';
  124. $Parentheses = strpos($Type, '(');
  125. $Enum = '';
  126. if ($Parentheses !== FALSE) {
  127. $LengthParts = explode(',', substr($Type, $Parentheses + 1, -1));
  128. $Type = substr($Type, 0, $Parentheses);
  129. if (strcasecmp($Type, 'enum') == 0) {
  130. $Enum = array();
  131. foreach($LengthParts as $Value)
  132. $Enum[] = trim($Value, "'");
  133. } else {
  134. $Length = trim($LengthParts[0]);
  135. if(count($LengthParts) > 1)
  136. $Precision = trim($LengthParts[1]);
  137. }
  138. }
  139. $Object = new stdClass();
  140. $Object->Name = $Field->Field;
  141. $Object->PrimaryKey = ($Field->Key == 'PRI' ? TRUE : FALSE);
  142. $Object->Type = $Type;
  143. //$Object->Type2 = $Field->Type;
  144. $Object->Unsigned = $Unsigned;
  145. $Object->AllowNull = ($Field->Null == 'YES');
  146. $Object->Default = $Field->Default;
  147. $Object->Length = $Length;
  148. $Object->Precision = $Precision;
  149. $Object->Enum = $Enum;
  150. $Object->KeyType = NULL; // give placeholder so it can be defined again.
  151. $Object->AutoIncrement = strpos($Field->Extra, 'auto_increment') === FALSE ? FALSE : TRUE;
  152. $Schema[$Field->Field] = $Object;
  153. }
  154. return $Schema;
  155. }
  156. /**
  157. * Returns a string of SQL that retrieves the database engine version in the
  158. * fieldname "version".
  159. */
  160. public function FetchVersionSql() {
  161. return "select version() as Version";
  162. }
  163. /**
  164. * Takes a table name and makes sure it is formatted for this database
  165. * engine.
  166. *
  167. * @param string $Table The name of the table name to format.
  168. */
  169. public function FormatTableName($Table) {
  170. if (strpos($Table, '.') !== FALSE){
  171. if(preg_match('/^([^\s]+)\s+(?:as\s+)?`?([^`]+)`?$/', $Table, $Matches)){
  172. $DatabaseTable = '`' . str_replace('.', '`.`', $Matches[1]) . '`';
  173. $Table = str_replace($Matches[1], $DatabaseTable, $Table);
  174. }else
  175. $Table = '`' . str_replace('.', '`.`', $Table) . '`';
  176. }
  177. return $Table;
  178. }
  179. /**
  180. * Returns a delete statement for the specified table and the supplied
  181. * conditions.
  182. *
  183. * @param string $TableName The name of the table to delete from.
  184. * @param array $Wheres An array of where conditions.
  185. */
  186. public function GetDelete($TableName, $Wheres = array()) {
  187. $Conditions = '';
  188. $Joins = '';
  189. if (count($this->_Joins) > 0) {
  190. $Joins .= "\n";
  191. // special consideration for table aliases
  192. // if (count($this->_AliasMap) > 0 && $this->Database->DatabasePrefix)
  193. // $Joins .= implode("\n", $this->_FilterTableAliases($this->_Joins));
  194. // else
  195. $Joins .= implode("\n", $this->_Joins);
  196. }
  197. if (count($Wheres) > 0) {
  198. $Conditions = "\nwhere ";
  199. $Conditions .= implode("\n", $Wheres);
  200. // Close any where groups that were left open.
  201. $this->_EndQuery();
  202. }
  203. return "delete ".$TableName." from ".$TableName.$Joins.$Conditions;
  204. }
  205. /**
  206. * Returns an insert statement for the specified $Table with the provided $Data.
  207. *
  208. * @param string $Table The name of the table to insert data into.
  209. * @param array $Data An associative array of FieldName => Value pairs that should be inserted,
  210. * or an array of FieldName values that should have values inserted from
  211. * $Select.
  212. * @param string $Select A select query that will fill the FieldNames specified in $Data.
  213. */
  214. public function GetInsert($Table, $Data, $Select = '') {
  215. if (!is_array($Data))
  216. trigger_error(ErrorMessage('The data provided is not in a proper format (Array).', 'MySQLDriver', 'GetInsert'), E_USER_ERROR);
  217. $Sql = 'insert '.$this->FormatTableName($Table).' ';
  218. if ($Select != '') {
  219. $Sql .= "\n(".implode(', ', $Data).') '
  220. ."\n".$Select;
  221. } else {
  222. if(array_key_exists(0, $Data)) {
  223. // This is a big insert with a bunch of rows.
  224. $Sql .= "\n(".implode(', ', array_keys($Data[0])).') '
  225. ."\nvalues ";
  226. // Append each insert statement.
  227. for($i = 0; $i < count($Data); $i++) {
  228. if($i > 0)
  229. $Sql .= ', ';
  230. $Sql .= "\n('".implode('\', \'', array_values($Data[$i])).'\')';
  231. }
  232. } else {
  233. $Sql .= "\n(".implode(', ', array_keys($Data)).') '
  234. ."\nvalues (".implode(', ', array_values($Data)).')';
  235. }
  236. }
  237. return $Sql;
  238. }
  239. /**
  240. * Adds a limit clause to the provided query for this database engine.
  241. *
  242. * @param string $Query The SQL string to which the limit statement should be appended.
  243. * @param int $Limit The number of records to limit the query to.
  244. * @param int $Offset The number of records to offset the query from.
  245. */
  246. public function GetLimit($Query, $Limit, $Offset) {
  247. $Offset = $Offset == 0 ? '' : $Offset.', ';
  248. return $Query."limit " . $Offset . $Limit;
  249. }
  250. /**
  251. * Returns an update statement for the specified table with the provided
  252. * $Data.
  253. *
  254. * @param array $Tables The name of the table to updated data in.
  255. * @param array $Data An associative array of FieldName => Value pairs that should be inserted
  256. * $Table.
  257. * @param mixed $Where A where clause (or array containing multiple where clauses) to be applied
  258. * to the where portion of the update statement.
  259. */
  260. public function GetUpdate($Tables, $Data, $Where) {
  261. if (!is_array($Data))
  262. trigger_error(ErrorMessage('The data provided is not in a proper format (Array).', 'MySQLDriver', '_GetUpdate'), E_USER_ERROR);
  263. $Sets = array();
  264. foreach($Data as $Field => $Value) {
  265. $Sets[] = $Field." = ".$Value;
  266. }
  267. $sql = 'update '.$this->_FromTables($Tables);
  268. if (count($this->_Joins) > 0) {
  269. $sql .= "\n";
  270. $Join = $this->_Joins[count($this->_Joins) - 1];
  271. $sql .= implode("\n", $this->_Joins);
  272. }
  273. $sql .= " set \n ".implode(",\n ", $Sets);
  274. if (is_array($Where) && count($Where) > 0) {
  275. $sql .= "\nwhere ".implode("\n ", $Where);
  276. // Close any where groups that were left open.
  277. for ($i = 0; $i < $this->_OpenWhereGroupCount; ++$i) {
  278. $sql .= ')';
  279. }
  280. $this->_OpenWhereGroupCount = 0;
  281. } else if (is_string($Where) && !StringIsNullOrEmpty($Where)) {
  282. $sql .= ' where '.$Where;
  283. }
  284. return $sql;
  285. }
  286. /**
  287. * Returns a truncate statement for this database engine.
  288. *
  289. * @param string The name of the table to updated data in.
  290. */
  291. public function GetTruncate($Table) {
  292. return 'truncate '.$this->FormatTableName($Table);
  293. }
  294. /**
  295. * Allows the specification of a case statement in the select list.
  296. *
  297. * @param string $Field The field being examined in the case statement.
  298. * @param array $Options The options and results in an associative array. A blank key will be the
  299. * final "else" option of the case statement. eg.
  300. * array('null' => 1, '' => 0) results in "when null then 1 else 0".
  301. * @param string $Alias The alias to give a column name.
  302. */
  303. public function SelectCase($Field, $Options, $Alias) {
  304. $CaseOptions = '';
  305. foreach ($Options as $Key => $Val) {
  306. if ($Key == '')
  307. $CaseOptions .= ' else ' . $Val;
  308. else
  309. $CaseOptions .= ' when ' . $Key . ' then ' . $Val;
  310. }
  311. $this->_Selects[] = array('Field' => $Field, 'Function' => '', 'Alias' => $Alias, 'CaseOptions' => $CaseOptions);
  312. return $this;
  313. }
  314. /**
  315. * Sets the character encoding for this database engine.
  316. *
  317. * @param string $Encoding
  318. * @todo $Encoding needs a description.
  319. */
  320. public function SetEncoding($Encoding) {
  321. if ($Encoding != '' && $Encoding !== FALSE) {
  322. // Make sure to pass through any named parameters from queries defined before the connection was opened.
  323. $SavedNamedParameters = $this->_NamedParameters;
  324. $this->_NamedParameters = array();
  325. $this->_NamedParameters[':encoding'] = $Encoding;
  326. $this->Query('set names :encoding');
  327. $this->_NamedParameters = $SavedNamedParameters;
  328. }
  329. }
  330. }