Skip to content
This repository

Oracle DBO Updates addressing persistModel and sequencing issues #123

Closed
wants to merge 3 commits into from

3 participants

Ben Murden José Lorenzo Rodríguez Mark Story
Ben Murden

These updates should address the ticket I made before #1666.

The _persist() method of Object is used to resolve the issue of sequence names not being available when persistModel is true in the controller. This was particularly a problem in the method lastInsertId(), which would only be passed a string of the table name, and would therefore have no other way of knowing the sequence name, which can be modified using a table prefix or through a model property. While caching is enabled, this update will resolve that issue, and when caching is disabled, so is persistModel, so this is no longer an issue.

Sequences now correctly take table prefixes.

A 'schema' key can be defined in the database configuration to allow faster enumeration of database tables by reducing the set to the relevant tables owner.

I've also changed the Oracle unit tests to be more inline with other DBOs, while including tests for these updates and some of the existing functionality.

Let me know if you think anything could be done better here.

Thanks

Ben

added some commits April 05, 2011
Ben Murden Added logic for use with defined schema in Oracle
A "schema" key can be used in the database config when you want to access a different schema without having to always use the dot syntax. This also improves efficiency when you only use tables from one schema.
fe4c193
Ben Murden Added tablePrefix to described sequences
This avoids conflicts with separate sequences used in test suite fixtures, for example.
f7f097b
Ben Murden Oracle DBO bugs fixed & added features + tests
This update addresses an issue with using persistModel and sequences in Oracle. The issue is described in ticket #1666 http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/1666

In the same ticket, the described sequence referencing bug has been addressed here.

A Schema switch has been added to the DB config. When the 'schema' key is defined, the DBO will use that to limit the table enumeration down to only those owned by the defined schema. This can greatly reduce table enumeration time, relative to the size of the database.

Added unit tests for these new features and reconfigured the Oracle test class to be consistent with other DBO unit tests. Some existing functionality will also now be tested.
4363637
Ben Murden

I can break this down into several smaller commits if that would be easier. Probably a sensible thing to do, huh?

José Lorenzo Rodríguez
Owner

Yeah, it is a hard to read pull request. That is why no one has already looked at it..

Mark Story
Owner

Thanks for the patch, but given the complexity and our inability to verify the fixes I'm going to err on the side of safety and not merge this.

Mark Story markstory closed this July 10, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 3 unique commits by 1 author.

Apr 05, 2011
Ben Murden Added logic for use with defined schema in Oracle
A "schema" key can be used in the database config when you want to access a different schema without having to always use the dot syntax. This also improves efficiency when you only use tables from one schema.
fe4c193
Apr 06, 2011
Ben Murden Added tablePrefix to described sequences
This avoids conflicts with separate sequences used in test suite fixtures, for example.
f7f097b
Jun 20, 2011
Ben Murden Oracle DBO bugs fixed & added features + tests
This update addresses an issue with using persistModel and sequences in Oracle. The issue is described in ticket #1666 http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/1666

In the same ticket, the described sequence referencing bug has been addressed here.

A Schema switch has been added to the DB config. When the 'schema' key is defined, the DBO will use that to limit the table enumeration down to only those owned by the defined schema. This can greatly reduce table enumeration time, relative to the size of the database.

