Skip to content

Commit 6af1528

Browse files
committedMay 9, 2018
Added documentation, fixed incorrect introspection of function based indexes with more than one expression
1 parent aa310b0 commit 6af1528

File tree

3 files changed

+91
-33
lines changed

3 files changed

+91
-33
lines changed
 

‎README.md

+40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,45 @@
11
# Doctrine DBAL
22

3+
This Fork adds the missing capabilities for Oracle 12.x
4+
* Function Based Indexes (with expressions on any index column)
5+
* IDENTITY columns instead of sequences/triggers
6+
* Column-level collations
7+
* Huge performance improvement on schema introspection
8+
9+
### How to use Oracle-specific features:
10+
11+
##### IDENTITY columns
12+
13+
Use `'autoincrement' => true` in the options of a column to define a column as `GENERATED BY DEFAULT ON NULL AS IDENTITY`
14+
15+
##### Column-level collations
16+
17+
Add `'platformOptions' => ['collation' => 'XGERMAN_CI']` to the options array
18+
19+
##### Function Based Indexes
20+
21+
Other DBMS either do not support function based indexes at all or support only one WHERE-condition for the whole index,
22+
that's why Doctrine does not support a convenient way to manage several expressions in one index.
23+
With this fork you can define as many expressions as different column names are mentioned in the expressions.
24+
You have to write the expression exactly in the same way as Oracle stores it in `user_ind_expressions.column_expression`,
25+
except that trailing spaces and double spaces are removed.
26+
Doctrine requires that the expressions are mapped to existing columns, use the column names in the order in which they appear in the expressions.
27+
It is a known limitation that not every possible combination of expressions can be mapped this way.
28+
29+
Example:
30+
````php
31+
$table->addUniqueIndex(
32+
['COLUMN_A', 'COLUMN_B'],
33+
'UX_INDEX_NAME',
34+
['where' => [
35+
'COLUMN_A' => 'CASE WHEN ("COLUMN_A"=0 AND "COLUMN_B"=1) THEN "COLUMN_C" END',
36+
'COLUMN_B' => 'CASE WHEN ("COLUMN_A"=0 AND "COLUMN_B"=1) THEN "COLUMN_D" END',
37+
]]);
38+
````
39+
40+
41+
## Original README from doctrine/dbal:
42+
343
| [Master][Master] | [2.7][2.7] | [2.6][2.6] |
444
|:----------------:|:----------:|:----------:|
545
| [![Build status][Master image]][Master] | [![Build status][2.7 image]][2.7] | [![Build status][2.6 image]][2.6] |

‎lib/Doctrine/DBAL/Platforms/Oracle121Platform.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ public function getListTableIndexesSQL($table, $currentDatabase = null)
162162
index_columns.column_name AS column_name,
163163
index_columns.column_position AS column_pos,
164164
constraints.constraint_type AS is_primary,
165-
CASE WHEN COLUMN_NAME LIKE 'SYS_%'
165+
CASE WHEN column_name LIKE 'SYS_%'
166166
THEN column_expression
167167
ELSE NULL END AS column_expression
168168
FROM user_ind_columns index_columns

‎lib/Doctrine/DBAL/Schema/Oracle121SchemaManager.php

+50-32
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,23 @@ protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null
192192
$buffer['non_unique'] = ($tableIndexRow['is_unique'] == 0) ? true : false;
193193
}
194194

