Commit c6d64e9d authored by Steve Müller's avatar Steve Müller

fix explicit case sensitive identifiers in Oracle

parent e9395946
......@@ -374,8 +374,10 @@ class OraclePlatform extends AbstractPlatform
*/
public function getListSequencesSQL($database)
{
$database = $this->normalizeIdentifier($database);
return "SELECT sequence_name, min_value, increment_by FROM sys.all_sequences ".
"WHERE SEQUENCE_OWNER = '".strtoupper($database)."'";
"WHERE SEQUENCE_OWNER = '" . $database->getName() . "'";
}
/**
......@@ -415,7 +417,7 @@ class OraclePlatform extends AbstractPlatform
*/
public function getListTableIndexesSQL($table, $currentDatabase = null)
{
$table = strtoupper($table);
$table = $this->normalizeIdentifier($table);
return "SELECT uind.index_name AS name, " .
" uind.index_type AS type, " .
......@@ -424,7 +426,8 @@ class OraclePlatform extends AbstractPlatform
" uind_col.column_position AS column_pos, " .
" (SELECT ucon.constraint_type FROM user_constraints ucon WHERE ucon.constraint_name = uind.index_name) AS is_primary ".
"FROM user_indexes uind, user_ind_columns uind_col " .
"WHERE uind.index_name = uind_col.index_name AND uind_col.table_name = '$table' ORDER BY uind_col.column_position ASC";
"WHERE uind.index_name = uind_col.index_name AND uind_col.table_name = '" . $table->getName() . "' " .
"ORDER BY uind_col.column_position ASC";
}
/**
......@@ -468,45 +471,49 @@ class OraclePlatform extends AbstractPlatform
*/
public function getCreateAutoincrementSql($name, $table, $start = 1)
{
$table = strtoupper($table);
$name = strtoupper($name);
$tableIdentifier = $this->normalizeIdentifier($table);
$quotedTableName = $tableIdentifier->getQuotedName($this);
$unquotedTableName = $tableIdentifier->getName();
$nameIdentifier = $this->normalizeIdentifier($name);
$quotedName = $nameIdentifier->getQuotedName($this);
$unquotedName = $nameIdentifier->getName();
$sql = array();
$sql = array();
$indexName = $table . '_AI_PK';
$autoincrementIdentifierName = $this->getAutoincrementIdentifierName($tableIdentifier);
$idx = new Index($indexName, array($name), true, true);
$idx = new Index($autoincrementIdentifierName, array($quotedName), true, true);
$sql[] = 'DECLARE
constraints_Count NUMBER;
BEGIN
SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \''.$table.'\' AND CONSTRAINT_TYPE = \'P\';
SELECT COUNT(CONSTRAINT_NAME) INTO constraints_Count FROM USER_CONSTRAINTS WHERE TABLE_NAME = \'' . $unquotedTableName . '\' AND CONSTRAINT_TYPE = \'P\';
IF constraints_Count = 0 OR constraints_Count = \'\' THEN
EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $table).'\';
EXECUTE IMMEDIATE \''.$this->getCreateConstraintSQL($idx, $quotedTableName).'\';
END IF;
END;';
$sequenceName = $this->getIdentitySequenceName($table, $name);
$sequenceName = $this->getIdentitySequenceName($unquotedTableName, $unquotedName);
$sequence = new Sequence($sequenceName, $start);
$sql[] = $this->getCreateSequenceSQL($sequence);
$triggerName = $table . '_AI_PK';
$sql[] = 'CREATE TRIGGER ' . $triggerName . '
$sql[] = 'CREATE TRIGGER ' . $autoincrementIdentifierName . '
BEFORE INSERT
ON ' . $table . '
ON ' . $quotedTableName . '
FOR EACH ROW
DECLARE
last_Sequence NUMBER;
last_InsertID NUMBER;
BEGIN
SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL;
IF (:NEW.' . $name . ' IS NULL OR :NEW.'.$name.' = 0) THEN
SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $name . ' FROM DUAL;
SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
IF (:NEW.' . $quotedName . ' IS NULL OR :NEW.'.$quotedName.' = 0) THEN
SELECT ' . $sequenceName . '.NEXTVAL INTO :NEW.' . $quotedName . ' FROM DUAL;
ELSE
SELECT NVL(Last_Number, 0) INTO last_Sequence
FROM User_Sequences
WHERE Sequence_Name = \'' . $sequenceName . '\';
SELECT :NEW.' . $name . ' INTO last_InsertID FROM DUAL;
SELECT :NEW.' . $quotedName . ' INTO last_InsertID FROM DUAL;
WHILE (last_InsertID > last_Sequence) LOOP
SELECT ' . $sequenceName . '.NEXTVAL INTO last_Sequence FROM DUAL;
END LOOP;
......@@ -517,22 +524,60 @@ END;';
}
/**
* @param string $table
* Returns the SQL statements to drop the autoincrement for the given table name.
*
* @param string $table The table name to drop the autoincrement for.
*
* @return array
*/
public function getDropAutoincrementSql($table)
{
$table = strtoupper($table);
$trigger = $table . '_AI_PK';
$table = $this->normalizeIdentifier($table);
$quotedTableName = $table->getQuotedName($this);
$unquotedTableName = $table->getName();
$autoincrementIdentifierName = $this->getAutoincrementIdentifierName($table);
$sql[] = 'DROP TRIGGER ' . $trigger;
$sql[] = $this->getDropSequenceSQL($table.'_SEQ');
return array(
'DROP TRIGGER ' . $autoincrementIdentifierName,
$this->getDropSequenceSQL($unquotedTableName . '_SEQ'), // todo: needs to be fixed
$this->getDropConstraintSQL($autoincrementIdentifierName, $quotedTableName),
);
}
$indexName = $table . '_AI_PK';
$sql[] = $this->getDropConstraintSQL($indexName, $table);
/**
* Normalizes the given identifier.
*
* Uppercases the given identifier if it is not quoted by intention
* to reflect Oracle's internal auto uppercasing strategy of unquoted identifiers.
*
* @param string $name The identifier to normalize.
*
* @return Identifier The normalized identifier.
*/
private function normalizeIdentifier($name)
{
$identifier = new Identifier($name);
return $sql;
return $identifier->isQuoted() ? $identifier : new Identifier(strtoupper($name));
}
/**
* Returns the autoincrement primary key identifier name for the given table identifier.
*
* Quotes the autoincrement primary key identifier name
* if the given table name is quoted by intention.
*
* @param Identifier $table The table identifier to return the autoincrement primary key identifier name for.
*
* @return string
*/
private function getAutoincrementIdentifierName(Identifier $table)
{
$identifierName = $table->getName() . '_AI_PK';
return $table->isQuoted()
? $this->quoteSingleIdentifier($identifierName)
: $identifierName;
}
/**
......@@ -540,7 +585,7 @@ END;';
*/
public function getListTableForeignKeysSQL($table)
{
$table = strtoupper($table);
$table = $table = $this->normalizeIdentifier($table);
return "SELECT alc.constraint_name,
alc.DELETE_RULE,
......@@ -559,7 +604,7 @@ LEFT JOIN user_cons_columns r_cols
AND cols.position = r_cols.position
WHERE alc.constraint_name = cols.constraint_name
AND alc.constraint_type = 'R'
AND alc.table_name = '".$table."'
AND alc.table_name = '" . $table->getName() . "'
ORDER BY alc.constraint_name ASC, cols.position ASC";
}
......@@ -568,8 +613,9 @@ LEFT JOIN user_cons_columns r_cols
*/
public function getListTableConstraintsSQL($table)
{
$table = strtoupper($table);
return 'SELECT * FROM user_constraints WHERE table_name = \'' . $table . '\'';
$table = $this->normalizeIdentifier($table);
return "SELECT * FROM user_constraints WHERE table_name = '" . $table->getName() . "'";
}
/**
......@@ -577,22 +623,22 @@ LEFT JOIN user_cons_columns r_cols
*/
public function getListTableColumnsSQL($table, $database = null)
{
$table = strtoupper($table);
$table = $this->normalizeIdentifier($table);
$tabColumnsTableName = "user_tab_columns";
$colCommentsTableName = "user_col_comments";
$ownerCondition = '';
if (null !== $database) {
$database = strtoupper($database);
$database = $this->normalizeIdentifier($database);
$tabColumnsTableName = "all_tab_columns";
$colCommentsTableName = "all_col_comments";
$ownerCondition = "AND c.owner = '".$database."'";
$ownerCondition = "AND c.owner = '" . $database->getName() . "'";
}
return "SELECT c.*, d.comments FROM $tabColumnsTableName c ".
"INNER JOIN " . $colCommentsTableName . " d ON d.TABLE_NAME = c.TABLE_NAME AND d.COLUMN_NAME = c.COLUMN_NAME ".
"WHERE c.table_name = '" . $table . "' ".$ownerCondition." ORDER BY c.column_name";
"WHERE c.table_name = '" . $table->getName() . "' ".$ownerCondition." ORDER BY c.column_name";
}
/**
......@@ -800,7 +846,17 @@ LEFT JOIN user_cons_columns r_cols
*/
public function getIdentitySequenceName($tableName, $columnName)
{
return $tableName . '_' . $columnName . '_SEQ';
$table = new Identifier($tableName);
$column = new Identifier($columnName);
$identitySequenceName = $table->getName() . '_' . $column->getName() . '_SEQ';
if ($table->isQuoted() || $column->isQuoted()) {
$identitySequenceName = '"' . $identitySequenceName . '"';
}
$identitySequenceIdentifier = $this->normalizeIdentifier($identitySequenceName);
return $identitySequenceIdentifier->getQuotedName($this);
}
/**
......
......@@ -19,6 +19,8 @@
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
/**
* Oracle Schema Manager.
*
......@@ -36,7 +38,7 @@ class OracleSchemaManager extends AbstractSchemaManager
{
$view = \array_change_key_case($view, CASE_LOWER);
return new View($view['view_name'], $view['text']);
return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
}
/**
......@@ -58,7 +60,7 @@ class OracleSchemaManager extends AbstractSchemaManager
{
$table = \array_change_key_case($table, CASE_LOWER);
return $table['table_name'];
return $this->getQuotedIdentifierName($table['table_name']);
}
/**
......@@ -84,7 +86,7 @@ class OracleSchemaManager extends AbstractSchemaManager
$buffer['non_unique'] = ($tableIndex['is_unique'] == 0) ? true : false;
}
$buffer['key_name'] = $keyName;
$buffer['column_name'] = $tableIndex['column_name'];
$buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
$indexBuffer[] = $buffer;
}
......@@ -207,7 +209,7 @@ class OracleSchemaManager extends AbstractSchemaManager
'platformDetails' => array(),
);
return new Column($tableColumn['column_name'], \Doctrine\DBAL\Types\Type::getType($type), $options);
return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
}
/**
......@@ -224,22 +226,26 @@ class OracleSchemaManager extends AbstractSchemaManager
}
$list[$value['constraint_name']] = array(
'name' => $value['constraint_name'],
'name' => $this->getQuotedIdentifierName($value['constraint_name']),
'local' => array(),
'foreign' => array(),
'foreignTable' => $value['references_table'],
'onDelete' => $value['delete_rule'],
);
}
$list[$value['constraint_name']]['local'][$value['position']] = $value['local_column'];
$list[$value['constraint_name']]['foreign'][$value['position']] = $value['foreign_column'];
$localColumn = $this->getQuotedIdentifierName($value['local_column']);
$foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
$list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
$list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
}
$result = array();
foreach ($list as $constraint) {
$result[] = new ForeignKeyConstraint(
array_values($constraint['local']), $constraint['foreignTable'],
array_values($constraint['foreign']), $constraint['name'],
array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']),
array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']),
array('onDelete' => $constraint['onDelete'])
);
}
......@@ -254,7 +260,11 @@ class OracleSchemaManager extends AbstractSchemaManager
{
$sequence = \array_change_key_case($sequence, CASE_LOWER);
return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['min_value']);
return new Sequence(
$this->getQuotedIdentifierName($sequence['sequence_name']),
$sequence['increment_by'],
$sequence['min_value']
);
}
/**
......@@ -323,4 +333,23 @@ class OracleSchemaManager extends AbstractSchemaManager
parent::dropTable($name);
}
/**
* Returns the quoted representation of the given identifier name.
*
* Quotes non-uppercase identifiers explicitly to preserve case
* and thus make references to the particular identifier work.
*
* @param string $identifier The identifier to quote.
*
* @return string The quoted identifier.
*/
private function getQuotedIdentifierName($identifier)
{
if (preg_match('/[a-z]/', $identifier)) {
return $this->_platform->quoteIdentifier($identifier);
}
return $identifier;
}
}
......@@ -102,4 +102,115 @@ class OracleSchemaManagerTest extends SchemaManagerFunctionalTestCase
$this->assertContains('c##test_create_database', $databases);
}
/**
* @group DBAL-831
*/
public function testListTableDetailsWithDifferentIdentifierQuotingRequirements()
{
$primaryTableName = '"Primary_Table"';
$offlinePrimaryTable = new Schema\Table($primaryTableName);
$offlinePrimaryTable->addColumn(
'"Id"',
'integer',
array('autoincrement' => true, 'comment' => 'Explicit casing.')
);
$offlinePrimaryTable->addColumn('select', 'integer', array('comment' => 'Reserved keyword.'));
$offlinePrimaryTable->addColumn('foo', 'integer', array('comment' => 'Implicit uppercasing.'));
$offlinePrimaryTable->addColumn('BAR', 'integer');
$offlinePrimaryTable->addColumn('"BAZ"', 'integer');
$offlinePrimaryTable->addIndex(array('select'), 'from');
$offlinePrimaryTable->addIndex(array('foo'), 'foo_index');
$offlinePrimaryTable->addIndex(array('BAR'), 'BAR_INDEX');
$offlinePrimaryTable->addIndex(array('"BAZ"'), 'BAZ_INDEX');
$offlinePrimaryTable->setPrimaryKey(array('"Id"'));
$foreignTableName = 'foreign';
$offlineForeignTable = new Schema\Table($foreignTableName);
$offlineForeignTable->addColumn('id', 'integer', array('autoincrement' => true));
$offlineForeignTable->addColumn('"Fk"', 'integer');
$offlineForeignTable->addIndex(array('"Fk"'), '"Fk_index"');
$offlineForeignTable->addForeignKeyConstraint(
$primaryTableName,
array('"Fk"'),
array('"Id"'),
array(),
'"Primary_Table_Fk"'
);
$offlineForeignTable->setPrimaryKey(array('id'));
$this->_sm->tryMethod('dropTable', $foreignTableName);
$this->_sm->tryMethod('dropTable', $primaryTableName);
$this->_sm->createTable($offlinePrimaryTable);
$this->_sm->createTable($offlineForeignTable);
$onlinePrimaryTable = $this->_sm->listTableDetails($primaryTableName);
$onlineForeignTable = $this->_sm->listTableDetails($foreignTableName);
$platform = $this->_sm->getDatabasePlatform();
// Primary table assertions
$this->assertSame($primaryTableName, $onlinePrimaryTable->getQuotedName($platform));
$this->assertTrue($onlinePrimaryTable->hasColumn('"Id"'));
$this->assertSame('"Id"', $onlinePrimaryTable->getColumn('"Id"')->getQuotedName($platform));
$this->assertTrue($onlinePrimaryTable->hasPrimaryKey());
$this->assertSame(array('"Id"'), $onlinePrimaryTable->getPrimaryKey()->getQuotedColumns($platform));
$this->assertTrue($onlinePrimaryTable->hasColumn('select'));
$this->assertSame('"select"', $onlinePrimaryTable->getColumn('select')->getQuotedName($platform));
$this->assertTrue($onlinePrimaryTable->hasColumn('foo'));
$this->assertSame('FOO', $onlinePrimaryTable->getColumn('foo')->getQuotedName($platform));
$this->assertTrue($onlinePrimaryTable->hasColumn('BAR'));
$this->assertSame('BAR', $onlinePrimaryTable->getColumn('BAR')->getQuotedName($platform));
$this->assertTrue($onlinePrimaryTable->hasColumn('"BAZ"'));
$this->assertSame('BAZ', $onlinePrimaryTable->getColumn('"BAZ"')->getQuotedName($platform));
$this->assertTrue($onlinePrimaryTable->hasIndex('from'));
$this->assertTrue($onlinePrimaryTable->getIndex('from')->hasColumnAtPosition('"select"'));
$this->assertSame(array('"select"'), $onlinePrimaryTable->getIndex('from')->getQuotedColumns($platform));
$this->assertTrue($onlinePrimaryTable->hasIndex('foo_index'));
$this->assertTrue($onlinePrimaryTable->getIndex('foo_index')->hasColumnAtPosition('foo'));
$this->assertSame(array('FOO'), $onlinePrimaryTable->getIndex('foo_index')->getQuotedColumns($platform));
$this->assertTrue($onlinePrimaryTable->hasIndex('BAR_INDEX'));
$this->assertTrue($onlinePrimaryTable->getIndex('BAR_INDEX')->hasColumnAtPosition('BAR'));
$this->assertSame(array('BAR'), $onlinePrimaryTable->getIndex('BAR_INDEX')->getQuotedColumns($platform));
$this->assertTrue($onlinePrimaryTable->hasIndex('BAZ_INDEX'));
$this->assertTrue($onlinePrimaryTable->getIndex('BAZ_INDEX')->hasColumnAtPosition('"BAZ"'));
$this->assertSame(array('BAZ'), $onlinePrimaryTable->getIndex('BAZ_INDEX')->getQuotedColumns($platform));
// Foreign table assertions
$this->assertTrue($onlineForeignTable->hasColumn('id'));
$this->assertSame('ID', $onlineForeignTable->getColumn('id')->getQuotedName($platform));
$this->assertTrue($onlineForeignTable->hasPrimaryKey());
$this->assertSame(array('ID'), $onlineForeignTable->getPrimaryKey()->getQuotedColumns($platform));
$this->assertTrue($onlineForeignTable->hasColumn('"Fk"'));
$this->assertSame('"Fk"', $onlineForeignTable->getColumn('"Fk"')->getQuotedName($platform));
$this->assertTrue($onlineForeignTable->hasIndex('"Fk_index"'));
$this->assertTrue($onlineForeignTable->getIndex('"Fk_index"')->hasColumnAtPosition('"Fk"'));
$this->assertSame(array('"Fk"'), $onlineForeignTable->getIndex('"Fk_index"')->getQuotedColumns($platform));
$this->assertTrue($onlineForeignTable->hasForeignKey('"Primary_Table_Fk"'));
$this->assertSame(
$primaryTableName,
$onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignTableName($platform)
);
$this->assertSame(
array('"Fk"'),
$onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedLocalColumns($platform)
);
$this->assertSame(
array('"Id"'),
$onlineForeignTable->getForeignKey('"Primary_Table_Fk"')->getQuotedForeignColumns($platform)
);
}
}
......@@ -392,10 +392,14 @@ class OraclePlatformTest extends AbstractPlatformTestCase
/**
* @group DBAL-563
* @group DBAL-831
*/
public function testReturnsIdentitySequenceName()
{
$this->assertSame('mytable_mycolumn_SEQ', $this->_platform->getIdentitySequenceName('mytable', 'mycolumn'));
$this->assertSame('MYTABLE_MYCOLUMN_SEQ', $this->_platform->getIdentitySequenceName('mytable', 'mycolumn'));
$this->assertSame('"mytable_mycolumn_SEQ"', $this->_platform->getIdentitySequenceName('"mytable"', 'mycolumn'));
$this->assertSame('"mytable_mycolumn_SEQ"', $this->_platform->getIdentitySequenceName('mytable', '"mycolumn"'));
$this->assertSame('"mytable_mycolumn_SEQ"', $this->_platform->getIdentitySequenceName('"mytable"', '"mycolumn"'));
}
/**
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment