PageRenderTime 48ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/libs/dibi/drivers/postgre.php

https://github.com/premiumcombination/nts
PHP | 584 lines | 290 code | 123 blank | 171 comment | 36 complexity | 8bea13e68bd2fa16fdc4e752c2e7ad8a MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the "dibi" - smart database abstraction layer.
  4. *
  5. * Copyright (c) 2005, 2010 David Grudl (http://davidgrudl.com)
  6. *
  7. * For the full copyright and license information, please view
  8. * the file license.txt that was distributed with this source code.
  9. *
  10. * @package dibi\drivers
  11. */
  12. /**
  13. * The dibi driver for PostgreSQL database.
  14. *
  15. * Driver options:
  16. * - host, hostaddr, port, dbname, user, password, connect_timeout, options, sslmode, service => see PostgreSQL API
  17. * - string => or use connection string
  18. * - schema => the schema search path
  19. * - charset => character encoding to set (default is utf8)
  20. * - persistent (bool) => try to find a persistent link?
  21. * - resource (resource) => existing connection resource
  22. * - lazy, profiler, result, substitutes, ... => see DibiConnection options
  23. *
  24. * @author David Grudl
  25. * @package dibi\drivers
  26. */
  27. class DibiPostgreDriver extends DibiObject implements IDibiDriver, IDibiResultDriver, IDibiReflector
  28. {
  29. /** @var resource Connection resource */
  30. private $connection;
  31. /** @var resource Resultset resource */
  32. private $resultSet;
  33. /** @var int|FALSE Affected rows */
  34. private $affectedRows = FALSE;
  35. /** @var bool Escape method */
  36. private $escMethod = FALSE;
  37. /**
  38. * @throws DibiNotSupportedException
  39. */
  40. public function __construct()
  41. {
  42. if (!extension_loaded('pgsql')) {
  43. throw new DibiNotSupportedException("PHP extension 'pgsql' is not loaded.");
  44. }
  45. }
  46. /**
  47. * Connects to a database.
  48. * @return void
  49. * @throws DibiException
  50. */
  51. public function connect(array &$config)
  52. {
  53. if (isset($config['resource'])) {
  54. $this->connection = $config['resource'];
  55. } else {
  56. if (!isset($config['charset'])) $config['charset'] = 'utf8';
  57. if (isset($config['string'])) {
  58. $string = $config['string'];
  59. } else {
  60. $string = '';
  61. DibiConnection::alias($config, 'user', 'username');
  62. DibiConnection::alias($config, 'dbname', 'database');
  63. foreach (array('host','hostaddr','port','dbname','user','password','connect_timeout','options','sslmode','service') as $key) {
  64. if (isset($config[$key])) $string .= $key . '=' . $config[$key] . ' ';
  65. }
  66. }
  67. DibiDriverException::tryError();
  68. if (empty($config['persistent'])) {
  69. $this->connection = pg_connect($string, PGSQL_CONNECT_FORCE_NEW);
  70. } else {
  71. $this->connection = pg_pconnect($string, PGSQL_CONNECT_FORCE_NEW);
  72. }
  73. if (DibiDriverException::catchError($msg)) {
  74. throw new DibiDriverException($msg, 0);
  75. }
  76. }
  77. if (!is_resource($this->connection)) {
  78. throw new DibiDriverException('Connecting error.');
  79. }
  80. if (isset($config['charset'])) {
  81. DibiDriverException::tryError();
  82. pg_set_client_encoding($this->connection, $config['charset']);
  83. if (DibiDriverException::catchError($msg)) {
  84. throw new DibiDriverException($msg, 0);
  85. }
  86. }
  87. if (isset($config['schema'])) {
  88. $this->query('SET search_path TO "' . $config['schema'] . '"');
  89. }
  90. $this->escMethod = version_compare(PHP_VERSION , '5.2.0', '>=');
  91. }
  92. /**
  93. * Disconnects from a database.
  94. * @return void
  95. */
  96. public function disconnect()
  97. {
  98. pg_close($this->connection);
  99. }
  100. /**
  101. * Executes the SQL query.
  102. * @param string SQL statement.
  103. * @return IDibiResultDriver|NULL
  104. * @throws DibiDriverException
  105. */
  106. public function query($sql)
  107. {
  108. $this->affectedRows = FALSE;
  109. $res = @pg_query($this->connection, $sql); // intentionally @
  110. if ($res === FALSE) {
  111. throw new DibiDriverException(pg_last_error($this->connection), 0, $sql);
  112. } elseif (is_resource($res)) {
  113. $this->affectedRows = pg_affected_rows($res);
  114. if (pg_num_fields($res)) {
  115. return $this->createResultDriver($res);
  116. }
  117. }
  118. }
  119. /**
  120. * Gets the number of affected rows by the last INSERT, UPDATE or DELETE query.
  121. * @return int|FALSE number of rows or FALSE on error
  122. */
  123. public function getAffectedRows()
  124. {
  125. return $this->affectedRows;
  126. }
  127. /**
  128. * Retrieves the ID generated for an AUTO_INCREMENT column by the previous INSERT query.
  129. * @return int|FALSE int on success or FALSE on failure
  130. */
  131. public function getInsertId($sequence)
  132. {
  133. if ($sequence === NULL) {
  134. // PostgreSQL 8.1 is needed
  135. $res = $this->query("SELECT LASTVAL()");
  136. } else {
  137. $res = $this->query("SELECT CURRVAL('$sequence')");
  138. }
  139. if (!$res) return FALSE;
  140. $row = $res->fetch(FALSE);
  141. return is_array($row) ? $row[0] : FALSE;
  142. }
  143. /**
  144. * Begins a transaction (if supported).
  145. * @param string optional savepoint name
  146. * @return void
  147. * @throws DibiDriverException
  148. */
  149. public function begin($savepoint = NULL)
  150. {
  151. $this->query($savepoint ? "SAVEPOINT $savepoint" : 'START TRANSACTION');
  152. }
  153. /**
  154. * Commits statements in a transaction.
  155. * @param string optional savepoint name
  156. * @return void
  157. * @throws DibiDriverException
  158. */
  159. public function commit($savepoint = NULL)
  160. {
  161. $this->query($savepoint ? "RELEASE SAVEPOINT $savepoint" : 'COMMIT');
  162. }
  163. /**
  164. * Rollback changes in a transaction.
  165. * @param string optional savepoint name
  166. * @return void
  167. * @throws DibiDriverException
  168. */
  169. public function rollback($savepoint = NULL)
  170. {
  171. $this->query($savepoint ? "ROLLBACK TO SAVEPOINT $savepoint" : 'ROLLBACK');
  172. }
  173. /**
  174. * Is in transaction?
  175. * @return bool
  176. */
  177. public function inTransaction()
  178. {
  179. return !in_array(pg_transaction_status($this->connection), array(PGSQL_TRANSACTION_UNKNOWN, PGSQL_TRANSACTION_IDLE), TRUE);
  180. }
  181. /**
  182. * Returns the connection resource.
  183. * @return mixed
  184. */
  185. public function getResource()
  186. {
  187. return $this->connection;
  188. }
  189. /**
  190. * Returns the connection reflector.
  191. * @return IDibiReflector
  192. */
  193. public function getReflector()
  194. {
  195. return $this;
  196. }
  197. /**
  198. * Result set driver factory.
  199. * @param resource
  200. * @return IDibiResultDriver
  201. */
  202. public function createResultDriver($resource)
  203. {
  204. $res = clone $this;
  205. $res->resultSet = $resource;
  206. return $res;
  207. }
  208. /********************* SQL ****************d*g**/
  209. /**
  210. * Encodes data for use in a SQL statement.
  211. * @param mixed value
  212. * @param string type (dibi::TEXT, dibi::BOOL, ...)
  213. * @return string encoded value
  214. * @throws InvalidArgumentException
  215. */
  216. public function escape($value, $type)
  217. {
  218. switch ($type) {
  219. case dibi::TEXT:
  220. if ($this->escMethod) {
  221. return "'" . pg_escape_string($this->connection, $value) . "'";
  222. } else {
  223. return "'" . pg_escape_string($value) . "'";
  224. }
  225. case dibi::BINARY:
  226. if ($this->escMethod) {
  227. return "'" . pg_escape_bytea($this->connection, $value) . "'";
  228. } else {
  229. return "'" . pg_escape_bytea($value) . "'";
  230. }
  231. case dibi::IDENTIFIER:
  232. // @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
  233. return '"' . str_replace('"', '""', $value) . '"';
  234. case dibi::BOOL:
  235. return $value ? 'TRUE' : 'FALSE';
  236. case dibi::DATE:
  237. return $value instanceof DateTime ? $value->format("'Y-m-d'") : date("'Y-m-d'", $value);
  238. case dibi::DATETIME:
  239. return $value instanceof DateTime ? $value->format("'Y-m-d H:i:s'") : date("'Y-m-d H:i:s'", $value);
  240. default:
  241. throw new InvalidArgumentException('Unsupported type.');
  242. }
  243. }
  244. /**
  245. * Encodes string for use in a LIKE statement.
  246. * @param string
  247. * @param int
  248. * @return string
  249. */
  250. public function escapeLike($value, $pos)
  251. {
  252. if ($this->escMethod) {
  253. $value = pg_escape_string($this->connection, $value);
  254. } else {
  255. $value = pg_escape_string($value);
  256. }
  257. $value = strtr($value, array( '%' => '\\\\%', '_' => '\\\\_'));
  258. return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
  259. }
  260. /**
  261. * Decodes data from result set.
  262. * @param string value
  263. * @param string type (dibi::BINARY)
  264. * @return string decoded value
  265. * @throws InvalidArgumentException
  266. */
  267. public function unescape($value, $type)
  268. {
  269. if ($type === dibi::BINARY) {
  270. return pg_unescape_bytea($value);
  271. }
  272. throw new InvalidArgumentException('Unsupported type.');
  273. }
  274. /**
  275. * Injects LIMIT/OFFSET to the SQL query.
  276. * @param string &$sql The SQL query that will be modified.
  277. * @param int $limit
  278. * @param int $offset
  279. * @return void
  280. */
  281. public function applyLimit(&$sql, $limit, $offset)
  282. {
  283. if ($limit >= 0)
  284. $sql .= ' LIMIT ' . (int) $limit;
  285. if ($offset > 0)
  286. $sql .= ' OFFSET ' . (int) $offset;
  287. }
  288. /********************* result set ****************d*g**/
  289. /**
  290. * Automatically frees the resources allocated for this result set.
  291. * @return void
  292. */
  293. public function __destruct()
  294. {
  295. $this->resultSet && @$this->free();
  296. }
  297. /**
  298. * Returns the number of rows in a result set.
  299. * @return int
  300. */
  301. public function getRowCount()
  302. {
  303. return pg_num_rows($this->resultSet);
  304. }
  305. /**
  306. * Fetches the row at current position and moves the internal cursor to the next position.
  307. * @param bool TRUE for associative array, FALSE for numeric
  308. * @return array array on success, nonarray if no next record
  309. */
  310. public function fetch($assoc)
  311. {
  312. return pg_fetch_array($this->resultSet, NULL, $assoc ? PGSQL_ASSOC : PGSQL_NUM);
  313. }
  314. /**
  315. * Moves cursor position without fetching row.
  316. * @param int the 0-based cursor pos to seek to
  317. * @return boolean TRUE on success, FALSE if unable to seek to specified record
  318. */
  319. public function seek($row)
  320. {
  321. return pg_result_seek($this->resultSet, $row);
  322. }
  323. /**
  324. * Frees the resources allocated for this result set.
  325. * @return void
  326. */
  327. public function free()
  328. {
  329. pg_free_result($this->resultSet);
  330. $this->resultSet = NULL;
  331. }
  332. /**
  333. * Returns metadata for all columns in a result set.
  334. * @return array
  335. */
  336. public function getResultColumns()
  337. {
  338. $hasTable = version_compare(PHP_VERSION , '5.2.0', '>=');
  339. $count = pg_num_fields($this->resultSet);
  340. $columns = array();
  341. for ($i = 0; $i < $count; $i++) {
  342. $row = array(
  343. 'name' => pg_field_name($this->resultSet, $i),
  344. 'table' => $hasTable ? pg_field_table($this->resultSet, $i) : NULL,
  345. 'nativetype'=> pg_field_type($this->resultSet, $i),
  346. );
  347. $row['fullname'] = $row['table'] ? $row['table'] . '.' . $row['name'] : $row['name'];
  348. $columns[] = $row;
  349. }
  350. return $columns;
  351. }
  352. /**
  353. * Returns the result set resource.
  354. * @return mixed
  355. */
  356. public function getResultResource()
  357. {
  358. return $this->resultSet;
  359. }
  360. /********************* IDibiReflector ****************d*g**/
  361. /**
  362. * Returns list of tables.
  363. * @return array
  364. */
  365. public function getTables()
  366. {
  367. $version = pg_version($this->connection);
  368. if ($version['server'] < 8) {
  369. throw new DibiDriverException('Reflection requires PostgreSQL 8.');
  370. }
  371. $res = $this->query("
  372. SELECT table_name as name, CAST(table_type = 'VIEW' AS INTEGER) as view
  373. FROM information_schema.tables
  374. WHERE table_schema = current_schema()
  375. ");
  376. $tables = pg_fetch_all($res->resultSet);
  377. return $tables ? $tables : array();
  378. }
  379. /**
  380. * Returns metadata for all columns in a table.
  381. * @param string
  382. * @return array
  383. */
  384. public function getColumns($table)
  385. {
  386. $_table = $this->escape($table, dibi::TEXT);
  387. $res = $this->query("
  388. SELECT indkey
  389. FROM pg_class
  390. LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid AND pg_index.indisprimary
  391. WHERE pg_class.relname = $_table
  392. ");
  393. $primary = (int) pg_fetch_object($res->resultSet)->indkey;
  394. $res = $this->query("
  395. SELECT *
  396. FROM information_schema.columns
  397. WHERE table_name = $_table AND table_schema = current_schema()
  398. ORDER BY ordinal_position
  399. ");
  400. $columns = array();
  401. while ($row = $res->fetch(TRUE)) {
  402. $size = (int) max($row['character_maximum_length'], $row['numeric_precision']);
  403. $columns[] = array(
  404. 'name' => $row['column_name'],
  405. 'table' => $table,
  406. 'nativetype' => strtoupper($row['udt_name']),
  407. 'size' => $size ? $size : NULL,
  408. 'nullable' => $row['is_nullable'] === 'YES',
  409. 'default' => $row['column_default'],
  410. 'autoincrement' => (int) $row['ordinal_position'] === $primary && substr($row['column_default'], 0, 7) === 'nextval',
  411. 'vendor' => $row,
  412. );
  413. }
  414. return $columns;
  415. }
  416. /**
  417. * Returns metadata for all indexes in a table.
  418. * @param string
  419. * @return array
  420. */
  421. public function getIndexes($table)
  422. {
  423. $_table = $this->escape($table, dibi::TEXT);
  424. $res = $this->query("
  425. SELECT ordinal_position, column_name
  426. FROM information_schema.columns
  427. WHERE table_name = $_table AND table_schema = current_schema()
  428. ORDER BY ordinal_position
  429. ");
  430. $columns = array();
  431. while ($row = $res->fetch(TRUE)) {
  432. $columns[$row['ordinal_position']] = $row['column_name'];
  433. }
  434. $res = $this->query("
  435. SELECT pg_class2.relname, indisunique, indisprimary, indkey
  436. FROM pg_class
  437. LEFT JOIN pg_index on pg_class.oid = pg_index.indrelid
  438. INNER JOIN pg_class as pg_class2 on pg_class2.oid = pg_index.indexrelid
  439. WHERE pg_class.relname = $_table
  440. ");
  441. $indexes = array();
  442. while ($row = $res->fetch(TRUE)) {
  443. $indexes[$row['relname']]['name'] = $row['relname'];
  444. $indexes[$row['relname']]['unique'] = $row['indisunique'] === 't';
  445. $indexes[$row['relname']]['primary'] = $row['indisprimary'] === 't';
  446. foreach (explode(' ', $row['indkey']) as $index) {
  447. $indexes[$row['relname']]['columns'][] = $columns[$index];
  448. }
  449. }
  450. return array_values($indexes);
  451. }
  452. /**
  453. * Returns metadata for all foreign keys in a table.
  454. * @param string
  455. * @return array
  456. */
  457. public function getForeignKeys($table)
  458. {
  459. throw new DibiNotImplementedException;
  460. }
  461. }