Skip to content

Commit f875417

Browse files
authored
Merge pull request #6468 from sclubricants/ForeignKeysFix
Improve Connection::_foreignKeyData()
2 parents d551715 + aa818d5 commit f875417

File tree

20 files changed

+353
-313
lines changed

20 files changed

+353
-313
lines changed

system/Database/BaseConnection.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,6 +1541,51 @@ public function getForeignKeyData(string $table)
15411541
return $this->_foreignKeyData($this->protectIdentifiers($table, true, false, false));
15421542
}
15431543

1544+
/**
1545+
* Converts array of arrays generated by _foreignKeyData() to array of objects
1546+
*
1547+
* @return array[
1548+
* {constraint_name} =>
1549+
* stdClass[
1550+
* 'constraint_name' => string,
1551+
* 'table_name' => string,
1552+
* 'column_name' => string[],
1553+
* 'foreign_table_name' => string,
1554+
* 'foreign_column_name' => string[],
1555+
* 'on_delete' => string,
1556+
* 'on_update' => string,
1557+
* 'match' => string
1558+
* ]
1559+
* ]
1560+
*/
1561+
protected function foreignKeyDataToObjects(array $data)
1562+
{
1563+
$retVal = [];
1564+
1565+
foreach ($data as $row) {
1566+
$name = $row['constraint_name'];
1567+
1568+
// for sqlite generate name
1569+
if ($name === null) {
1570+
$name = $row['table_name'] . '_' . implode('_', $row['column_name']) . '_foreign';
1571+
}
1572+
1573+
$obj = new stdClass();
1574+
$obj->constraint_name = $name;
1575+
$obj->table_name = $row['table_name'];
1576+
$obj->column_name = $row['column_name'];
1577+
$obj->foreign_table_name = $row['foreign_table_name'];
1578+
$obj->foreign_column_name = $row['foreign_column_name'];
1579+
$obj->on_delete = $row['on_delete'];
1580+
$obj->on_update = $row['on_update'];
1581+
$obj->match = $row['match'];
1582+
1583+
$retVal[$name] = $obj;
1584+
}
1585+
1586+
return $retVal;
1587+
}
1588+
15441589
/**
15451590
* Disables foreign key checks temporarily.
15461591
*/

system/Database/Forge.php

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,13 @@ class Forge
176176
*/
177177
protected $dropIndexStr = 'DROP INDEX %s ON %s';
178178

179+
/**
180+
* Foreign Key Allowed Actions
181+
*
182+
* @var array
183+
*/
184+
protected $fkAllowActions = ['CASCADE', 'SET NULL', 'NO ACTION', 'RESTRICT', 'SET DEFAULT'];
185+
179186
/**
180187
* Constructor.
181188
*/
@@ -401,7 +408,7 @@ public function addField($field)
401408
*
402409
* @throws DatabaseException
403410
*/
404-
public function addForeignKey($fieldName = '', string $tableName = '', $tableField = '', string $onUpdate = '', string $onDelete = '')
411+
public function addForeignKey($fieldName = '', string $tableName = '', $tableField = '', string $onUpdate = '', string $onDelete = '', string $fkName = '')
405412
{
406413
$fieldName = (array) $fieldName;
407414
$tableField = (array) $tableField;
@@ -425,6 +432,7 @@ public function addForeignKey($fieldName = '', string $tableName = '', $tableFie
425432
'referenceField' => $tableField,
426433
'onDelete' => strtoupper($onDelete),
427434
'onUpdate' => strtoupper($onUpdate),
435+
'fkName' => $fkName,
428436
];
429437

430438
return $this;
@@ -480,7 +488,7 @@ public function dropForeignKey(string $table, string $foreignName)
480488
$sql = sprintf(
481489
(string) $this->dropConstraintStr,
482490
$this->db->escapeIdentifiers($this->db->DBPrefix . $table),
483-
$this->db->escapeIdentifiers($this->db->DBPrefix . $foreignName)
491+
$this->db->escapeIdentifiers($foreignName)
484492
);
485493

486494
if ($sql === '') {
@@ -1055,20 +1063,18 @@ protected function _processIndexes(string $table)
10551063
return $sqls;
10561064
}
10571065

1066+
/**
1067+
* Generates SQL to process foreign keys
1068+
*/
10581069
protected function _processForeignKeys(string $table): string
10591070
{
10601071
$sql = '';
10611072

1062-
$allowActions = [
1063-
'CASCADE',
1064-
'SET NULL',
1065-
'NO ACTION',
1066-
'RESTRICT',
1067-
'SET DEFAULT',
1068-
];
1069-
10701073
foreach ($this->foreignKeys as $fkey) {
1071-
$nameIndex = $table . '_' . implode('_', $fkey['field']) . '_foreign';
1074+
$nameIndex = $fkey['fkName'] !== '' ?
1075+
$fkey['fkName'] :
1076+
$table . '_' . implode('_', $fkey['field']) . ($this->db->DBDriver === 'OCI8' ? '_fk' : '_foreign');
1077+
10721078
$nameIndexFilled = $this->db->escapeIdentifiers($nameIndex);
10731079
$foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field']));
10741080
$referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']);
@@ -1077,11 +1083,11 @@ protected function _processForeignKeys(string $table): string
10771083
$formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)";
10781084
$sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled);
10791085

1080-
if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) {
1086+
if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $this->fkAllowActions, true)) {
10811087
$sql .= ' ON DELETE ' . $fkey['onDelete'];
10821088
}
10831089

