Subversion-Projekte lars-tiefland.cakephp

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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