Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit 918ce5b

Browse files
committed
common: Include changed table data in diff results
This adds diffing of table data and includes the changed rows in the diff results. Note that while this tells you which rows have changed it does not yet include the SQL statements required for merging the data. This also makes the schema diff optional. If there are no changes to the schema, the resulting schema diff will be nil.
1 parent 17d37a1 commit 918ce5b

File tree

1 file changed

+145
-12
lines changed

1 file changed

+145
-12
lines changed

common/diff.go

Lines changed: 145 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ type DiffType string
1313

1414
const (
1515
ACTION_ADD DiffType = "add"
16-
ACTION_DELETE = "delete"
17-
ACTION_MODIFY = "modify"
16+
ACTION_DELETE DiffType = "delete"
17+
ACTION_MODIFY DiffType = "modify"
1818
)
1919

2020
type MergeStrategy int
2121

2222
const (
23-
NoMerge MergeStrategy = iota
23+
NoMerge MergeStrategy = iota
2424
PreservePkMerge
2525
NewPkMerge
2626
)
@@ -37,10 +37,10 @@ type DataDiff struct {
3737
}
3838

3939
type DiffObjectChangeset struct {
40-
ObjectName string `json:"object_name"`
41-
ObjectType string `json:"object_type"`
42-
Schema SchemaDiff `json:"schema"`
43-
Data []DataDiff `json:"data"`
40+
ObjectName string `json:"object_name"`
41+
ObjectType string `json:"object_type"`
42+
Schema *SchemaDiff `json:"schema"`
43+
Data []DataDiff `json:"data"`
4444
}
4545

