Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions vendor/wheels/migrator/AutoMigrator.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,8 @@ component extends="wheels.migrator.Base" {
// "text" where the model declared "string" (and similarly for
// the INTEGER/REAL affinities). Normalize the affinity-equivalent
// pairs so `migrate diff` doesn't emit spurious `text -> string`
// changes on a schema that is already in sync.
if ($getDBType() == "sqlite" && local.actualMigType == "text" && ListFindNoCase("string,text,datetime,date,time,boolean", local.expectedMigType)) {
local.actualMigType = local.expectedMigType;
}
// changes on a schema that is already in sync (#3565).
local.actualMigType = $normalizeMigTypeForDiff(local.actualMigType, local.expectedMigType);

local.typeChanged = (local.expectedMigType != local.actualMigType && local.actualMigType != "unknown");
local.sizeChanged = (
Expand Down Expand Up @@ -609,4 +607,29 @@ component extends="wheels.migrator.Base" {
return "unknown";
}

/**
* Normalize an actual (DB-probed) migration type against the expected
* (model-introspected) type before the diff comparison, so SQLite's
* type-affinity collapse doesn't read as a spurious type change (#3565).
*
* SQLite stores VARCHAR/TEXT/DATETIME/DATE/TIME under a single TEXT
* storage class. The model layer maps that TEXT back to "string"
* (cf_sql_varchar), while the DB probe maps it to "text", so every stable
* string-like column looked like a `text -> string` change. Return the
* expected type in that case; otherwise return the actual type unchanged.
*
* Public ONLY so autoMigratorSpec can unit-test the affinity families
* (same carve-out as $cfSqlTypeToMigrationType / $dbTypeToMigrationType).
*/
public string function $normalizeMigTypeForDiff(required string actualMigType, required string expectedMigType) {
if (
$getDBType() == "sqlite"
&& arguments.actualMigType == "text"
&& ListFindNoCase("string,text,datetime,date,time,boolean", arguments.expectedMigType)
) {
return arguments.expectedMigType;
}
return arguments.actualMigType;
}

}
37 changes: 37 additions & 0 deletions vendor/wheels/tests/specs/migrator/autoMigratorSpec.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,43 @@ component extends="wheels.WheelsTest" {

});

describe("$normalizeMigTypeForDiff (SQLite text affinity, issue 3565)", () => {

it("treats text vs string as equivalent", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "string")).toBe("string");
});

it("treats text vs text as a no-op", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "text")).toBe("text");
});

it("treats text vs datetime as equivalent", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "datetime")).toBe("datetime");
});

it("treats text vs date as equivalent", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "date")).toBe("date");
});

it("treats text vs time as equivalent", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "time")).toBe("time");
});

it("treats text vs boolean as equivalent", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "boolean")).toBe("boolean");
});

it("does NOT normalize text vs integer (not a text-affinity pair)", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("text", "integer")).toBe("text");
});

it("does NOT normalize a non-text actual type", () => {
expect(autoMigrator.$normalizeMigTypeForDiff("integer", "string")).toBe("integer");
expect(autoMigrator.$normalizeMigTypeForDiff("real", "float")).toBe("real");
});

});

describe("diff()", () => {

it("returns a struct with required keys", () => {
Expand Down
Loading