195-
if ($tableIndexRow['type'] == 'FUNCTION-BASED NORMAL' && !empty($tableIndexRow['column_expression'])
195+
// the AbstractSchemaManager only recognizes the "where" option of the first index row,
196+
// so we take note which key holds the first row of the current index
197+
if (is_null($firstIndexRowKey) OR $tableIndexRows[$firstIndexRowKey]['key_name'] != $buffer['key_name']) {
198+
// this is the first iteration or the first row of the next index
199+
$firstIndexRowKey = $key;
200+
}
201+
202+
if ($tableIndexRow['type'] != 'FUNCTION-BASED NORMAL' OR empty($tableIndexRow['column_expression'])
196203
) {
197-
// remove multiple an trailing whitespaces
204+
// normal index column
205+
$buffer['column_name'] = $this->getQuotedIdentifierName($tableIndexRow['column_name']);
206+
$tableIndexRow = $buffer;
207+
}
208+
else {
209+
// this index column contains an expression
210+
211+
// remove multiple and trailing whitespaces
198212
$tableIndexRow['column_expression'] = preg_replace('!\s+!', ' ', $tableIndexRow['column_expression']);
199213
$tableIndexRow['column_expression'] = trim($tableIndexRow['column_expression']);
200214
/*
@@ -206,47 +220,51 @@ protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null
206220
* columns appear in these expressions and you might need to adjust the order
207221
* (this is a limitation of doctrine, not Oracle)
208222
*/
209-
preg_match("/\"(.*?)\"/", $tableIndexRow['column_expression'],$columnNamesInExpression);
223+
preg_match_all(
224+
"/\"(.*?)\"/",
225+
$tableIndexRow['column_expression'],
226+
$columnNamesInExpression,
227+
PREG_PATTERN_ORDER
228+
);
210229
if (empty($columnNamesInExpression[1])) {
211-
throw new DBALException('No column name in double quotation marks found in column expression: ' . $tableIndexRow['column_expression']);
230+
throw new DBALException('No column name in double quotation marks found in column expression: '
231+
. $tableIndexRow['column_expression']
232+
);
212233
}
213-
$expressionMapped = false;
214-
for ($i = 1; $i <= count($columnNamesInExpression); $i++) {
215-
$columnName = $this->getQuotedIdentifierName($columnNamesInExpression[$i]);
216-
if (!isset($buffer['where'][$columnName])) {
234+
if ($key == $firstIndexRowKey) {
235+
$buffer['column_name'] = $columnNamesInExpression[1][0];
236+
$buffer['where'] = [$columnNamesInExpression[1][0] => $tableIndexRow['column_expression']];
237+
} else {
238+
// this is not the first row of an index and it has a column expression (stored in the "where" option)
239+
$expressionMapped = false;
240+
foreach ($columnNamesInExpression[1] as $columnName) {
241+
$columnName = $this->getQuotedIdentifierName($columnName);
242+
for ($i = $key - 1; $i >= $firstIndexRowKey; $i--) {
243+
if ($tableIndexRows[$i]['column_name'] == $columnName) {
244+
// column name already in use
245+
continue 2;
246+
}
247+
}
217248
$buffer['column_name'] = $columnName;
218-
$buffer['where'][$columnName] = $tableIndexRow['column_expression'];
249+
// make sure the where option of the first index row is set
250+
if (!isset($tableIndexRows[$firstIndexRowKey]['where'])) {
251+
$tableIndexRows[$firstIndexRowKey]['where'] = [];
252+
}
253+
// add the expression to the first index row's where option
254+
$tableIndexRows[$firstIndexRowKey]['where'] += [$columnName => $tableIndexRow['column_expression']];
219255
$expressionMapped = true;
220256
break;
221257
}
258+
if (!$expressionMapped) {
259+
throw new DBALException('Could not map the column expression ' . $tableIndexRow['column_expression']
260+
. 'to a column, because other expressions in this index have been mapped to all available column names');
261+
}
222262
}
223-
if (!$expressionMapped) {
224-
throw new DBALException('Could not map the column expression ' . $tableIndexRow['column_expression']
225-
. 'to a column, because other expressions in this index have been mapped to all available column names');
226-
}
227-
228-
} else {
229-
$buffer['column_name'] = $this->getQuotedIdentifierName($tableIndexRow['column_name']);
263+
$tableIndexRow = $buffer;
230264
}
231265

232-
$tableIndexRow = $buffer;
233266
// $tableIndexRow is a reference, unset $buffer
234267
unset($buffer);
235-
236-
// the AbstractSchemaManager only recognizes the "where" option of the first index row
237-
// so we move/merge the "where" option if we have one on another index row but the first
238-
if (is_null($firstIndexRowKey) OR $tableIndexRows[$firstIndexRowKey]['key_name'] != $tableIndexRow['key_name']) {
239-
// this is the first iteration or the first row of the next index
240-
$firstIndexRowKey = $key;
241-
} elseif (!empty($tableIndexRow['where'])) {
242-
// this is not the first row of an index and it has a column expression (stored in the "where" option)
243-
if (!isset($tableIndexRows[$firstIndexRowKey]['where'])) {
244-
$tableIndexRows[$firstIndexRowKey]['where'] = [];
245-
}
246-
$tableIndexRows[$firstIndexRowKey]['where'] += $tableIndexRow['where'];
247-
unset($tableIndexRow['where']);
248-
}
249-
250268
}
251269
// unset reference
252270
unset($tableIndexRow);

0 commit comments

Comments
 (0)
Please sign in to comment.