PageRenderTime 55ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/system/libraries/drivers/Database.php

https://github.com/robertleeplummerjr/bluebox
PHP | 636 lines | 274 code | 76 blank | 286 comment | 22 complexity | 5c78af5ccc894954efc3d0440a353dc0 MD5 | raw file
  1. <?php defined('SYSPATH') OR die('No direct access allowed.');
  2. /**
  3. * Database API driver
  4. *
  5. * $Id: Database.php 3769 2008-12-15 00:48:56Z zombor $
  6. *
  7. * @package Core
  8. * @author Kohana Team
  9. * @copyright (c) 2007-2008 Kohana Team
  10. * @license http://kohanaphp.com/license.html
  11. */
  12. abstract class Database_Driver {
  13. static $query_cache;
  14. /**
  15. * Connect to our database.
  16. * Returns FALSE on failure or a MySQL resource.
  17. *
  18. * @return mixed
  19. */
  20. abstract public function connect();
  21. /**
  22. * Perform a query based on a manually written query.
  23. *
  24. * @param string SQL query to execute
  25. * @return Database_Result
  26. */
  27. abstract public function query($sql);
  28. /**
  29. * Builds a DELETE query.
  30. *
  31. * @param string table name
  32. * @param array where clause
  33. * @return string
  34. */
  35. public function delete($table, $where)
  36. {
  37. return 'DELETE FROM '.$this->escape_table($table).' WHERE '.implode(' ', $where);
  38. }
  39. /**
  40. * Builds an UPDATE query.
  41. *
  42. * @param string table name
  43. * @param array key => value pairs
  44. * @param array where clause
  45. * @return string
  46. */
  47. public function update($table, $values, $where)
  48. {
  49. foreach ($values as $key => $val)
  50. {
  51. $valstr[] = $this->escape_column($key).' = '.$val;
  52. }
  53. return 'UPDATE '.$this->escape_table($table).' SET '.implode(', ', $valstr).' WHERE '.implode(' ',$where);
  54. }
  55. /**
  56. * Set the charset using 'SET NAMES <charset>'.
  57. *
  58. * @param string character set to use
  59. */
  60. public function set_charset($charset)
  61. {
  62. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  63. }
  64. /**
  65. * Wrap the tablename in backticks, has support for: table.field syntax.
  66. *
  67. * @param string table name
  68. * @return string
  69. */
  70. abstract public function escape_table($table);
  71. /**
  72. * Escape a column/field name, has support for special commands.
  73. *
  74. * @param string column name
  75. * @return string
  76. */
  77. abstract public function escape_column($column);
  78. /**
  79. * Builds a WHERE portion of a query.
  80. *
  81. * @param mixed key
  82. * @param string value
  83. * @param string type
  84. * @param int number of where clauses
  85. * @param boolean escape the value
  86. * @return string
  87. */
  88. public function where($key, $value, $type, $num_wheres, $quote)
  89. {
  90. $prefix = ($num_wheres == 0) ? '' : $type;
  91. if ($quote === -1)
  92. {
  93. $value = '';
  94. }
  95. else
  96. {
  97. if ($value === NULL)
  98. {
  99. if ( ! $this->has_operator($key))
  100. {
  101. $key .= ' IS';
  102. }
  103. $value = ' NULL';
  104. }
  105. elseif (is_bool($value))
  106. {
  107. if ( ! $this->has_operator($key))
  108. {
  109. $key .= ' =';
  110. }
  111. $value = ($value == TRUE) ? ' 1' : ' 0';
  112. }
  113. else
  114. {
  115. if ( ! $this->has_operator($key))
  116. {
  117. $key = $this->escape_column($key).' =';
  118. }
  119. else
  120. {
  121. preg_match('/^(.+?)([<>!=]+|\bIS(?:\s+NULL))\s*$/i', $key, $matches);
  122. if (isset($matches[1]) AND isset($matches[2]))
  123. {
  124. $key = $this->escape_column(trim($matches[1])).' '.trim($matches[2]);
  125. }
  126. }
  127. $value = ' '.(($quote == TRUE) ? $this->escape($value) : $value);
  128. }
  129. }
  130. return $prefix.$key.$value;
  131. }
  132. /**
  133. * Builds a LIKE portion of a query.
  134. *
  135. * @param mixed field name
  136. * @param string value to match with field
  137. * @param boolean add wildcards before and after the match
  138. * @param string clause type (AND or OR)
  139. * @param int number of likes
  140. * @return string
  141. */
  142. public function like($field, $match = '', $auto = TRUE, $type = 'AND ', $num_likes)
  143. {
  144. $prefix = ($num_likes == 0) ? '' : $type;
  145. $match = $this->escape_str($match);
  146. if ($auto === TRUE)
  147. {
  148. // Add the start and end quotes
  149. $match = '%'.str_replace('%', '\\%', $match).'%';
  150. }
  151. return $prefix.' '.$this->escape_column($field).' LIKE \''.$match . '\'';
  152. }
  153. /**
  154. * Builds a NOT LIKE portion of a query.
  155. *
  156. * @param mixed field name
  157. * @param string value to match with field
  158. * @param string clause type (AND or OR)
  159. * @param int number of likes
  160. * @return string
  161. */
  162. public function notlike($field, $match = '', $auto = TRUE, $type = 'AND ', $num_likes)
  163. {
  164. $prefix = ($num_likes == 0) ? '' : $type;
  165. $match = $this->escape_str($match);
  166. if ($auto === TRUE)
  167. {
  168. // Add the start and end quotes
  169. $match = '%'.$match.'%';
  170. }
  171. return $prefix.' '.$this->escape_column($field).' NOT LIKE \''.$match.'\'';
  172. }
  173. /**
  174. * Builds a REGEX portion of a query.
  175. *
  176. * @param string field name
  177. * @param string value to match with field
  178. * @param string clause type (AND or OR)
  179. * @param integer number of regexes
  180. * @return string
  181. */
  182. public function regex($field, $match, $type, $num_regexs)
  183. {
  184. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  185. }
  186. /**
  187. * Builds a NOT REGEX portion of a query.
  188. *
  189. * @param string field name
  190. * @param string value to match with field
  191. * @param string clause type (AND or OR)
  192. * @param integer number of regexes
  193. * @return string
  194. */
  195. public function notregex($field, $match, $type, $num_regexs)
  196. {
  197. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  198. }
  199. /**
  200. * Builds an INSERT query.
  201. *
  202. * @param string table name
  203. * @param array keys
  204. * @param array values
  205. * @return string
  206. */
  207. public function insert($table, $keys, $values)
  208. {
  209. // Escape the column names
  210. foreach ($keys as $key => $value)
  211. {
  212. $keys[$key] = $this->escape_column($value);
  213. }
  214. return 'INSERT INTO '.$this->escape_table($table).' ('.implode(', ', $keys).') VALUES ('.implode(', ', $values).')';
  215. }
  216. /**
  217. * Builds a MERGE portion of a query.
  218. *
  219. * @param string table name
  220. * @param array keys
  221. * @param array values
  222. * @return string
  223. */
  224. public function merge($table, $keys, $values)
  225. {
  226. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  227. }
  228. /**
  229. * Builds a LIMIT portion of a query.
  230. *
  231. * @param integer limit
  232. * @param integer offset
  233. * @return string
  234. */
  235. abstract public function limit($limit, $offset = 0);
  236. /**
  237. * Creates a prepared statement.
  238. *
  239. * @param string SQL query
  240. * @return Database_Stmt
  241. */
  242. public function stmt_prepare($sql = '')
  243. {
  244. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  245. }
  246. /**
  247. * Compiles the SELECT statement.
  248. * Generates a query string based on which functions were used.
  249. * Should not be called directly, the get() function calls it.
  250. *
  251. * @param array select query values
  252. * @return string
  253. */
  254. abstract public function compile_select($database);
  255. /**
  256. * Determines if the string has an arithmetic operator in it.
  257. *
  258. * @param string string to check
  259. * @return boolean
  260. */
  261. public function has_operator($str)
  262. {
  263. return (bool) preg_match('/[<>!=]|\sIS(?:\s+NOT\s+)?\b/i', trim($str));
  264. }
  265. /**
  266. * Escapes any input value.
  267. *
  268. * @param mixed value to escape
  269. * @return string
  270. */
  271. public function escape($value)
  272. {
  273. if ( ! $this->db_config['escape'])
  274. return $value;
  275. switch (gettype($value))
  276. {
  277. case 'string':
  278. $value = '\''.$this->escape_str($value).'\'';
  279. break;
  280. case 'boolean':
  281. $value = (int) $value;
  282. break;
  283. case 'double':
  284. // Convert to non-locale aware float to prevent possible commas
  285. $value = sprintf('%F', $value);
  286. break;
  287. default:
  288. $value = ($value === NULL) ? 'NULL' : $value;
  289. break;
  290. }
  291. return (string) $value;
  292. }
  293. /**
  294. * Escapes a string for a query.
  295. *
  296. * @param mixed value to escape
  297. * @return string
  298. */
  299. abstract public function escape_str($str);
  300. /**
  301. * Lists all tables in the database.
  302. *
  303. * @return array
  304. */
  305. abstract public function list_tables(Database $db);
  306. /**
  307. * Lists all fields in a table.
  308. *
  309. * @param string table name
  310. * @return array
  311. */
  312. abstract function list_fields($table);
  313. /**
  314. * Returns the last database error.
  315. *
  316. * @return string
  317. */
  318. abstract public function show_error();
  319. /**
  320. * Returns field data about a table.
  321. *
  322. * @param string table name
  323. * @return array
  324. */
  325. abstract public function field_data($table);
  326. /**
  327. * Fetches SQL type information about a field, in a generic format.
  328. *
  329. * @param string field datatype
  330. * @return array
  331. */
  332. protected function sql_type($str)
  333. {
  334. static $sql_types;
  335. if ($sql_types === NULL)
  336. {
  337. // Load SQL data types
  338. $sql_types = Kohana::config('sql_types');
  339. }
  340. $str = strtolower(trim($str));
  341. if (($open = strpos($str, '(')) !== FALSE)
  342. {
  343. // Find closing bracket
  344. $close = strpos($str, ')', $open) - 1;
  345. // Find the type without the size
  346. $type = substr($str, 0, $open);
  347. }
  348. else
  349. {
  350. // No length
  351. $type = $str;
  352. }
  353. empty($sql_types[$type]) and exit
  354. (
  355. 'Unknown field type: '.$type.'. '.
  356. 'Please report this: http://trac.kohanaphp.com/newticket'
  357. );
  358. // Fetch the field definition
  359. $field = $sql_types[$type];
  360. switch ($field['type'])
  361. {
  362. case 'string':
  363. case 'float':
  364. if (isset($close))
  365. {
  366. // Add the length to the field info
  367. $field['length'] = substr($str, $open + 1, $close - $open);
  368. }
  369. break;
  370. case 'int':
  371. // Add unsigned value
  372. $field['unsigned'] = (strpos($str, 'unsigned') !== FALSE);
  373. break;
  374. }
  375. return $field;
  376. }
  377. /**
  378. * Clears the internal query cache.
  379. *
  380. * @param string SQL query
  381. */
  382. public function clear_cache($sql = NULL)
  383. {
  384. if (empty($sql))
  385. {
  386. self::$query_cache = array();
  387. }
  388. else
  389. {
  390. unset(self::$query_cache[$this->query_hash($sql)]);
  391. }
  392. Kohana::log('debug', 'Database cache cleared: '.get_class($this));
  393. }
  394. /**
  395. * Creates a hash for an SQL query string. Replaces newlines with spaces,
  396. * trims, and hashes.
  397. *
  398. * @param string SQL query
  399. * @return string
  400. */
  401. protected function query_hash($sql)
  402. {
  403. return sha1(str_replace("\n", ' ', trim($sql)));
  404. }
  405. } // End Database Driver Interface
  406. /**
  407. * Database_Result
  408. *
  409. */
  410. abstract class Database_Result implements ArrayAccess, Iterator, Countable {
  411. // Result resource, insert id, and SQL
  412. protected $result;
  413. protected $insert_id;
  414. protected $sql;
  415. // Current and total rows
  416. protected $current_row = 0;
  417. protected $total_rows = 0;
  418. // Fetch function and return type
  419. protected $fetch_type;
  420. protected $return_type;
  421. /**
  422. * Returns the SQL used to fetch the result.
  423. *
  424. * @return string
  425. */
  426. public function sql()
  427. {
  428. return $this->sql;
  429. }
  430. /**
  431. * Returns the insert id from the result.
  432. *
  433. * @return mixed
  434. */
  435. public function insert_id()
  436. {
  437. return $this->insert_id;
  438. }
  439. /**
  440. * Prepares the query result.
  441. *
  442. * @param boolean return rows as objects
  443. * @param mixed type
  444. * @return Database_Result
  445. */
  446. abstract function result($object = TRUE, $type = FALSE);
  447. /**
  448. * Builds an array of query results.
  449. *
  450. * @param boolean return rows as objects
  451. * @param mixed type
  452. * @return array
  453. */
  454. abstract function result_array($object = NULL, $type = FALSE);
  455. /**
  456. * Gets the fields of an already run query.
  457. *
  458. * @return array
  459. */
  460. abstract public function list_fields();
  461. /**
  462. * Seek to an offset in the results.
  463. *
  464. * @return boolean
  465. */
  466. abstract public function seek($offset);
  467. /**
  468. * Countable: count
  469. */
  470. public function count()
  471. {
  472. return $this->total_rows;
  473. }
  474. /**
  475. * ArrayAccess: offsetExists
  476. */
  477. public function offsetExists($offset)
  478. {
  479. if ($this->total_rows > 0)
  480. {
  481. $min = 0;
  482. $max = $this->total_rows - 1;
  483. return ! ($offset < $min OR $offset > $max);
  484. }
  485. return FALSE;
  486. }
  487. /**
  488. * ArrayAccess: offsetGet
  489. */
  490. public function offsetGet($offset)
  491. {
  492. if ( ! $this->seek($offset))
  493. return FALSE;
  494. // Return the row by calling the defined fetching callback
  495. return call_user_func($this->fetch_type, $this->result, $this->return_type);
  496. }
  497. /**
  498. * ArrayAccess: offsetSet
  499. *
  500. * @throws Kohana_Database_Exception
  501. */
  502. final public function offsetSet($offset, $value)
  503. {
  504. throw new Kohana_Database_Exception('database.result_read_only');
  505. }
  506. /**
  507. * ArrayAccess: offsetUnset
  508. *
  509. * @throws Kohana_Database_Exception
  510. */
  511. final public function offsetUnset($offset)
  512. {
  513. throw new Kohana_Database_Exception('database.result_read_only');
  514. }
  515. /**
  516. * Iterator: current
  517. */
  518. public function current()
  519. {
  520. return $this->offsetGet($this->current_row);
  521. }
  522. /**
  523. * Iterator: key
  524. */
  525. public function key()
  526. {
  527. return $this->current_row;
  528. }
  529. /**
  530. * Iterator: next
  531. */
  532. public function next()
  533. {
  534. ++$this->current_row;
  535. return $this;
  536. }
  537. /**
  538. * Iterator: prev
  539. */
  540. public function prev()
  541. {
  542. --$this->current_row;
  543. return $this;
  544. }
  545. /**
  546. * Iterator: rewind
  547. */
  548. public function rewind()
  549. {
  550. $this->current_row = 0;
  551. return $this;
  552. }
  553. /**
  554. * Iterator: valid
  555. */
  556. public function valid()
  557. {
  558. return $this->offsetExists($this->current_row);
  559. }
  560. } // End Database Result Interface