diff --git a/lib/dml/oci_native_moodle_database.php b/lib/dml/oci_native_moodle_database.php index b5d60188891..6db6a7bca86 100644 --- a/lib/dml/oci_native_moodle_database.php +++ b/lib/dml/oci_native_moodle_database.php @@ -438,9 +438,10 @@ class oci_native_moodle_database extends moodle_database { $indexes = array(); $tablename = strtoupper($this->prefix.$table); - $sql = "SELECT i.INDEX_NAME, i.UNIQUENESS, c.COLUMN_POSITION, c.COLUMN_NAME, ac.CONSTRAINT_TYPE + $sql = "SELECT i.INDEX_NAME, i.INDEX_TYPE, i.UNIQUENESS, c.COLUMN_POSITION, c.COLUMN_NAME, e.COLUMN_EXPRESSION, ac.CONSTRAINT_TYPE FROM ALL_INDEXES i JOIN ALL_IND_COLUMNS c ON c.INDEX_NAME=i.INDEX_NAME + LEFT JOIN ALL_IND_EXPRESSIONS e ON (e.INDEX_NAME = c.INDEX_NAME AND e.COLUMN_POSITION = c.COLUMN_POSITION) LEFT JOIN ALL_CONSTRAINTS ac ON (ac.TABLE_NAME=i.TABLE_NAME AND ac.CONSTRAINT_NAME=i.INDEX_NAME AND ac.CONSTRAINT_TYPE='P') WHERE i.TABLE_NAME = '$tablename' ORDER BY i.INDEX_NAME, c.COLUMN_POSITION"; @@ -463,6 +464,20 @@ class oci_native_moodle_database extends moodle_database { 'unique' => ($record['UNIQUENESS'] === 'UNIQUE'), 'columns' => array()); } + + // If this is an unique, function-based, index, then we have to look to the expression + // and calculate the column name by parsing it. + if ($record['UNIQUENESS'] === 'UNIQUE' && $record['INDEX_TYPE'] === 'FUNCTION-BASED NORMAL') { + // Only if there is an expression to look. + if (!empty($record['COLUMN_EXPRESSION'])) { + // Let's parse the usual code used for these unique indexes. + $regex = '/^CASE *WHEN .* THEN "(?[^"]+)" ELSE NULL END *$/'; + if (preg_match($regex, $record['COLUMN_EXPRESSION'], $matches)) { + $record['COLUMN_NAME'] = $matches['column_name'] ?? $record['COLUMN_NAME']; + } + } + } + $indexes[$indexname]['columns'][] = strtolower($record['COLUMN_NAME']); } diff --git a/lib/dml/tests/dml_test.php b/lib/dml/tests/dml_test.php index 7337b3e050b..23f9b7a3bfa 100644 --- a/lib/dml/tests/dml_test.php +++ b/lib/dml/tests/dml_test.php @@ -719,6 +719,41 @@ EOD; $this->assertSame('id', $composed['columns'][1]); } + /** + * Let's verify get_indexes() when we mix null and not null columns in unique indexes. + * + * Some databases, for unique indexes of this type, need to create function indexes to + * provide cross-db behaviour. Here we check that those indexes don't break get_indexes(). + * + * Note that, strictly speaking, unique indexes on null columns are far from ideal. Both + * conceptually and also in practice, because they cause DBs to use full scans in a + * number of situations. But if we support them, we need to ensure get_indexes() work on them. + */ + public function test_get_indexes_unique_mixed_nullability() { + $DB = $this->tdb; + $dbman = $this->tdb->get_manager(); + $table = $this->get_test_table(); + $tablename = $table->getName(); + + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('nullable01', XMLDB_TYPE_INTEGER, 10, null, null, null, null); + $table->add_field('nullable02', XMLDB_TYPE_INTEGER, 10, null, null, null, null); + $table->add_field('nonullable01', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('nonullable02', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $indexcolumns = ['nullable01', 'nonullable01', 'nullable02', 'nonullable02']; + $table->add_index('course-id', XMLDB_INDEX_UNIQUE, $indexcolumns); + $dbman->create_table($table); + + $indexes = $DB->get_indexes($tablename); + $this->assertIsArray($indexes); + $this->assertCount(1, $indexes); + + $index = array_shift($indexes); + $this->assertTrue($index['unique']); + $this->assertSame($indexcolumns, $index['columns']); + } + public function test_get_columns() { $DB = $this->tdb; $dbman = $this->tdb->get_manager();