Added unit tests for these new features and reconfigured the Oracle test class to be consistent with other DBO unit tests. Some existing functionality will also now be tested.
4363637
This page is out of date. Refresh to see the latest.
99  cake/libs/model/datasources/dbo/dbo_oracle.php
@@ -140,7 +140,7 @@ class DboOracle extends DboSource {
140 140
 	var $_error;
141 141
 
142 142
 /**
143  
- * Base configuration settings for MySQL driver
  143
+ * Base configuration settings for Oracle driver
144 144
  *
145 145
  * @var array
146 146
  */
@@ -187,6 +187,9 @@ function connect() {
187 187
 			if (!empty($config['nls_comp'])) {
188 188
 				$this->execute('ALTER SESSION SET NLS_COMP='.$config['nls_comp']);
189 189
 			}
  190
+			if (!empty($config['schema'])) {
  191
+				$this->execute('ALTER SESSION SET CURRENT_SCHEMA='.$config['schema']);
  192
+			}
190 193
 			$this->execute("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
191 194
 		} else {
192 195
 			$this->connected = false;
@@ -456,6 +459,76 @@ function createTrigger($table) {
456 459
 	}
457 460
 
458 461
 /**
  462
+ * Cache the sequence details
  463
+ * 
  464
+ * @param object $model
  465
+ * @access private
  466
+ * @return mixed
  467
+ * 
  468
+ */
  469
+	function _cacheSequence(&$model) {
  470
+		if ($this->cacheSources === false) {
  471
+			return null;
  472
+		}
  473
+		
  474
+		$cache = null;
  475
+		
  476
+		$table = $this->fullTableName($model, false);
  477
+		$key = ConnectionManager::getSourceName($this) . '_' . $table . '_seq';
  478
+		$cached = $this->_persist($key, null, $cache);
  479
+				
  480
+		if ($cached === false) {
  481
+			$cache = $this->_getSequenceName($model);
  482
+			$this->_persist($key, true, $cache);
  483
+		} else {
  484
+			$this->_persist($key, true, $cache);
  485
+			$cache = $this->{$key};
  486
+		}
  487
+		
  488
+		return $cache;
  489
+	}
  490
+	
  491
+/**
  492
+ * Get cached sequence details from a table name only.
  493
+ * Required for proper functionality of lastInsertId when models are cached.
  494
+ * 
  495
+ * @param String table
  496
+ * @access private
  497
+ * @return mixed
  498
+ */
  499
+	function _getCachedSequence($table) {
  500
+		if ($this->cacheSources === false) {
  501
+			return null;
  502
+		}
  503
+		
  504
+		$cache = null;
  505
+		
  506
+		$key = ConnectionManager::getSourceName($this) . '_' . $table . '_seq';
  507
+		$this->_persist($key, true, $cache);
  508
+		$cache = $this->{$key};
  509
+		
  510
+		return $cache;
  511
+	}
  512
+	
  513
+/**
  514
+ * Get the sequence name for a model
  515
+ * 
  516
+ * @param object model
  517
+ * @access private
  518
+ * @return String
  519
+ * 
  520
+ */
  521
+	function _getSequenceName(&$model) {
  522
+		if (!empty($model->sequence)) {
  523
+			$seq = $model->sequence;
  524
+		} elseif (!empty($model->table)) {
  525
+			$seq = $this->fullTableName($model, false) . '_seq';
  526
+		}
  527
+		
  528
+		return $seq;
  529
+	}
  530
+
  531
+/**
459 532
  * Returns an array of tables in the database. If there are no tables, an error is
460 533
  * raised and the application exits.
461 534
  *
@@ -463,11 +536,17 @@ function createTrigger($table) {
463 536
  * @access public
464 537
  */
465 538
 	function listSources() {
  539
+		$config = $this->config;
466 540
 		$cache = parent::listSources();
467 541
 		if ($cache != null) {
468 542
 			return $cache;
469 543
 		}
470  
-		$sql = 'SELECT view_name AS name FROM all_views UNION SELECT table_name AS name FROM all_tables';
  544
+		
  545
+		if (!empty($config['schema'])) {
  546
+			$sql = 'SELECT view_name AS name FROM all_views WHERE owner = \'' . $config['schema'] . '\' UNION SELECT table_name AS name FROM all_tables WHERE owner = \'' . $config['schema'] . '\'';
  547
+		} else {
  548
+			$sql = 'SELECT view_name AS name FROM all_views UNION SELECT table_name AS name FROM all_tables';
  549
+		}
471 550
 
472 551
 		if (!$this->execute($sql)) {
473 552
 			return false;
@@ -490,13 +569,9 @@ function listSources() {
490 569
  */
491 570
 	function describe(&$model) {
492 571
 		$table = $this->fullTableName($model, false);
493  
-
494  
-		if (!empty($model->sequence)) {
495  
-			$this->_sequenceMap[$table] = $model->sequence;
496  
-		} elseif (!empty($model->table)) {
497  
-			$this->_sequenceMap[$table] = $model->table . '_seq';
498  
-		}
499  
-
  572
+		
  573
+		$this->_sequenceMap[$table] = $this->_cacheSequence($model);
  574
+		
500 575
 		$cache = parent::describe($model);
501 576
 
502 577
 		if ($cache != null) {
@@ -897,7 +972,11 @@ function value($data, $column = null, $safe = false) {
897 972
  * @access public
898 973
  */
899 974
 	function lastInsertId($source) {
900  
-		$sequence = $this->_sequenceMap[$source];
  975
+		if (isset($this->_sequenceMap[$source])) {
  976
+			$sequence = $this->_sequenceMap[$source];
  977
+		} else {
  978
+			$sequence = $this->_getCachedSequence($source);
  979
+		}
901 980
 		$sql = "SELECT $sequence.currval FROM dual";
902 981
 
903 982
 		if (!$this->execute($sql)) {
262  cake/tests/cases/libs/model/datasources/dbo/dbo_oracle.test.php
@@ -17,11 +17,144 @@
17 17
  * @since         CakePHP(tm) v 1.2.0
18 18
  * @license       MIT License (http://www.opensource.org/licenses/mit-license.php)
19 19
  */
20  
-if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
21  
-	define('CAKEPHP_UNIT_TEST_EXECUTION', 1);
  20
+
  21
+App::import('Core', array('Model', 'DataSource', 'DboSource', 'DboOracle'));
  22
+
  23
+/**
  24
+ * DboOracleTestDb class
  25
+ *
  26
+ * @package       cake
  27
+ * @subpackage    cake.tests.cases.libs.model.datasources
  28
+ */
  29
+class DboOracleTestDb extends DboOracle {
  30
+
  31
+/**
  32
+ * simulated property
  33
+ *
  34
+ * @var array
  35
+ * @access public
  36
+ */
  37
+	var $simulated = array();
  38
+
  39
+/**
  40
+ * execute method
  41
+ *
  42
+ * @param mixed $sql
  43
+ * @access protected
  44
+ * @return void
  45
+ */
  46
+	function _execute($sql) {
  47
+		$this->simulated[] = $sql;
  48
+		$this->_statementId = null;
  49
+		return null;
  50
+	}
  51
+
  52
+/**
  53
+ * getLastQuery method
  54
+ *
  55
+ * @access public
  56
+ * @return void
  57
+ */
  58
+	function getLastQuery() {
  59
+		return $this->simulated[count($this->simulated) - 1];
  60
+	}
  61
+	
  62
+/**
  63
+ * getHistoricalQuery method
  64
+ * Get a query from the passed steps $ago.
  65
+ * E.g. getHistoricalQuery(1) is the same as getLastQuery().
  66
+ * getHistoricalQuery(3) is the query executed 3 times ago.
  67
+ * 
  68
+ * @param int $ago
  69
+ * @access public
  70
+ * @return String
  71
+ */
  72
+	function getHistoricalQuery($ago) {
  73
+		return $this->simulated[count($this->simulated) - $ago];
  74
+	}
  75
+}
  76
+
  77
+/**
  78
+ * OracleTestModel class
  79
+ *
  80
+ * @package       cake
  81
+ * @subpackage    cake.tests.cases.libs.model.datasources
  82
+ */
  83
+class OracleTestModel extends Model {
  84
+
  85
+/**
  86
+ * name property
  87
+ *
  88
+ * @var string 'OracleTestModel'
  89
+ * @access public
  90
+ */
  91
+	var $name = 'OracleTestModel';
  92
+
  93
+/**
  94
+ * useTable property
  95
+ *
  96
+ * @var bool false
  97
+ * @access public
  98
+ */
  99
+	var $useTable = false;
  100
+
  101
+/**
  102
+ * find method
  103
+ *
  104
+ * @param mixed $conditions
  105
+ * @param mixed $fields
  106
+ * @param mixed $order
  107
+ * @param mixed $recursive
  108
+ * @access public
  109
+ * @return void
  110
+ */
  111
+	function find($conditions = null, $fields = null, $order = null, $recursive = null) {
  112
+		return $conditions;
  113
+	}
  114
+
  115
+/**
  116
+ * findAll method
  117
+ *
  118
+ * @param mixed $conditions
  119
+ * @param mixed $fields
  120
+ * @param mixed $order
  121
+ * @param mixed $recursive
  122
+ * @access public
  123
+ * @return void
  124
+ */
  125
+	function findAll($conditions = null, $fields = null, $order = null, $recursive = null) {
  126
+		return $conditions;
  127
+	}
  128
+
  129
+/**
  130
+ * schema method
  131
+ *
  132
+ * @access public
  133
+ * @return void
  134
+ */
  135
+	function schema() {
  136
+		return array(
  137
+			'id'		=> array('type' => 'integer', 'null' => '', 'default' => '', 'length' => '8'),
  138
+			'client_id' => array('type' => 'integer', 'null' => '', 'default' => '0', 'length' => '11'),
  139
+			'name'		=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'),
  140
+			'login'		=> array('type' => 'string', 'null' => '', 'default' => '', 'length' => '255'),
  141
+			'passwd'	=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'),
  142
+			'addr_1'	=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'),
  143
+			'addr_2'	=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '25'),
  144
+			'zip_code'	=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
  145
+			'city'		=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
  146
+			'country'	=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
  147
+			'phone'		=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
  148
+			'fax'		=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
  149
+			'url'		=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '255'),
  150
+			'email'		=> array('type' => 'string', 'null' => '1', 'default' => '', 'length' => '155'),
  151
+			'comments'	=> array('type' => 'text', 'null' => '1', 'default' => '', 'length' => ''),
  152
+			'last_login'=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => ''),
  153
+			'created'	=> array('type' => 'date', 'null' => '1', 'default' => '', 'length' => ''),
  154
+			'updated'	=> array('type' => 'datetime', 'null' => '1', 'default' => '', 'length' => null)
  155
+		);
  156
+	}
22 157
 }
23  
-require_once LIBS . 'model' . DS . 'datasources' . DS . 'dbo_source.php';
24  
-require_once LIBS . 'model' . DS . 'datasources' . DS . 'dbo' . DS . 'dbo_oracle.php';
25 158
 
26 159
 /**
27 160
  * DboOracleTest class
@@ -37,26 +170,137 @@ class DboOracleTest extends CakeTestCase {
37 170
 	var $fixtures = array('core.oracle_user');
38 171
 
39 172
 /**
  173
+ * Actual DB connection used in testing
  174
+ *
  175
+ * @var DboSource
  176
+ * @access public
  177
+ */
  178
+	var $db = null;
  179
+
  180
+/**
  181
+ * Simulated DB connection used in testing
  182
+ *
  183
+ * @var DboSource
  184
+ * @access public
  185
+ */
  186
+	var $simDb = null;
  187
+	
  188
+/**
  189
+ * Testing model
  190
+ * 
  191
+ * @var Model
  192
+ * @access public
  193
+ */
  194
+	var $model = null;
  195
+	
  196
+/**
  197
+ * Set up test suite database connection
  198
+ *
  199
+ * @access public
  200
+ */
  201
+	function startTest() {
  202
+		$this->_initDb();
  203
+	}
  204
+	
  205
+/**
40 206
  * setup method
41 207
  *
42 208
  * @access public
43 209
  * @return void
44 210
  */
45 211
 	function setUp() {
46  
-		$this->_initDb();
  212
+		Configure::write('Cache.disable', true);
  213
+		$this->startTest();
  214
+		$this->db =& ConnectionManager::getDataSource('test_suite');
  215
+		$this->simDb = new DboOracleTestDb($this->db->config, false);
  216
+		$this->model = new OracleTestModel();
47 217
 	}
48 218
 
49 219
 /**
  220
+ * Tears down the Dbo class instance
  221
+ *
  222
+ * @access public
  223
+ */
  224
+	function tearDown() {
  225
+		Configure::write('Cache.disable', false);
  226
+		unset($this->db);
  227
+		unset($this->simDb);
  228
+		unset($this->model);
  229
+	}
  230
+	
  231
+/**
50 232
  * skip method
51 233
  *
52 234
  * @access public
53 235
  * @return void
54 236
  */
55  
-    function skip() {
56  
-    	$this->_initDb();
57  
-    	$this->skipUnless($this->db->config['driver'] == 'oracle', '%s Oracle connection not available');
58  
-    }
  237
+	function skip() {
  238
+		$this->_initDb();
  239
+		$this->skipUnless($this->db->config['driver'] == 'oracle', '%s Oracle connection not available');
  240
+	}
  241
+
  242
+/**
  243
+ * testConnect method
  244
+ * 
  245
+ * @access public
  246
+ * @return void
  247
+ */
  248
+	function testConnect() {
  249
+		$result = $this->db->connect();
  250
+		$this->assertTrue($result);
  251
+		
  252
+		$this->simDb->config['schema'] = 'SAIBOT';
  253
+		$this->simDb->connect();
  254
+		$result = $this->simDb->getLastQuery();
  255
+		$expected = "ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'";
  256
+		$this->assertEqual($expected, $result);
  257
+		
  258
+		$result = $this->simDb->getHistoricalQuery(2);
  259
+		$expected = 'ALTER SESSION SET CURRENT_SCHEMA=SAIBOT';
  260
+		$this->assertEqual($expected, $result);
  261
+	}
  262
+	
  263
+/**
  264
+ * testDescribe method
  265
+ * 
  266
+ * @access public
  267
+ * @return void
  268
+ */
  269
+	function testDescribe() {
  270
+		$this->model->table = 'test_table';
  271
+		$this->simDb->describe($this->model);
  272
+		$expected = 'test_table_seq';
  273
+		$result = $this->simDb->_sequenceMap['test_table'];
  274
+		$this->assertEqual($expected, $result);
  275
+		
  276
+		$this->model->tablePrefix = 'ultimate_';
  277
+		$this->simDb->describe($this->model);
  278
+		$expected = 'ultimate_test_table_seq';
  279
+		$result = $this->simDb->_sequenceMap['ultimate_test_table'];
  280
+		$this->assertEqual($expected, $result);
59 281
 
  282
+		$this->model->tablePrefix = null;
  283
+		$this->model->sequence = 'test_sequence_dude';
  284
+		$this->simDb->describe($this->model);
  285
+		$expected = 'test_sequence_dude';
  286
+		$result = $this->simDb->_sequenceMap['test_table'];
  287
+		$this->assertEqual($expected, $result);
  288
+	}
  289
+	
  290
+/**
  291
+ * testLastInsertId method
  292
+ * 
  293
+ * @access public
  294
+ * @return void
  295
+ */
  296
+	function testLastInsertId() {
  297
+		$this->model->table = 'test_table';
  298
+		$this->simDb->describe($this->model);
  299
+		$this->simDb->lastInsertId('test_table');
  300
+		$expected = "SELECT test_table_seq.currval FROM dual";
  301
+		$result = $this->simDb->getLastQuery();
  302
+		$this->assertEqual($expected, $result);
  303
+	}
60 304
 /**
61 305
  * testLastErrorStatement method
62 306
  *
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.