From 31e7c21dd4e2f0d27e861a87b0cea54193937cc5 Mon Sep 17 00:00:00 2001 From: DevSoftTech Server Date: Wed, 18 Mar 2026 03:12:55 +0530 Subject: [PATCH] Order schema create statements by FK dependencies --- app/Controllers/Setup.php | 86 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/app/Controllers/Setup.php b/app/Controllers/Setup.php index 6cd33bc..79f6b15 100644 --- a/app/Controllers/Setup.php +++ b/app/Controllers/Setup.php @@ -633,6 +633,8 @@ class Setup extends BaseController $createCount = 0; $alterCount = 0; + $missingCreateSqlByTable = []; + $alterStatements = []; foreach ($masterTables as $table => $masterTableDef) { if ($processedTables >= $maxTables) { @@ -656,7 +658,7 @@ class Setup extends BaseController } } if ($createSql !== '') { - $sql[] = $createSql; + $missingCreateSqlByTable[$table] = $createSql; $createCount++; } continue; @@ -665,13 +667,24 @@ class Setup extends BaseController $tableAlterSql = $this->buildAlterSqlForTableFromSchema((array) $masterTableDef, $client, $table); if (!empty($tableAlterSql)) { foreach ($tableAlterSql as $stmt) { - $sql[] = $stmt; + $alterStatements[] = $stmt; } $alterCount += count($tableAlterSql); } } + if (!empty($missingCreateSqlByTable)) { + $orderedCreateStatements = $this->orderCreateTableStatementsByDependency($missingCreateSqlByTable, array_keys($clientTables)); + foreach ($orderedCreateStatements as $stmt) { + $sql[] = $stmt; + } + } + + foreach ($alterStatements as $stmt) { + $sql[] = $stmt; + } + return [ 'errors' => $errors, 'sql' => $sql, @@ -687,6 +700,75 @@ class Setup extends BaseController ]; } + /** + * @param array $createSqlByTable + * @param array $alreadyExistingTables + * @return array + */ + private function orderCreateTableStatementsByDependency(array $createSqlByTable, array $alreadyExistingTables): array + { + $existing = array_fill_keys(array_map(static fn ($t) => strtolower((string) $t), $alreadyExistingTables), true); + $pending = $createSqlByTable; + $ordered = []; + + while (!empty($pending)) { + $progress = false; + + foreach ($pending as $table => $createSql) { + $deps = $this->extractReferencedTablesFromCreateSql($createSql); + $blocked = false; + + foreach ($deps as $depTable) { + $depKey = strtolower($depTable); + if (isset($pending[$depTable]) && !isset($existing[$depKey])) { + $blocked = true; + break; + } + } + + if ($blocked) { + continue; + } + + $ordered[] = $createSql; + $existing[strtolower($table)] = true; + unset($pending[$table]); + $progress = true; + } + + if ($progress) { + continue; + } + + // Cycles or unresolved references: keep deterministic fallback order. + ksort($pending); + foreach ($pending as $table => $createSql) { + $ordered[] = $createSql; + } + break; + } + + return $ordered; + } + + /** + * @return array + */ + private function extractReferencedTablesFromCreateSql(string $createSql): array + { + $refs = []; + if (preg_match_all('/REFERENCES\s+`([^`]+)`/i', $createSql, $matches) === 1 || !empty($matches[1])) { + foreach ((array) ($matches[1] ?? []) as $table) { + $name = trim((string) $table); + if ($name !== '') { + $refs[] = $name; + } + } + } + + return array_values(array_unique($refs)); + } + /** * @param array $sqlList * @return array{applied:int,errors:array,truncated:bool}