Firebird.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category ZendX
  16. * @package ZendX_Db
  17. * @subpackage Adapter
  18. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id: $
  21. */
  22. /**
  23. * @see Zend_Db_Adapter_Abstract
  24. */
  25. require_once 'Zend/Db/Adapter/Abstract.php';
  26. /**
  27. * @see Zend_Db_Profiler
  28. */
  29. require_once 'Zend/Db/Profiler.php';
  30. /**
  31. * @see Zend_Db_Select
  32. */
  33. require_once 'Zend/Db/Select.php';
  34. /**
  35. * @see Zend_Db_Statement_Firebird
  36. */
  37. require_once 'ZendX/Db/Statement/Firebird.php';
  38. /**
  39. * @category ZendX
  40. * @package ZendX_Db
  41. * @subpackage Adapter
  42. * @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
  43. * @license http://framework.zend.com/license/new-bsd New BSD License
  44. */
  45. class ZendX_Db_Adapter_Firebird extends Zend_Db_Adapter_Abstract
  46. {
  47. /**
  48. * Specifies whether the adapter automatically quotes identifiers.
  49. * If true, most SQL generated by Zend_Db classes applies
  50. * identifier quoting automatically.
  51. * If false, developer must quote identifiers themselves
  52. * by calling quoteIdentifier().
  53. *
  54. * @var bool
  55. */
  56. protected $_autoQuoteIdentifiers = true;
  57. /**
  58. * The transaction resource.
  59. *
  60. * @var transaction
  61. */
  62. protected $_transResource = null;
  63. /**
  64. * Return the status of current transaction.
  65. * @return bool
  66. */
  67. public function getTransaction()
  68. {
  69. return (is_resource($this->_transResource) ? $this->_transResource : null);
  70. }
  71. /**
  72. * Keys are UPPERCASE SQL datatypes or the constants
  73. * Zend_Db::INT_TYPE, Zend_Db::BIGINT_TYPE, or Zend_Db::FLOAT_TYPE.
  74. *
  75. * Values are:
  76. * 0 = 32-bit integer
  77. * 1 = 64-bit integer
  78. * 2 = float or decimal
  79. *
  80. * @var array Associative array of datatypes to values 0, 1, or 2.
  81. */
  82. protected $_numericDataTypes = array(
  83. Zend_Db::INT_TYPE => Zend_Db::INT_TYPE,
  84. Zend_Db::BIGINT_TYPE => Zend_Db::BIGINT_TYPE,
  85. Zend_Db::FLOAT_TYPE => Zend_Db::FLOAT_TYPE,
  86. 'SMALLINT' => Zend_Db::INT_TYPE,
  87. 'INT' => Zend_Db::INT_TYPE,
  88. 'INTEGER' => Zend_Db::INT_TYPE,
  89. 'BIGINT' => Zend_Db::BIGINT_TYPE,
  90. 'INT64' => Zend_Db::BIGINT_TYPE,
  91. 'DECIMAL' => Zend_Db::FLOAT_TYPE,
  92. 'DOUBLE PRECISION' => Zend_Db::FLOAT_TYPE,
  93. 'DOUBLE' => Zend_Db::FLOAT_TYPE,
  94. 'NUMERIC' => Zend_Db::FLOAT_TYPE,
  95. 'FLOAT' => Zend_Db::FLOAT_TYPE
  96. );
  97. /**
  98. * Quote a raw string.
  99. *
  100. * @param string $value Raw string
  101. * @return string Quoted string
  102. */
  103. protected function _quote($value)
  104. {
  105. if (is_int($value) || is_float($value)) {
  106. return $value;
  107. }
  108. $value = str_replace("'", "''", $value);
  109. return "'" . $value . "'";
  110. }
  111. /**
  112. * Returns a list of the tables in the database.
  113. *
  114. * @return array
  115. */
  116. public function listTables()
  117. {
  118. $data = $this->fetchCol('SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG = 0');
  119. foreach($data as &$v)
  120. $v = trim($v);
  121. return $data;
  122. }
  123. /**
  124. * Returns the column descriptions for a table.
  125. *
  126. * The return value is an associative array keyed by the column name,
  127. * as returned by the RDBMS.
  128. *
  129. * The value of each array element is an associative array
  130. * with the following keys:
  131. *
  132. * SCHEMA_NAME => string; name of database or schema
  133. * TABLE_NAME => string;
  134. * COLUMN_NAME => string; column name
  135. * COLUMN_POSITION => number; ordinal position of column in table
  136. * DATA_TYPE => string; SQL datatype name of column
  137. * DEFAULT => string; default expression of column, null if none
  138. * NULLABLE => boolean; true if column can have nulls
  139. * LENGTH => number; length of CHAR/VARCHAR
  140. * SCALE => number; scale of NUMERIC/DECIMAL
  141. * PRECISION => number; precision of NUMERIC/DECIMAL
  142. * UNSIGNED => boolean; unsigned property of an integer type
  143. * PRIMARY => boolean; true if column is part of the primary key
  144. * PRIMARY_POSITION => integer; position of column in primary key
  145. * IDENTITY => integer; true if column is auto-generated with unique values
  146. *
  147. * @param string $tableName
  148. * @param string $schemaName OPTIONAL
  149. * @return array
  150. */
  151. public function describeTable($tableName, $schemaName = null)
  152. {
  153. $fieldMaps = array(
  154. 'TEXT' => 'CHAR',
  155. 'VARYING' => 'VARCHAR',
  156. 'SHORT' => 'SMALLINT',
  157. 'LONG' => 'INTEGER',
  158. 'FLOAT' => 'FLOAT',
  159. 'INT64' => array(0 => 'BIGINT', 'NUMERIC', 'DECIMAL'),
  160. 'DATE' => 'DATE',
  161. 'TIME' => 'TIME',
  162. 'BLOB' => 'BLOB',
  163. 'DOUBLE' => 'DOUBLE PRECISION',
  164. 'TIMESTAMP' => 'TIMESTAMP'
  165. );
  166. $sql = 'select
  167. RF.RDB$RELATION_NAME, \'\', RF.RDB$FIELD_NAME, T.RDB$TYPE_NAME,
  168. RF.RDB$DEFAULT_VALUE, RF.RDB$NULL_FLAG, RF.RDB$FIELD_POSITION,
  169. F.RDB$CHARACTER_LENGTH, F.RDB$FIELD_SCALE, F.RDB$FIELD_PRECISION,
  170. IXS.RDB$FIELD_POSITION, IXS.RDB$FIELD_POSITION, F.RDB$FIELD_SUB_TYPE
  171. from RDB$RELATION_FIELDS RF
  172. left join RDB$RELATION_CONSTRAINTS RC
  173. on (RF.RDB$RELATION_NAME = RC.RDB$RELATION_NAME and RC.RDB$CONSTRAINT_TYPE = \'PRIMARY KEY\')
  174. left join RDB$INDEX_SEGMENTS IXS
  175. on (IXS.RDB$FIELD_NAME = RF.RDB$FIELD_NAME and RC.RDB$INDEX_NAME = IXS.RDB$INDEX_NAME)
  176. inner join RDB$FIELDS F on (RF.RDB$FIELD_SOURCE = F.RDB$FIELD_NAME)
  177. inner join RDB$TYPES T on (T.RDB$TYPE = F.RDB$FIELD_TYPE and T.RDB$FIELD_NAME = \'RDB$FIELD_TYPE\')
  178. where ' . $this->quoteInto('(UPPER(RF.RDB$RELATION_NAME) = UPPER(?)) ', $tableName) . '
  179. order by RF.RDB$FIELD_POSITION';
  180. $stmt = $this->query($sql);
  181. /**
  182. * Use FETCH_NUM so we are not dependent on the CASE attribute of the PDO connection
  183. */
  184. $result = $stmt->fetchAll(Zend_Db::FETCH_NUM);
  185. $table_name = 0;
  186. $owner = 1;
  187. $column_name = 2;
  188. $data_type = 3;
  189. $data_default = 4;
  190. $nullable = 5;
  191. $column_id = 6;
  192. $data_length = 7;
  193. $data_scale = 8;
  194. $data_precision = 9;
  195. $constraint_type = 10;
  196. $position = 11;
  197. $sub_type = 12;
  198. $desc = array();
  199. foreach ($result as $key => $row) {
  200. list ($primary, $primaryPosition, $identity) = array(false, null, false);
  201. if (strlen($row[$constraint_type])) {
  202. $primary = true;
  203. $primaryPosition = $row[$position];
  204. /**
  205. * Firebird does not support auto-increment keys.
  206. */
  207. $identity = false;
  208. }
  209. $dataType = trim($row[$data_type]);
  210. $newType = $fieldMaps[$dataType];
  211. if (is_array($newType) && $dataType == 'INT64')
  212. $newType = $newType[$row[$sub_type]];
  213. $row[$data_type] = $newType;
  214. $desc[trim($row[$column_name])] = array(
  215. 'SCHEMA_NAME' => '',
  216. 'TABLE_NAME' => trim($row[$table_name]),
  217. 'COLUMN_NAME' => trim($row[$column_name]),
  218. 'COLUMN_POSITION' => $row[$column_id] +1,
  219. 'DATA_TYPE' => $row[$data_type],
  220. 'DEFAULT' => $row[$data_default],
  221. 'NULLABLE' => (bool) ($row[$nullable] != '1'),
  222. 'LENGTH' => $row[$data_length],
  223. 'SCALE' => ($row[$data_scale] == 0 ? null : $row[$data_scale]),
  224. 'PRECISION' => ($row[$data_precision] == 0 ? null : $row[$data_precision]),
  225. 'UNSIGNED' => false,
  226. 'PRIMARY' => $primary,
  227. 'PRIMARY_POSITION' => ($primary ? $primaryPosition+1 : null),
  228. 'IDENTITY' => $identity
  229. );
  230. }
  231. return $desc;
  232. }
  233. /**
  234. * Format a connection string to connect to database
  235. *
  236. * @return void
  237. */
  238. protected function _formatDbConnString($host, $port, $dbname)
  239. {
  240. if (is_numeric($port))
  241. $port = '/' . (integer) $port;
  242. if ($dbname)
  243. $dbname = ':' . $dbname;
  244. return $host . $port . $dbname;
  245. }
  246. /**
  247. * Creates a connection to the database.
  248. *
  249. * @return void
  250. * @throws ZendX_Db_Adapter_Firebird_Exception
  251. */
  252. protected function _connect()
  253. {
  254. if (isset($this->_connection) && is_resource($this->_connection)) {
  255. return;
  256. }
  257. if (!extension_loaded('interbase')) {
  258. /**
  259. * @see ZendX_Db_Adapter_Firebird_Exception
  260. */
  261. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  262. throw new ZendX_Db_Adapter_Firebird_Exception('The Interbase extension is required for this adapter but the extension is not loaded');
  263. }
  264. // Suppress connection warnings here.
  265. // Throw an exception instead.
  266. $this->_connection = @ibase_connect(
  267. $this->_formatDbConnString($this->_config['host'],$this->_config['port'] ,$this->_config['dbname']),
  268. $this->_config['username'],
  269. $this->_config['password'],
  270. $this->_config['charset'],
  271. $this->_config['buffers'],
  272. $this->_config['dialect'],
  273. $this->_config['role']
  274. );
  275. if ($this->_connection === false) {
  276. /**
  277. * @see ZendX_Db_Adapter_Firebird_Exception
  278. */
  279. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  280. throw new ZendX_Db_Adapter_Firebird_Exception(ibase_errmsg());
  281. }
  282. }
  283. /**
  284. * Force the connection to close.
  285. *
  286. * @return void
  287. */
  288. public function closeConnection()
  289. {
  290. if (is_resource($this->_transResource)) {
  291. ibase_rollback($this->_transResource);
  292. }
  293. $this->_transResource = null;
  294. if (is_resource($this->_connection)) {
  295. unset($this->_connection);
  296. }
  297. //$this->_connection = false;
  298. }
  299. /**
  300. * Prepare a statement and return a Statement resource.
  301. *
  302. * @param string $sql SQL query
  303. * @return ZendX_Db_Statement_Firebird
  304. */
  305. public function prepare($sql)
  306. {
  307. $this->_connect();
  308. $stmt = new ZendX_Db_Statement_Firebird($this, $sql);
  309. if ($stmt === false) {
  310. return false;
  311. }
  312. $stmt->setFetchMode($this->_fetchMode);
  313. return $stmt;
  314. }
  315. /**
  316. * Gets the last ID generated automatically by an IDENTITY/AUTOINCREMENT column.
  317. *
  318. * As a convention, on RDBMS brands that support sequences
  319. * (e.g. Firebird, Oracle, PostgreSQL, DB2), this method forms the name of a sequence
  320. * from the arguments and returns the last id generated by that sequence.
  321. * On RDBMS brands that support IDENTITY/AUTOINCREMENT columns, this method
  322. * returns the last value generated for such a column, and the table name
  323. * argument is disregarded.
  324. *
  325. * Firebird does not support IDENTITY columns, so if the sequence is not
  326. * specified, this method returns null.
  327. *
  328. * @param string $tableName OPTIONAL Name of table.
  329. * @param string $primaryKey OPTIONAL Name of primary key column.
  330. * @return string
  331. * @throws ZendX_Db_Adapter_Firebird_Exception
  332. */
  333. public function lastInsertId($tableName = null, $primaryKey = null)
  334. {
  335. if ($tableName !== null) {
  336. $sequenceName = $tableName;
  337. if ($primaryKey) {
  338. $sequenceName .= "_$primaryKey";
  339. }
  340. $sequenceName .= '_seq';
  341. return $this->lastSequenceId($sequenceName);
  342. }
  343. // No support for IDENTITY columns; return null
  344. return null;
  345. }
  346. /**
  347. * Begin a transaction.
  348. *
  349. * @return void
  350. */
  351. protected function _beginTransaction()
  352. {
  353. $this->_connect();
  354. if (is_resource($this->_transResource)){
  355. return;
  356. }
  357. $this->_transResource = ibase_trans(IBASE_DEFAULT, $this->_connection);
  358. }
  359. /**
  360. * Commit a transaction.
  361. *
  362. * @return void
  363. */
  364. protected function _commit()
  365. {
  366. if (false === ibase_commit(is_resource($this->_transResource) ? $this->_transResource : $this->_connection)) {
  367. /**
  368. * @see ZendX_Db_Adapter_Firebird_Exception
  369. */
  370. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  371. throw new ZendX_Db_Adapter_Firebird_Exception(ibase_errmsg());
  372. }
  373. $this->_transResource = null;
  374. }
  375. /**
  376. * Roll-back a transaction.
  377. *
  378. * @return void
  379. */
  380. protected function _rollBack()
  381. {
  382. if (false === ibase_rollback(is_resource($this->_transResource) ? $this->_transResource : $this->_connection)) {
  383. /**
  384. * @see ZendX_Db_Adapter_Firebird_Exception
  385. */
  386. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  387. throw new ZendX_Db_Adapter_Firebird_Exception(ibase_errmsg());
  388. }
  389. $this->_transResource = null;
  390. }
  391. /**
  392. * Set the fetch mode.
  393. *
  394. * @param int $mode
  395. * @return void
  396. */
  397. public function setFetchMode($mode)
  398. {
  399. switch ($mode) {
  400. case Zend_Db::FETCH_LAZY:
  401. case Zend_Db::FETCH_ASSOC:
  402. case Zend_Db::FETCH_NUM:
  403. case Zend_Db::FETCH_BOTH:
  404. case Zend_Db::FETCH_NAMED:
  405. case Zend_Db::FETCH_OBJ:
  406. case Zend_Db::FETCH_BOUND: // bound to PHP variable
  407. $this->_fetchMode = $mode;
  408. break;
  409. default:
  410. /**
  411. * @see ZendX_Db_Adapter_Firebird_Exception
  412. */
  413. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  414. throw new ZendX_Db_Adapter_Firebird_Exception("Invalid fetch mode '$mode' specified");
  415. }
  416. }
  417. /**
  418. * Adds an adapter-specific LIMIT clause to the SELECT statement.
  419. *
  420. * @param string $sql
  421. * @param integer $count
  422. * @param integer $offset
  423. * @throws Zend_Db_Adapter_Exception
  424. * @return string
  425. */
  426. public function limit($sql, $count, $offset = 0)
  427. {
  428. $count = intval($count);
  429. if ($count <= 0) {
  430. /**
  431. * @see ZendX_Db_Adapter_Firebird_Exception
  432. */
  433. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  434. throw new ZendX_Db_Adapter_Firebird_Exception("LIMIT argument count=$count is not valid");
  435. }
  436. $offset = intval($offset);
  437. if ($offset < 0) {
  438. /**
  439. * @see ZendX_Db_Adapter_Firebird_Exception
  440. */
  441. require_once 'ZendX/Db/Adapter/Firebird/Exception.php';
  442. throw new ZendX_Db_Adapter_Firebird_Exception("LIMIT argument offset=$offset is not valid");
  443. }
  444. if (trim($sql) == ''){
  445. //Only compatible with FB 2.0 or newer
  446. //ZF 1.5.0 don't support limit sql syntax that don't only append texto to sql, fixed in 1.5.1
  447. $sql .= " rows $count";
  448. if ($offset > 0)
  449. $sql .= " to $offset";
  450. }
  451. else
  452. $sql = substr_replace($sql, "select first $count skip $offset ", stripos($sql, 'select'), 6);
  453. return $sql;
  454. }
  455. /**
  456. * Quote a table identifier and alias.
  457. *
  458. * @param string|array|Zend_Db_Expr $ident The identifier or expression.
  459. * @param string $alias An alias for the table.
  460. * @param boolean $auto If true, heed the AUTO_QUOTE_IDENTIFIERS config option.
  461. * @return string The quoted identifier and alias.
  462. */
  463. public function quoteTableAs($ident, $alias = null, $auto=false)
  464. {
  465. // Firebird doesn't allow the 'AS' keyword between the table identifier/expression and alias.
  466. return $this->_quoteIdentifierAs($ident, $alias, $auto, ' ');
  467. }
  468. /**
  469. * Return the most recent value from the specified sequence in the database.
  470. * This is supported only on RDBMS brands that support sequences
  471. * (e.g. Firebird, Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
  472. *
  473. * @param string $sequenceName
  474. * @return string
  475. */
  476. public function lastSequenceId($sequenceName)
  477. {
  478. $this->_connect();
  479. $sql = 'SELECT GEN_ID('.$this->quoteIdentifier($sequenceName).', 0) FROM RDB$DATABASE';
  480. $value = $this->fetchOne($sql);
  481. return $value;
  482. }
  483. /**
  484. * Generate a new value from the specified sequence in the database, and return it.
  485. * This is supported only on RDBMS brands that support sequences
  486. * (e.g. Firebird, Oracle, PostgreSQL, DB2). Other RDBMS brands return null.
  487. *
  488. * @param string $sequenceName
  489. * @return integer
  490. */
  491. public function nextSequenceId($sequenceName)
  492. {
  493. $this->_connect();
  494. $sql = 'SELECT GEN_ID('.$this->quoteIdentifier($sequenceName).', 1) FROM RDB$DATABASE';
  495. $value = $this->fetchOne($sql);
  496. return $value;
  497. }
  498. /**
  499. * Check if the adapter supports real SQL parameters.
  500. *
  501. * @param string $type 'positional' or 'named'
  502. * @return bool
  503. */
  504. public function supportsParameters($type)
  505. {
  506. switch ($type) {
  507. case 'positional':
  508. return true;
  509. case 'named':
  510. default:
  511. return false;
  512. }
  513. }
  514. /**
  515. * Test if a connection is active
  516. *
  517. * @return boolean
  518. */
  519. public function isConnected()
  520. {
  521. return ((bool) (is_resource($this->_connection)
  522. && get_resource_type($this->_connection) == 'Firebird/InterBase link'));
  523. }
  524. /**
  525. * Retrieve server version in PHP style
  526. *
  527. * @return string
  528. */
  529. public function getServerVersion()
  530. {
  531. $this->_connect();
  532. $service = ibase_service_attach($this->_formatDbConnString($this->_config['host'], $this->_config['port'], ''), $this->_config['username'], $this->_config['password']);
  533. if ($service != FALSE) {
  534. $server_info = ibase_server_info($service, IBASE_SVC_SERVER_VERSION);
  535. ibase_service_detach($service);
  536. $matches = null;
  537. if (preg_match('/((?:[0-9]{1,2}\.){1,3}[0-9]{1,2})/', $server_info, $matches)) {
  538. return $matches[1];
  539. } else {
  540. return null;
  541. }
  542. } else {
  543. return null;
  544. }
  545. }
  546. }