Ein Task kann jetzt auf beliebig viele Vorgaenger warten. Die Einzelspalte tasks.depends_on_task_id wird durch die Beziehungstabelle task_dependencies ersetzt (Composite-PK, beide FKs ON DELETE CASCADE -- das reproduziert das alte ON DELETE SET NULL: geloeschter Vorgaenger = Kante weg = Task wieder pickbar). BlockedByTaskId bleibt unberuehrt. - Migration AddTaskDependencies kopiert jede bestehende depends_on_task_id verlustfrei in die Tabelle, bevor die Spalte fallengelassen wird - QueuePicker gatet per doppelt negiertem NOT EXISTS: kein Task mit 0 Kanten wird blockiert, alle Kanten muessen done sein, eine Kante auf einen verschwundenen Task blockiert weiter - SetDependsOnAsync ersetzt die ganze Menge; Zykluspruefung ist jetzt eine BFS ueber den Graphen, faengt also auch A->B->C->A - blockedReason nennt alle offenen Vorgaenger (External/TaskBlocking, von ExternalMcpService und TaskWaitMcpTools gemeinsam genutzt) - add_task/update_task nehmen dependsOnTaskIds; die alte Skalarform bleibt als Ein-Element-Menge inkl. ""-Clear-Sentinel gueltig - UI: Fan-in-Zeile bleibt flach und listet alle Vorgaenger im After-Label, die lineare Kette rendert unveraendert
105 lines
4.1 KiB
C#
105 lines
4.1 KiB
C#
using Microsoft.EntityFrameworkCore.Migrations;
|
|
|
|
#nullable disable
|
|
|
|
namespace ClaudeDo.Data.Migrations
|
|
{
|
|
/// <summary>
|
|
/// Turns the single <c>tasks.depends_on_task_id</c> column into a set: one row per declared
|
|
/// "run after" edge in <c>task_dependencies</c>. Hand-edited after scaffolding so the data copy
|
|
/// sits between CreateTable and DropColumn — the scaffolded order dropped the column first and
|
|
/// would have discarded every existing link.
|
|
/// </summary>
|
|
public partial class AddTaskDependencies : Migration
|
|
{
|
|
/// <inheritdoc />
|
|
protected override void Up(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.CreateTable(
|
|
name: "task_dependencies",
|
|
columns: table => new
|
|
{
|
|
task_id = table.Column<string>(type: "TEXT", nullable: false),
|
|
depends_on_task_id = table.Column<string>(type: "TEXT", nullable: false)
|
|
},
|
|
constraints: table =>
|
|
{
|
|
table.PrimaryKey("PK_task_dependencies", x => new { x.task_id, x.depends_on_task_id });
|
|
table.ForeignKey(
|
|
name: "FK_task_dependencies_tasks_depends_on_task_id",
|
|
column: x => x.depends_on_task_id,
|
|
principalTable: "tasks",
|
|
principalColumn: "id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
table.ForeignKey(
|
|
name: "FK_task_dependencies_tasks_task_id",
|
|
column: x => x.task_id,
|
|
principalTable: "tasks",
|
|
principalColumn: "id",
|
|
onDelete: ReferentialAction.Cascade);
|
|
});
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "idx_task_dependencies_depends_on",
|
|
table: "task_dependencies",
|
|
column: "depends_on_task_id");
|
|
|
|
// Every existing link becomes a one-element set. The old FK was ON DELETE SET NULL, so
|
|
// a surviving value always points at an existing task and can't violate the new FK.
|
|
migrationBuilder.Sql("""
|
|
INSERT INTO task_dependencies (task_id, depends_on_task_id)
|
|
SELECT id, depends_on_task_id FROM tasks WHERE depends_on_task_id IS NOT NULL
|
|
""");
|
|
|
|
migrationBuilder.DropForeignKey(
|
|
name: "FK_tasks_tasks_depends_on_task_id",
|
|
table: "tasks");
|
|
|
|
migrationBuilder.DropIndex(
|
|
name: "idx_tasks_depends_on",
|
|
table: "tasks");
|
|
|
|
migrationBuilder.DropColumn(
|
|
name: "depends_on_task_id",
|
|
table: "tasks");
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void Down(MigrationBuilder migrationBuilder)
|
|
{
|
|
migrationBuilder.AddColumn<string>(
|
|
name: "depends_on_task_id",
|
|
table: "tasks",
|
|
type: "TEXT",
|
|
nullable: true);
|
|
|
|
// Inherently lossy: a task with several predecessors keeps only one. There is no
|
|
// single-column shape that could hold the rest.
|
|
migrationBuilder.Sql("""
|
|
UPDATE tasks SET depends_on_task_id = (
|
|
SELECT td.depends_on_task_id FROM task_dependencies td
|
|
WHERE td.task_id = tasks.id
|
|
ORDER BY td.depends_on_task_id
|
|
LIMIT 1
|
|
)
|
|
""");
|
|
|
|
migrationBuilder.DropTable(
|
|
name: "task_dependencies");
|
|
|
|
migrationBuilder.CreateIndex(
|
|
name: "idx_tasks_depends_on",
|
|
table: "tasks",
|
|
column: "depends_on_task_id");
|
|
|
|
migrationBuilder.AddForeignKey(
|
|
name: "FK_tasks_tasks_depends_on_task_id",
|
|
table: "tasks",
|
|
column: "depends_on_task_id",
|
|
principalTable: "tasks",
|
|
principalColumn: "id",
|
|
onDelete: ReferentialAction.SetNull);
|
|
}
|
|
}
|
|
}
|