PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/3.0/obsolete/web_client/system/libraries/Database_Builder.php

http://github.com/gallery/gallery3-contrib
PHP | 1005 lines | 618 code | 134 blank | 253 comment | 55 complexity | c5b71fed5e0b91ea7702d066e98bf322 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, LGPL-2.1
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * Database builder
  4. *
  5. * @package Kohana
  6. * @author Kohana Team
  7. * @copyright (c) 2008-2009 Kohana Team
  8. * @license http://kohanaphp.com/license
  9. */
  10. class Database_Builder_Core {
  11. // Valid ORDER BY directions
  12. protected $order_directions = array('ASC', 'DESC', 'RAND()');
  13. // Database object
  14. protected $db;
  15. // Builder members
  16. protected $select = array();
  17. protected $from = array();
  18. protected $join = array();
  19. protected $where = array();
  20. protected $group_by = array();
  21. protected $having = array();
  22. protected $order_by = array();
  23. protected $limit = NULL;
  24. protected $offset = NULL;
  25. protected $set = array();
  26. protected $columns = array();
  27. protected $values = array();
  28. protected $type;
  29. protected $distinct = FALSE;
  30. // TTL for caching (using Cache library)
  31. protected $ttl = FALSE;
  32. public function __construct($db = 'default')
  33. {
  34. $this->db = $db;
  35. }
  36. /**
  37. * Resets all query components
  38. */
  39. public function reset()
  40. {
  41. $this->select = array();
  42. $this->from = array();
  43. $this->join = array();
  44. $this->where = array();
  45. $this->group_by = array();
  46. $this->having = array();
  47. $this->order_by = array();
  48. $this->limit = NULL;
  49. $this->offset = NULL;
  50. $this->set = array();
  51. $this->values = array();
  52. }
  53. public function __toString()
  54. {
  55. return $this->compile();
  56. }
  57. /**
  58. * Compiles the builder object into a SQL query
  59. *
  60. * @return string Compiled query
  61. */
  62. protected function compile()
  63. {
  64. if ( ! is_object($this->db))
  65. {
  66. // Use default database for compiling to string if none is given
  67. $this->db = Database::instance($this->db);
  68. }
  69. if ($this->type === Database::SELECT)
  70. {
  71. // SELECT columns FROM table
  72. $sql = $this->distinct ? 'SELECT DISTINCT ' : 'SELECT ';
  73. $sql .= $this->compile_select();
  74. if ( ! empty($this->from))
  75. {
  76. $sql .= "\nFROM ".$this->compile_from();
  77. }
  78. }
  79. elseif ($this->type === Database::UPDATE)
  80. {
  81. $sql = 'UPDATE '.$this->compile_from()."\n".'SET '.$this->compile_set();
  82. }
  83. elseif ($this->type === Database::INSERT)
  84. {
  85. $sql = 'INSERT INTO '.$this->compile_from()."\n".$this->compile_columns()."\nVALUES ".$this->compile_values();
  86. }
  87. elseif ($this->type === Database::DELETE)
  88. {
  89. $sql = 'DELETE FROM '.$this->compile_from();
  90. }
  91. if ( ! empty($this->join))
  92. {
  93. $sql .= $this->compile_join();
  94. }
  95. if ( ! empty($this->where))
  96. {
  97. $sql .= "\n".'WHERE '.$this->compile_conditions($this->where);
  98. }
  99. if ( ! empty($this->having))
  100. {
  101. $sql .= "\n".'HAVING '.$this->compile_conditions($this->having);
  102. }
  103. if ( ! empty($this->group_by))
  104. {
  105. $sql .= "\n".'GROUP BY '.$this->compile_group_by();
  106. }
  107. if ( ! empty($this->order_by))
  108. {
  109. $sql .= "\nORDER BY ".$this->compile_order_by();
  110. }
  111. if (is_int($this->limit))
  112. {
  113. $sql .= "\nLIMIT ".$this->limit;
  114. }
  115. if (is_int($this->offset))
  116. {
  117. $sql .= "\nOFFSET ".$this->offset;
  118. }
  119. return $sql;
  120. }
  121. /**
  122. * Compiles the SELECT portion of the query
  123. *
  124. * @return string
  125. */
  126. protected function compile_select()
  127. {
  128. $vals = array();
  129. foreach ($this->select as $alias => $name)
  130. {
  131. if (is_string($alias))
  132. {
  133. $vals[] = $this->db->quote_column($name, $alias);
  134. }
  135. else
  136. {
  137. $vals[] = $this->db->quote_column($name);
  138. }
  139. }
  140. return implode(', ', $vals);
  141. }
  142. /**
  143. * Compiles the FROM portion of the query
  144. *
  145. * @return string
  146. */
  147. protected function compile_from()
  148. {
  149. $vals = array();
  150. foreach ($this->from as $alias => $name)
  151. {
  152. if (is_string($alias))
  153. {
  154. // Using AS format so escape both
  155. $vals[] = $this->db->quote_table($name, $alias);
  156. }
  157. else
  158. {
  159. // Just using the table name itself
  160. $vals[] = $this->db->quote_table($name);
  161. }
  162. }
  163. return implode(', ', $vals);
  164. }
  165. /**
  166. * Compiles the JOIN portion of the query
  167. *
  168. * @return string
  169. */
  170. protected function compile_join()
  171. {
  172. $sql = '';
  173. foreach ($this->join as $join)
  174. {
  175. list($table, $keys, $type) = $join;
  176. if ($type !== NULL)
  177. {
  178. // Join type
  179. $sql .= ' '.$type;
  180. }
  181. $sql .= ' JOIN '.$this->db->quote_table($table);
  182. $condition = '';
  183. if ($keys instanceof Database_Expression)
  184. {
  185. $condition = (string) $keys;
  186. }
  187. elseif (is_array($keys))
  188. {
  189. // ON condition is an array of matches
  190. foreach ($keys as $key => $value)
  191. {
  192. if ( ! empty($condition))
  193. {
  194. $condition .= ' AND ';
  195. }
  196. $condition .= $this->db->quote_column($key).' = '.$this->db->quote_column($value);
  197. }
  198. }
  199. if ( ! empty($condition))
  200. {
  201. // Add ON condition
  202. $sql .= ' ON ('.$condition.')';
  203. }
  204. }
  205. return $sql;
  206. }
  207. /**
  208. * Compiles the GROUP BY portion of the query
  209. *
  210. * @return string
  211. */
  212. protected function compile_group_by()
  213. {
  214. $vals = array();
  215. foreach ($this->group_by as $column)
  216. {
  217. // Escape the column
  218. $vals[] = $this->db->quote_column($column);
  219. }
  220. return implode(', ', $vals);
  221. }
  222. /**
  223. * Compiles the ORDER BY portion of the query
  224. *
  225. * @return string
  226. */
  227. public function compile_order_by()
  228. {
  229. $ordering = array();
  230. foreach ($this->order_by as $column => $order_by)
  231. {
  232. list($column, $direction) = each($order_by);
  233. $column = $this->db->quote_column($column);
  234. if ($direction !== NULL)
  235. {
  236. $direction = ' '.$direction;
  237. }
  238. $ordering[] = $column.$direction;
  239. }
  240. return implode(', ', $ordering);
  241. }
  242. /**
  243. * Compiles the SET portion of the query for UPDATE
  244. *
  245. * @return string
  246. */
  247. public function compile_set()
  248. {
  249. $vals = array();
  250. foreach ($this->set as $key => $value)
  251. {
  252. // Using an UPDATE so Key = Val
  253. $vals[] = $this->db->quote_column($key).' = '.$this->db->quote($value);
  254. }
  255. return implode(', ', $vals);
  256. }
  257. /**
  258. * Join tables to the builder
  259. *
  260. * @param mixed Table name
  261. * @param mixed Key, or an array of key => value pair, for join condition (can be a Database_Expression)
  262. * @param mixed Value if $keys is not an array or Database_Expression
  263. * @param string Join type (LEFT, RIGHT, INNER, etc.)
  264. * @return Database_Builder
  265. */
  266. public function join($table, $keys, $value = NULL, $type = NULL)
  267. {
  268. if (is_string($keys))
  269. {
  270. $keys = array($keys => $value);
  271. }
  272. if ($type !== NULL)
  273. {
  274. $type = strtoupper($type);
  275. }
  276. $this->join[] = array($table, $keys, $type);
  277. return $this;
  278. }
  279. /**
  280. * Add tables to the FROM portion of the builder
  281. *
  282. * @param string|array table name or array(alias => table)
  283. * @return Database_Builder
  284. */
  285. public function from($tables)
  286. {
  287. if ( ! is_array($tables))
  288. {
  289. $tables = func_get_args();
  290. }
  291. $this->from = array_merge($this->from, $tables);
  292. return $this;
  293. }
  294. /**
  295. * Add fields to the GROUP BY portion
  296. *
  297. * @param mixed Field names or an array of fields
  298. * @return Database_Builder
  299. */
  300. public function group_by($columns)
  301. {
  302. if ( ! is_array($columns))
  303. {
  304. $columns = func_get_args();
  305. }
  306. $this->group_by = array_merge($this->group_by, $columns);
  307. return $this;
  308. }
  309. /**
  310. * Add conditions to the HAVING clause (AND)
  311. *
  312. * @param mixed Column name or array of columns => vals
  313. * @param string Operation to perform
  314. * @param mixed Value
  315. * @return Database_Builder
  316. */
  317. public function having($columns, $op = '=', $value = NULL)
  318. {
  319. return $this->and_having($columns, $op, $value);
  320. }
  321. /**
  322. * Add conditions to the HAVING clause (AND)
  323. *
  324. * @param mixed Column name or array of triplets
  325. * @param string Operation to perform
  326. * @param mixed Value
  327. * @return Database_Builder
  328. */
  329. public function and_having($columns, $op = '=', $value = NULL)
  330. {
  331. if (is_array($columns))
  332. {
  333. foreach ($columns as $column)
  334. {
  335. $this->having[] = array('AND' => $column);
  336. }
  337. }
  338. else
  339. {
  340. $this->having[] = array('AND' => array($columns, $op, $value));
  341. }
  342. return $this;
  343. }
  344. /**
  345. * Add conditions to the HAVING clause (OR)
  346. *
  347. * @param mixed Column name or array of triplets
  348. * @param string Operation to perform
  349. * @param mixed Value
  350. * @return Database_Builder
  351. */
  352. public function or_having($columns, $op = '=', $value = NULL)
  353. {
  354. if (is_array($columns))
  355. {
  356. foreach ($columns as $column)
  357. {
  358. $this->having[] = array('OR' => $column);
  359. }
  360. }
  361. else
  362. {
  363. $this->having[] = array('OR' => array($columns, $op, $value));
  364. }
  365. return $this;
  366. }
  367. /**
  368. * Add fields to the ORDER BY portion
  369. *
  370. * @param mixed Field names or an array of fields (field => direction)
  371. * @param string Direction or NULL for ascending
  372. * @return Database_Builder
  373. */
  374. public function order_by($columns, $direction = NULL)
  375. {
  376. if (is_array($columns))
  377. {
  378. foreach ($columns as $column => $direction)
  379. {
  380. if (is_string($column))
  381. {
  382. $this->order_by[] = array($column => $direction);
  383. }
  384. else
  385. {
  386. // $direction is the column name when the array key is numeric
  387. $this->order_by[] = array($direction => NULL);
  388. }
  389. }
  390. }
  391. else
  392. {
  393. $this->order_by[] = array($columns => $direction);
  394. }
  395. return $this;
  396. }
  397. /**
  398. * Limit rows returned
  399. *
  400. * @param int Number of rows
  401. * @return Database_Builder
  402. */
  403. public function limit($number)
  404. {
  405. $this->limit = (int) $number;
  406. return $this;
  407. }
  408. /**
  409. * Offset into result set
  410. *
  411. * @param int Offset
  412. * @return Database_Builder
  413. */
  414. public function offset($number)
  415. {
  416. $this->offset = (int) $number;
  417. return $this;
  418. }
  419. public function left_join($table, $keys, $value = NULL)
  420. {
  421. return $this->join($table, $keys, $value, 'LEFT');
  422. }
  423. public function right_join($table, $keys, $value = NULL)
  424. {
  425. return $this->join($table, $keys, $value, 'RIGHT');
  426. }
  427. public function inner_join($table, $keys, $value = NULL)
  428. {
  429. return $this->join($table, $keys, $value, 'INNER');
  430. }
  431. public function outer_join($table, $keys, $value = NULL)
  432. {
  433. return $this->join($table, $keys, $value, 'OUTER');
  434. }
  435. public function full_join($table, $keys, $value = NULL)
  436. {
  437. return $this->join($table, $keys, $value, 'FULL');
  438. }
  439. public function left_inner_join($table, $keys, $value = NULL)
  440. {
  441. return $this->join($table, $keys, $value, 'LEFT INNER');
  442. }
  443. public function right_inner_join($table, $keys, $value = NULL)
  444. {
  445. return $this->join($table, $keys, $value, 'RIGHT INNER');
  446. }
  447. public function open($clause = 'WHERE')
  448. {
  449. return $this->and_open($clause);
  450. }
  451. public function and_open($clause = 'WHERE')
  452. {
  453. if ($clause === 'WHERE')
  454. {
  455. $this->where[] = array('AND' => '(');
  456. }
  457. else
  458. {
  459. $this->having[] = array('AND' => '(');
  460. }
  461. return $this;
  462. }
  463. public function or_open($clause = 'WHERE')
  464. {
  465. if ($clause === 'WHERE')
  466. {
  467. $this->where[] = array('OR' => '(');
  468. }
  469. else
  470. {
  471. $this->having[] = array('OR' => '(');
  472. }
  473. return $this;
  474. }
  475. public function close($clause = 'WHERE')
  476. {
  477. if ($clause === 'WHERE')
  478. {
  479. $this->where[] = array(')');
  480. }
  481. else
  482. {
  483. $this->having[] = array(')');
  484. }
  485. return $this;
  486. }
  487. /**
  488. * Add conditions to the WHERE clause (AND)
  489. *
  490. * @param mixed Column name or array of columns => vals
  491. * @param string Operation to perform
  492. * @param mixed Value
  493. * @return Database_Builder
  494. */
  495. public function where($columns, $op = '=', $value = NULL)
  496. {
  497. return $this->and_where($columns, $op, $value);
  498. }
  499. /**
  500. * Add conditions to the WHERE clause (AND)
  501. *
  502. * @param mixed Column name or array of triplets
  503. * @param string Operation to perform
  504. * @param mixed Value
  505. * @return Database_Builder
  506. */
  507. public function and_where($columns, $op = '=', $value = NULL)
  508. {
  509. if (is_array($columns))
  510. {
  511. foreach ($columns as $column)
  512. {
  513. $this->where[] = array('AND' => $column);
  514. }
  515. }
  516. else
  517. {
  518. $this->where[] = array('AND' => array($columns, $op, $value));
  519. }
  520. return $this;
  521. }
  522. /**
  523. * Add conditions to the WHERE clause (OR)
  524. *
  525. * @param mixed Column name or array of triplets
  526. * @param string Operation to perform
  527. * @param mixed Value
  528. * @return Database_Builder
  529. */
  530. public function or_where($columns, $op = '=', $value = NULL)
  531. {
  532. if (is_array($columns))
  533. {
  534. foreach ($columns as $column)
  535. {
  536. $this->where[] = array('OR' => $column);
  537. }
  538. }
  539. else
  540. {
  541. $this->where[] = array('OR' => array($columns, $op, $value));
  542. }
  543. return $this;
  544. }
  545. /**
  546. * Compiles the given clause's conditions
  547. *
  548. * @param array Clause conditions
  549. * @return string
  550. */
  551. protected function compile_conditions($groups)
  552. {
  553. $last_condition = NULL;
  554. $sql = '';
  555. foreach ($groups as $group)
  556. {
  557. // Process groups of conditions
  558. foreach ($group as $logic => $condition)
  559. {
  560. if ($condition === '(')
  561. {
  562. if ( ! empty($sql) AND $last_condition !== '(')
  563. {
  564. // Include logic operator
  565. $sql .= ' '.$logic.' ';
  566. }
  567. $sql .= '(';
  568. }
  569. elseif ($condition === ')')
  570. {
  571. $sql .= ')';
  572. }
  573. else
  574. {
  575. list($columns, $op, $value) = $condition;
  576. // Stores each individual condition
  577. $vals = array();
  578. if ($columns instanceof Database_Expression)
  579. {
  580. // Add directly to condition list
  581. $vals[] = (string) $columns;
  582. }
  583. else
  584. {
  585. $op = strtoupper($op);
  586. if ( ! is_array($columns))
  587. {
  588. $columns = array($columns => $value);
  589. }
  590. foreach ($columns as $column => $value)
  591. {
  592. if ($value instanceof Database_Builder)
  593. {
  594. // Using a subquery
  595. $value->db = $this->db;
  596. $value = '('.(string) $value.')';
  597. }
  598. elseif (is_array($value))
  599. {
  600. if ($op === 'BETWEEN' OR $op === 'NOT BETWEEN')
  601. {
  602. // Falls between two values
  603. $value = $this->db->quote($value[0]).' AND '.$this->db->quote($value[1]);
  604. }
  605. else
  606. {
  607. // Return as list
  608. $value = array_map(array($this->db, 'quote'), $value);
  609. $value = '('.implode(', ', $value).')';
  610. }
  611. }
  612. else
  613. {
  614. $value = $this->db->quote($value);
  615. }
  616. if ( ! empty($column))
  617. {
  618. // Ignore blank columns
  619. $column = $this->db->quote_column($column);
  620. }
  621. // Add to condition list
  622. $vals[] = $column.' '.$op.' '.$value;
  623. }
  624. }
  625. if ( ! empty($sql) AND $last_condition !== '(')
  626. {
  627. // Add the logic operator
  628. $sql .= ' '.$logic.' ';
  629. }
  630. // Join the condition list items together by the given logic operator
  631. $sql .= implode(' '.$logic.' ', $vals);
  632. }
  633. $last_condition = $condition;
  634. }
  635. }
  636. return $sql;
  637. }
  638. /**
  639. * Set values for UPDATE
  640. *
  641. * @param mixed Column name or array of columns => vals
  642. * @param mixed Value (can be a Database_Expression)
  643. * @return Database_Builder
  644. */
  645. public function set($keys, $value = NULL)
  646. {
  647. if (is_string($keys))
  648. {
  649. $keys = array($keys => $value);
  650. }
  651. $this->set = array_merge($keys, $this->set);
  652. return $this;
  653. }
  654. /**
  655. * Columns used for INSERT queries
  656. *
  657. * @param array Columns
  658. * @return Database_Builder
  659. */
  660. public function columns($columns)
  661. {
  662. if ( ! is_array($columns))
  663. {
  664. $columns = func_get_args();
  665. }
  666. $this->columns = $columns;
  667. return $this;
  668. }
  669. /**
  670. * Compiles the columns portion of the query for INSERT
  671. *
  672. * @return string
  673. */
  674. protected function compile_columns()
  675. {
  676. return '('.implode(', ', array_map(array($this->db, 'quote_column'), $this->columns)).')';
  677. }
  678. /**
  679. * Values used for INSERT queries
  680. *
  681. * @param array Values
  682. * @return Database_Builder
  683. */
  684. public function values($values)
  685. {
  686. if ( ! is_array($values))
  687. {
  688. $values = func_get_args();
  689. }
  690. $this->values[] = $values;
  691. return $this;
  692. }
  693. /**
  694. * Compiles the VALUES portion of the query for INSERT
  695. *
  696. * @return string
  697. */
  698. protected function compile_values()
  699. {
  700. $values = array();
  701. foreach ($this->values as $group)
  702. {
  703. // Each set of values to be inserted
  704. $values[] = '('.implode(', ', array_map(array($this->db, 'quote'), $group)).')';
  705. }
  706. return implode(', ', $values);
  707. }
  708. /**
  709. * Create a SELECT query and specify selected columns
  710. *
  711. * @param string|array column name or array(alias => column)
  712. * @return Database_Builder
  713. */
  714. public function select($columns = NULL)
  715. {
  716. $this->type = Database::SELECT;
  717. if ($columns === NULL)
  718. {
  719. $columns = array('*');
  720. }
  721. elseif ( ! is_array($columns))
  722. {
  723. $columns = func_get_args();
  724. }
  725. $this->select = array_merge($this->select, $columns);
  726. return $this;
  727. }
  728. /**
  729. * Create a SELECT query and specify selected columns
  730. *
  731. * @param string|array column name or array(alias => column)
  732. * @return Database_Builder
  733. */
  734. public function select_distinct($columns = NULL)
  735. {
  736. $this->select($columns);
  737. $this->distinct = TRUE;
  738. return $this;
  739. }
  740. /**
  741. * Create an UPDATE query
  742. *
  743. * @param string Table name
  744. * @param array Array of Keys => Values
  745. * @param array WHERE conditions
  746. * @return Database_Builder
  747. */
  748. public function update($table = NULL, $set = NULL, $where = NULL)
  749. {
  750. $this->type = Database::UPDATE;
  751. if (is_array($set))
  752. {
  753. $this->set($set);
  754. }
  755. if ($where !== NULL)
  756. {
  757. $this->where($where);
  758. }
  759. if ($table !== NULL)
  760. {
  761. $this->from($table);
  762. }
  763. return $this;
  764. }
  765. /**
  766. * Create an INSERT query. Use 'columns' and 'values' methods for multi-row inserts
  767. *
  768. * @param string Table name
  769. * @param array Array of Keys => Values
  770. * @return Database_Builder
  771. */
  772. public function insert($table = NULL, $set = NULL)
  773. {
  774. $this->type = Database::INSERT;
  775. if (is_array($set))
  776. {
  777. $this->columns(array_keys($set));
  778. $this->values(array_values($set));
  779. }
  780. if ($table !== NULL)
  781. {
  782. $this->from($table);
  783. }
  784. return $this;
  785. }
  786. /**
  787. * Create a DELETE query
  788. *
  789. * @param string Table name
  790. * @param array WHERE conditions
  791. * @return Database_Builder
  792. */
  793. public function delete($table, $where = NULL)
  794. {
  795. $this->type = Database::DELETE;
  796. if ($where !== NULL)
  797. {
  798. $this->where($where);
  799. }
  800. if ($table !== NULL)
  801. {
  802. $this->from($table);
  803. }
  804. return $this;
  805. }
  806. /**
  807. * Count records for a given table
  808. *
  809. * @param string Table name
  810. * @param array WHERE conditions
  811. * @return int
  812. */
  813. public function count_records($table = FALSE, $where = NULL)
  814. {
  815. if (count($this->from) < 1)
  816. {
  817. if ($table === FALSE)
  818. throw new Database_Exception('Database count_records requires a table');
  819. $this->from($table);
  820. }
  821. if ($where !== NULL)
  822. {
  823. $this->where($where);
  824. }
  825. // Grab the count AS records_found
  826. $result = $this->select(array('records_found' => 'COUNT("*")'))->execute();
  827. return $result->get('records_found');
  828. }
  829. /**
  830. * Executes the built query
  831. *
  832. * @param mixed Database name or object
  833. * @return Database_Result
  834. */
  835. public function execute($db = NULL)
  836. {
  837. if ($db !== NULL)
  838. {
  839. $this->db = $db;
  840. }
  841. if ( ! is_object($this->db))
  842. {
  843. // Get the database instance
  844. $this->db = Database::instance($this->db);
  845. }
  846. $query = $this->compile();
  847. // Reset the query after executing
  848. $this->reset();
  849. if ($this->ttl !== FALSE AND $this->type === Database::SELECT)
  850. {
  851. // Return result from cache (only allowed with SELECT)
  852. return $this->db->query_cache($query, $this->ttl);
  853. }
  854. else
  855. {
  856. // Load the result (no caching)
  857. return $this->db->query($query);
  858. }
  859. }
  860. /**
  861. * Set caching for the query
  862. *
  863. * @param mixed Time-to-live (FALSE to disable, NULL for Cache default, seconds otherwise)
  864. * @return Database_Builder
  865. */
  866. public function cache($ttl = NULL)
  867. {
  868. $this->ttl = $ttl;
  869. return $this;
  870. }
  871. } // End Database_Builder