@@ -9,40 +9,61 @@ import (
9
9
sqlite "github.com/gwenn/gosqlite"
10
10
)
11
11
12
+ // DiffType specifies the type of change in a row or object
12
13
type DiffType string
13
14
14
15
const (
15
- ACTION_ADD DiffType = "add"
16
- ACTION_DELETE DiffType = "delete"
17
- ACTION_MODIFY DiffType = "modify"
16
+ // ActionAdd is used for inserted rows and created objects
17
+ ActionAdd DiffType = "add"
18
+
19
+ // ActionDelete is used for deleted rows and dropped objects
20
+ ActionDelete DiffType = "delete"
21
+
22
+ // ActionModify is used for updated rows and altered objects
23
+ ActionModify DiffType = "modify"
18
24
)
19
25
26
+ // MergeStrategy specifies the type of SQL statements included in the diff results.
27
+ // The SQL statements can be used for merging databases and depending on whether and
28
+ // how you want to merge you should choose your merge strategy.
20
29
type MergeStrategy int
21
30
22
31
const (
32
+ // NoMerge removes any SQL statements for merging from the diff results
23
33
NoMerge MergeStrategy = iota
34
+
35
+ // PreservePkMerge produces SQL statements which preserve the values of the primary key columns.
36
+ // Executing these statements on the first database produces a database similar to the second.
24
37
PreservePkMerge
38
+
39
+ // NewPkMerge produces SQL statements which generate new values for the primary key columns when
40
+ // executed. This avoids a couple of possible conflicts and allows merging more distant databases.
25
41
NewPkMerge
26
42
)
27
43
44
+ // SchemaDiff describes the changes to the schema of a database object, i.e. a created, dropped or altered object
28
45
type SchemaDiff struct {
29
46
ActionType DiffType `json:"action_type"`
30
47
Sql string `json:"sql"`
31
48
}
32
49
50
+ // DataDiff stores a single change in the data of a table, i.e. a single new, deleted, or changed row
33
51
type DataDiff struct {
34
52
ActionType DiffType `json:"action_type"`
35
53
Sql string `json:"sql"`
36
54
Pk []DataValue `json:"pk"`
37
55
}
38
56
57
+ // DiffObjectChangeset stores all the differences between two objects in a database, for example two tables.
58
+ // Both Schema and Data are optional and can be nil if there are no respective changes in this object.
39
59
type DiffObjectChangeset struct {
40
60
ObjectName string `json:"object_name"`
41
61
ObjectType string `json:"object_type"`
42
62
Schema * SchemaDiff `json:"schema"`
43
63
Data []DataDiff `json:"data"`
44
64
}
45
65
66
+ // Diffs is able to store all the differences between two databases.
46
67
type Diffs struct {
47
68
Diff []DiffObjectChangeset `json:"diff"`
48
69
// TODO Add PRAGMAs here
@@ -171,15 +192,15 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
171
192
172
193
// Check for dropped object
173
194
if sqlInMain != "" && sqlInAux == "" {
174
- diff .Schema = & SchemaDiff {ActionType : ACTION_DELETE }
195
+ diff .Schema = & SchemaDiff {ActionType : ActionDelete }
175
196
if merge != NoMerge {
176
197
diff .Schema .Sql = "DROP " + strings .ToUpper (objectType ) + " " + EscapeId (objectName ) + ";"
177
198
}
178
199
179
200
// If this is a table, also add all the deleted data to the diff
180
201
if objectType == "table" {
181
202
// We never include the SQL statements because there is no need to delete all the rows when we DROP the table anyway
182
- diff .Data , err = dataDiffForAllTableRows (sdb , "main" , objectName , ACTION_DELETE , false )
203
+ diff .Data , err = dataDiffForAllTableRows (sdb , "main" , objectName , ActionDelete , false )
183
204
if err != nil {
184
205
return false , DiffObjectChangeset {}, err
185
206
}
@@ -191,14 +212,14 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
191
212
192
213
// Check for added object
193
214
if sqlInMain == "" && sqlInAux != "" {
194
- diff .Schema = & SchemaDiff {ActionType : ACTION_ADD }
215
+ diff .Schema = & SchemaDiff {ActionType : ActionAdd }
195
216
if merge != NoMerge {
196
217
diff .Schema .Sql = sqlInAux + ";"
197
218
}
198
219
199
220
// If this is a table, also add all the added data to the diff
200
221
if objectType == "table" {
201
- diff .Data , err = dataDiffForAllTableRows (sdb , "aux" , objectName , ACTION_ADD , merge != NoMerge )
222
+ diff .Data , err = dataDiffForAllTableRows (sdb , "aux" , objectName , ActionAdd , merge != NoMerge )
202
223
if err != nil {
203
224
return false , DiffObjectChangeset {}, err
204
225
}
@@ -210,7 +231,7 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
210
231
211
232
// Check for modified object
212
233
if sqlInMain != "" && sqlInAux != "" && sqlInMain != sqlInAux {
213
- diff .Schema = & SchemaDiff {ActionType : ACTION_MODIFY }
234
+ diff .Schema = & SchemaDiff {ActionType : ActionModify }
214
235
if merge != NoMerge {
215
236
diff .Schema .Sql = "DROP " + strings .ToUpper (objectType ) + " " + EscapeId (objectName ) + ";" + sqlInAux + ";"
216
237
}
@@ -219,15 +240,15 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
219
240
220
241
// If this is a table, also add all the data to the diff
221
242
if objectType == "table" {
222
- delete_data , err := dataDiffForAllTableRows (sdb , "main" , objectName , ACTION_DELETE , false )
243
+ deleteData , err := dataDiffForAllTableRows (sdb , "main" , objectName , ActionDelete , false )
223
244
if err != nil {
224
245
return false , DiffObjectChangeset {}, err
225
246
}
226
- add_data , err := dataDiffForAllTableRows (sdb , "aux" , objectName , ACTION_ADD , merge != NoMerge )
247
+ addData , err := dataDiffForAllTableRows (sdb , "aux" , objectName , ActionAdd , merge != NoMerge )
227
248
if err != nil {
228
249
return false , DiffObjectChangeset {}, err
229
250
}
230
- diff .Data = append (delete_data , add_data ... )
251
+ diff .Data = append (deleteData , addData ... )
231
252
}
232
253
233
254
// No further changes for modified objects. So we can return here
@@ -256,26 +277,21 @@ func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string, me
256
277
257
278
func dataDiffForAllTableRows (sdb * sqlite.Conn , schemaName string , tableName string , action DiffType , includeSql bool ) (diff []DataDiff , err error ) {
258
279
// Retrieve a list of all primary key columns and other columns in this table
259
- pk , implicit_pk , other_columns , err := GetPrimaryKeyAndOtherColumns (sdb , schemaName , tableName )
280
+ pk , implicitPk , otherColumns , err := GetPrimaryKeyAndOtherColumns (sdb , schemaName , tableName )
260
281
if err != nil {
261
282
return nil , err
262
283
}
263
284
264
285
// Escape all the column names
265
- var pk_escaped , other_escaped []string
266
- for _ , v := range pk {
267
- pk_escaped = append (pk_escaped , EscapeId (v ))
268
- }
269
- for _ , v := range other_columns {
270
- other_escaped = append (other_escaped , EscapeId (v ))
271
- }
286
+ pkEscaped := EscapeIds (pk )
287
+ otherEscaped := EscapeIds (otherColumns )
272
288
273
289
// Prepare query for the primary keys of all rows in this table. Only include the rest of the data
274
290
// in the rows if required
275
- query := "SELECT " + strings .Join (pk_escaped , "," )
276
- if includeSql && action == ACTION_ADD {
277
- if len (other_escaped ) > 0 {
278
- query += "," + strings .Join (other_escaped , "," )
291
+ query := "SELECT " + strings .Join (pkEscaped , "," )
292
+ if includeSql && action == ActionAdd {
293
+ if len (otherEscaped ) > 0 {
294
+ query += "," + strings .Join (otherEscaped , "," )
279
295
}
280
296
}
281
297
query += " FROM " + EscapeId (schemaName ) + "." + EscapeId (tableName )
@@ -292,17 +308,17 @@ func dataDiffForAllTableRows(sdb *sqlite.Conn, schemaName string, tableName stri
292
308
293
309
// Prepare SQL statement when needed
294
310
if includeSql {
295
- if action == ACTION_DELETE {
311
+ if action == ActionDelete {
296
312
d .Sql = "DELETE FROM " + EscapeId (tableName ) + " WHERE "
297
- } else if action == ACTION_ADD {
298
- var insert_columns []string
313
+ } else if action == ActionAdd {
314
+ var insertColumns []string
299
315
// Don't include rowid column, only regular PK
300
- if ! implicit_pk {
301
- insert_columns = append (insert_columns , pk_escaped ... )
316
+ if ! implicitPk {
317
+ insertColumns = append (insertColumns , pkEscaped ... )
302
318
}
303
- insert_columns = append (insert_columns , other_escaped ... )
319
+ insertColumns = append (insertColumns , otherEscaped ... )
304
320
305
- d .Sql = "INSERT INTO " + EscapeId (tableName ) + "(" + strings .Join (insert_columns , "," ) + ") VALUES("
321
+ d .Sql = "INSERT INTO " + EscapeId (tableName ) + "(" + strings .Join (insertColumns , "," ) + ") VALUES("
306
322
}
307
323
}
308
324
@@ -315,8 +331,8 @@ func dataDiffForAllTableRows(sdb *sqlite.Conn, schemaName string, tableName stri
315
331
316
332
// If we want to include a SQL statement for deleting data and this is still
317
333
// part of the primary key, add this to the prepared DELETE statement
318
- if includeSql && action == ACTION_DELETE && i < len (pk ) {
319
- d .Sql += pk_escaped [i ]
334
+ if includeSql && action == ActionDelete && i < len (pk ) {
335
+ d .Sql += pkEscaped [i ]
320
336
if row [i ].Type == Null {
321
337
d .Sql += " IS NULL"
322
338
} else {
@@ -327,17 +343,17 @@ func dataDiffForAllTableRows(sdb *sqlite.Conn, schemaName string, tableName stri
327
343
328
344
// If we want to include a SQL statement for adding data and this is the regular
329
345
// data part, add this to the prepared INSERT statement
330
- if includeSql && action == ACTION_ADD && i >= len (pk ) {
346
+ if includeSql && action == ActionAdd && i >= len (pk ) {
331
347
d .Sql += EscapeValue (row [i ]) + ","
332
348
}
333
349
}
334
350
335
351
// Remove the last " AND " of the SQL query for DELETE statements and the last "," for INSERT statements
336
352
// and add a semicolon instead
337
353
if includeSql {
338
- if action == ACTION_DELETE {
354
+ if action == ActionDelete {
339
355
d .Sql = strings .TrimSuffix (d .Sql , " AND " ) + ";"
340
- } else if action == ACTION_ADD {
356
+ } else if action == ActionAdd {
341
357
d .Sql = strings .TrimSuffix (d .Sql , "," ) + ");"
342
358
}
343
359
}
@@ -354,7 +370,7 @@ func dataDiffForAllTableRows(sdb *sqlite.Conn, schemaName string, tableName stri
354
370
// schemas match.
355
371
func dataDiffForModifiedTableRows (sdb * sqlite.Conn , tableName string , merge MergeStrategy ) (diff []DataDiff , err error ) {
356
372
// Retrieve a list of all primary key columns and other columns in this table
357
- pk , implicitPk , other_columns , err := GetPrimaryKeyAndOtherColumns (sdb , "aux" , tableName )
373
+ pk , implicitPk , otherColumns , err := GetPrimaryKeyAndOtherColumns (sdb , "aux" , tableName )
358
374
if err != nil {
359
375
return nil , err
360
376
}
@@ -371,7 +387,7 @@ func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge Merg
371
387
372
388
// Escape all column names
373
389
pkEscaped := EscapeIds (pk )
374
- otherEscaped := EscapeIds (other_columns )
390
+ otherEscaped := EscapeIds (otherColumns )
375
391
376
392
// Build query for getting differences. This is based on the query produced by the sqldiff utility for SQLite.
377
393
// The resulting query returns n+1+m*2 number of rows where n is the number of columns in the primary key and
@@ -384,12 +400,12 @@ func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge Merg
384
400
385
401
// Updated rows
386
402
// There can only be updated rows in tables with more columns than the primary key columns
387
- if len (other_columns ) > 0 {
403
+ if len (otherColumns ) > 0 {
388
404
query = "SELECT "
389
405
for _ , c := range pkEscaped { // Primary key columns first
390
406
query += "B." + c + ","
391
407
}
392
- query += "'" + string (ACTION_MODIFY ) + "'" // Updated row
408
+ query += "'" + string (ActionModify ) + "'" // Updated row
393
409
for _ , c := range otherEscaped { // Other columns last
394
410
query += ",A." + c + " IS NOT B." + c + ",B." + c
395
411
}
@@ -414,7 +430,7 @@ func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge Merg
414
430
for _ , c := range pkEscaped { // Primary key columns first. This needs to be from the first table for deleted rows
415
431
query += "A." + c + ","
416
432
}
417
- query += "'" + string (ACTION_DELETE ) + "'" // Deleted row
433
+ query += "'" + string (ActionDelete ) + "'" // Deleted row
418
434
query += strings .Repeat (",NULL" , len (otherEscaped )* 2 ) // Just NULL for all the other columns. They don't matter for deleted rows
419
435
420
436
query += " FROM main." + EscapeId (tableName ) + " A WHERE "
@@ -430,7 +446,7 @@ func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge Merg
430
446
for _ , c := range pkEscaped { // Primary key columns first. This needs to be from the second table for inserted rows
431
447
query += "B." + c + ","
432
448
}
433
- query += "'" + string (ACTION_ADD ) + "'" // Inserted row
449
+ query += "'" + string (ActionAdd ) + "'" // Inserted row
434
450
for _ , c := range otherEscaped { // Other columns last. Always set the modified flag for inserted rows
435
451
query += ",1,B." + c
436
452
}
@@ -473,11 +489,11 @@ func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge Merg
473
489
474
490
// Produce the SQL statement for merging
475
491
if merge != NoMerge {
476
- if d .ActionType == ACTION_MODIFY || d .ActionType == ACTION_DELETE {
492
+ if d .ActionType == ActionModify || d .ActionType == ActionDelete {
477
493
// For updated and deleted rows the merge strategy doesn't matter
478
494
479
495
// The first part of the UPDATE and DELETE statements is different
480
- if d .ActionType == ACTION_MODIFY {
496
+ if d .ActionType == ActionModify {
481
497
d .Sql = "UPDATE " + EscapeId (tableName ) + " SET "
482
498
483
499
// For figuring out which values to set, start with the first column after the diff type column.
@@ -511,7 +527,7 @@ func dataDiffForModifiedTableRows(sdb *sqlite.Conn, tableName string, merge Merg
511
527
d .Sql += " AND "
512
528
}
513
529
d .Sql = strings .TrimSuffix (d .Sql , " AND " ) + ";"
514
- } else if d .ActionType == ACTION_ADD {
530
+ } else if d .ActionType == ActionAdd {
515
531
// For inserted rows the merge strategy actually does matter. The PreservePkMerge strategy is simple:
516
532
// We just include all columns, no matter whether primary key or not, in the INSERT statement as-is.
517
533
// For tables which don't have a primary key the same applies even when using the NewPkMerge strategy.
@@ -650,7 +666,6 @@ func hasIncrementingIntPk(sdb *sqlite.Conn, schemaName string, tableName string)
650
666
// a table with an incrementing primary key.
651
667
if err != nil && err != io .EOF {
652
668
return false , nil
653
- } else {
654
- return true , nil
655
669
}
670
+ return true , nil
656
671
}
0 commit comments