1084-
if ($fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $allowActions, true)) {
1090+
if ($this->db->DBDriver !== 'OCI8' && $fkey['onUpdate'] !== false && in_array($fkey['onUpdate'], $this->fkAllowActions, true)) {
10851091
$sql .= ' ON UPDATE ' . $fkey['onUpdate'];
10861092
}
10871093
}

system/Database/MySQLi/Connection.php

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -482,43 +482,46 @@ protected function _indexData(string $table): array
482482
protected function _foreignKeyData(string $table): array
483483
{
484484
$sql = '
485-
SELECT
486-
tc.CONSTRAINT_NAME,
487-
tc.TABLE_NAME,
488-
kcu.COLUMN_NAME,
489-
rc.REFERENCED_TABLE_NAME,
490-
kcu.REFERENCED_COLUMN_NAME
491-
FROM information_schema.TABLE_CONSTRAINTS AS tc
492-
INNER JOIN information_schema.REFERENTIAL_CONSTRAINTS AS rc
493-
ON tc.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
494-
AND tc.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
495-
INNER JOIN information_schema.KEY_COLUMN_USAGE AS kcu
496-
ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
497-
AND tc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
498-
WHERE
499-
tc.CONSTRAINT_TYPE = ' . $this->escape('FOREIGN KEY') . ' AND
500-
tc.TABLE_SCHEMA = ' . $this->escape($this->database) . ' AND
501-
tc.TABLE_NAME = ' . $this->escape($table);
485+
SELECT
486+
tc.CONSTRAINT_NAME,
487+
tc.TABLE_NAME,
488+
kcu.COLUMN_NAME,
489+
rc.REFERENCED_TABLE_NAME,
490+
kcu.REFERENCED_COLUMN_NAME,
491+
rc.DELETE_RULE,
492+
rc.UPDATE_RULE,
493+
rc.MATCH_OPTION
494+
FROM information_schema.table_constraints AS tc
495+
INNER JOIN information_schema.referential_constraints AS rc
496+
ON tc.constraint_name = rc.constraint_name
497+
AND tc.constraint_schema = rc.constraint_schema
498+
INNER JOIN information_schema.key_column_usage AS kcu
499+
ON tc.constraint_name = kcu.constraint_name
500+
AND tc.constraint_schema = kcu.constraint_schema
501+
WHERE
502+
tc.constraint_type = ' . $this->escape('FOREIGN KEY') . ' AND
503+
tc.table_schema = ' . $this->escape($this->database) . ' AND
504+
tc.table_name = ' . $this->escape($table);
502505

503506
if (($query = $this->query($sql)) === false) {
504507
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
505508
}
506-
$query = $query->getResultObject();
507509

508-
$retVal = [];
510+
$query = $query->getResultObject();
511+
$indexes = [];
509512

510513
foreach ($query as $row) {
511-
$obj = new stdClass();
512-
$obj->constraint_name = $row->CONSTRAINT_NAME;
513-
$obj->table_name = $row->TABLE_NAME;
514-
$obj->column_name = $row->COLUMN_NAME;
515-
$obj->foreign_table_name = $row->REFERENCED_TABLE_NAME;
516-
$obj->foreign_column_name = $row->REFERENCED_COLUMN_NAME;
517-
518-
$retVal[] = $obj;
514+
$indexes[$row->CONSTRAINT_NAME]['constraint_name'] = $row->CONSTRAINT_NAME;
515+
$indexes[$row->CONSTRAINT_NAME]['table_name'] = $row->TABLE_NAME;
516+
$indexes[$row->CONSTRAINT_NAME]['column_name'][] = $row->COLUMN_NAME;
517+
$indexes[$row->CONSTRAINT_NAME]['foreign_table_name'] = $row->REFERENCED_TABLE_NAME;
518+
$indexes[$row->CONSTRAINT_NAME]['foreign_column_name'][] = $row->REFERENCED_COLUMN_NAME;
519+
$indexes[$row->CONSTRAINT_NAME]['on_delete'] = $row->DELETE_RULE;
520+
$indexes[$row->CONSTRAINT_NAME]['on_update'] = $row->UPDATE_RULE;
521+
$indexes[$row->CONSTRAINT_NAME]['match'] = $row->MATCH_OPTION;
519522
}
520523

521-
return $retVal;
524+
return $this->foreignKeyDataToObjects($indexes);
522525
}
523526

