connection.php 18 KB

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