PageRenderTime 50ms CodeModel.GetById 5ms RepoModel.GetById 1ms app.codeStats 0ms

/system/libraries/drivers/Database/Pgsql.php

https://github.com/lmorchard/friendfeedarchiver
PHP | 643 lines | 404 code | 104 blank | 135 comment | 41 complexity | 8808232382e6bc7b0aff9c0b2619372b MD5 | raw file
  1. <?php defined('SYSPATH') or die('No direct script access.');
  2. /**
  3. * PostgreSQL 8.1+ Database Driver
  4. *
  5. * $Id: Pgsql.php 2733 2008-06-02 14:34:39Z 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. class Database_Pgsql_Driver extends Database_Driver {
  13. // Database connection link
  14. protected $link;
  15. protected $db_config;
  16. /**
  17. * Sets the config for the class.
  18. *
  19. * @param array database configuration
  20. */
  21. public function __construct($config)
  22. {
  23. $this->db_config = $config;
  24. Log::add('debug', 'PgSQL Database Driver Initialized');
  25. }
  26. public function connect()
  27. {
  28. // Check if link already exists
  29. if (is_resource($this->link))
  30. return $this->link;
  31. // Import the connect variables
  32. extract($this->db_config['connection']);
  33. // Persistent connections enabled?
  34. $connect = ($this->db_config['persistent'] == TRUE) ? 'pg_pconnect' : 'pg_connect';
  35. // Build the connection info
  36. $port = (isset($port)) ? 'port=\''.$port.'\'' : '';
  37. $host = (isset($host)) ? 'host=\''.$host.'\' '.$port : ''; // if no host, connect with the socket
  38. $connection_string = $host.' dbname=\''.$database.'\' user=\''.$user.'\' password=\''.$pass.'\'';
  39. // Make the connection and select the database
  40. if ($this->link = $connect($connection_string))
  41. {
  42. if ($charset = $this->db_config['character_set'])
  43. {
  44. echo $this->set_charset($charset);
  45. }
  46. // Clear password after successful connect
  47. $this->config['connection']['pass'] = NULL;
  48. return $this->link;
  49. }
  50. return FALSE;
  51. }
  52. public function query($sql)
  53. {
  54. // Only cache if it's turned on, and only cache if it's not a write statement
  55. if ($this->db_config['cache'] AND ! preg_match('#\b(?:INSERT|UPDATE|SET)\b#i', $sql))
  56. {
  57. $hash = $this->query_hash($sql);
  58. if ( ! isset(self::$query_cache[$hash]))
  59. {
  60. // Set the cached object
  61. self::$query_cache[$hash] = new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
  62. }
  63. return self::$query_cache[$hash];
  64. }
  65. return new Pgsql_Result(pg_query($this->link, $sql), $this->link, $this->db_config['object'], $sql);
  66. }
  67. public function set_charset($charset)
  68. {
  69. $this->query('SET client_encoding TO '.pg_escape_string($this->link, $charset));
  70. }
  71. public function escape_table($table)
  72. {
  73. return '"'.str_replace('.', '"."', $table).'"';
  74. }
  75. public function escape_column($column)
  76. {
  77. if (strtolower($column) == 'count(*)' OR $column == '*')
  78. return $column;
  79. // This matches any modifiers we support to SELECT.
  80. if ( ! preg_match('/\b(?:all|distinct)\s/i', $column))
  81. {
  82. if (stripos($column, ' AS ') !== FALSE)
  83. {
  84. // Force 'AS' to uppercase
  85. $column = str_ireplace(' AS ', ' AS ', $column);
  86. // Runs escape_column on both sides of an AS statement
  87. $column = array_map(array($this, __FUNCTION__), explode(' AS ', $column));
  88. // Re-create the AS statement
  89. return implode(' AS ', $column);
  90. }
  91. return preg_replace('/[^.*]+/', '"$0"', $column);
  92. }
  93. $parts = explode(' ', $column);
  94. $column = '';
  95. for ($i = 0, $c = count($parts); $i < $c; $i++)
  96. {
  97. // The column is always last
  98. if ($i == ($c - 1))
  99. {
  100. $column .= preg_replace('/[^.*]+/', '"$0"', $parts[$i]);
  101. }
  102. else // otherwise, it's a modifier
  103. {
  104. $column .= $parts[$i].' ';
  105. }
  106. }
  107. return $column;
  108. }
  109. public function regex($field, $match = '', $type = 'AND ', $num_regexs)
  110. {
  111. $prefix = ($num_regexs == 0) ? '' : $type;
  112. return $prefix.' '.$this->escape_column($field).' REGEXP \''.$this->escape_str($match).'\'';
  113. }
  114. public function notregex($field, $match = '', $type = 'AND ', $num_regexs)
  115. {
  116. $prefix = $num_regexs == 0 ? '' : $type;
  117. return $prefix.' '.$this->escape_column($field).' NOT REGEXP \''.$this->escape_str($match) . '\'';
  118. }
  119. public function merge($table, $keys, $values)
  120. {
  121. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  122. }
  123. public function limit($limit, $offset = 0)
  124. {
  125. return 'LIMIT '.$limit.' OFFSET '.$offset;
  126. }
  127. public function stmt_prepare($sql = '')
  128. {
  129. is_object($this->link) or $this->connect();
  130. return new Kohana_Mysqli_Statement($sql, $this->link);
  131. }
  132. public function compile_select($database)
  133. {
  134. $sql = ($database['distinct'] == TRUE) ? 'SELECT DISTINCT ' : 'SELECT ';
  135. $sql .= (count($database['select']) > 0) ? implode(', ', $database['select']) : '*';
  136. if (count($database['from']) > 0)
  137. {
  138. $sql .= "\nFROM ";
  139. $sql .= implode(', ', $database['from']);
  140. }
  141. if (count($database['join']) > 0)
  142. {
  143. $sql .= ' '.implode("\n", $database['join']);
  144. }
  145. if (count($database['where']) > 0)
  146. {
  147. $sql .= "\nWHERE ";
  148. }
  149. $sql .= implode("\n", $database['where']);
  150. if (count($database['groupby']) > 0)
  151. {
  152. $sql .= "\nGROUP BY ";
  153. $sql .= implode(', ', $database['groupby']);
  154. }
  155. if (count($database['having']) > 0)
  156. {
  157. $sql .= "\nHAVING ";
  158. $sql .= implode("\n", $database['having']);
  159. }
  160. if (count($database['orderby']) > 0)
  161. {
  162. $sql .= "\nORDER BY ";
  163. $sql .= implode(', ', $database['orderby']);
  164. }
  165. if (is_numeric($database['limit']))
  166. {
  167. $sql .= "\n";
  168. $sql .= $this->limit($database['limit'], $database['offset']);
  169. }
  170. return $sql;
  171. }
  172. public function escape_str($str)
  173. {
  174. is_resource($this->link) or $this->connect();
  175. return pg_escape_string($this->link, $str);
  176. }
  177. public function list_tables()
  178. {
  179. $sql = 'SELECT table_schema || \'.\' || table_name FROM information_schema.tables WHERE table_schema NOT IN (\'pg_catalog\', \'information_schema\')';
  180. $result = $this->query($sql)->result(FALSE, PGSQL_ASSOC);
  181. $retval = array();
  182. foreach($result as $row)
  183. {
  184. $retval[] = current($row);
  185. }
  186. return $retval;
  187. }
  188. public function show_error()
  189. {
  190. return pg_last_error($this->link);
  191. }
  192. public function list_fields($table, $query = FALSE)
  193. {
  194. static $tables;
  195. if (is_object($query))
  196. {
  197. if (empty($tables[$table]))
  198. {
  199. $tables[$table] = array();
  200. foreach($query as $row)
  201. {
  202. $tables[$table][] = $row->Field;
  203. }
  204. }
  205. return $tables[$table];
  206. }
  207. // WOW...REALLY?!?
  208. // Taken from http://www.postgresql.org/docs/7.4/interactive/catalogs.html
  209. $query = $this->query('SELECT
  210. -- Field
  211. pg_attribute.attname AS "Field",
  212. -- Type
  213. CASE pg_type.typname
  214. WHEN \'int2\' THEN \'smallint\'
  215. WHEN \'int4\' THEN \'int\'
  216. WHEN \'int8\' THEN \'bigint\'
  217. WHEN \'varchar\' THEN \'varchar(\' || pg_attribute.atttypmod-4 || \')\'
  218. ELSE pg_type.typname
  219. END AS "Type",
  220. -- Null
  221. CASE WHEN pg_attribute.attnotnull THEN \'\'
  222. ELSE \'YES\'
  223. END AS "Null",
  224. -- Default
  225. CASE pg_type.typname
  226. WHEN \'varchar\' THEN substring(pg_attrdef.adsrc from \'^(.*).*$\')
  227. ELSE pg_attrdef.adsrc
  228. END AS "Default"
  229. FROM pg_class
  230. INNER JOIN pg_attribute
  231. ON (pg_class.oid=pg_attribute.attrelid)
  232. INNER JOIN pg_type
  233. ON (pg_attribute.atttypid=pg_type.oid)
  234. LEFT JOIN pg_attrdef
  235. ON (pg_class.oid=pg_attrdef.adrelid AND pg_attribute.attnum=pg_attrdef.adnum)
  236. WHERE pg_class.relname=\''.$this->escape_str($table).'\' AND pg_attribute.attnum>=1 AND NOT pg_attribute.attisdropped
  237. ORDER BY pg_attribute.attnum');
  238. $fields = array();
  239. foreach ($query as $row)
  240. {
  241. $fields[$row->Field]=$row->Type;
  242. }
  243. return $fields;
  244. }
  245. public function field_data($table)
  246. {
  247. // TODO: This whole function needs to be debugged.
  248. $query = pg_query('SELECT * FROM '.$this->escape_table($table).' LIMIT 1', $this->link);
  249. $fields = pg_num_fields($query);
  250. $table = array();
  251. for ($i=0; $i < $fields; $i++)
  252. {
  253. $table[$i]['type'] = pg_field_type($query, $i);
  254. $table[$i]['name'] = pg_field_name($query, $i);
  255. $table[$i]['len'] = pg_field_prtlen($query, $i);
  256. }
  257. return $table;
  258. }
  259. } // End Database_Pgsql_Driver Class
  260. /**
  261. * PostgreSQL result.
  262. */
  263. class Pgsql_Result implements Database_Result, ArrayAccess, Iterator, Countable {
  264. // Result resource
  265. protected $result = NULL;
  266. // Total rows and current row
  267. protected $total_rows = FALSE;
  268. protected $current_row = FALSE;
  269. // Insert id
  270. protected $insert_id = FALSE;
  271. // Data fetching types
  272. protected $fetch_type = 'pgsql_fetch_object';
  273. protected $return_type = PGSQL_ASSOC;
  274. /**
  275. * Sets up the result variables.
  276. *
  277. * @param resource query result
  278. * @param resource database link
  279. * @param boolean return objects or arrays
  280. * @param string SQL query that was run
  281. */
  282. public function __construct($result, $link, $object = TRUE, $sql)
  283. {
  284. $this->result = $result;
  285. // If the query is a resource, it was a SELECT, SHOW, DESCRIBE, EXPLAIN query
  286. if (is_resource($result))
  287. {
  288. // Its an DELETE, INSERT, REPLACE, or UPDATE query
  289. if (preg_match('/^(?:delete|insert|replace|update)\s+/i', trim($sql), $matches))
  290. {
  291. $this->insert_id = (strtolower($matches[0]) == 'insert') ? $this->get_insert_id($link) : FALSE;
  292. $this->total_rows = pg_affected_rows($this->result);
  293. }
  294. else
  295. {
  296. $this->current_row = 0;
  297. $this->total_rows = pg_num_rows($this->result);
  298. $this->fetch_type = ($object === TRUE) ? 'pg_fetch_object' : 'pg_fetch_array';
  299. }
  300. }
  301. else
  302. throw new Kohana_Database_Exception('database.error', pg_last_error().' - '.$sql);
  303. // Set result type
  304. $this->result($object);
  305. }
  306. /**
  307. * Magic __destruct function, frees the result.
  308. */
  309. public function __destruct()
  310. {
  311. if (is_resource($this->result))
  312. {
  313. pg_free_result($this->result);
  314. }
  315. }
  316. public function result($object = TRUE, $type = PGSQL_ASSOC)
  317. {
  318. $this->fetch_type = ((bool) $object) ? 'pg_fetch_object' : 'pg_fetch_array';
  319. // This check has to be outside the previous statement, because we do not
  320. // know the state of fetch_type when $object = NULL
  321. // NOTE - The class set by $type must be defined before fetching the result,
  322. // autoloading is disabled to save a lot of stupid overhead.
  323. if ($this->fetch_type == 'pg_fetch_object')
  324. {
  325. $this->return_type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
  326. }
  327. else
  328. {
  329. $this->return_type = $type;
  330. }
  331. return $this;
  332. }
  333. public function result_array($object = NULL, $type = PGSQL_ASSOC)
  334. {
  335. $rows = array();
  336. if (is_string($object))
  337. {
  338. $fetch = $object;
  339. }
  340. elseif (is_bool($object))
  341. {
  342. if ($object === TRUE)
  343. {
  344. $fetch = 'pg_fetch_object';
  345. // NOTE - The class set by $type must be defined before fetching the result,
  346. // autoloading is disabled to save a lot of stupid overhead.
  347. $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
  348. }
  349. else
  350. {
  351. $fetch = 'pg_fetch_array';
  352. }
  353. }
  354. else
  355. {
  356. // Use the default config values
  357. $fetch = $this->fetch_type;
  358. if ($fetch == 'pg_fetch_object')
  359. {
  360. $type = (is_string($type) AND Kohana::auto_load($type)) ? $type : 'stdClass';
  361. }
  362. }
  363. while ($row = $fetch($this->result, NULL, $type))
  364. {
  365. $rows[] = $row;
  366. }
  367. return $rows;
  368. }
  369. public function insert_id()
  370. {
  371. return $this->insert_id;
  372. }
  373. public function list_fields()
  374. {
  375. throw new Kohana_Database_Exception('database.not_implemented', __FUNCTION__);
  376. }
  377. // End Interface
  378. private function get_insert_id($link)
  379. {
  380. $query = 'SELECT LASTVAL() as insert_id';
  381. $result = pg_query($link, $query);
  382. $insert_id = pg_fetch_array($result, NULL, PGSQL_ASSOC);
  383. return $insert_id['insert_id'];
  384. }
  385. // Interface: Countable
  386. /**
  387. * Counts the number of rows in the result set.
  388. *
  389. * @return integer
  390. */
  391. public function count()
  392. {
  393. return $this->total_rows;
  394. }
  395. // End Interface
  396. // Interface: ArrayAccess
  397. /**
  398. * Determines if the requested offset of the result set exists.
  399. *
  400. * @param integer offset id
  401. * @return boolean
  402. */
  403. public function offsetExists($offset)
  404. {
  405. if ($this->total_rows > 0)
  406. {
  407. $min = 0;
  408. $max = $this->total_rows - 1;
  409. return ($offset < $min OR $offset > $max) ? FALSE : TRUE;
  410. }
  411. return FALSE;
  412. }
  413. /**
  414. * Retreives the requested query result offset.
  415. *
  416. * @param integer offset id
  417. * @return mixed
  418. */
  419. public function offsetGet($offset)
  420. {
  421. // Check to see if the requested offset exists.
  422. if (!$this->offsetExists($offset))
  423. return FALSE;
  424. // Go to the offset and return the row
  425. $fetch = $this->fetch_type;
  426. return $fetch($this->result, $offset, $this->return_type);
  427. }
  428. /**
  429. * Sets the offset with the provided value. Since you can't modify query result sets, this function just throws an exception.
  430. *
  431. * @param integer offset id
  432. * @param integer value
  433. * @throws Kohana_Database_Exception
  434. */
  435. public function offsetSet($offset, $value)
  436. {
  437. throw new Kohana_Database_Exception('database.result_read_only');
  438. }
  439. /**
  440. * Unsets the offset. Since you can't modify query result sets, this function just throws an exception.
  441. *
  442. * @param integer offset id
  443. * @throws Kohana_Database_Exception
  444. */
  445. public function offsetUnset($offset)
  446. {
  447. throw new Kohana_Database_Exception('database.result_read_only');
  448. }
  449. // End Interface
  450. // Interface: Iterator
  451. /**
  452. * Retrieves the current result set row.
  453. *
  454. * @return mixed
  455. */
  456. public function current()
  457. {
  458. return $this->offsetGet($this->current_row);
  459. }
  460. /**
  461. * Retreives the current row id.
  462. *
  463. * @return integer
  464. */
  465. public function key()
  466. {
  467. return $this->current_row;
  468. }
  469. /**
  470. * Moves the result pointer ahead one step.
  471. *
  472. * @return integer
  473. */
  474. public function next()
  475. {
  476. return ++$this->current_row;
  477. }
  478. /**
  479. * Moves the result pointer back one step.
  480. *
  481. * @return integer
  482. */
  483. public function prev()
  484. {
  485. return --$this->current_row;
  486. }
  487. /**
  488. * Moves the result pointer to the beginning of the result set.
  489. *
  490. * @return integer
  491. */
  492. public function rewind()
  493. {
  494. return $this->current_row = 0;
  495. }
  496. /**
  497. * Determines if the current result pointer is valid.
  498. *
  499. * @return boolean
  500. */
  501. public function valid()
  502. {
  503. return $this->offsetExists($this->current_row);
  504. }
  505. // End Interface
  506. } // End Pgsql_Result Class
  507. /**
  508. * PostgreSQL Prepared Statement (experimental)
  509. */
  510. class Kohana_Pgsql_Statement {
  511. protected $link = NULL;
  512. protected $stmt;
  513. public function __construct($sql, $link)
  514. {
  515. $this->link = $link;
  516. $this->stmt = $this->link->prepare($sql);
  517. return $this;
  518. }
  519. public function __destruct()
  520. {
  521. $this->stmt->close();
  522. }
  523. // Sets the bind parameters
  524. public function bind_params()
  525. {
  526. $argv = func_get_args();
  527. return $this;
  528. }
  529. // sets the statement values to the bound parameters
  530. public function set_vals()
  531. {
  532. return $this;
  533. }
  534. // Runs the statement
  535. public function execute()
  536. {
  537. return $this;
  538. }
  539. }