524527
/**

system/Database/OCI8/Connection.php

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -384,43 +384,44 @@ protected function _indexData(string $table): array
384384
protected function _foreignKeyData(string $table): array
385385
{
386386
$sql = 'SELECT
387-
acc.constraint_name,
388-
acc.table_name,
389-
acc.column_name,
390-
ccu.table_name foreign_table_name,
391-
accu.column_name foreign_column_name
392-
FROM all_cons_columns acc
393-
JOIN all_constraints ac
394-
ON acc.owner = ac.owner
395-
AND acc.constraint_name = ac.constraint_name
396-
JOIN all_constraints ccu
397-
ON ac.r_owner = ccu.owner
398-
AND ac.r_constraint_name = ccu.constraint_name
399-
JOIN all_cons_columns accu
400-
ON accu.constraint_name = ccu.constraint_name
401-
AND accu.table_name = ccu.table_name
402-
WHERE ac.constraint_type = ' . $this->escape('R') . '
403-
AND acc.table_name = ' . $this->escape($table);
387+
acc.constraint_name,
388+
acc.table_name,
389+
acc.column_name,
390+
ccu.table_name foreign_table_name,
391+
accu.column_name foreign_column_name,
392+
ac.delete_rule
393+
FROM all_cons_columns acc
394+
JOIN all_constraints ac ON acc.owner = ac.owner
395+
AND acc.constraint_name = ac.constraint_name
396+
JOIN all_constraints ccu ON ac.r_owner = ccu.owner
397+
AND ac.r_constraint_name = ccu.constraint_name
398+
JOIN all_cons_columns accu ON accu.constraint_name = ccu.constraint_name
399+
AND accu.position = acc.position
400+
AND accu.table_name = ccu.table_name
401+
WHERE ac.constraint_type = ' . $this->escape('R') . '
402+
AND acc.table_name = ' . $this->escape($table);
403+
404404
$query = $this->query($sql);
405405

406406
if ($query === false) {
407407
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
408408
}
409-
$query = $query->getResultObject();
410409

411-
$retVal = [];
410+
$query = $query->getResultObject();
411+
$indexes = [];
412412

413413
foreach ($query as $row) {
414-
$obj = new stdClass();
415-
$obj->constraint_name = $row->CONSTRAINT_NAME;
416-
$obj->table_name = $row->TABLE_NAME;
417-
$obj->column_name = $row->COLUMN_NAME;
418-
$obj->foreign_table_name = $row->FOREIGN_TABLE_NAME;
419-
$obj->foreign_column_name = $row->FOREIGN_COLUMN_NAME;
420-
$retVal[] = $obj;
414+
$indexes[$row->CONSTRAINT_NAME]['constraint_name'] = $row->CONSTRAINT_NAME;
415+
$indexes[$row->CONSTRAINT_NAME]['table_name'] = $row->TABLE_NAME;
416+
$indexes[$row->CONSTRAINT_NAME]['column_name'][] = $row->COLUMN_NAME;
417+
$indexes[$row->CONSTRAINT_NAME]['foreign_table_name'] = $row->FOREIGN_TABLE_NAME;
418+
$indexes[$row->CONSTRAINT_NAME]['foreign_column_name'][] = $row->FOREIGN_COLUMN_NAME;
419+
$indexes[$row->CONSTRAINT_NAME]['on_delete'] = $row->DELETE_RULE;
420+
$indexes[$row->CONSTRAINT_NAME]['on_update'] = null;
421+
$indexes[$row->CONSTRAINT_NAME]['match'] = null;
421422
}
422423

423-
return $retVal;
424+
return $this->foreignKeyDataToObjects($indexes);
424425
}
425426

426427
/**

system/Database/OCI8/Forge.php

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ class Forge extends BaseForge
8383
*/
8484
protected $dropConstraintStr = 'ALTER TABLE %s DROP CONSTRAINT %s';
8585

