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

Commit 16b736f

Browse files
committed
common: Check for schema changes in diff function
Implement basic checks for schema changes in the diff functions. This completely ignores table data at the moment and is not very clever with simple tables changes either.
1 parent d7cca79 commit 16b736f

File tree

2 files changed

+132
-6
lines changed

2 files changed

+132
-6
lines changed

common/diff.go

Lines changed: 128 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,37 @@ package common
22

33
import (
44
"fmt"
5+
"io"
56
"log"
7+
"strings"
8+
9+
sqlite "github.com/gwenn/gosqlite"
10+
)
11+
12+
type DiffType string
13+
14+
const (
15+
ACTION_ADD DiffType = "add"
16+
ACTION_DELETE = "delete"
17+
ACTION_MODIFY = "modify"
618
)
719

20+
type SchemaDiff struct {
21+
ActionType DiffType `json:"action_type"`
22+
Sql string `json:"sql"`
23+
}
24+
25+
type DiffObjectChangeset struct {
26+
ObjectName string `json:"object_name"`
27+
ObjectType string `json:"object_type"`
28+
Schema SchemaDiff `json:"schema"`
29+
}
30+
831
type Diffs struct {
9-
Dummy string `json:"dummy"` // TODO: This is only used for debugging purposes and needs to be removed later
32+
Diff []DiffObjectChangeset `json:"diff"`
1033
}
1134

