Oracle.php 29 KB

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