Database.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725
  1. <?php
  2. /**
  3. * Database connection wrapper/helper.
  4. *
  5. * You may get a database instance using `Database::instance('name')` where
  6. * name is the [config](database/config) group.
  7. *
  8. * This class provides connection instance management via Database Drivers, as
  9. * well as quoting, escaping and other related functions. Querys are done using
  10. * [Database_Query] and [Database_Query_Builder] objects, which can be easily
  11. * created using the [DB] helper class.
  12. *
  13. * @package Kohana/Database
  14. * @category Base
  15. * @author Kohana Team
  16. * @copyright (c) Kohana Team
  17. * @license https://koseven.ga/LICENSE.md
  18. */
  19. abstract class Kohana_Database {
  20. // Query types
  21. const SELECT = 1;
  22. const INSERT = 2;
  23. const UPDATE = 3;
  24. const DELETE = 4;
  25. /**
  26. * @var string default instance name
  27. */
  28. public static $default = 'default';
  29. /**
  30. * @var array Database instances
  31. */
  32. public static $instances = [];
  33. /**
  34. * Get a singleton Database instance. If configuration is not specified,
  35. * it will be loaded from the database configuration file using the same
  36. * group as the name.
  37. *
  38. * // Load the default database
  39. * $db = Database::instance();
  40. *
  41. * // Create a custom configured instance
  42. * $db = Database::instance('custom', $config);
  43. *
  44. * @param string $name instance name
  45. * @param array $config configuration parameters
  46. * @return Database
  47. */
  48. public static function instance($name = NULL, array $config = NULL)
  49. {
  50. if ($name === NULL)
  51. {
  52. // Use the default instance name
  53. $name = Database::$default;
  54. }
  55. if ( ! isset(Database::$instances[$name]))
  56. {
  57. if ($config === NULL)
  58. {
  59. // Load the configuration for this database
  60. $config = Kohana::$config->load('database')->$name;
  61. }
  62. if ( ! isset($config['type']))
  63. {
  64. throw new Kohana_Exception('Database type not defined in :name configuration',
  65. [':name' => $name]);
  66. }
  67. // Set the driver class name
  68. $driver = 'Database_'.ucfirst($config['type']);
  69. // Create the database connection instance
  70. $driver = new $driver($name, $config);
  71. // Store the database instance
  72. Database::$instances[$name] = $driver;
  73. }
  74. return Database::$instances[$name];
  75. }
  76. /**
  77. * @var string the last query executed
  78. */
  79. public $last_query;
  80. // Character that is used to quote identifiers
  81. protected $_identifier = '"';
  82. // Instance name
  83. protected $_instance;
  84. // Raw server connection
  85. protected $_connection;
  86. // Configuration array
  87. protected $_config;
  88. /**
  89. * Stores the database configuration locally and name the instance.
  90. *
  91. * [!!] This method cannot be accessed directly, you must use [Database::instance].
  92. *
  93. * @return void
  94. */
  95. public function __construct($name, array $config)
  96. {
  97. // Set the instance name
  98. $this->_instance = $name;
  99. // Store the config locally
  100. $this->_config = $config;
  101. if (empty($this->_config['table_prefix']))
  102. {
  103. $this->_config['table_prefix'] = '';
  104. }
  105. }
  106. /**
  107. * Disconnect from the database when the object is destroyed.
  108. *
  109. * // Destroy the database instance
  110. * unset(Database::instances[(string) $db], $db);
  111. *
  112. * [!!] Calling `unset($db)` is not enough to destroy the database, as it
  113. * will still be stored in `Database::$instances`.
  114. *
  115. * @return void
  116. */
  117. public function __destruct()
  118. {
  119. $this->disconnect();
  120. }
  121. /**
  122. * Returns the database instance name.
  123. *
  124. * echo (string) $db;
  125. *
  126. * @return string
  127. */
  128. public function __toString()
  129. {
  130. return $this->_instance;
  131. }
  132. /**
  133. * Connect to the database. This is called automatically when the first
  134. * query is executed.
  135. *
  136. * $db->connect();
  137. *
  138. * @throws Database_Exception
  139. * @return void
  140. */
  141. abstract public function connect();
  142. /**
  143. * Disconnect from the database. This is called automatically by [Database::__destruct].
  144. * Clears the database instance from [Database::$instances].
  145. *
  146. * $db->disconnect();
  147. *
  148. * @return boolean
  149. */
  150. public function disconnect()
  151. {
  152. unset(Database::$instances[$this->_instance]);
  153. return TRUE;
  154. }
  155. /**
  156. * Set the connection character set. This is called automatically by [Database::connect].
  157. *
  158. * $db->set_charset('utf8');
  159. *
  160. * @throws Database_Exception
  161. * @param string $charset character set name
  162. * @return void
  163. */
  164. abstract public function set_charset($charset);
  165. /**
  166. * Perform an SQL query of the given type.
  167. *
  168. * // Make a SELECT query and use objects for results
  169. * $db->query(Database::SELECT, 'SELECT * FROM groups', TRUE);
  170. *
  171. * // Make a SELECT query and use "Model_User" for the results
  172. * $db->query(Database::SELECT, 'SELECT * FROM users LIMIT 1', 'Model_User');
  173. *
  174. * @param integer $type Database::SELECT, Database::INSERT, etc
  175. * @param string $sql SQL query
  176. * @param mixed $as_object result object class string, TRUE for stdClass, FALSE for assoc array
  177. * @param array $params object construct parameters for result class
  178. * @return object Database_Result for SELECT queries
  179. * @return array list (insert id, row count) for INSERT queries
  180. * @return integer number of affected rows for all other queries
  181. */
  182. abstract public function query($type, $sql, $as_object = FALSE, array $params = NULL);
  183. /**
  184. * Start a SQL transaction
  185. *
  186. * // Start the transactions
  187. * $db->begin();
  188. *
  189. * try {
  190. * DB::insert('users')->values($user1)...
  191. * DB::insert('users')->values($user2)...
  192. * // Insert successful commit the changes
  193. * $db->commit();
  194. * }
  195. * catch (Database_Exception $e)
  196. * {
  197. * // Insert failed. Rolling back changes...
  198. * $db->rollback();
  199. * }
  200. *
  201. * @param string $mode transaction mode
  202. * @return boolean
  203. */
  204. abstract public function begin($mode = NULL);
  205. /**
  206. * Commit the current transaction
  207. *
  208. * // Commit the database changes
  209. * $db->commit();
  210. *
  211. * @return boolean
  212. */
  213. abstract public function commit();
  214. /**
  215. * Abort the current transaction
  216. *
  217. * // Undo the changes
  218. * $db->rollback();
  219. *
  220. * @return boolean
  221. */
  222. abstract public function rollback();
  223. /**
  224. * Count the number of records in a table.
  225. *
  226. * // Get the total number of records in the "users" table
  227. * $count = $db->count_records('users');
  228. *
  229. * @param mixed $table table name string or array(query, alias)
  230. * @return integer
  231. */
  232. public function count_records($table)
  233. {
  234. // Quote the table name
  235. $table = $this->quote_table($table);
  236. return $this->query(Database::SELECT, 'SELECT COUNT(*) AS total_row_count FROM '.$table, FALSE)
  237. ->get('total_row_count');
  238. }
  239. /**
  240. * Returns a normalized array describing the SQL data type
  241. *
  242. * $db->datatype('char');
  243. *
  244. * @param string $type SQL data type
  245. * @return array
  246. */
  247. public function datatype($type)
  248. {
  249. static $types = [
  250. // SQL-92
  251. 'bit' => ['type' => 'string', 'exact' => TRUE],
  252. 'bit varying' => ['type' => 'string'],
  253. 'char' => ['type' => 'string', 'exact' => TRUE],
  254. 'char varying' => ['type' => 'string'],
  255. 'character' => ['type' => 'string', 'exact' => TRUE],
  256. 'character varying' => ['type' => 'string'],
  257. 'date' => ['type' => 'string'],
  258. 'dec' => ['type' => 'float', 'exact' => TRUE],
  259. 'decimal' => ['type' => 'float', 'exact' => TRUE],
  260. 'double precision' => ['type' => 'float'],
  261. 'float' => ['type' => 'float'],
  262. 'int' => ['type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'],
  263. 'integer' => ['type' => 'int', 'min' => '-2147483648', 'max' => '2147483647'],
  264. 'interval' => ['type' => 'string'],
  265. 'national char' => ['type' => 'string', 'exact' => TRUE],
  266. 'national char varying' => ['type' => 'string'],
  267. 'national character' => ['type' => 'string', 'exact' => TRUE],
  268. 'national character varying' => ['type' => 'string'],
  269. 'nchar' => ['type' => 'string', 'exact' => TRUE],
  270. 'nchar varying' => ['type' => 'string'],
  271. 'numeric' => ['type' => 'float', 'exact' => TRUE],
  272. 'real' => ['type' => 'float'],
  273. 'smallint' => ['type' => 'int', 'min' => '-32768', 'max' => '32767'],
  274. 'time' => ['type' => 'string'],
  275. 'time with time zone' => ['type' => 'string'],
  276. 'timestamp' => ['type' => 'string'],
  277. 'timestamp with time zone' => ['type' => 'string'],
  278. 'varchar' => ['type' => 'string'],
  279. // SQL:1999
  280. 'binary large object' => ['type' => 'string', 'binary' => TRUE],
  281. 'blob' => ['type' => 'string', 'binary' => TRUE],
  282. 'boolean' => ['type' => 'bool'],
  283. 'char large object' => ['type' => 'string'],
  284. 'character large object' => ['type' => 'string'],
  285. 'clob' => ['type' => 'string'],
  286. 'national character large object' => ['type' => 'string'],
  287. 'nchar large object' => ['type' => 'string'],
  288. 'nclob' => ['type' => 'string'],
  289. 'time without time zone' => ['type' => 'string'],
  290. 'timestamp without time zone' => ['type' => 'string'],
  291. // SQL:2003
  292. 'bigint' => ['type' => 'int', 'min' => '-9223372036854775808', 'max' => '9223372036854775807'],
  293. // SQL:2008
  294. 'binary' => ['type' => 'string', 'binary' => TRUE, 'exact' => TRUE],
  295. 'binary varying' => ['type' => 'string', 'binary' => TRUE],
  296. 'varbinary' => ['type' => 'string', 'binary' => TRUE],
  297. ];
  298. if (isset($types[$type]))
  299. return $types[$type];
  300. return [];
  301. }
  302. /**
  303. * List all of the tables in the database. Optionally, a LIKE string can
  304. * be used to search for specific tables.
  305. *
  306. * // Get all tables in the current database
  307. * $tables = $db->list_tables();
  308. *
  309. * // Get all user-related tables
  310. * $tables = $db->list_tables('user%');
  311. *
  312. * @param string $like table to search for
  313. * @return array
  314. */
  315. abstract public function list_tables($like = NULL);
  316. /**
  317. * Lists all of the columns in a table. Optionally, a LIKE string can be
  318. * used to search for specific fields.
  319. *
  320. * // Get all columns from the "users" table
  321. * $columns = $db->list_columns('users');
  322. *
  323. * // Get all name-related columns
  324. * $columns = $db->list_columns('users', '%name%');
  325. *
  326. * // Get the columns from a table that doesn't use the table prefix
  327. * $columns = $db->list_columns('users', NULL, FALSE);
  328. *
  329. * @param string $table table to get columns from
  330. * @param string $like column to search for
  331. * @param boolean $add_prefix whether to add the table prefix automatically or not
  332. * @return array
  333. */
  334. abstract public function list_columns($table, $like = NULL, $add_prefix = TRUE);
  335. /**
  336. * Extracts the text between parentheses, if any.
  337. *
  338. * // Returns: array('CHAR', '6')
  339. * list($type, $length) = $db->_parse_type('CHAR(6)');
  340. *
  341. * @param string $type
  342. * @return array list containing the type and length, if any
  343. */
  344. protected function _parse_type($type)
  345. {
  346. if (($open = strpos($type, '(')) === FALSE)
  347. {
  348. // No length specified
  349. return [$type, NULL];
  350. }
  351. // Closing parenthesis
  352. $close = strrpos($type, ')', $open);
  353. // Length without parentheses
  354. $length = substr($type, $open + 1, $close - 1 - $open);
  355. // Type without the length
  356. $type = substr($type, 0, $open).substr($type, $close + 1);
  357. return [$type, $length];
  358. }
  359. /**
  360. * Return the table prefix defined in the current configuration.
  361. *
  362. * $prefix = $db->table_prefix();
  363. *
  364. * @return string
  365. */
  366. public function table_prefix()
  367. {
  368. return $this->_config['table_prefix'];
  369. }
  370. /**
  371. * Quote a value for an SQL query.
  372. *
  373. * $db->quote(NULL); // 'NULL'
  374. * $db->quote(10); // 10
  375. * $db->quote('fred'); // 'fred'
  376. *
  377. * Objects passed to this function will be converted to strings.
  378. * [Database_Expression] objects will be compiled.
  379. * [Database_Query] objects will be compiled and converted to a sub-query.
  380. * All other objects will be converted using the `__toString` method.
  381. *
  382. * @param mixed $value any value to quote
  383. * @return string
  384. * @uses Database::escape
  385. */
  386. public function quote($value)
  387. {
  388. if ($value === NULL)
  389. {
  390. return 'NULL';
  391. }
  392. elseif ($value === TRUE)
  393. {
  394. return "'1'";
  395. }
  396. elseif ($value === FALSE)
  397. {
  398. return "'0'";
  399. }
  400. elseif (is_object($value))
  401. {
  402. if ($value instanceof Database_Query)
  403. {
  404. // Create a sub-query
  405. return '('.$value->compile($this).')';
  406. }
  407. elseif ($value instanceof Database_Expression)
  408. {
  409. // Compile the expression
  410. return $value->compile($this);
  411. }
  412. else
  413. {
  414. // Convert the object to a string
  415. return $this->quote( (string) $value);
  416. }
  417. }
  418. elseif (is_array($value))
  419. {
  420. return '('.implode(', ', array_map([$this, __FUNCTION__], $value)).')';
  421. }
  422. elseif (is_int($value))
  423. {
  424. return (int) $value;
  425. }
  426. elseif (is_float($value))
  427. {
  428. // Convert to non-locale aware float to prevent possible commas
  429. return sprintf('%F', $value);
  430. }
  431. return $this->escape($value);
  432. }
  433. /**
  434. * Quote a database column name and add the table prefix if needed.
  435. *
  436. * $column = $db->quote_column($column);
  437. *
  438. * You can also use SQL methods within identifiers.
  439. *
  440. * $column = $db->quote_column(DB::expr('COUNT(`column`)'));
  441. *
  442. * Objects passed to this function will be converted to strings.
  443. * [Database_Expression] objects will be compiled.
  444. * [Database_Query] objects will be compiled and converted to a sub-query.
  445. * All other objects will be converted using the `__toString` method.
  446. *
  447. * @param mixed $column column name or array(column, alias)
  448. * @return string
  449. * @uses Database::quote_identifier
  450. * @uses Database::table_prefix
  451. */
  452. public function quote_column($column)
  453. {
  454. // Identifiers are escaped by repeating them
  455. $escaped_identifier = $this->_identifier.$this->_identifier;
  456. if (is_array($column))
  457. {
  458. list($column, $alias) = $column;
  459. $alias = str_replace($this->_identifier, $escaped_identifier, $alias);
  460. }
  461. if ($column instanceof Database_Query)
  462. {
  463. // Create a sub-query
  464. $column = '('.$column->compile($this).')';
  465. }
  466. elseif ($column instanceof Database_Expression)
  467. {
  468. // Compile the expression
  469. $column = $column->compile($this);
  470. }
  471. else
  472. {
  473. // Convert to a string
  474. $column = (string) $column;
  475. $column = str_replace($this->_identifier, $escaped_identifier, $column);
  476. if ($column === '*')
  477. {
  478. return $column;
  479. }
  480. elseif (strpos($column, '.') !== FALSE)
  481. {
  482. $parts = explode('.', $column);
  483. if ($prefix = $this->table_prefix())
  484. {
  485. // Get the offset of the table name, 2nd-to-last part
  486. $offset = count($parts) - 2;
  487. // Add the table prefix to the table name
  488. $parts[$offset] = $prefix.$parts[$offset];
  489. }
  490. foreach ($parts as & $part)
  491. {
  492. if ($part !== '*')
  493. {
  494. // Quote each of the parts
  495. $part = $this->_identifier.$part.$this->_identifier;
  496. }
  497. }
  498. $column = implode('.', $parts);
  499. }
  500. else
  501. {
  502. $column = $this->_identifier.$column.$this->_identifier;
  503. }
  504. }
  505. if (isset($alias))
  506. {
  507. $column .= ' AS '.$this->_identifier.$alias.$this->_identifier;
  508. }
  509. return $column;
  510. }
  511. /**
  512. * Quote a database table name and adds the table prefix if needed.
  513. *
  514. * $table = $db->quote_table($table);
  515. *
  516. * Objects passed to this function will be converted to strings.
  517. * [Database_Expression] objects will be compiled.
  518. * [Database_Query] objects will be compiled and converted to a sub-query.
  519. * All other objects will be converted using the `__toString` method.
  520. *
  521. * @param mixed $table table name or array(table, alias)
  522. * @return string
  523. * @uses Database::quote_identifier
  524. * @uses Database::table_prefix
  525. */
  526. public function quote_table($table)
  527. {
  528. // Identifiers are escaped by repeating them
  529. $escaped_identifier = $this->_identifier.$this->_identifier;
  530. if (is_array($table))
  531. {
  532. list($table, $alias) = $table;
  533. $alias = str_replace($this->_identifier, $escaped_identifier, $alias);
  534. }
  535. if ($table instanceof Database_Query)
  536. {
  537. // Create a sub-query
  538. $table = '('.$table->compile($this).')';
  539. }
  540. elseif ($table instanceof Database_Expression)
  541. {
  542. // Compile the expression
  543. $table = $table->compile($this);
  544. }
  545. else
  546. {
  547. // Convert to a string
  548. $table = (string) $table;
  549. $table = str_replace($this->_identifier, $escaped_identifier, $table);
  550. if (strpos($table, '.') !== FALSE)
  551. {
  552. $parts = explode('.', $table);
  553. if ($prefix = $this->table_prefix())
  554. {
  555. // Get the offset of the table name, last part
  556. $offset = count($parts) - 1;
  557. // Add the table prefix to the table name
  558. $parts[$offset] = $prefix.$parts[$offset];
  559. }
  560. foreach ($parts as & $part)
  561. {
  562. // Quote each of the parts
  563. $part = $this->_identifier.$part.$this->_identifier;
  564. }
  565. $table = implode('.', $parts);
  566. }
  567. else
  568. {
  569. // Add the table prefix
  570. $table = $this->_identifier.$this->table_prefix().$table.$this->_identifier;
  571. }
  572. }
  573. if (isset($alias))
  574. {
  575. // Attach table prefix to alias
  576. $table .= ' AS '.$this->_identifier.$this->table_prefix().$alias.$this->_identifier;
  577. }
  578. return $table;
  579. }
  580. /**
  581. * Quote a database identifier
  582. *
  583. * Objects passed to this function will be converted to strings.
  584. * [Database_Expression] objects will be compiled.
  585. * [Database_Query] objects will be compiled and converted to a sub-query.
  586. * All other objects will be converted using the `__toString` method.
  587. *
  588. * @param mixed $value any identifier
  589. * @return string
  590. */
  591. public function quote_identifier($value)
  592. {
  593. // Identifiers are escaped by repeating them
  594. $escaped_identifier = $this->_identifier.$this->_identifier;
  595. if (is_array($value))
  596. {
  597. list($value, $alias) = $value;
  598. $alias = str_replace($this->_identifier, $escaped_identifier, $alias);
  599. }
  600. if ($value instanceof Database_Query)
  601. {
  602. // Create a sub-query
  603. $value = '('.$value->compile($this).')';
  604. }
  605. elseif ($value instanceof Database_Expression)
  606. {
  607. // Compile the expression
  608. $value = $value->compile($this);
  609. }
  610. else
  611. {
  612. // Convert to a string
  613. $value = (string) $value;
  614. $value = str_replace($this->_identifier, $escaped_identifier, $value);
  615. if (strpos($value, '.') !== FALSE)
  616. {
  617. $parts = explode('.', $value);
  618. foreach ($parts as & $part)
  619. {
  620. // Quote each of the parts
  621. $part = $this->_identifier.$part.$this->_identifier;
  622. }
  623. $value = implode('.', $parts);
  624. }
  625. else
  626. {
  627. $value = $this->_identifier.$value.$this->_identifier;
  628. }
  629. }
  630. if (isset($alias))
  631. {
  632. $value .= ' AS '.$this->_identifier.$alias.$this->_identifier;
  633. }
  634. return $value;
  635. }
  636. /**
  637. * Sanitize a string by escaping characters that could cause an SQL
  638. * injection attack.
  639. *
  640. * $value = $db->escape('any string');
  641. *
  642. * @param string $value value to quote
  643. * @return string
  644. */
  645. abstract public function escape($value);
  646. } // End Database_Connection