12-
// Diff
35+
// Diff generates the differences between the two commits commitA and commitB of the two databases specified in the other parameters
1336
func Diff(ownerA string, folderA string, nameA string, commitA string, ownerB string, folderB string, nameB string, commitB string, loggedInUser string) (Diffs, error) {
1437
// Check if the user has access to the requested databases
1538
bucketA, idA, _, err := MinioLocation(ownerA, folderA, nameA, commitA, loggedInUser)
@@ -49,19 +72,118 @@ func Diff(ownerA string, folderA string, nameA string, commitA string, ownerB st
4972
return dbDiff(dbA, dbB)
5073
}
5174

52-
// dbDiff
75+
// dbDiff generates the differences between the two database files in dbA and dbD
5376
func dbDiff(dbA string, dbB string) (Diffs, error) {
5477
var diff Diffs
5578

5679
// Check if this is the same database and exit early
5780
if dbA == dbB {
58-
diff.Dummy = "same dbs"
5981
return diff, nil
6082
}
6183

62-
// TODO
63-
diff.Dummy = "different dbs"
84+
// Open the first SQLite database in read only mode
85+
var sdb *sqlite.Conn
86+
sdb, err := sqlite.Open(dbA, sqlite.OpenReadOnly)
87+
if err != nil {
88+
log.Printf("Couldn't open database in dbDiff(): %s", err)
89+
return Diffs{}, err
90+
}
91+
if err = sdb.EnableExtendedResultCodes(true); err != nil {
92+
log.Printf("Couldn't enable extended result codes in dbDiff(): %v\n", err.Error())
93+
return Diffs{}, err
94+
}
95+
96+
// Attach the second database
97+
err = sdb.Exec("ATTACH '" + dbB + "' AS aux")
98+
if err != nil {
99+
log.Printf("Couldn't attach database in dbDiff(): %s", err)
100+
return Diffs{}, err
101+
}
102+
103+
// Get list of all objects in both databases, excluding virtual tables because they tend to be unpredictable
104+
var stmt *sqlite.Stmt
105+
stmt, err = sdb.Prepare("SELECT name, type FROM main.sqlite_master WHERE name NOT LIKE 'sqlite_%' AND (type != 'table' OR (type = 'table' AND sql NOT LIKE 'CREATE VIRTUAL%%'))\n" +
106+
" UNION\n" +
107+
"SELECT name, type FROM aux.sqlite_master WHERE name NOT LIKE 'sqlite_%' AND (type != 'table' OR (type = 'table' AND sql NOT LIKE 'CREATE VIRTUAL%%'))\n" +
108+
" ORDER BY name")
109+
if err != nil {
110+
log.Printf("Error when preparing statement for object list in dbDiff(): %s\n", err)
111+
return Diffs{}, err
112+
}
113+
defer stmt.Finalize()
114+
err = stmt.Select(func(s *sqlite.Stmt) error {
115+
objectName, _ := s.ScanText(0)
116+
objectType, _ := s.ScanText(1)
117+
changed, objectDiff, err := diffSingleObject(sdb, objectName, objectType)
118+
if err != nil {
119+
return err
120+
}
121+
if changed {
122+
diff.Diff = append(diff.Diff, objectDiff)
123+
}
124+
return nil
125+
})
126+
if err != nil {
127+
log.Printf("Error when diffing single object in dbDiff: %s\n", err)
128+
return Diffs{}, err
129+
}
64130

65131
// Return
66132
return diff, nil
67133
}
134+
135+
// diffSingleObject compares the object with name objectName and of type objectType in the main and aux schemata of the connection sdb
136+
// and returns three values: a boolean to indicate whether there are differences, a DiffObjectChangeset object containing all the differences, and an optional error object
137+
func diffSingleObject(sdb *sqlite.Conn, objectName string, objectType string) (bool, DiffObjectChangeset, error) {
138+
// Prepare diff object to return
139+
var diff DiffObjectChangeset
140+
diff.ObjectName = objectName
141+
diff.ObjectType = objectType
142+
143+
// Check for object's existence in both databases
144+
var sqlInMain, sqlInAux string
145+
err := sdb.OneValue("SELECT sql FROM main.sqlite_master WHERE name = ? AND type = ?", &sqlInMain, objectName, objectType)
146+
if err != nil && err != io.EOF { // io.EOF is okay. It is returned when the object does not exist in the main database
147+
return false, diff, err
148+
}
149+
err = sdb.OneValue("SELECT sql FROM aux.sqlite_master WHERE name = ? AND type = ?", &sqlInAux, objectName, objectType)
150+
if err != nil && err != io.EOF { // io.EOF is okay. It is returned when the object does not exist in the aux database
151+
return false, diff, err
152+
}
153+
154+
// Check for dropped object
155+
if sqlInMain != "" && sqlInAux == "" {
156+
diff.Schema.ActionType = ACTION_DELETE
157+
diff.Schema.Sql = "DROP " + strings.ToUpper(objectType) + " " + EscapeId(objectName) + ";"
158+
159+
// No data changes for added objects so we can return here
160+
return true, diff, nil
161+
}
162+
163+
// Check for added object
164+
if sqlInMain == "" && sqlInAux != "" {
165+
diff.Schema.ActionType = ACTION_ADD
166+
diff.Schema.Sql = sqlInAux + ";"
167+
168+
// TODO If this is a table, also add all the data to the diff
169+
170+
return true, diff, nil
171+
}
172+
173+
// Check for modified object
174+
if sqlInMain != "" && sqlInAux != "" && sqlInMain != sqlInAux {
175+
diff.Schema.ActionType = ACTION_MODIFY
176+
diff.Schema.Sql = "DROP " + strings.ToUpper(objectType) + " " + EscapeId(objectName) + ";" + sqlInAux + ";"
177+
178+
// TODO If this is a table, be more clever and try to get away with ALTER TABLE instead of DROP and CREATE
179+
180+
// TODO If this is a table, also add all the data to the diff
181+
182+
return true, diff, nil
183+
}
184+
185+
// TODO If this is a table, check for modified data
186+
187+
// Nothing has changed
188+
return false, diff, nil
189+
}

common/sqlite.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,3 +941,7 @@ func Views(sdb *sqlite.Conn) (vw []string, err error) {
941941
}
942942
return
943943
}
944+
945+
func EscapeId(id string) string {
946+
return sqlite.Mprintf("\"%w\"", id)
947+
}

0 commit comments

Comments
 (0)