PageRenderTime 58ms CodeModel.GetById 27ms RepoModel.GetById 1ms app.codeStats 0ms

/Oracle.php

https://bitbucket.org/gregnuj/cakephp-2.x-oracle-datasource
PHP | 1190 lines | 1021 code | 26 blank | 143 comment | 13 complexity | 032ca60d5a2376f5540959ba7a50301c MD5 | raw file
  1. <?php
  2. /**
  3. * Oracle layer for DBO.
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  8. * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  9. *
  10. * Licensed under The MIT License
  11. * Redistributions of files must retain the above copyright notice.
  12. *
  13. * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
  14. * @link http://cakephp.org CakePHP(tm) Project
  15. * @package cake
  16. * @subpackage cake.cake.libs.model.datasources.dbo
  17. * @since CakePHP v 1.2.0.4041
  18. * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
  19. */
  20. App::uses('DboSource', 'Model/Datasource');
  21. /**
  22. * Oracle layer for DBO.
  23. *
  24. * Long description for class
  25. *
  26. * @package cake
  27. * @subpackage cake.cake.libs.model.datasources.dbo
  28. */
  29. class Oracle extends DboSource {
  30. /**
  31. * Configuration options
  32. *
  33. * @var array
  34. * @access public
  35. */
  36. var $config = array();
  37. /**
  38. * Alias
  39. *
  40. * @var string
  41. */
  42. var $alias = '';
  43. /**
  44. * Sequence names as introspected from the database
  45. */
  46. var $_sequences = array();
  47. /**
  48. * Transaction in progress flag
  49. *
  50. * @var boolean
  51. */
  52. var $__transactionStarted = false;
  53. /**
  54. * Column definitions
  55. *
  56. * @var array
  57. * @access public
  58. */
  59. var $columns = array(
  60. 'primary_key' => array('name' => ''),
  61. 'string' => array('name' => 'varchar2', 'limit' => '255'),
  62. 'text' => array('name' => 'varchar2'),
  63. 'integer' => array('name' => 'number'),
  64. 'float' => array('name' => 'float'),
  65. 'datetime' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'),
  66. 'timestamp' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'),
  67. 'time' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'),
  68. 'date' => array('name' => 'date', 'format' => 'Y-m-d H:i:s'),
  69. 'binary' => array('name' => 'bytea'),
  70. 'boolean' => array('name' => 'boolean'),
  71. 'number' => array('name' => 'number'),
  72. 'inet' => array('name' => 'inet'));
  73. /**
  74. * Connection object
  75. *
  76. * @var mixed
  77. * @access protected
  78. */
  79. var $connection;
  80. /**
  81. * Query limit
  82. *
  83. * @var int
  84. * @access protected
  85. */
  86. var $_limit = -1;
  87. /**
  88. * Query offset
  89. *
  90. * @var int
  91. * @access protected
  92. */
  93. var $_offset = 0;
  94. /**
  95. * Enter description here...
  96. *
  97. * @var unknown_type
  98. * @access protected
  99. */
  100. var $_map;
  101. /**
  102. * Current Row
  103. *
  104. * @var mixed
  105. * @access protected
  106. */
  107. var $_currentRow;
  108. /**
  109. * Number of rows
  110. *
  111. * @var int
  112. * @access protected
  113. */
  114. var $_numRows;
  115. /**
  116. * Query results
  117. *
  118. * @var mixed
  119. * @access protected
  120. */
  121. var $_results;
  122. /**
  123. * Last error issued by oci extension
  124. *
  125. * @var unknown_type
  126. */
  127. var $_error;
  128. /**
  129. * Base configuration settings for MySQL driver
  130. *
  131. * @var array
  132. */
  133. var $_baseConfig = array(
  134. 'persistent' => true,
  135. 'host' => 'localhost',
  136. 'login' => 'system',
  137. 'password' => '',
  138. 'database' => 'cake',
  139. 'nls_sort' => '',
  140. 'nls_sort' => ''
  141. );
  142. /**
  143. * Table-sequence map
  144. *
  145. * @var unknown_type
  146. */
  147. var $_sequenceMap = array();
  148. /**
  149. * Connects to the database using options in the given configuration array.
  150. *
  151. * @return boolean True if the database could be connected, else false
  152. * @access public
  153. */
  154. function connect() {
  155. $config = $this->config;
  156. $this->connected = false;
  157. $config['charset'] = !empty($config['charset']) ? $config['charset'] : null;
  158. if (!$config['persistent']) {
  159. $this->connection = ocilogon($config['login'], $config['password'], $config['database'], $config['charset']);
  160. } else {
  161. $this->connection = ociplogon($config['login'], $config['password'], $config['database'], $config['charset']);
  162. }
  163. if ($this->connection) {
  164. $this->connected = true;
  165. if (!empty($config['nls_sort'])) {
  166. $this->execute('ALTER SESSION SET NLS_SORT='.$config['nls_sort']);
  167. }
  168. if (!empty($config['nls_comp'])) {
  169. $this->execute('ALTER SESSION SET NLS_COMP='.$config['nls_comp']);
  170. }
  171. $this->execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
  172. } else {
  173. $this->connected = false;
  174. $this->_setError();
  175. return false;
  176. }
  177. return $this->connected;
  178. }
  179. /**
  180. * Keeps track of the most recent Oracle error
  181. *
  182. */
  183. function _setError($source = null, $clear = false) {
  184. if ($source) {
  185. $e = ocierror($source);
  186. } else {
  187. $e = ocierror();
  188. }
  189. $this->_error = $e['message'];
  190. if ($clear) {
  191. $this->_error = null;
  192. }
  193. }
  194. /**
  195. * Sets the encoding language of the session
  196. *
  197. * @param string $lang language constant
  198. * @return bool
  199. */
  200. function setEncoding($lang) {
  201. if (!$this->execute('ALTER SESSION SET NLS_LANGUAGE='.$lang)) {
  202. return false;
  203. }
  204. return true;
  205. }
  206. /**
  207. * Gets the current encoding language
  208. *
  209. * @return string language constant
  210. */
  211. function getEncoding() {
  212. $sql = 'SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER=\'NLS_LANGUAGE\'';
  213. if (!$this->execute($sql)) {
  214. return false;
  215. }
  216. if (!$row = $this->fetchRow()) {
  217. return false;
  218. }
  219. return $row[0]['VALUE'];
  220. }
  221. /**
  222. * Disconnects from database.
  223. *
  224. * @return boolean True if the database could be disconnected, else false
  225. * @access public
  226. */
  227. function disconnect() {
  228. if ($this->connection) {
  229. $this->connected = !ocilogoff($this->connection);
  230. return !$this->connected;
  231. }
  232. }
  233. /**
  234. * Scrape the incoming SQL to create the association map. This is an extremely
  235. * experimental method that creates the association maps since Oracle will not tell us.
  236. *
  237. * @param string $sql
  238. * @return false if sql is nor a SELECT
  239. * @access protected
  240. */
  241. function _scrapeSQL($sql) {
  242. $sql = str_replace("\"", '', $sql);
  243. $preFrom = preg_split('/\bFROM\b/', $sql);
  244. $preFrom = $preFrom[0];
  245. $find = array('SELECT');
  246. $replace = array('');
  247. $fieldList = trim(str_replace($find, $replace, $preFrom));
  248. $fields = preg_split('/,\s+/', $fieldList);//explode(', ', $fieldList);
  249. $lastTableName = '';
  250. foreach($fields as $key => $value) {
  251. if ($value != 'COUNT(*) AS count') {
  252. if (preg_match('/\s+(\w+(\.\w+)*)$/', $value, $matches)) {
  253. $fields[$key] = $matches[1];
  254. if (preg_match('/^(\w+\.)/', $value, $matches)) {
  255. $fields[$key] = $matches[1] . $fields[$key];
  256. $lastTableName = $matches[1];
  257. }
  258. }
  259. /*
  260. if (preg_match('/(([[:alnum:]_]+)\.[[:alnum:]_]+)(\s+AS\s+(\w+))?$/i', $value, $matches)) {
  261. $fields[$key] = isset($matches[4]) ? $matches[2] . '.' . $matches[4] : $matches[1];
  262. }
  263. */
  264. }
  265. }
  266. $this->_map = array();
  267. foreach($fields as $f) {
  268. $e = explode('.', $f);
  269. if (count($e) > 1) {
  270. $table = $e[0];
  271. $field = strtolower($e[1]);
  272. } else {
  273. $table = 0;
  274. $field = $e[0];
  275. }
  276. $this->_map[] = array($table, $field);
  277. }
  278. }
  279. /**
  280. * Modify a SQL query to limit (and offset) the result set
  281. *
  282. * @param integer $limit Maximum number of rows to return
  283. * @param integer $offset Row to begin returning
  284. * @return modified SQL Query
  285. * @access public
  286. */
  287. function limit($limit = -1, $offset = 0) {
  288. $this->_limit = (int) $limit;
  289. $this->_offset = (int) $offset;
  290. }
  291. /**
  292. * Returns number of rows in previous resultset. If no previous resultset exists,
  293. * this returns false.
  294. *
  295. * @return integer Number of rows in resultset
  296. * @access public
  297. */
  298. function lastNumRows() {
  299. return $this->_numRows;
  300. }
  301. /**
  302. * Executes given SQL statement. This is an overloaded method.
  303. *
  304. * @param string $sql SQL statement
  305. * @return resource Result resource identifier or null
  306. * @access protected
  307. */
  308. function _execute($sql) {
  309. $this->_statementId = ociparse($this->connection, $sql);
  310. if (!$this->_statementId) {
  311. $this->_setError($this->connection);
  312. return false;
  313. }
  314. if ($this->__transactionStarted) {
  315. $mode = OCI_DEFAULT;
  316. } else {
  317. $mode = OCI_COMMIT_ON_SUCCESS;
  318. }
  319. if (!ociexecute($this->_statementId, $mode)) {
  320. $this->_setError($this->_statementId);
  321. return false;
  322. }
  323. $this->_setError(null, true);
  324. switch(ocistatementtype($this->_statementId)) {
  325. case 'DESCRIBE':
  326. case 'SELECT':
  327. $this->_scrapeSQL($sql);
  328. break;
  329. default:
  330. return $this->_statementId;
  331. break;
  332. }
  333. if ($this->_limit >= 1) {
  334. ocisetprefetch($this->_statementId, $this->_limit);
  335. } else {
  336. ocisetprefetch($this->_statementId, 3000);
  337. }
  338. $this->_numRows = ocifetchstatement($this->_statementId, $this->_results, $this->_offset, $this->_limit, OCI_NUM | OCI_FETCHSTATEMENT_BY_ROW);
  339. $this->_currentRow = 0;
  340. $this->limit();
  341. return $this->_statementId;
  342. }
  343. /**
  344. * Fetch result row
  345. *
  346. * @return array
  347. * @access public
  348. */
  349. function fetchRow() {
  350. if ($this->_currentRow >= $this->_numRows) {
  351. #ocifreestatement($this->_statementId);
  352. $this->_statementId = null;
  353. $this->_map = null;
  354. $this->_results = null;
  355. $this->_currentRow = null;
  356. $this->_numRows = null;
  357. return false;
  358. }
  359. $resultRow = array();
  360. foreach($this->_results[$this->_currentRow] as $index => $field) {
  361. list($table, $column) = $this->_map[$index];
  362. if (strpos($column, ' count')) {
  363. $resultRow[0]['count'] = $field;
  364. } else {
  365. $resultRow[$table][$column] = $this->_results[$this->_currentRow][$index];
  366. }
  367. }
  368. $this->_currentRow++;
  369. return $resultRow;
  370. }
  371. /**
  372. * Fetches the next row from the current result set
  373. *
  374. * @return unknown
  375. */
  376. function fetchResult() {
  377. return $this->fetchRow();
  378. }
  379. /**
  380. * Checks to see if a named sequence exists
  381. *
  382. * @param string $sequence
  383. * @return bool
  384. * @access public
  385. */
  386. function sequenceExists($sequence) {
  387. $sql = "SELECT SEQUENCE_NAME FROM USER_SEQUENCES WHERE SEQUENCE_NAME = '$sequence'";
  388. if (!$this->execute($sql)) {
  389. return false;
  390. }
  391. return $this->fetchRow();
  392. }
  393. /**
  394. * Creates a database sequence
  395. *
  396. * @param string $sequence
  397. * @return bool
  398. * @access public
  399. */
  400. function createSequence($sequence) {
  401. $sql = "CREATE SEQUENCE $sequence";
  402. return $this->execute($sql);
  403. }
  404. /**
  405. * Create trigger
  406. *
  407. * @param string $table
  408. * @return mixed
  409. * @access public
  410. */
  411. function createTrigger($table) {
  412. $sql = "CREATE OR REPLACE TRIGGER pk_$table" . "_trigger BEFORE INSERT ON $table FOR EACH ROW BEGIN SELECT pk_$table.NEXTVAL INTO :NEW.ID FROM DUAL; END;";
  413. return $this->execute($sql);
  414. }
  415. /**
  416. * Returns an array of tables in the database. If there are no tables, an error is
  417. * raised and the application exits.
  418. *
  419. * @return array tablenames in the database
  420. * @access public
  421. */
  422. function listSources() {
  423. $cache = parent::listSources();
  424. if ($cache != null) {
  425. return $cache;
  426. }
  427. $sql = 'SELECT view_name AS name FROM all_views UNION SELECT table_name AS name FROM all_tables';
  428. if (!$this->execute($sql)) {
  429. return false;
  430. }
  431. $sources = array();
  432. while($r = $this->fetchRow()) {
  433. $sources[] = strtolower($r[0]['name']);
  434. }
  435. parent::listSources($sources);
  436. return $sources;
  437. }
  438. /**
  439. * Returns an array of the fields in given table name.
  440. *
  441. * @param object instance of a model to inspect
  442. * @return array Fields in table. Keys are name and type
  443. * @access public
  444. */
  445. public function describe(&$model) {
  446. $table = $this->fullTableName($model, false);
  447. if (!empty($model->sequence)) {
  448. $this->_sequenceMap[$table] = $model->sequence;
  449. } elseif (!empty($model->table)) {
  450. $this->_sequenceMap[$table] = $model->table . '_seq';
  451. }
  452. $cache = parent::describe($model);
  453. if ($cache != null) {
  454. return $cache;
  455. }
  456. $sql = 'SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM all_tab_columns WHERE table_name = \'';
  457. $sql .= strtoupper($this->fullTableName($model)) . '\'';
  458. if (!$this->execute($sql)) {
  459. return false;
  460. }
  461. $fields = array();
  462. for ($i = 0; $row = $this->fetchRow(); $i++) {
  463. $fields[strtolower($row[0]['COLUMN_NAME'])] = array(
  464. 'type'=> $this->column($row[0]['DATA_TYPE']),
  465. 'length'=> $row[0]['DATA_LENGTH']
  466. );
  467. }
  468. #$this->__cacheDescription($this->fullTableName($model, false), $fields);
  469. return $fields;
  470. }
  471. /**
  472. * Deletes all the records in a table and drops all associated auto-increment sequences.
  473. * Using DELETE instead of TRUNCATE because it causes locking problems.
  474. *
  475. * @param mixed $table A string or model class representing the table to be truncated
  476. * @param integer $reset If -1, sequences are dropped, if 0 (default), sequences are reset,
  477. * and if 1, sequences are not modified
  478. * @return boolean SQL TRUNCATE TABLE statement, false if not applicable.
  479. * @access public
  480. *
  481. */
  482. function truncate($table, $reset = 0) {
  483. if (empty($this->_sequences)) {
  484. $sql = "SELECT sequence_name FROM all_sequences";
  485. $this->execute($sql);
  486. while ($row = $this->fetchRow()) {
  487. $this->_sequences[] = strtolower($row[0]['sequence_name']);
  488. }
  489. }
  490. $this->execute('DELETE FROM ' . $this->fullTableName($table));
  491. if (!isset($this->_sequenceMap[$table]) || !in_array($this->_sequenceMap[$table], $this->_sequences)) {
  492. return true;
  493. }
  494. if ($reset === 0) {
  495. $this->execute("SELECT {$this->_sequenceMap[$table]}.nextval FROM dual");
  496. $row = $this->fetchRow();
  497. $currval = $row[$this->_sequenceMap[$table]]['nextval'];
  498. $this->execute("SELECT min_value FROM all_sequences WHERE sequence_name = '{$this->_sequenceMap[$table]}'");
  499. $row = $this->fetchRow();
  500. $min_value = $row[0]['min_value'];
  501. if ($min_value == 1) $min_value = 0;
  502. $offset = -($currval - $min_value);
  503. $this->execute("ALTER SEQUENCE {$this->_sequenceMap[$table]} INCREMENT BY $offset MINVALUE $min_value");
  504. $this->execute("SELECT {$this->_sequenceMap[$table]}.nextval FROM dual");
  505. $this->execute("ALTER SEQUENCE {$this->_sequenceMap[$table]} INCREMENT BY 1");
  506. } else {
  507. //$this->execute("DROP SEQUENCE {$this->_sequenceMap[$table]}");
  508. }
  509. return true;
  510. }
  511. /**
  512. * Enables, disables, and lists table constraints
  513. *
  514. * Note: This method could have been written using a subselect for each table,
  515. * however the effort Oracle expends to run the constraint introspection is very high.
  516. * Therefore, this method caches the result once and loops through the arrays to find
  517. * what it needs. It reduced my query time by 50%. YMMV.
  518. *
  519. * @param string $action
  520. * @param string $table
  521. * @return mixed boolean true or array of constraints
  522. */
  523. function constraint($action, $table) {
  524. if (empty($table)) {
  525. trigger_error(__('Must specify table to operate on constraints', true));
  526. }
  527. $table = strtoupper($table);
  528. if (empty($this->_keyConstraints)) {
  529. $sql = "SELECT
  530. table_name,
  531. c.constraint_name
  532. FROM all_cons_columns cc
  533. LEFT JOIN all_indexes i ON (cc.constraint_name = i.index_name)
  534. LEFT JOIN all_constraints c ON(c.constraint_name = cc.constraint_name)";
  535. $this->execute($sql);
  536. while ($row = $this->fetchRow()) {
  537. $this->_keyConstraints[] = array($row[0]['table_name'], $row['c']['constraint_name']);
  538. }
  539. }
  540. $relatedKeys = array();
  541. foreach ($this->_keyConstraints as $c) {
  542. if ($c[0] == $table) {
  543. $relatedKeys[] = $c[1];
  544. }
  545. }
  546. if (empty($this->_constraints)) {
  547. $sql = "SELECT
  548. table_name,
  549. constraint_name,
  550. r_constraint_name
  551. FROM
  552. all_constraints";
  553. $this->execute($sql);
  554. while ($row = $this->fetchRow()) {
  555. $this->_constraints[] = $row[0];
  556. }
  557. }
  558. $constraints = array();
  559. foreach ($this->_constraints as $c) {
  560. if (in_array($c['r_constraint_name'], $relatedKeys)) {
  561. $constraints[] = array($c['table_name'], $c['constraint_name']);
  562. }
  563. }
  564. foreach ($constraints as $c) {
  565. list($table, $constraint) = $c;
  566. switch ($action) {
  567. case 'enable':
  568. $this->execute("ALTER TABLE $table ENABLE CONSTRAINT $constraint");
  569. break;
  570. case 'disable':
  571. $this->execute("ALTER TABLE $table DISABLE CONSTRAINT $constraint");
  572. break;
  573. case 'list':
  574. return $constraints;
  575. break;
  576. default:
  577. trigger_error(__('DboOracle::constraint() accepts only enable, disable, or list', true));
  578. }
  579. }
  580. return true;
  581. }
  582. /**
  583. * Returns an array of the indexes in given table name.
  584. *
  585. * @param string $model Name of model to inspect
  586. * @return array Fields in table. Keys are column and unique
  587. */
  588. function index($model) {
  589. $index = array();
  590. $table = $this->fullTableName($model, false);
  591. if ($table) {
  592. $indexes = $this->query('SELECT
  593. cc.table_name,
  594. cc.column_name,
  595. cc.constraint_name,
  596. c.constraint_type,
  597. i.index_name,
  598. i.uniqueness
  599. FROM all_cons_columns cc
  600. LEFT JOIN all_indexes i ON(cc.constraint_name = i.index_name)
  601. LEFT JOIN all_constraints c ON(c.constraint_name = cc.constraint_name)
  602. WHERE cc.table_name = \'' . strtoupper($table) .'\'');
  603. foreach ($indexes as $i => $idx) {
  604. if ($idx['c']['constraint_type'] == 'P') {
  605. $key = 'PRIMARY';
  606. } else {
  607. continue;
  608. }
  609. if (!isset($index[$key])) {
  610. $index[$key]['column'] = strtolower($idx['cc']['column_name']);
  611. $index[$key]['unique'] = intval($idx['i']['uniqueness'] == 'UNIQUE');
  612. } else {
  613. if (!is_array($index[$key]['column'])) {
  614. $col[] = $index[$key]['column'];
  615. }
  616. $col[] = strtolower($idx['cc']['column_name']);
  617. $index[$key]['column'] = $col;
  618. }
  619. }
  620. }
  621. return $index;
  622. }
  623. /**
  624. * Generate a Oracle Alter Table syntax for the given Schema comparison
  625. *
  626. * @param unknown_type $schema
  627. * @return unknown
  628. */
  629. function alterSchema($compare, $table = null) {
  630. if (!is_array($compare)) {
  631. return false;
  632. }
  633. $out = '';
  634. $colList = array();
  635. foreach($compare as $curTable => $types) {
  636. if (!$table || $table == $curTable) {
  637. $out .= 'ALTER TABLE ' . $this->fullTableName($curTable) . " \n";
  638. foreach($types as $type => $column) {
  639. switch($type) {
  640. case 'add':
  641. foreach($column as $field => $col) {
  642. $col['name'] = $field;
  643. $alter = 'ADD '.$this->buildColumn($col);
  644. if (isset($col['after'])) {
  645. $alter .= ' AFTER '. $this->name($col['after']);
  646. }
  647. $colList[] = $alter;
  648. }
  649. break;
  650. case 'drop':
  651. foreach($column as $field => $col) {
  652. $col['name'] = $field;
  653. $colList[] = 'DROP '.$this->name($field);
  654. }
  655. break;
  656. case 'change':
  657. foreach($column as $field => $col) {
  658. if (!isset($col['name'])) {
  659. $col['name'] = $field;
  660. }
  661. $colList[] = 'CHANGE '. $this->name($field).' '.$this->buildColumn($col);
  662. }
  663. break;
  664. }
  665. }
  666. $out .= "\t" . implode(",\n\t", $colList) . ";\n\n";
  667. }
  668. }
  669. return $out;
  670. }
  671. /**
  672. * This method should quote Oracle identifiers. Well it doesn't.
  673. * It would break all scaffolding and all of Cake's default assumptions.
  674. *
  675. * @param unknown_type $var
  676. * @return unknown
  677. * @access public
  678. */
  679. function name($name) {
  680. if (strpos($name, '.') !== false && strpos($name, '"') === false) {
  681. list($model, $field) = explode('.', $name);
  682. if ($field[0] == "_") {
  683. $name = "$model.\"$field\"";
  684. }
  685. } else {
  686. if ($name[0] == "_") {
  687. $name = "\"$name\"";
  688. }
  689. }
  690. return $name;
  691. }
  692. /**
  693. * Begin a transaction
  694. *
  695. * @param unknown_type $model
  696. * @return boolean True on success, false on fail
  697. * (i.e. if the database/model does not support transactions).
  698. */
  699. function begin() {
  700. $this->__transactionStarted = true;
  701. return true;
  702. }
  703. /**
  704. * Rollback a transaction
  705. *
  706. * @param unknown_type $model
  707. * @return boolean True on success, false on fail
  708. * (i.e. if the database/model does not support transactions,
  709. * or a transaction has not started).
  710. */
  711. function rollback() {
  712. return ocirollback($this->connection);
  713. }
  714. /**
  715. * Commit a transaction
  716. *
  717. * @param unknown_type $model
  718. * @return boolean True on success, false on fail
  719. * (i.e. if the database/model does not support transactions,
  720. * or a transaction has not started).
  721. */
  722. function commit() {
  723. $this->__transactionStarted = false;
  724. return ocicommit($this->connection);
  725. }
  726. /**
  727. * Converts database-layer column types to basic types
  728. *
  729. * @param string $real Real database-layer column type (i.e. "varchar(255)")
  730. * @return string Abstract column type (i.e. "string")
  731. * @access public
  732. */
  733. function column($real) {
  734. if (is_array($real)) {
  735. $col = $real['name'];
  736. if (isset($real['limit'])) {
  737. $col .= '('.$real['limit'].')';
  738. }
  739. return $col;
  740. } else {
  741. $real = strtolower($real);
  742. }
  743. $col = str_replace(')', '', $real);
  744. $limit = null;
  745. if (strpos($col, '(') !== false) {
  746. list($col, $limit) = explode('(', $col);
  747. }
  748. if (in_array($col, array('date', 'timestamp'))) {
  749. return $col;
  750. }
  751. if (strpos($col, 'number') !== false) {
  752. return 'integer';
  753. }
  754. if (strpos($col, 'integer') !== false) {
  755. return 'integer';
  756. }
  757. if (strpos($col, 'char') !== false) {
  758. return 'string';
  759. }
  760. if (strpos($col, 'text') !== false) {
  761. return 'text';
  762. }
  763. if (strpos($col, 'blob') !== false) {
  764. return 'binary';
  765. }
  766. if (in_array($col, array('float', 'double', 'decimal'))) {
  767. return 'float';
  768. }
  769. if ($col == 'boolean') {
  770. return $col;
  771. }
  772. return 'text';
  773. }
  774. /**
  775. * Returns a quoted and escaped string of $data for use in an SQL statement.
  776. *
  777. * @param string $data String to be prepared for use in an SQL statement
  778. * @return string Quoted and escaped
  779. * @access public
  780. */
  781. function value($data, $column = null, $safe = false) {
  782. if (is_array($data) && !empty($data)) {
  783. return array_map(
  784. array(&$this, 'value'),
  785. $data, array_fill(0, count($data), $column)
  786. );
  787. } elseif (is_object($data) && isset($data->type, $data->value)) {
  788. if ($data->type == 'identifier') {
  789. return $this->name($data->value);
  790. } elseif ($data->type == 'expression') {
  791. return $data->value;
  792. }
  793. } elseif (in_array($data, array('{$__cakeID__$}', '{$__cakeForeignKey__$}'), true)) {
  794. return $data;
  795. }
  796. if ($data === null || (is_array($data) && empty($data))) {
  797. return 'NULL';
  798. }
  799. if (empty($column)) {
  800. $column = $this->introspectType($data);
  801. }
  802. switch ($column) {
  803. case 'date':
  804. $data = date('Y-m-d H:i:s', strtotime($data));
  805. $data = "TO_DATE('$data', 'YYYY-MM-DD HH24:MI:SS')";
  806. break;
  807. case 'binary':
  808. case 'integer' :
  809. case 'float' :
  810. case 'boolean':
  811. case 'string':
  812. case 'text':
  813. default:
  814. if ($data === '') {
  815. return 'NULL';
  816. } elseif (is_float($data)) {
  817. return str_replace(',', '.', strval($data));
  818. } elseif ((is_int($data) || $data === '0') || (
  819. is_numeric($data) && strpos($data, ',') === false &&
  820. $data[0] != '0' && strpos($data, 'e') === false)
  821. ) {
  822. return $data;
  823. }
  824. $data = str_replace("'", "''", $data);
  825. $data = "'$data'";
  826. return $data;
  827. break;
  828. }
  829. return $data;
  830. }
  831. /**
  832. * Returns the ID generated from the previous INSERT operation.
  833. *
  834. * @param string
  835. * @return integer
  836. * @access public
  837. */
  838. function lastInsertId($source) {
  839. $sequence = $this->_sequenceMap[$source];
  840. $sql = "SELECT $sequence.currval FROM dual";
  841. if (!$this->execute($sql)) {
  842. return false;
  843. }
  844. while($row = $this->fetchRow()) {
  845. return $row[$sequence]['currval'];
  846. }
  847. return false;
  848. }
  849. /**
  850. * Returns a formatted error message from previous database operation.
  851. *
  852. * @return string Error message with error number
  853. * @access public
  854. */
  855. function lastError() {
  856. return $this->_error;
  857. }
  858. /**
  859. * Returns number of affected rows in previous database operation. If no previous operation exists, this returns false.
  860. *
  861. * @return int Number of affected rows
  862. * @access public
  863. */
  864. function lastAffected() {
  865. return $this->_statementId ? ocirowcount($this->_statementId): false;
  866. }
  867. /**
  868. * Renders a final SQL statement by putting together the component parts in the correct order
  869. *
  870. * @param string $type
  871. * @param array $data
  872. * @return string
  873. */
  874. function renderStatement($type, $data) {
  875. extract($data);
  876. $aliases = null;
  877. switch (strtolower($type)) {
  878. case 'select':
  879. return "SELECT {$fields} FROM {$table} {$alias} {$joins} {$conditions} {$group} {$order} {$limit}";
  880. break;
  881. case 'create':
  882. return "INSERT INTO {$table} ({$fields}) VALUES ({$values})";
  883. break;
  884. case 'update':
  885. if (!empty($alias)) {
  886. $aliases = "{$this->alias}{$alias} ";
  887. }
  888. return "UPDATE {$table} {$aliases}SET {$fields} {$conditions}";
  889. break;
  890. case 'delete':
  891. if (!empty($alias)) {
  892. $aliases = "{$this->alias}{$alias} ";
  893. }
  894. return "DELETE FROM {$table} {$aliases}{$conditions}";
  895. break;
  896. case 'schema':
  897. foreach (array('columns', 'indexes') as $var) {
  898. if (is_array(${$var})) {
  899. ${$var} = "\t" . implode(",\n\t", array_filter(${$var}));
  900. }
  901. }
  902. if (trim($indexes) != '') {
  903. $columns .= ',';
  904. }
  905. return "CREATE TABLE {$table} (\n{$columns}{$indexes})";
  906. break;
  907. case 'alter':
  908. break;
  909. }
  910. }
  911. /**
  912. * Enter description here...
  913. *
  914. * @param Model $model
  915. * @param unknown_type $linkModel
  916. * @param string $type Association type
  917. * @param unknown_type $association
  918. * @param unknown_type $assocData
  919. * @param unknown_type $queryData
  920. * @param unknown_type $external
  921. * @param unknown_type $resultSet
  922. * @param integer $recursive Number of levels of association
  923. * @param array $stack
  924. */
  925. function queryAssociation(&$model, &$linkModel, $type, $association, $assocData, &$queryData, $external = false, &$resultSet, $recursive, $stack) {
  926. if ($query = $this->generateAssociationQuery($model, $linkModel, $type, $association, $assocData, $queryData, $external, $resultSet)) {
  927. if (!isset($resultSet) || !is_array($resultSet)) {
  928. if (Configure::read() > 0) {
  929. echo '<div style = "font: Verdana bold 12px; color: #FF0000">' . sprintf(__('SQL Error in model %s:', true), $model->alias) . ' ';
  930. if (isset($this->error) && $this->error != null) {
  931. echo $this->error;
  932. }
  933. echo '</div>';
  934. }
  935. return null;
  936. }
  937. $count = count($resultSet);
  938. if ($type === 'hasMany' && (!isset($assocData['limit']) || empty($assocData['limit']))) {
  939. $ins = $fetch = array();
  940. for ($i = 0; $i < $count; $i++) {
  941. if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
  942. $ins[] = $in;
  943. }
  944. }
  945. if (!empty($ins)) {
  946. $fetch = array();
  947. $ins = array_chunk($ins, 1000);
  948. foreach ($ins as $i) {
  949. $q = str_replace('{$__cakeID__$}', implode(', ', $i), $query);
  950. $q = str_replace('= (', 'IN (', $q);
  951. $res = $this->fetchAll($q, $model->cacheQueries, $model->alias);
  952. $fetch = array_merge($fetch, $res);
  953. }
  954. }
  955. if (!empty($fetch) && is_array($fetch)) {
  956. if ($recursive > 0) {
  957. foreach ($linkModel->__associations as $type1) {
  958. foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
  959. $deepModel =& $linkModel->{$assoc1};
  960. $tmpStack = $stack;
  961. $tmpStack[] = $assoc1;
  962. if ($linkModel->useDbConfig === $deepModel->useDbConfig) {
  963. $db =& $this;
  964. } else {
  965. $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
  966. }
  967. $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
  968. }
  969. }
  970. }
  971. }
  972. return $this->_mergeHasMany($resultSet, $fetch, $association, $model, $linkModel, $recursive);
  973. } elseif ($type === 'hasAndBelongsToMany') {
  974. $ins = $fetch = array();
  975. for ($i = 0; $i < $count; $i++) {
  976. if ($in = $this->insertQueryData('{$__cakeID__$}', $resultSet[$i], $association, $assocData, $model, $linkModel, $stack)) {
  977. $ins[] = $in;
  978. }
  979. }
  980. $foreignKey = $model->hasAndBelongsToMany[$association]['foreignKey'];
  981. $joinKeys = array($foreignKey, $model->hasAndBelongsToMany[$association]['associationForeignKey']);
  982. list($with, $habtmFields) = $model->joinModel($model->hasAndBelongsToMany[$association]['with'], $joinKeys);
  983. $habtmFieldsCount = count($habtmFields);
  984. if (!empty($ins)) {
  985. $fetch = array();
  986. $ins = array_chunk($ins, 1000);
  987. foreach ($ins as $i) {
  988. $q = str_replace('{$__cakeID__$}', '(' .implode(', ', $i) .')', $query);
  989. $q = str_replace('= (', 'IN (', $q);
  990. $q = str_replace(' WHERE 1 = 1', '', $q);
  991. $q = $this->insertQueryData($q, null, $association, $assocData, $model, $linkModel, $stack);
  992. if ($q != false) {
  993. $res = $this->fetchAll($q, $model->cacheQueries, $model->alias);
  994. $fetch = array_merge($fetch, $res);
  995. }
  996. }
  997. }
  998. }
  999. for ($i = 0; $i < $count; $i++) {
  1000. $row =& $resultSet[$i];
  1001. if ($type !== 'hasAndBelongsToMany') {
  1002. $q = $this->insertQueryData($query, $resultSet[$i], $association, $assocData, $model, $linkModel, $stack);
  1003. if ($q != false) {
  1004. $fetch = $this->fetchAll($q, $model->cacheQueries, $model->alias);
  1005. } else {
  1006. $fetch = null;
  1007. }
  1008. }
  1009. if (!empty($fetch) && is_array($fetch)) {
  1010. if ($recursive > 0) {
  1011. foreach ($linkModel->__associations as $type1) {
  1012. foreach ($linkModel->{$type1} as $assoc1 => $assocData1) {
  1013. $deepModel =& $linkModel->{$assoc1};
  1014. if (($type1 === 'belongsTo') || ($deepModel->alias === $model->alias && $type === 'belongsTo') || ($deepModel->alias != $model->alias)) {
  1015. $tmpStack = $stack;
  1016. $tmpStack[] = $assoc1;
  1017. if ($linkModel->useDbConfig == $deepModel->useDbConfig) {
  1018. $db =& $this;
  1019. } else {
  1020. $db =& ConnectionManager::getDataSource($deepModel->useDbConfig);
  1021. }
  1022. $db->queryAssociation($linkModel, $deepModel, $type1, $assoc1, $assocData1, $queryData, true, $fetch, $recursive - 1, $tmpStack);
  1023. }
  1024. }
  1025. }
  1026. }
  1027. if ($type == 'hasAndBelongsToMany') {
  1028. $merge = array();
  1029. foreach($fetch as $j => $data) {
  1030. if (isset($data[$with]) && $data[$with][$foreignKey] === $row[$model->alias][$model->primaryKey]) {
  1031. if ($habtmFieldsCount > 2) {
  1032. $merge[] = $data;
  1033. } else {
  1034. $merge[] = Set::diff($data, array($with => $data[$with]));
  1035. }
  1036. }
  1037. }
  1038. if (empty($merge) && !isset($row[$association])) {
  1039. $row[$association] = $merge;
  1040. } else {
  1041. $this->_mergeAssociation($resultSet[$i], $merge, $association, $type);
  1042. }
  1043. } else {
  1044. $this->_mergeAssociation($resultSet[$i], $fetch, $association, $type);
  1045. }
  1046. $resultSet[$i][$association] = $linkModel->afterfind($resultSet[$i][$association]);
  1047. } else {
  1048. $tempArray[0][$association] = false;
  1049. $this->_mergeAssociation($resultSet[$i], $tempArray, $association, $type);
  1050. }
  1051. }
  1052. }
  1053. }
  1054. /**
  1055. * Generate a "drop table" statement for the given Schema object
  1056. *
  1057. * @param object $schema An instance of a subclass of CakeSchema
  1058. * @param string $table Optional. If specified only the table name given will be generated.
  1059. * Otherwise, all tables defined in the schema are generated.
  1060. * @return string
  1061. */
  1062. function dropSchema($schema, $table = null) {
  1063. if (!is_a($schema, 'CakeSchema')) {
  1064. trigger_error(__('Invalid schema object', true), E_USER_WARNING);
  1065. return null;
  1066. }
  1067. $out = '';
  1068. foreach ($schema->tables as $curTable => $columns) {
  1069. if (!$table || $table == $curTable) {
  1070. $out .= 'DROP TABLE ' . $this->fullTableName($curTable) . "\n";
  1071. }
  1072. }
  1073. return $out;
  1074. }
  1075. function hasResult()
  1076. {
  1077. return true;
  1078. }
  1079. }