86+
/**
87+
* Foreign Key Allowed Actions
88+
*
89+
* @var array
90+
*/
91+
protected $fkAllowActions = ['CASCADE', 'SET NULL', 'NO ACTION'];
92+
8693
/**
8794
* ALTER TABLE
8895
*
@@ -272,32 +279,4 @@ protected function _dropTable(string $table, bool $ifExists, bool $cascade)
272279

273280
return $sql;
274281
}
275-
276-
protected function _processForeignKeys(string $table): string
277-
{
278-
$sql = '';
279-
280-
$allowActions = [
281-
'CASCADE',
282-
'SET NULL',
283-
'NO ACTION',
284-
];
285-
286-
foreach ($this->foreignKeys as $fkey) {
287-
$nameIndex = $table . '_' . implode('_', $fkey['field']) . '_fk';
288-
$nameIndexFilled = $this->db->escapeIdentifiers($nameIndex);
289-
$foreignKeyFilled = implode(', ', $this->db->escapeIdentifiers($fkey['field']));
290-
$referenceTableFilled = $this->db->escapeIdentifiers($this->db->DBPrefix . $fkey['referenceTable']);
291-
$referenceFieldFilled = implode(', ', $this->db->escapeIdentifiers($fkey['referenceField']));
292-
293-
$formatSql = ",\n\tCONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)";
294-
$sql .= sprintf($formatSql, $nameIndexFilled, $foreignKeyFilled, $referenceTableFilled, $referenceFieldFilled);
295-
296-
if ($fkey['onDelete'] !== false && in_array($fkey['onDelete'], $allowActions, true)) {
297-
$sql .= ' ON DELETE ' . $fkey['onDelete'];
298-
}
299-
}
300-
301-
return $sql;
302-
}
303282
}

system/Database/Postgre/Connection.php

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -326,38 +326,42 @@ protected function _indexData(string $table): array
326326
*/
327327
protected function _foreignKeyData(string $table): array
328328
{
329-
$sql = 'SELECT
330-
tc.constraint_name, tc.table_name, kcu.column_name,
331-
ccu.table_name AS foreign_table_name,
332-
ccu.column_name AS foreign_column_name
333-
FROM information_schema.table_constraints AS tc
334-
JOIN information_schema.key_column_usage AS kcu
335-
ON tc.constraint_name = kcu.constraint_name
336-
JOIN information_schema.constraint_column_usage AS ccu
337-
ON ccu.constraint_name = tc.constraint_name
338-
WHERE constraint_type = ' . $this->escape('FOREIGN KEY') . ' AND
339-
tc.table_name = ' . $this->escape($table);
329+
$sql = 'SELECT c.constraint_name,
330+
x.table_name,
331+
x.column_name,
332+
y.table_name as foreign_table_name,
333+
y.column_name as foreign_column_name,
334+
c.delete_rule,
335+
c.update_rule,
336+
c.match_option
337+
FROM information_schema.referential_constraints c
338+
JOIN information_schema.key_column_usage x
339+
on x.constraint_name = c.constraint_name
340+
JOIN information_schema.key_column_usage y
341+
on y.ordinal_position = x.position_in_unique_constraint
342+
and y.constraint_name = c.unique_constraint_name
343+
WHERE x.table_name = ' . $this->escape($table) .
344+
'order by c.constraint_name, x.ordinal_position';
340345

341346
if (($query = $this->query($sql)) === false) {
342347
throw new DatabaseException(lang('Database.failGetForeignKeyData'));
343348
}
344349

345-
$query = $query->getResultObject();
346-
$retVal = [];
350+
$query = $query->getResultObject();
351+
$indexes = [];
347352

348353
foreach ($query as $row) {
349-
$obj = new stdClass();
350-
351-
$obj->constraint_name = $row->constraint_name;
352-
$obj->table_name = $row->table_name;
353-
$obj->column_name = $row->column_name;
354-
$obj->foreign_table_name = $row->foreign_table_name;
355-
$obj->foreign_column_name = $row->foreign_column_name;
356-
357-
$retVal[] = $obj;
354+
$indexes[$row->constraint_name]['constraint_name'] = $row->constraint_name;
355+
$indexes[$row->constraint_name]['table_name'] = $table;
356+
$indexes[$row->constraint_name]['column_name'][] = $row->column_name;
357+
$indexes[$row->constraint_name]['foreign_table_name'] = $row->foreign_table_name;
358+
$indexes[$row->constraint_name]['foreign_column_name'][] = $row->foreign_column_name;
359+
$indexes[$row->constraint_name]['on_delete'] = $row->delete_rule;
360+
$indexes[$row->constraint_name]['on_update'] = $row->update_rule;
361+
$indexes[$row->constraint_name]['match'] = $row->match_option;
358362
}
359363

360-
return $retVal;
364+
return $this->foreignKeyDataToObjects($indexes);
361365
}
362366

363367
/**

0 commit comments

Comments
 (0)