4646
type Diffs struct {
@@ -171,7 +171,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
171171

172172
// Check for dropped object
173173
if sqlInMain != "" && sqlInAux == "" {
174-
diff.Schema.ActionType = ACTION_DELETE
174+
diff.Schema = &SchemaDiff{ActionType: ACTION_DELETE}
175175
if merge != NoMerge {
176176
diff.Schema.Sql = "DROP " + strings.ToUpper(objectType) + " " + EscapeId(objectName) + ";"
177177
}
@@ -191,7 +191,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
191191

192192
// Check for added object
193193
if sqlInMain == "" && sqlInAux != "" {
194-
diff.Schema.ActionType = ACTION_ADD
194+
diff.Schema = &SchemaDiff{ActionType: ACTION_ADD}
195195
if merge != NoMerge {
196196
diff.Schema.Sql = sqlInAux + ";"
197197
}
@@ -210,7 +210,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
210210

211211
// Check for modified object
212212
if sqlInMain != "" && sqlInAux != "" && sqlInMain != sqlInAux {
213-
diff.Schema.ActionType = ACTION_MODIFY
213+
diff.Schema = &SchemaDiff{ActionType: ACTION_MODIFY}
214214
if merge != NoMerge {
215215
diff.Schema.Sql = "DROP " + strings.ToUpper(objectType) + " " + EscapeId(objectName) + ";" + sqlInAux + ";"
216216
}
@@ -236,11 +236,22 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
236236

237237
// If this is a table, check for modified data
238238
if objectType == "table" {
239-
// TODO
239+
diff.Data, err = dataDiffForModifiedTableRows(sdb, objectName, merge)
240+
if err != nil {
241+
return false, DiffObjectChangeset{}, err
242+
}
243+
244+
// When there are data changes, fill in the rest of the diff information and return the diff
245+
if diff.Data != nil {
246+
diff.ObjectName = objectName
247+
diff.ObjectType = objectType
248+
249+
return true, diff, nil
250+
}
240251
}
241252

242253
// Nothing has changed
243-
return false, diff, nil
254+
return false, DiffObjectChangeset{}, nil
244255
}
245256

246257
func dataDiffForAllTableRows(sdb *sqlite.Conn, schemaName string, tableName string, action DiffType, includeSql bool) (diff []DataDiff, err error) {
@@ -337,3 +348,125 @@ func dataDiffForAllTableRows(sdb *sqlite.Conn, schemaName string, tableName stri
337348

338349
return diff, nil
339350
}
351+
352+
// This helper function gets the differences between the two tables named tableName in the main and in the aux schema.
353+
// It then builds an array of DataDiff objects which represents these differences. This function assumes that the table
354+
// schemas match.
355+
func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge MergeStrategy) (diff []DataDiff, err error) {
356+
// Retrieve a list of all primary key columns and other columns in this table
357+
pk, _, other_columns, err := GetPrimaryKeyAndOtherColumns(sdb, "aux", tableName)
358+
if err != nil {
359+
return nil, err
360+
}
361+
362+
// Escape all column names
363+
var pk_escaped, other_escaped []string
364+
for _, v := range pk {
365+
pk_escaped = append(pk_escaped, EscapeId(v))
366+
}
367+
for _, v := range other_columns {
368+
other_escaped = append(other_escaped, EscapeId(v))
369+
}
370+
371+
// Build query for getting differences. This is based on the query produced by the sqldiff utility for SQLite.
372+
// The resulting query returns n+1+m*2 number of rows where n is the number of columns in the primary key and
373+
// m is the number of columns in the table which are not part of the primary key. The extra column between the
374+
// primary key columns and the other columns contains the diff type as specified by the DiffType constants.
375+
// We generate two columns for each table column. The first item of each pair is 0 if the value of this column
376+
// has not been modified and 1 if it was modified. The second item of each pair contains the new value.
377+
378+
var query string
379+
380+
// Updated rows
381+
// There can only be updated rows in tables with more columns than the primary key columns
382+
if len(other_columns) > 0 {
383+
query = "SELECT "
384+
for _, c := range pk_escaped { // Primary key columns first
385+
query += "B." + c + ","
386+
}
387+
query += "'" + string(ACTION_MODIFY) + "'" // Updated row
388+
for _, c := range other_escaped { // Other columns last
389+
query += ",A." + c + " IS NOT B." + c + ",B." + c
390+
}
391+
392+
query += " FROM main." + EscapeId(tableName) + " A, aux." + EscapeId(tableName) + " B WHERE "
393+
394+
for _, c := range pk_escaped { // Where all primary key columns equal
395+
query += "A." + c + "=B." + c + " AND "
396+
}
397+
398+
query += "(" // And at least one of the other columns differs
399+
for _, c := range other_escaped {
400+
query += "A." + c + " IS NOT B." + c + " OR "
401+
}
402+
query = strings.TrimSuffix(query, " OR ") + ")"
403+
404+
query += " UNION ALL "
405+
}
406+
407+
// Deleted rows
408+
query += "SELECT "
409+
for _, c := range pk_escaped { // Primary key columns first. This needs to be from the first table for deleted rows
410+
query += "A." + c + ","
411+
}
412+
query += "'" + string(ACTION_DELETE) + "'" // Deleted row
413+
query += strings.Repeat(",NULL", len(other_escaped)*2) // Just NULL for all the other columns. They don't matter for deleted rows
414+
415+
query += " FROM main." + EscapeId(tableName) + " A WHERE "
416+
417+
query += "NOT EXISTS(SELECT 1 FROM aux." + EscapeId(tableName) + " B WHERE " // Where a row with the same primary key doesn't exist in the second table
418+
for _, c := range pk_escaped {
419+
query += "A." + c + " IS B." + c + " AND "
420+
}
421+
query = strings.TrimSuffix(query, " AND ") + ") UNION ALL "
422+
423+
// Inserted rows
424+
query += "SELECT "
425+
for _, c := range pk_escaped { // Primary key columns first. This needs to be from the second table for inserted rows
426+
query += "B." + c + ","
427+
}
428+
query += "'" + string(ACTION_ADD) + "'" // Inserted row
429+
for _, c := range other_escaped { // Other columns last. Always set the modified flag for inserted rows
430+
query += ",1,B." + c
431+
}
432+
433+
query += " FROM aux." + EscapeId(tableName) + " B WHERE "
434+
435+
query += "NOT EXISTS(SELECT 1 FROM main." + EscapeId(tableName) + " A WHERE " // Where a row with the same primary key doesn't exist in the first table
436+
for _, c := range pk_escaped {
437+
query += "A." + c + " IS B." + c + " AND "
438+
}
439+
query = strings.TrimSuffix(query, " AND ") + ")"
440+
441+
// Finish query
442+
query += " ORDER BY 1;" // Order by first primary key column
443+
444+
// Run the query and retrieve the data. Each row in the result set is a difference between the two tables.
445+
// The column after the primary key columns and before the other columns specifies the type of change, i.e.
446+
// update, insert or delete. While the primary key bit of the DataDiff object we create for each row can
447+
// be taken directly from the first couple of columns and the action type of the DataDiff object can be
448+
// deduced from the type column in a straightforward way, the generated SQL statements for merging highly
449+
// depend on the diff type.
450+
451+
// Retrieve data and generate a new DataDiff object for each row
452+
_, _, data, err := SQLiteRunQuery(sdb, Internal, query, false, false)
453+
if err != nil {
454+
log.Printf("Error getting rows in dataDiffForModifiedTableRows(): %s\n", err)
455+
return nil, err
456+
}
457+
for _, row := range data.Records {
458+
var d DataDiff
459+
460+
// Get the diff type
461+
d.ActionType = DiffType(row[len(pk)].Value.(string))
462+
463+
// Fill in the primary key columns
464+
for i := 0; i < len(pk); i++ {
465+
d.Pk = append(d.Pk, row[i])
466+
}
467+
468+
diff = append(diff, d)
469+
}
470+
471+
return diff, nil
472+
}

0 commit comments

Comments
 (0)