@@ -4,16 +4,15 @@ import (
4
4
"context"
5
5
"crypto/sha256"
6
6
"encoding/binary"
7
- "errors"
8
7
"fmt"
9
8
"log/slog"
10
9
"math/rand"
11
10
"regexp"
11
+ "slices"
12
12
"strings"
13
13
"time"
14
14
15
15
"github.com/brianvoe/gofakeit/v7"
16
- "github.com/dominikbraun/graph"
17
16
"github.com/google/uuid"
18
17
"github.com/jackc/pgx/v5"
19
18
"github.com/lib/pq"
@@ -154,7 +153,7 @@ func prepareValue(rawValue string) (string, error) {
154
153
return fakerResult , nil
155
154
}
156
155
157
- func buildQueryForRow (primaryKeys PrimaryKeysResult , rowId string , row Row , dependencyGraph graph. Graph [string , string ] ) (string , error ) {
156
+ func buildQueryForRow (primaryKeys PrimaryKeysResult , rowId string , row Row , dependencyGraph map [string ][] string ) (string , error ) {
158
157
parts := strings .Split (rowId , ":" )
159
158
if len (parts ) < 2 {
160
159
return "" , fmt .Errorf ("invalid id: %s" , rowId )
@@ -203,12 +202,8 @@ func buildQueryForRow(primaryKeys PrimaryKeysResult, rowId string, row Row, depe
203
202
default :
204
203
return "" , fmt .Errorf ("cannot parse ~dependencies value in row %s" , rowId )
205
204
}
206
- for _ , dependency := range dependencies {
207
- err := dependencyGraph .AddEdge (rowId , dependency )
208
- if isRealGraphError (err ) {
209
- return "" , err
210
- }
211
- }
205
+ dependencyGraph [rowId ] = append (dependencyGraph [rowId ], dependencies ... )
206
+ dependencyGraph [rowId ] = slices .Compact (dependencyGraph [rowId ])
212
207
continue
213
208
}
214
209
@@ -225,10 +220,8 @@ func buildQueryForRow(primaryKeys PrimaryKeysResult, rowId string, row Row, depe
225
220
addEdge := referenceRegex .MatchString (value )
226
221
// Don't add edges to and from the same row.
227
222
if addEdge && rowId != value {
228
- err := dependencyGraph .AddEdge (rowId , value )
229
- if isRealGraphError (err ) {
230
- return "" , err
231
- }
223
+ dependencyGraph [rowId ] = append (dependencyGraph [rowId ], value )
224
+ dependencyGraph [rowId ] = slices .Compact (dependencyGraph [rowId ])
232
225
}
233
226
234
227
columns = append (columns , pq .QuoteIdentifier (column ))
@@ -265,15 +258,12 @@ func buildQueryForRow(primaryKeys PrimaryKeysResult, rowId string, row Row, depe
265
258
266
259
// Returns a sorted array of queries to run based on a given ripoff file.
267
260
func buildQueriesForRipoff (primaryKeys PrimaryKeysResult , totalRipoff RipoffFile ) ([]string , error ) {
268
- dependencyGraph := graph . New ( graph . StringHash , graph . Directed (), graph . Acyclic ())
261
+ dependencyGraph := map [ string ][] string {}
269
262
queries := map [string ]string {}
270
263
271
264
// Add vertexes first, since rows can be in any order.
272
265
for rowId := range totalRipoff .Rows {
273
- err := dependencyGraph .AddVertex (rowId )
274
- if err != nil {
275
- return []string {}, err
276
- }
266
+ dependencyGraph [rowId ] = []string {}
277
267
}
278
268
279
269
// Build queries.
@@ -286,7 +276,10 @@ func buildQueriesForRipoff(primaryKeys PrimaryKeysResult, totalRipoff RipoffFile
286
276
}
287
277
288
278
// Sort and reverse the graph, so queries are in order of least (hopefully none) to most dependencies.
289
- ordered , _ := graph .TopologicalSort (dependencyGraph )
279
+ ordered , err := topologicalSort (dependencyGraph )
280
+ if err != nil {
281
+ return []string {}, err
282
+ }
290
283
sortedQueries := []string {}
291
284
for i := len (ordered ) - 1 ; i >= 0 ; i -- {
292
285
query , ok := queries [ordered [i ]]
@@ -392,9 +385,38 @@ func getForeignKeysResult(ctx context.Context, conn pgx.Tx) (ForeignKeysResult,
392
385
return result , nil
393
386
}
394
387
395
- func isRealGraphError (err error ) bool {
396
- if err == nil || errors .Is (err , graph .ErrEdgeAlreadyExists ) {
397
- return false
388
+ // Copy of github.com/amwolff/gorder DFS topological sort implementation,
389
+ // with the only change being allowing non-acyclic graphs (for better or worse).
390
+ func topologicalSort (digraph map [string ][]string ) ([]string , error ) {
391
+ var (
392
+ acyclic = true
393
+ order []string
394
+ permanentMark = make (map [string ]bool )
395
+ temporaryMark = make (map [string ]bool )
396
+ visit func (string )
397
+ )
398
+
399
+ visit = func (u string ) {
400
+ if temporaryMark [u ] {
401
+ acyclic = false
402
+ } else if ! (temporaryMark [u ] || permanentMark [u ]) {
403
+ temporaryMark [u ] = true
404
+ for _ , v := range digraph [u ] {
405
+ visit (v )
406
+ if ! acyclic {
407
+ slog .Debug ("Ripoff file appears to have cycle" , slog .String ("rowId" , u ))
408
+ }
409
+ }
410
+ delete (temporaryMark , u )
411
+ permanentMark [u ] = true
412
+ order = append ([]string {u }, order ... )
413
+ }
414
+ }
415
+
416
+ for u := range digraph {
417
+ if ! permanentMark [u ] {
418
+ visit (u )
419
+ }
398
420
}
399
- return true
421
+ return order , nil
400
422
}
0 commit comments