Fix MariaDB CHECK constraint quoting that crash-looped startup seeding
Three CHECK constraints shipped with PostgreSQL identifier quoting left over
from the MySQL port:
ck_grn_config_singleton CHECK ("ConfigId" = 1)
ck_sales_config_singleton CHECK ("ConfigId" = 1)
ck_warranty_periods_months CHECK ("Months" BETWEEN 1 AND 600)
ANSI_QUOTES is not in MariaDB's default sql_mode, so a double-quoted token is a
*string literal*, not an identifier. `"ConfigId" = 1` therefore evaluates as
`'ConfigId' = 1`; MariaDB coerces the string to a number to compare it and,
under strict mode, raises `Truncated incorrect DECIMAL value: 'ConfigId'`.
The DDL creates fine and only breaks at runtime, so this got through the
deploy and then failed every INSERT into grn_config, sales_config and
warranty_periods. That includes DataSeeder's two singleton config rows, which
run immediately after MigrateAsync() in Program.cs — so the service threw
"Database migration succeeded, but startup seeding failed" on every boot and
systemd restart-looped (counter reached 110, each restart dumping core).
Fixed in the entity configurations and repaired on existing databases by
migration 20261005190000_FixCheckConstraintQuotingForMySql, which drops and
re-adds the three constraints with backticks. The drop/add is emitted as raw
SQL rather than DropCheckConstraint because MariaDB spells the removal
DROP CONSTRAINT, not MySQL's DROP CHECK; IF EXISTS keeps it re-runnable on a
database where the broken constraint was already removed by hand.
The identical trap was swept during the port itself; these three were
reintroduced by a feature migration written afterwards.
No data cleanup is needed: the failing inserts never committed, so the
affected tables are empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,7 @@ public sealed class GrnConfigConfiguration : IEntityTypeConfiguration<GrnConfig>
|
|||||||
public void Configure(EntityTypeBuilder<GrnConfig> builder)
|
public void Configure(EntityTypeBuilder<GrnConfig> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("grn_config", t =>
|
builder.ToTable("grn_config", t =>
|
||||||
t.HasCheckConstraint("ck_grn_config_singleton", $"\"ConfigId\" = {GrnConfig.SingletonId}"));
|
t.HasCheckConstraint("ck_grn_config_singleton", $"`ConfigId` = {GrnConfig.SingletonId}"));
|
||||||
|
|
||||||
builder.HasKey(c => c.ConfigId);
|
builder.HasKey(c => c.ConfigId);
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public sealed class SalesConfigConfiguration : IEntityTypeConfiguration<SalesCon
|
|||||||
public void Configure(EntityTypeBuilder<SalesConfig> builder)
|
public void Configure(EntityTypeBuilder<SalesConfig> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("sales_config", t =>
|
builder.ToTable("sales_config", t =>
|
||||||
t.HasCheckConstraint("ck_sales_config_singleton", $"\"ConfigId\" = {SalesConfig.SingletonId}"));
|
t.HasCheckConstraint("ck_sales_config_singleton", $"`ConfigId` = {SalesConfig.SingletonId}"));
|
||||||
|
|
||||||
builder.HasKey(c => c.ConfigId);
|
builder.HasKey(c => c.ConfigId);
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class WarrantyPeriodConfiguration : IEntityTypeConfiguration<Warra
|
|||||||
public void Configure(EntityTypeBuilder<WarrantyPeriod> builder)
|
public void Configure(EntityTypeBuilder<WarrantyPeriod> builder)
|
||||||
{
|
{
|
||||||
builder.ToTable("warranty_periods", t =>
|
builder.ToTable("warranty_periods", t =>
|
||||||
t.HasCheckConstraint("ck_warranty_periods_months", "\"Months\" BETWEEN 1 AND 600"));
|
t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600"));
|
||||||
builder.HasKey(p => p.WarrantyPeriodId);
|
builder.HasKey(p => p.WarrantyPeriodId);
|
||||||
|
|
||||||
builder.Property(p => p.Months).IsRequired();
|
builder.Property(p => p.Months).IsRequired();
|
||||||
|
|||||||
+8916
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace ERPCore.Migrations
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Repairs three CHECK constraints that were created with PostgreSQL identifier quoting
|
||||||
|
/// (<c>"ConfigId"</c>) left over from the MySQL port. Without ANSI_QUOTES — which is not in
|
||||||
|
/// MariaDB's default sql_mode — a double-quoted token is a *string literal*, so
|
||||||
|
/// <c>"ConfigId" = 1</c> evaluates as <c>'ConfigId' = 1</c>. MariaDB coerces the string to a
|
||||||
|
/// number to compare it and, under strict mode, raises
|
||||||
|
/// <c>Truncated incorrect DECIMAL value: 'ConfigId'</c> — which made every INSERT into
|
||||||
|
/// grn_config / sales_config / warranty_periods fail, including the startup seeding of the
|
||||||
|
/// two singleton config rows (crash-looping the service on boot).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Written by hand rather than scaffolded, and the drop/add is emitted as raw SQL instead of
|
||||||
|
/// <c>DropCheckConstraint</c>: MariaDB spells the removal <c>DROP CONSTRAINT</c> (MySQL's
|
||||||
|
/// <c>DROP CHECK</c> is not a safe assumption here), and <c>IF EXISTS</c> keeps this
|
||||||
|
/// re-runnable on a database where the broken constraint was already removed by hand.
|
||||||
|
/// </remarks>
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class FixCheckConstraintQuotingForMySql : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `grn_config` DROP CONSTRAINT IF EXISTS `ck_grn_config_singleton`;");
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `grn_config` ADD CONSTRAINT `ck_grn_config_singleton` CHECK (`ConfigId` = 1);");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `sales_config` DROP CONSTRAINT IF EXISTS `ck_sales_config_singleton`;");
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `sales_config` ADD CONSTRAINT `ck_sales_config_singleton` CHECK (`ConfigId` = 1);");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `warranty_periods` DROP CONSTRAINT IF EXISTS `ck_warranty_periods_months`;");
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `warranty_periods` ADD CONSTRAINT `ck_warranty_periods_months` CHECK (`Months` BETWEEN 1 AND 600);");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
// Restores the original (broken) expressions so the migration is a faithful inverse.
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `grn_config` DROP CONSTRAINT IF EXISTS `ck_grn_config_singleton`;");
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `grn_config` ADD CONSTRAINT `ck_grn_config_singleton` CHECK (\"ConfigId\" = 1);");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `sales_config` DROP CONSTRAINT IF EXISTS `ck_sales_config_singleton`;");
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `sales_config` ADD CONSTRAINT `ck_sales_config_singleton` CHECK (\"ConfigId\" = 1);");
|
||||||
|
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `warranty_periods` DROP CONSTRAINT IF EXISTS `ck_warranty_periods_months`;");
|
||||||
|
migrationBuilder.Sql("ALTER TABLE `warranty_periods` ADD CONSTRAINT `ck_warranty_periods_months` CHECK (\"Months\" BETWEEN 1 AND 600);");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1537,7 +1537,7 @@ namespace ERPCore.Migrations
|
|||||||
|
|
||||||
b.ToTable("grn_config", null, t =>
|
b.ToTable("grn_config", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_grn_config_singleton", "\"ConfigId\" = 1");
|
t.HasCheckConstraint("ck_grn_config_singleton", "`ConfigId` = 1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -4443,7 +4443,7 @@ namespace ERPCore.Migrations
|
|||||||
|
|
||||||
b.ToTable("sales_config", null, t =>
|
b.ToTable("sales_config", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_sales_config_singleton", "\"ConfigId\" = 1");
|
t.HasCheckConstraint("ck_sales_config_singleton", "`ConfigId` = 1");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -6710,7 +6710,7 @@ namespace ERPCore.Migrations
|
|||||||
|
|
||||||
b.ToTable("warranty_periods", null, t =>
|
b.ToTable("warranty_periods", null, t =>
|
||||||
{
|
{
|
||||||
t.HasCheckConstraint("ck_warranty_periods_months", "\"Months\" BETWEEN 1 AND 600");
|
t.HasCheckConstraint("ck_warranty_periods_months", "`Months` BETWEEN 1 AND 600");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user