fix(tasks): match legacy_ref_json by extracted fields for idempotent migrate
Memind CI / Test, build, and release guards (push) Successful in 3m32s
Memind CI / Test, build, and release guards (push) Successful in 3m32s
MySQL JSON key order differs from JS stringify, which caused duplicate h5_tasks rows on re-run migrate. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+21
-10
@@ -37,6 +37,17 @@ function legacyKey(ref) {
|
||||
return `${ref.table}:${ref.id}`;
|
||||
}
|
||||
|
||||
/** MySQL JSON column key order is not stable; match by extracted fields. */
|
||||
function legacyRefMatchSql(column = 'legacy_ref_json') {
|
||||
return `${column} IS NOT NULL
|
||||
AND JSON_UNQUOTE(JSON_EXTRACT(${column}, '$.table')) = ?
|
||||
AND JSON_UNQUOTE(JSON_EXTRACT(${column}, '$.id')) = ?`;
|
||||
}
|
||||
|
||||
function legacyRefMatchParams(legacyRef) {
|
||||
return [legacyRef.table, legacyRef.id];
|
||||
}
|
||||
|
||||
function formatTriggerLabel(task, timezone = DEFAULT_TIMEZONE) {
|
||||
const trigger = task.trigger ?? {};
|
||||
if (trigger.at) return trigger.at;
|
||||
@@ -352,9 +363,9 @@ export function createTaskUnifiedService(pool, { clock = { now: () => Date.now()
|
||||
if (mapped.legacyRef?.table && mapped.legacyRef?.id) {
|
||||
const [existing] = await pool.query(
|
||||
`SELECT * FROM h5_tasks
|
||||
WHERE user_id = ? AND legacy_ref_json = ?
|
||||
WHERE user_id = ? AND ${legacyRefMatchSql()}
|
||||
LIMIT 1`,
|
||||
[mapped.userId, JSON.stringify(mapped.legacyRef)],
|
||||
[mapped.userId, ...legacyRefMatchParams(mapped.legacyRef)],
|
||||
);
|
||||
if (existing?.[0]) return rowToUnifiedTask(existing[0]);
|
||||
}
|
||||
@@ -373,8 +384,8 @@ export function createTaskUnifiedService(pool, { clock = { now: () => Date.now()
|
||||
const [result] = await pool.query(
|
||||
`UPDATE h5_tasks
|
||||
SET status = 'cancelled', updated_at = ?
|
||||
WHERE user_id = ? AND legacy_ref_json = ?`,
|
||||
[clock.now(), userId, JSON.stringify(legacyRef)],
|
||||
WHERE user_id = ? AND ${legacyRefMatchSql()}`,
|
||||
[clock.now(), userId, ...legacyRefMatchParams(legacyRef)],
|
||||
);
|
||||
return Number(result?.affectedRows ?? 0);
|
||||
};
|
||||
@@ -400,17 +411,17 @@ export function createTaskUnifiedService(pool, { clock = { now: () => Date.now()
|
||||
}
|
||||
if (lastError !== undefined) {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT spec_json FROM h5_tasks WHERE user_id = ? AND legacy_ref_json = ? LIMIT 1`,
|
||||
[userId, JSON.stringify(legacyRef)],
|
||||
`SELECT spec_json FROM h5_tasks WHERE user_id = ? AND ${legacyRefMatchSql()} LIMIT 1`,
|
||||
[userId, ...legacyRefMatchParams(legacyRef)],
|
||||
);
|
||||
const spec = parseJson(rows?.[0]?.spec_json) ?? {};
|
||||
spec.lastError = lastError ?? null;
|
||||
sets.push('spec_json = ?');
|
||||
params.push(JSON.stringify(spec));
|
||||
}
|
||||
params.push(userId, JSON.stringify(legacyRef));
|
||||
params.push(userId, ...legacyRefMatchParams(legacyRef));
|
||||
const [result] = await pool.query(
|
||||
`UPDATE h5_tasks SET ${sets.join(', ')} WHERE user_id = ? AND legacy_ref_json = ?`,
|
||||
`UPDATE h5_tasks SET ${sets.join(', ')} WHERE user_id = ? AND ${legacyRefMatchSql()}`,
|
||||
params,
|
||||
);
|
||||
return Number(result?.affectedRows ?? 0);
|
||||
@@ -568,8 +579,8 @@ export function createTaskUnifiedService(pool, { clock = { now: () => Date.now()
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
const before = await pool.query(
|
||||
`SELECT id FROM h5_tasks WHERE user_id = ? AND legacy_ref_json = ? LIMIT 1`,
|
||||
[candidate.userId, JSON.stringify(candidate.legacyRef)],
|
||||
`SELECT id FROM h5_tasks WHERE user_id = ? AND ${legacyRefMatchSql()} LIMIT 1`,
|
||||
[candidate.userId, ...legacyRefMatchParams(candidate.legacyRef)],
|
||||
);
|
||||
if (before?.[0]?.[0]) {
|
||||
stats.skipped += 1;
|
||||
|
||||
Reference in New Issue
Block a user