fix(db): provision CV builder + AI workspace tables via the MySQL-safe reconciler
Deploy failed on prod (MariaDB) while the test job was green: backend startup threw during Database.Migrate(), so deploy.sh's post-deploy health check exited. Root cause: AddCvVariants/AddAiInteractions were scaffolded against SQLite, so they bake SQLite type names into their DDL — DateTimeOffset emits `TEXT`, bool/int emit `INTEGER`, and the PK gets no AUTO_INCREMENT. Run against MariaDB that yields a structurally wrong table, and the composite index over a TEXT column then trips "ERROR 1071: Specified key was too long; max key length is 3072 bytes". SQLite accepts all of it, which is why local/container verification passed. Fix, following the pattern already used for CareerProfiles/AiWorkspaceNotes: - both migrations become no-ops; the three tables are reconciler-owned - reconciler provisions them idempotently per dialect (MySQL: varchar/int AUTO_INCREMENT/datetime(6); SQLite: CREATE TABLE IF NOT EXISTS) - DropMalformedMySqlTable rebuilds a half-built table left by the failed migration, guarded on row count so a table with ANY rows is never dropped - bound the indexed string columns with HasMaxLength so the model matches Verified against a real MariaDB 11 container: reproduced error 1071, then confirmed the corrected DDL yields auto_increment PKs, varchar/datetime columns and all previously-failing indexes. 306 backend tests green; SQLite container starts clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -298,6 +298,12 @@ namespace JobTrackerApi.Data
|
||||
// owns career data. PublicSlug is globally unique so /cv/{slug} can resolve it anonymously.
|
||||
modelBuilder.Entity<CvVariant>()
|
||||
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
|
||||
// Bound the indexed string columns so Pomelo maps them to varchar, not longtext (MariaDB
|
||||
// cannot index a TEXT/longtext column without a prefix length). Actual prod DDL is applied by
|
||||
// the reconciler (StartupInitializationExtensions), MySQL-safe; see that file + DbContext note.
|
||||
modelBuilder.Entity<CvVariant>().Property(x => x.OwnerUserId).HasMaxLength(255);
|
||||
modelBuilder.Entity<CvVariant>().Property(x => x.PublicSlug).HasMaxLength(64);
|
||||
modelBuilder.Entity<CvVariantVersion>().Property(x => x.OwnerUserId).HasMaxLength(255);
|
||||
modelBuilder.Entity<CvVariant>()
|
||||
.HasIndex(x => x.PublicSlug)
|
||||
.IsUnique();
|
||||
@@ -324,6 +330,9 @@ namespace JobTrackerApi.Data
|
||||
// docs/architecture/ai-career-assistant.md.
|
||||
modelBuilder.Entity<AiInteraction>()
|
||||
.HasQueryFilter(x => CurrentUserId != null && x.OwnerUserId == CurrentUserId);
|
||||
// varchar (not longtext) for the indexed columns — see the CvVariant note above.
|
||||
modelBuilder.Entity<AiInteraction>().Property(x => x.OwnerUserId).HasMaxLength(255);
|
||||
modelBuilder.Entity<AiInteraction>().Property(x => x.Module).HasMaxLength(64);
|
||||
modelBuilder.Entity<AiInteraction>()
|
||||
.HasIndex(x => new { x.OwnerUserId, x.JobApplicationId, x.Module, x.CreatedAtUtc });
|
||||
modelBuilder.Entity<AiInteraction>()
|
||||
|
||||
@@ -8,90 +8,20 @@ namespace JobTrackerApi.Migrations
|
||||
/// <inheritdoc />
|
||||
public partial class AddCvVariants : Migration
|
||||
{
|
||||
// Intentionally a no-op. CvVariants/CvVariantVersions are provisioned by the reconciler in
|
||||
// StartupInitializationExtensions (idempotent, MySQL-safe: varchar not longtext so MariaDB can
|
||||
// index PublicSlug/OwnerUserId). The original CreateTable + CREATE INDEX here indexed unbounded
|
||||
// string columns, which succeeds on SQLite but throws on MariaDB ("BLOB/TEXT column used in key
|
||||
// specification without a key length"), crashing backend startup on prod. Reconciler-owned now,
|
||||
// like CareerProfiles/AiWorkspaceNotes. This migration stays recorded so the chain is intact.
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CvVariants",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PublicSlug = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
JobApplicationId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
SettingsJson = table.Column<string>(type: "TEXT", nullable: false),
|
||||
IsPublic = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Version = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTimeOffset>(type: "TEXT", nullable: false),
|
||||
UpdatedAtUtc = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CvVariants", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CvVariants_JobApplications_JobApplicationId",
|
||||
column: x => x.JobApplicationId,
|
||||
principalTable: "JobApplications",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CvVariantVersions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CvVariantId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Version = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SettingsJson = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Source = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CvVariantVersions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CvVariantVersions_CvVariants_CvVariantId",
|
||||
column: x => x.CvVariantId,
|
||||
principalTable: "CvVariants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CvVariants_JobApplicationId",
|
||||
table: "CvVariants",
|
||||
column: "JobApplicationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CvVariants_OwnerUserId_UpdatedAtUtc",
|
||||
table: "CvVariants",
|
||||
columns: new[] { "OwnerUserId", "UpdatedAtUtc" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CvVariants_PublicSlug",
|
||||
table: "CvVariants",
|
||||
column: "PublicSlug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CvVariantVersions_CvVariantId_Version",
|
||||
table: "CvVariantVersions",
|
||||
columns: new[] { "CvVariantId", "Version" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CvVariantVersions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CvVariants");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,51 +8,18 @@ namespace JobTrackerApi.Migrations
|
||||
/// <inheritdoc />
|
||||
public partial class AddAiInteractions : Migration
|
||||
{
|
||||
// Intentionally a no-op — AiInteractions is provisioned by the reconciler in
|
||||
// StartupInitializationExtensions (idempotent, MySQL-safe: Module/OwnerUserId are varchar so
|
||||
// MariaDB can index them). The original CREATE INDEX here indexed unbounded string columns,
|
||||
// which crashes backend startup on MariaDB. See the AddCvVariants migration for the full note.
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AiInteractions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
OwnerUserId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
JobApplicationId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Module = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Mode = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Title = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Provider = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ResultJson = table.Column<string>(type: "TEXT", nullable: false),
|
||||
CreatedAtUtc = table.Column<DateTimeOffset>(type: "TEXT", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_AiInteractions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_AiInteractions_JobApplications_JobApplicationId",
|
||||
column: x => x.JobApplicationId,
|
||||
principalTable: "JobApplications",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AiInteractions_JobApplicationId",
|
||||
table: "AiInteractions",
|
||||
column: "JobApplicationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_AiInteractions_OwnerUserId_JobApplicationId_Module_CreatedAtUtc",
|
||||
table: "AiInteractions",
|
||||
columns: new[] { "OwnerUserId", "JobApplicationId", "Module", "CreatedAtUtc" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "AiInteractions");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +104,45 @@ public static class StartupInitializationExtensions
|
||||
return cmd.ExecuteScalar() is not null;
|
||||
}
|
||||
|
||||
// A migration scaffolded against SQLite bakes SQLite type names into its DDL ("TEXT" for
|
||||
// DateTimeOffset, "INTEGER" for bool/int, no AUTO_INCREMENT). Run against MariaDB it produces a
|
||||
// structurally wrong table -- and a composite index over a TEXT column then blows MySQL's
|
||||
// 3072-byte key limit, which is what crashed backend startup on prod. Detect that shape by
|
||||
// probing one column's data type and rebuild the table from the correct MySQL DDL.
|
||||
//
|
||||
// Guarded on row count: an empty malformed table is dropped and recreated; a table with ANY rows
|
||||
// is left untouched (never destroy user data -- surface it instead of silently deleting).
|
||||
private static bool DropMalformedMySqlTable(DbConnection c, string table, string probeColumn, string expectedDataType)
|
||||
{
|
||||
if (!HasMySqlTable(c, table)) return false;
|
||||
|
||||
string? actual;
|
||||
using (var probe = c.CreateCommand())
|
||||
{
|
||||
probe.CommandText = "SELECT DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = @schema AND TABLE_NAME = @table AND COLUMN_NAME = @column LIMIT 1;";
|
||||
var s = probe.CreateParameter(); s.ParameterName = "@schema"; s.Value = c.Database; probe.Parameters.Add(s);
|
||||
var t = probe.CreateParameter(); t.ParameterName = "@table"; t.Value = table; probe.Parameters.Add(t);
|
||||
var col = probe.CreateParameter(); col.ParameterName = "@column"; col.Value = probeColumn; probe.Parameters.Add(col);
|
||||
actual = probe.ExecuteScalar() as string;
|
||||
}
|
||||
|
||||
// Unknown column or already the right type: nothing to repair.
|
||||
if (string.IsNullOrEmpty(actual) || string.Equals(actual, expectedDataType, StringComparison.OrdinalIgnoreCase)) return false;
|
||||
|
||||
long rows;
|
||||
using (var count = c.CreateCommand())
|
||||
{
|
||||
count.CommandText = $"SELECT COUNT(*) FROM `{table}`;";
|
||||
rows = Convert.ToInt64(count.ExecuteScalar() ?? 0L);
|
||||
}
|
||||
if (rows > 0) return false;
|
||||
|
||||
using var drop = c.CreateCommand();
|
||||
drop.CommandText = $"DROP TABLE `{table}`;";
|
||||
drop.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool MySqlColumnExists(DbConnection c, string table, string column)
|
||||
{
|
||||
using var cmd = c.CreateCommand();
|
||||
@@ -760,6 +799,65 @@ public static class StartupInitializationExtensions
|
||||
Exec(c, """CREATE UNIQUE INDEX IF NOT EXISTS "IX_AiWorkspaceNotes_OwnerUserId_JobApplicationId_NoteType" ON "AiWorkspaceNotes" ("OwnerUserId", "JobApplicationId", "NoteType");""");
|
||||
}
|
||||
|
||||
// Phase 4 CV Builder: a variant is a lens over the master profile; versions are its
|
||||
// autosave history. Reconciler-owned (not migration-owned) so the schema is MySQL-safe
|
||||
// on prod -- see the AddCvVariants migration note. docs/architecture/cv-builder.md.
|
||||
static void EnsureCvBuilderTables(DbConnection c)
|
||||
{
|
||||
Exec(c, """
|
||||
CREATE TABLE IF NOT EXISTS "CvVariants" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CvVariants" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"PublicSlug" TEXT NOT NULL,
|
||||
"Name" TEXT NOT NULL,
|
||||
"JobApplicationId" INTEGER NULL,
|
||||
"SettingsJson" TEXT NOT NULL,
|
||||
"IsPublic" INTEGER NOT NULL,
|
||||
"Version" INTEGER NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL,
|
||||
"UpdatedAtUtc" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_CvVariants_JobApplications_JobApplicationId" FOREIGN KEY ("JobApplicationId") REFERENCES "JobApplications" ("Id") ON DELETE SET NULL
|
||||
);
|
||||
""");
|
||||
Exec(c, """
|
||||
CREATE TABLE IF NOT EXISTS "CvVariantVersions" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_CvVariantVersions" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"CvVariantId" INTEGER NOT NULL,
|
||||
"Version" INTEGER NOT NULL,
|
||||
"SettingsJson" TEXT NOT NULL,
|
||||
"Source" TEXT NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_CvVariantVersions_CvVariants_CvVariantId" FOREIGN KEY ("CvVariantId") REFERENCES "CvVariants" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
""");
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_CvVariants_JobApplicationId" ON "CvVariants" ("JobApplicationId");""");
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_CvVariants_OwnerUserId_UpdatedAtUtc" ON "CvVariants" ("OwnerUserId", "UpdatedAtUtc");""");
|
||||
Exec(c, """CREATE UNIQUE INDEX IF NOT EXISTS "IX_CvVariants_PublicSlug" ON "CvVariants" ("PublicSlug");""");
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_CvVariantVersions_CvVariantId_Version" ON "CvVariantVersions" ("CvVariantId", "Version");""");
|
||||
}
|
||||
|
||||
// Phase 5 AI Workspace: append-only AI interaction history per job application.
|
||||
static void EnsureAiInteractionsTable(DbConnection c)
|
||||
{
|
||||
Exec(c, """
|
||||
CREATE TABLE IF NOT EXISTS "AiInteractions" (
|
||||
"Id" INTEGER NOT NULL CONSTRAINT "PK_AiInteractions" PRIMARY KEY AUTOINCREMENT,
|
||||
"OwnerUserId" TEXT NOT NULL,
|
||||
"JobApplicationId" INTEGER NOT NULL,
|
||||
"Module" TEXT NOT NULL,
|
||||
"Mode" TEXT NULL,
|
||||
"Title" TEXT NOT NULL,
|
||||
"Provider" TEXT NOT NULL,
|
||||
"ResultJson" TEXT NOT NULL,
|
||||
"CreatedAtUtc" TEXT NOT NULL,
|
||||
CONSTRAINT "FK_AiInteractions_JobApplications_JobApplicationId" FOREIGN KEY ("JobApplicationId") REFERENCES "JobApplications" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
""");
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_AiInteractions_JobApplicationId" ON "AiInteractions" ("JobApplicationId");""");
|
||||
Exec(c, """CREATE INDEX IF NOT EXISTS "IX_AiInteractions_Owner_Job_Module_Created" ON "AiInteractions" ("OwnerUserId", "JobApplicationId", "Module", "CreatedAtUtc");""");
|
||||
}
|
||||
|
||||
EnsureGmailConnectionsTable(conn);
|
||||
EnsureMicrosoftGraphConnectionsTable(conn);
|
||||
EnsureImapConnectionsTable(conn);
|
||||
@@ -770,6 +868,8 @@ public static class StartupInitializationExtensions
|
||||
EnsureCareerProfileTables(conn);
|
||||
EnsureInterviewPrepNotesTable(conn);
|
||||
EnsureAiWorkspaceNotesTable(conn);
|
||||
EnsureCvBuilderTables(conn);
|
||||
EnsureAiInteractionsTable(conn);
|
||||
|
||||
// Legacy DB signature: migration history exists (AddCorrespondence applied), but 20260310195000 not recorded,
|
||||
// and at least one of the new columns already exists.
|
||||
@@ -1241,6 +1341,93 @@ public static class StartupInitializationExtensions
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Phase 4 CV Builder + Phase 5 AI Workspace. Reconciler-owned rather than
|
||||
// migration-owned: those migrations were scaffolded against SQLite, so on MariaDB
|
||||
// they emit TEXT datetimes and a PK with no AUTO_INCREMENT, and the composite
|
||||
// index over a TEXT column then exceeds MySQL's 3072-byte key limit -- which
|
||||
// crashed backend startup on prod. Drop any such half-built table (only when it
|
||||
// holds no rows) and rebuild from the correct MySQL DDL below.
|
||||
// Children first: CvVariantVersions FKs into CvVariants.
|
||||
DropMalformedMySqlTable(conn, "CvVariantVersions", "CreatedAtUtc", "datetime");
|
||||
DropMalformedMySqlTable(conn, "CvVariants", "UpdatedAtUtc", "datetime");
|
||||
DropMalformedMySqlTable(conn, "AiInteractions", "CreatedAtUtc", "datetime");
|
||||
|
||||
if (!HasMySqlTable(conn, "CvVariants"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS `CvVariants` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`OwnerUserId` varchar(255) NOT NULL,
|
||||
`PublicSlug` varchar(64) NOT NULL,
|
||||
`Name` varchar(255) NOT NULL,
|
||||
`JobApplicationId` int NULL,
|
||||
`SettingsJson` longtext NOT NULL,
|
||||
`IsPublic` tinyint(1) NOT NULL,
|
||||
`Version` int NOT NULL,
|
||||
`CreatedAtUtc` datetime(6) NOT NULL,
|
||||
`UpdatedAtUtc` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CvVariants_JobApplications_JobApplicationId` FOREIGN KEY (`JobApplicationId`) REFERENCES `JobApplications` (`Id`) ON DELETE SET NULL
|
||||
);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (!HasMySqlTable(conn, "CvVariantVersions"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS `CvVariantVersions` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`OwnerUserId` varchar(255) NOT NULL,
|
||||
`CvVariantId` int NOT NULL,
|
||||
`Version` int NOT NULL,
|
||||
`SettingsJson` longtext NOT NULL,
|
||||
`Source` varchar(100) NOT NULL,
|
||||
`CreatedAtUtc` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_CvVariantVersions_CvVariants_CvVariantId` FOREIGN KEY (`CvVariantId`) REFERENCES `CvVariants` (`Id`) ON DELETE CASCADE
|
||||
);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (!HasMySqlTable(conn, "AiInteractions"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"CREATE TABLE IF NOT EXISTS `AiInteractions` (
|
||||
`Id` int NOT NULL AUTO_INCREMENT,
|
||||
`OwnerUserId` varchar(255) NOT NULL,
|
||||
`JobApplicationId` int NOT NULL,
|
||||
`Module` varchar(64) NOT NULL,
|
||||
`Mode` varchar(64) NULL,
|
||||
`Title` varchar(255) NOT NULL,
|
||||
`Provider` varchar(100) NOT NULL,
|
||||
`ResultJson` longtext NOT NULL,
|
||||
`CreatedAtUtc` datetime(6) NOT NULL,
|
||||
PRIMARY KEY (`Id`),
|
||||
CONSTRAINT `FK_AiInteractions_JobApplications_JobApplicationId` FOREIGN KEY (`JobApplicationId`) REFERENCES `JobApplications` (`Id`) ON DELETE CASCADE
|
||||
);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "CvVariants", "Id");
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "CvVariantVersions", "Id");
|
||||
EnsureMySqlAutoIncrementPrimaryKey(conn, "AiInteractions", "Id");
|
||||
|
||||
foreach (var (ixTable, ixName, ixColumns, ixUnique) in new[]
|
||||
{
|
||||
("CvVariants", "IX_CvVariants_JobApplicationId", "`JobApplicationId`", false),
|
||||
("CvVariants", "IX_CvVariants_OwnerUserId_UpdatedAtUtc", "`OwnerUserId`, `UpdatedAtUtc`", false),
|
||||
("CvVariants", "IX_CvVariants_PublicSlug", "`PublicSlug`", true),
|
||||
("CvVariantVersions", "IX_CvVariantVersions_CvVariantId_Version", "`CvVariantId`, `Version`", false),
|
||||
("AiInteractions", "IX_AiInteractions_JobApplicationId", "`JobApplicationId`", false),
|
||||
("AiInteractions", "IX_AiInteractions_Owner_Job_Module_Created", "`OwnerUserId`, `JobApplicationId`, `Module`, `CreatedAtUtc`", false),
|
||||
})
|
||||
{
|
||||
if (MySqlIndexExists(conn, ixTable, ixName)) continue;
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = $"CREATE {(ixUnique ? "UNIQUE " : string.Empty)}INDEX `{ixName}` ON `{ixTable}` ({ixColumns});";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
if (!MySqlIndexExists(conn, "CareerProfiles", "IX_CareerProfiles_OwnerUserId"))
|
||||
{
|
||||
using var cmd = conn.CreateCommand();
|
||||
|
||||
Reference in New Issue
Block a user