Skip to content

Commit 8878e41

Browse files
committed
fix: correct outcome shape handling in extractStats and formatOutcome
Rust serializes ReladiffOutcome with serde tag 'mode', producing: {mode: 'diff', diff_rows: [...], stats: {rows_table1, rows_table2, exclusive_table1, exclusive_table2, updated, unchanged}} Previous code checked for {Match: {...}} / {Diff: {...}} shapes that never matched, causing partitioned diff to report all partitions as 'identical' with 0 rows. - extractStats(): check outcome.mode === 'diff', read from stats fields - mergeOutcomes(): aggregate mode-based outcomes correctly - summarize()/formatOutcome(): display mode-based shape with correct labels
1 parent fb9cb19 commit 8878e41

File tree

2 files changed

+82
-66
lines changed

2 files changed

+82
-66
lines changed

packages/opencode/src/altimate/native/connections/data-diff.ts

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ function buildPartitionWhereClause(
229229

230230
/**
231231
* Extract DiffStats from a successful outcome (if present).
232+
*
233+
* Rust serializes ReladiffOutcome as: {mode: "diff", diff_rows: [...], stats: {...}}
234+
* stats fields: rows_table1, rows_table2, exclusive_table1, exclusive_table2, updated, unchanged
232235
*/
233236
function extractStats(outcome: unknown): {
234237
rows_source: number
@@ -239,57 +242,54 @@ function extractStats(outcome: unknown): {
239242
const o = outcome as any
240243
if (!o) return { rows_source: 0, rows_target: 0, differences: 0, status: "identical" }
241244

242-
if (o.Match) {
243-
return {
244-
rows_source: o.Match.row_count ?? 0,
245-
rows_target: o.Match.row_count ?? 0,
246-
differences: 0,
247-
status: "identical",
248-
}
249-
}
250-
251-
if (o.Diff) {
252-
const d = o.Diff
245+
if (o.mode === "diff") {
246+
const s = o.stats ?? {}
247+
const exclusive1 = Number(s.exclusive_table1 ?? 0)
248+
const exclusive2 = Number(s.exclusive_table2 ?? 0)
249+
const updated = Number(s.updated ?? 0)
250+
const differences = exclusive1 + exclusive2 + updated
253251
return {
254-
rows_source: d.total_source_rows ?? 0,
255-
rows_target: d.total_target_rows ?? 0,
256-
differences: (d.rows_only_in_source ?? 0) + (d.rows_only_in_target ?? 0) + (d.rows_updated ?? 0),
257-
status: "differ",
252+
rows_source: Number(s.rows_table1 ?? 0),
253+
rows_target: Number(s.rows_table2 ?? 0),
254+
differences,
255+
status: differences > 0 ? "differ" : "identical",
258256
}
259257
}
260258

261259
return { rows_source: 0, rows_target: 0, differences: 0, status: "identical" }
262260
}
263261

264262
/**
265-
* Merge two Diff outcomes into one aggregated Diff outcome.
263+
* Merge two diff outcomes into one aggregated outcome.
264+
*
265+
* Both outcomes use the Rust shape: {mode: "diff", diff_rows: [...], stats: {...}}
266266
*/
267267
function mergeOutcomes(accumulated: unknown, next: unknown): unknown {
268+
if (!accumulated) return next
269+
if (!next) return accumulated
270+
268271
const a = accumulated as any
269272
const n = next as any
270273

271-
const aD = a?.Diff ?? (a?.Match ? { total_source_rows: a.Match.row_count, total_target_rows: a.Match.row_count, rows_only_in_source: 0, rows_only_in_target: 0, rows_updated: 0, rows_identical: a.Match.row_count, sample_diffs: [] } : null)
272-
const nD = n?.Diff ?? (n?.Match ? { total_source_rows: n.Match.row_count, total_target_rows: n.Match.row_count, rows_only_in_source: 0, rows_only_in_target: 0, rows_updated: 0, rows_identical: n.Match.row_count, sample_diffs: [] } : null)
273-
274-
if (!aD && !nD) return { Match: { row_count: 0 } }
275-
if (!aD) return next
276-
if (!nD) return accumulated
277-
278-
const merged = {
279-
total_source_rows: (aD.total_source_rows ?? 0) + (nD.total_source_rows ?? 0),
280-
total_target_rows: (aD.total_target_rows ?? 0) + (nD.total_target_rows ?? 0),
281-
rows_only_in_source: (aD.rows_only_in_source ?? 0) + (nD.rows_only_in_source ?? 0),
282-
rows_only_in_target: (aD.rows_only_in_target ?? 0) + (nD.rows_only_in_target ?? 0),
283-
rows_updated: (aD.rows_updated ?? 0) + (nD.rows_updated ?? 0),
284-
rows_identical: (aD.rows_identical ?? 0) + (nD.rows_identical ?? 0),
285-
sample_diffs: [...(aD.sample_diffs ?? []), ...(nD.sample_diffs ?? [])].slice(0, 20),
286-
}
274+
const aS = a.stats ?? {}
275+
const nS = n.stats ?? {}
276+
277+
const rows_table1 = (Number(aS.rows_table1) || 0) + (Number(nS.rows_table1) || 0)
278+
const rows_table2 = (Number(aS.rows_table2) || 0) + (Number(nS.rows_table2) || 0)
279+
const exclusive_table1 = (Number(aS.exclusive_table1) || 0) + (Number(nS.exclusive_table1) || 0)
280+
const exclusive_table2 = (Number(aS.exclusive_table2) || 0) + (Number(nS.exclusive_table2) || 0)
281+
const updated = (Number(aS.updated) || 0) + (Number(nS.updated) || 0)
282+
const unchanged = (Number(aS.unchanged) || 0) + (Number(nS.unchanged) || 0)
287283

288-
const totalDiff = merged.rows_only_in_source + merged.rows_only_in_target + merged.rows_updated
289-
if (totalDiff === 0) {
290-
return { Match: { row_count: merged.total_source_rows, algorithm: "partitioned" } }
284+
const totalRows = rows_table1 + rows_table2
285+
const totalDiff = exclusive_table1 + exclusive_table2 + updated
286+
const diff_percent = totalRows > 0 ? (totalDiff / totalRows) * 100 : 0
287+
288+
return {
289+
mode: "diff",
290+
diff_rows: [...(a.diff_rows ?? []), ...(n.diff_rows ?? [])].slice(0, 100),
291+
stats: { rows_table1, rows_table2, exclusive_table1, exclusive_table2, updated, unchanged, diff_percent },
291292
}
292-
return { Diff: merged }
293293
}
294294

295295
/**

packages/opencode/src/altimate/tools/data-diff.ts

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,23 @@ export const DataDiffTool = Tool.define("data_diff", {
129129

130130
function summarize(outcome: any): string {
131131
if (!outcome) return "complete"
132-
if (outcome.Match) return "IDENTICAL ✓"
133-
if (outcome.Diff) {
134-
const r = outcome.Diff
132+
133+
// Rust serializes ReladiffOutcome as {mode: "diff"|"profile"|..., stats: {...}, diff_rows: [...]}
134+
if (outcome.mode === "diff") {
135+
const s = outcome.stats ?? {}
136+
const e1 = Number(s.exclusive_table1 ?? 0)
137+
const e2 = Number(s.exclusive_table2 ?? 0)
138+
const upd = Number(s.updated ?? 0)
139+
if (e1 === 0 && e2 === 0 && upd === 0) return "IDENTICAL ✓"
135140
const parts: string[] = []
136-
if (r.rows_only_in_source > 0) parts.push(`${r.rows_only_in_source} only in source`)
137-
if (r.rows_only_in_target > 0) parts.push(`${r.rows_only_in_target} only in target`)
138-
if (r.rows_updated > 0) parts.push(`${r.rows_updated} updated`)
139-
return parts.length ? parts.join(", ") : "differences found"
141+
if (e1 > 0) parts.push(`${e1} only in source`)
142+
if (e2 > 0) parts.push(`${e2} only in target`)
143+
if (upd > 0) parts.push(`${upd} updated`)
144+
return parts.join(", ")
140145
}
141-
if (outcome.Profile) return "profile complete"
146+
if (outcome.mode === "profile") return "profile complete"
147+
if (outcome.mode === "cascade") return "cascade complete"
148+
142149
return "complete"
143150
}
144151

@@ -147,45 +154,54 @@ function formatOutcome(outcome: any, source: string, target: string): string {
147154

148155
const lines: string[] = []
149156

150-
if (outcome.Match) {
151-
lines.push(`✓ Tables are IDENTICAL`)
152-
const m = outcome.Match
153-
if (m.row_count != null) lines.push(` Rows checked: ${m.row_count.toLocaleString()}`)
154-
if (m.algorithm) lines.push(` Algorithm: ${m.algorithm}`)
155-
return lines.join("\n")
156-
}
157+
// Rust serializes ReladiffOutcome as {mode: "diff", diff_rows: [...], stats: {...}}
158+
// stats: rows_table1, rows_table2, exclusive_table1, exclusive_table2, updated, unchanged
159+
if (outcome.mode === "diff") {
160+
const s = outcome.stats ?? {}
161+
const rows1 = Number(s.rows_table1 ?? 0)
162+
const rows2 = Number(s.rows_table2 ?? 0)
163+
const e1 = Number(s.exclusive_table1 ?? 0)
164+
const e2 = Number(s.exclusive_table2 ?? 0)
165+
const updated = Number(s.updated ?? 0)
166+
const unchanged = Number(s.unchanged ?? 0)
167+
168+
if (e1 === 0 && e2 === 0 && updated === 0) {
169+
lines.push(`✓ Tables are IDENTICAL`)
170+
if (rows1 > 0) lines.push(` Rows checked: ${rows1.toLocaleString()}`)
171+
return lines.join("\n")
172+
}
157173

158-
if (outcome.Diff) {
159-
const r = outcome.Diff
160174
lines.push(`✗ Tables DIFFER`)
161175
lines.push(``)
162176
lines.push(` Source: ${source}`)
163177
lines.push(` Target: ${target}`)
164178
lines.push(``)
165179

166-
if (r.total_source_rows != null) lines.push(` Source rows: ${r.total_source_rows.toLocaleString()}`)
167-
if (r.total_target_rows != null) lines.push(` Target rows: ${r.total_target_rows.toLocaleString()}`)
168-
if (r.rows_only_in_source > 0) lines.push(` Only in source: ${r.rows_only_in_source.toLocaleString()}`)
169-
if (r.rows_only_in_target > 0) lines.push(` Only in target: ${r.rows_only_in_target.toLocaleString()}`)
170-
if (r.rows_updated > 0) lines.push(` Updated rows: ${r.rows_updated.toLocaleString()}`)
171-
if (r.rows_identical > 0) lines.push(` Identical rows: ${r.rows_identical.toLocaleString()}`)
180+
if (rows1 > 0) lines.push(` Source rows: ${rows1.toLocaleString()}`)
181+
if (rows2 > 0) lines.push(` Target rows: ${rows2.toLocaleString()}`)
182+
if (e1 > 0) lines.push(` Only in source: ${e1.toLocaleString()}`)
183+
if (e2 > 0) lines.push(` Only in target: ${e2.toLocaleString()}`)
184+
if (updated > 0) lines.push(` Updated rows: ${updated.toLocaleString()}`)
185+
if (unchanged > 0) lines.push(` Identical rows: ${unchanged.toLocaleString()}`)
172186

173-
if (r.sample_diffs?.length) {
187+
const diffRows = outcome.diff_rows ?? []
188+
if (diffRows.length > 0) {
174189
lines.push(``)
175-
lines.push(` Sample differences (first ${r.sample_diffs.length}):`)
176-
for (const d of r.sample_diffs.slice(0, 5)) {
177-
lines.push(` key=${JSON.stringify(d.key)} col=${d.column}: ${d.source_value}${d.target_value}`)
190+
lines.push(` Sample differences (first ${Math.min(diffRows.length, 5)}):`)
191+
for (const d of diffRows.slice(0, 5)) {
192+
const label = d.sign === "-" ? "source only" : "target only"
193+
lines.push(` [${label}] ${d.values?.join(" | ")}`)
178194
}
179195
}
180196

181197
return lines.join("\n")
182198
}
183199

184-
if (outcome.Profile) {
185-
const p = outcome.Profile
200+
if (outcome.mode === "profile") {
201+
const cols = outcome.column_stats ?? outcome.columns ?? []
186202
lines.push(`Column Profile Comparison`)
187203
lines.push(``)
188-
for (const col of p.columns ?? []) {
204+
for (const col of cols) {
189205
const verdict = col.verdict === "match" ? "✓" : col.verdict === "within_tolerance" ? "~" : "✗"
190206
lines.push(` ${verdict} ${col.column}: ${col.verdict}`)
191207
if (col.source_stats && col.target_stats) {

0 commit comments

Comments
 (0)