@@ -13,14 +13,14 @@ type DiffType string
13
13
14
14
const (
15
15
ACTION_ADD DiffType = "add"
16
- ACTION_DELETE = "delete"
17
- ACTION_MODIFY = "modify"
16
+ ACTION_DELETE DiffType = "delete"
17
+ ACTION_MODIFY DiffType = "modify"
18
18
)
19
19
20
20
type MergeStrategy int
21
21
22
22
const (
23
- NoMerge MergeStrategy = iota
23
+ NoMerge MergeStrategy = iota
24
24
PreservePkMerge
25
25
NewPkMerge
26
26
)
@@ -37,10 +37,10 @@ type DataDiff struct {
37
37
}
38
38
39
39
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"`
44
44
}
45
45
46
46
type Diffs struct {
@@ -171,7 +171,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
171
171
172
172
// Check for dropped object
173
173
if sqlInMain != "" && sqlInAux == "" {
174
- diff .Schema . ActionType = ACTION_DELETE
174
+ diff .Schema = & SchemaDiff { ActionType : ACTION_DELETE }
175
175
if merge != NoMerge {
176
176
diff .Schema .Sql = "DROP " + strings .ToUpper (objectType ) + " " + EscapeId (objectName ) + ";"
177
177
}
@@ -191,7 +191,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
191
191
192
192
// Check for added object
193
193
if sqlInMain == "" && sqlInAux != "" {
194
- diff .Schema . ActionType = ACTION_ADD
194
+ diff .Schema = & SchemaDiff { ActionType : ACTION_ADD }
195
195
if merge != NoMerge {
196
196
diff .Schema .Sql = sqlInAux + ";"
197
197
}
@@ -210,7 +210,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
210
210
211
211
// Check for modified object
212
212
if sqlInMain != "" && sqlInAux != "" && sqlInMain != sqlInAux {
213
- diff .Schema . ActionType = ACTION_MODIFY
213
+ diff .Schema = & SchemaDiff { ActionType : ACTION_MODIFY }
214
214
if merge != NoMerge {
215
215
diff .Schema .Sql = "DROP " + strings .ToUpper (objectType ) + " " + EscapeId (objectName ) + ";" + sqlInAux + ";"
216
216
}
@@ -236,11 +236,22 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
236
236
237
237
// If this is a table, check for modified data
238
238
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
+ }
240
251
}
241
252
242
253
// Nothing has changed
243
- return false , diff , nil
254
+ return false , DiffObjectChangeset {} , nil
244
255
}
245
256
246
257
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
337
348
338
349
return diff , nil
339
350
}
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