Oracle.php 29 KB

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