edit in utils/wav_metadata.go at line 215
[3.33518]→[3.33518:33542](∅→∅) −
− // Verify RIFF header
edit in utils/wav_metadata.go at line 218
[3.33650]→[3.33650:33674](∅→∅) −
− // Verify WAVE format
edit in utils/wav_metadata.go at line 223
[3.33812]→[3.33812:33830](∅→∅) edit in utils/wav_metadata.go at line 225
[3.33872]→[3.33872:33900](∅→∅) − // Read chunk ID and size
replacement in utils/wav_metadata.go at line 231
[3.34068]→[3.34068:34440](∅→∅) − // Parse format chunk - need at least 16 bytes of data
− if chunkSize >= 16 && offset+16 <= len(data) {
− metadata.Channels = int(binary.LittleEndian.Uint16(data[offset+2 : offset+4]))
− metadata.SampleRate = int(binary.LittleEndian.Uint32(data[offset+4 : offset+8]))
− metadata.BitsPerSample = int(binary.LittleEndian.Uint16(data[offset+14 : offset+16]))
− }
−
+ parseFmtChunkData(data[offset:], chunkSize, metadata)
replacement in utils/wav_metadata.go at line 233
[3.34455]→[3.34455:34972](∅→∅) − // Calculate duration from data chunk size
− // We only need the chunkSize from the header, not the actual audio data
− if metadata.SampleRate > 0 && metadata.Channels > 0 && metadata.BitsPerSample > 0 {
− bytesPerSample := metadata.BitsPerSample / 8
− bytesPerSecond := metadata.SampleRate * metadata.Channels * bytesPerSample
− if bytesPerSecond > 0 {
− metadata.Duration = float64(chunkSize) / float64(bytesPerSecond)
− }
− }
− // Data chunk content is the audio data - we don't need to read it
−
+ calcDataChunkDuration(chunkSize, metadata)
replacement in utils/wav_metadata.go at line 235
[3.34987]→[3.34987:35234](∅→∅) − // Parse LIST chunk for INFO metadata
− if chunkSize >= 4 && offset+chunkSize <= len(data) {
− listType := string(data[offset : offset+4])
− if listType == "INFO" {
− parseINFOChunk(data[offset+4:offset+chunkSize], metadata)
− }
− }
+ parseLISTChunkData(data[offset:], chunkSize, metadata)
edit in utils/wav_metadata.go at line 238
[3.35239]→[3.35239:35289](∅→∅) − // Move to next chunk (chunks are word-aligned)
replacement in utils/wav_metadata.go at line 240
[3.35335]→[3.35335:35368](∅→∅) − offset++ // Skip padding byte
edit in utils/wav_metadata.go at line 244
[3.35376]→[3.35376:35420](∅→∅) − // Validate that we found essential chunks
edit in utils/wav_metadata.go at line 250
+ return metadata, nil
+ }
replacement in utils/wav_metadata.go at line 253
[3.35638]→[3.35638:35660](∅→∅) + // parseFmtChunkData extracts format info from a fmt chunk.
+ func parseFmtChunkData(data []byte, chunkSize int, m *WAVMetadata) {
+ if chunkSize >= 16 && len(data) >= 16 {
+ m.Channels = int(binary.LittleEndian.Uint16(data[2:4]))
+ m.SampleRate = int(binary.LittleEndian.Uint32(data[4:8]))
+ m.BitsPerSample = int(binary.LittleEndian.Uint16(data[14:16]))
+ }
+ }
+
+ // calcDataChunkDuration computes duration from the data chunk size.
+ func calcDataChunkDuration(chunkSize int, m *WAVMetadata) {
+ if m.SampleRate > 0 && m.Channels > 0 && m.BitsPerSample > 0 {
+ bytesPerSample := m.BitsPerSample / 8
+ bytesPerSecond := m.SampleRate * m.Channels * bytesPerSample
+ if bytesPerSecond > 0 {
+ m.Duration = float64(chunkSize) / float64(bytesPerSecond)
+ }
+ }
edit in utils/wav_metadata.go at line 273
+ // parseLISTChunkData parses a LIST chunk for INFO metadata.
+ func parseLISTChunkData(data []byte, chunkSize int, m *WAVMetadata) {
+ if chunkSize >= 4 && len(data) >= chunkSize {
+ if string(data[:4]) == "INFO" {
+ parseINFOChunk(data[4:chunkSize], m)
+ }
+ }
+ }
+
replacement in utils/clip_times.go at line 74
[3.192360]→[3.192360:192401](∅→∅) − increment := clipDuration - clipOverlap
+ starts, ends := buildClipStartsEnds(fullDuration, clipDuration, clipOverlap, roundingPrecision)
replacement in utils/clip_times.go at line 76
[3.192402]→[3.192402:192503](∅→∅) − // numpy.arange(0, fullDuration, increment): half-open interval
− // stop when start >= fullDuration
+ switch finalClip {
+ case FinalClipNone:
+ return dedupClips(clipWindowsNone(starts, ends, fullDuration)), nil
+ case FinalClipRemainder:
+ return dedupClips(clipWindowsRemainder(starts, ends, fullDuration)), nil
+ case FinalClipFull:
+ return dedupClips(clipWindowsFull(starts, ends, fullDuration)), nil
+ case FinalClipExtend:
+ return dedupClips(clipWindowsExtend(starts, ends)), nil
+ default:
+ return nil, fmt.Errorf("invalid FinalClipMode %d", finalClip)
+ }
+ }
+
+ // buildClipStartsEnds generates the start and end arrays for clips.
+ func buildClipStartsEnds(fullDuration, clipDuration, clipOverlap float64, roundingPrecision int) ([]float64, []float64) {
+ increment := clipDuration - clipOverlap
edit in utils/clip_times.go at line 98
[3.192658]→[3.192658:192735](∅→∅) − // Defensive — shouldn't happen since fullDuration > 0 and increment > 0
edit in utils/clip_times.go at line 104
replacement in utils/clip_times.go at line 107
[3.192861]→[3.192861:193131](∅→∅) − switch finalClip {
− case FinalClipNone:
− // Drop any window whose end exceeds fullDuration.
− kept := make([]ClipWindow, 0, len(starts))
− for i := range starts {
− if ends[i] <= fullDuration {
− kept = append(kept, ClipWindow{Start: starts[i], End: ends[i]})
− }
+ // clipWindowsNone drops any window whose end exceeds fullDuration.
+ func clipWindowsNone(starts, ends []float64, fullDuration float64) []ClipWindow {
+ out := make([]ClipWindow, 0, len(starts))
+ for i := range starts {
+ if ends[i] <= fullDuration {
+ out = append(out, ClipWindow{Start: starts[i], End: ends[i]})
replacement in utils/clip_times.go at line 114
[3.193135]→[3.193135:193166](∅→∅) − return dedupClips(kept), nil
replacement in utils/clip_times.go at line 118
[3.193167]→[3.193167:193441](∅→∅) − case FinalClipRemainder:
− // Trim ends > fullDuration down to fullDuration.
− out := make([]ClipWindow, 0, len(starts))
− for i := range starts {
− e := ends[i]
− if e > fullDuration {
− e = fullDuration
− }
− out = append(out, ClipWindow{Start: starts[i], End: e})
+ // clipWindowsRemainder trims ends beyond fullDuration down to fullDuration.
+ func clipWindowsRemainder(starts, ends []float64, fullDuration float64) []ClipWindow {
+ out := make([]ClipWindow, 0, len(starts))
+ for i := range starts {
+ e := ends[i]
+ if e > fullDuration {
+ e = fullDuration
replacement in utils/clip_times.go at line 126
[3.193445]→[3.193445:193475](∅→∅) − return dedupClips(out), nil
+ out = append(out, ClipWindow{Start: starts[i], End: e})
+ }
+ return out
+ }
replacement in utils/clip_times.go at line 131
[3.193476]→[3.193476:193905](∅→∅) − case FinalClipFull:
− // Shift any window whose end exceeds fullDuration back so its end == fullDuration.
− // Keep clip length == clipDuration. Clamp start to >= 0 (audio shorter than clip_duration).
− out := make([]ClipWindow, 0, len(starts))
− for i := range starts {
− s := starts[i]
− e := ends[i]
− if e > fullDuration {
− delta := e - fullDuration
− s -= delta
− e = fullDuration
− if s < 0 {
− s = 0
− }
+ // clipWindowsFull shifts windows whose end exceeds fullDuration back so end == fullDuration.
+ func clipWindowsFull(starts, ends []float64, fullDuration float64) []ClipWindow {
+ out := make([]ClipWindow, 0, len(starts))
+ for i := range starts {
+ s, e := starts[i], ends[i]
+ if e > fullDuration {
+ s -= e - fullDuration
+ e = fullDuration
+ if s < 0 {
+ s = 0
edit in utils/clip_times.go at line 142
[3.193910]→[3.193910:193961](∅→∅) − out = append(out, ClipWindow{Start: s, End: e})
replacement in utils/clip_times.go at line 143
[3.193965]→[3.193965:193995](∅→∅) − return dedupClips(out), nil
+ out = append(out, ClipWindow{Start: s, End: e})
+ }
+ return out
+ }
replacement in utils/clip_times.go at line 148
[3.193996]→[3.193996:194309](∅→∅) − case FinalClipExtend:
− // Keep ends as-is, even past fullDuration.
− out := make([]ClipWindow, 0, len(starts))
− for i := range starts {
− out = append(out, ClipWindow{Start: starts[i], End: ends[i]})
− }
− return dedupClips(out), nil
−
− default:
− return nil, fmt.Errorf("invalid FinalClipMode %d", finalClip)
+ // clipWindowsExtend keeps ends as-is, even past fullDuration.
+ func clipWindowsExtend(starts, ends []float64) []ClipWindow {
+ out := make([]ClipWindow, 0, len(starts))
+ for i := range starts {
+ out = append(out, ClipWindow{Start: starts[i], End: ends[i]})
edit in utils/clip_times.go at line 154
replacement in tools/dataset.go at line 54
[3.372120]→[3.372120:372646](∅→∅) − datasetType := db.DatasetTypeStructured // Default
− if input.Type != nil {
− typeStr := strings.ToLower(strings.TrimSpace(*input.Type))
− switch typeStr {
− case "structured":
− datasetType = db.DatasetTypeStructured
− case "unstructured":
− datasetType = db.DatasetTypeUnstructured
− case "test":
− datasetType = db.DatasetTypeTest
− case "train":
− datasetType = db.DatasetTypeTrain
− default:
− return output, fmt.Errorf("invalid type '%s': must be 'structured', 'unstructured', 'test', or 'train'", *input.Type)
− }
+ datasetType, err := parseDatasetType(input.Type)
+ if err != nil {
+ return output, err
replacement in tools/dataset.go at line 85
[3.373330]→[3.373330:373827](∅→∅) − // Dataset with this name already exists - return existing (consistent duplicate handling)
− var dataset db.Dataset
− err = tx.QueryRowContext(ctx,
− "SELECT id, name, description, created_at, last_modified, active, type FROM dataset WHERE id = ?",
− existingID,
− ).Scan(&dataset.ID, &dataset.Name, &dataset.Description, &dataset.CreatedAt, &dataset.LastModified, &dataset.Active, &dataset.Type)
− if err != nil {
− return output, fmt.Errorf("failed to fetch existing dataset: %w", err)
− }
+ return handleExistingDataset(ctx, tx, existingID)
+ }
+
+ return insertNewDataset(ctx, tx, *input.Name, input.Description, datasetType)
+ }
replacement in tools/dataset.go at line 91
[3.373828]→[3.373828:373935](∅→∅) − if err = tx.Commit(); err != nil {
− return output, fmt.Errorf("failed to commit transaction: %w", err)
+ // parseDatasetType validates and returns the dataset type from input.
+ func parseDatasetType(t *string) (db.DatasetType, error) {
+ datasetType := db.DatasetTypeStructured // Default
+ if t != nil {
+ typeStr := strings.ToLower(strings.TrimSpace(*t))
+ switch typeStr {
+ case "structured":
+ datasetType = db.DatasetTypeStructured
+ case "unstructured":
+ datasetType = db.DatasetTypeUnstructured
+ case "test":
+ datasetType = db.DatasetTypeTest
+ case "train":
+ datasetType = db.DatasetTypeTrain
+ default:
+ return "", fmt.Errorf("invalid type '%s': must be 'structured', 'unstructured', 'test', or 'train'", *t)
edit in tools/dataset.go at line 108
+ }
+ return datasetType, nil
+ }
replacement in tools/dataset.go at line 112
[3.373940]→[3.373940:374124](∅→∅) − output.Dataset = dataset
− output.Message = fmt.Sprintf("Dataset with name '%s' already exists (ID: %s) - returning existing dataset", dataset.Name, dataset.ID)
− return output, nil
+ // handleExistingDataset returns an existing dataset found by ID within a transaction.
+ func handleExistingDataset(ctx context.Context, tx *db.LoggedTx, existingID string) (DatasetOutput, error) {
+ var dataset db.Dataset
+ err := tx.QueryRowContext(ctx,
+ "SELECT id, name, description, created_at, last_modified, active, type FROM dataset WHERE id = ?",
+ existingID,
+ ).Scan(&dataset.ID, &dataset.Name, &dataset.Description, &dataset.CreatedAt, &dataset.LastModified, &dataset.Active, &dataset.Type)
+ if err != nil {
+ return DatasetOutput{}, fmt.Errorf("failed to fetch existing dataset: %w", err)
+ }
+
+ if err = tx.Commit(); err != nil {
+ return DatasetOutput{}, fmt.Errorf("failed to commit transaction: %w", err)
replacement in tools/dataset.go at line 127
[3.374128]→[3.374128:374144](∅→∅) + return DatasetOutput{
+ Dataset: dataset,
+ Message: fmt.Sprintf("Dataset with name '%s' already exists (ID: %s) - returning existing dataset", dataset.Name, dataset.ID),
+ }, nil
+ }
+
+ // insertNewDataset inserts a new dataset row and returns it within a transaction.
+ func insertNewDataset(ctx context.Context, tx *db.LoggedTx, name string, description *string, datasetType db.DatasetType) (DatasetOutput, error) {
replacement in tools/dataset.go at line 137
[3.374197]→[3.374197:374259](∅→∅) − return output, fmt.Errorf("failed to generate ID: %w", err)
+ return DatasetOutput{}, fmt.Errorf("failed to generate ID: %w", err)
edit in tools/dataset.go at line 140
[3.374263]→[3.374263:374282](∅→∅) replacement in tools/dataset.go at line 142
[3.374466]→[3.374466:374525](∅→∅) − id, *input.Name, input.Description, string(datasetType),
+ id, name, description, string(datasetType),
replacement in tools/dataset.go at line 145
[3.374545]→[3.374545:374610](∅→∅) − return output, fmt.Errorf("failed to create dataset: %w", err)
+ return DatasetOutput{}, fmt.Errorf("failed to create dataset: %w", err)
edit in tools/dataset.go at line 148
[3.374614]→[3.374614:374644](∅→∅) − // Fetch the created dataset
replacement in tools/dataset.go at line 154
[3.374956]→[3.374956:375028](∅→∅) − return output, fmt.Errorf("failed to fetch created dataset: %w", err)
+ return DatasetOutput{}, fmt.Errorf("failed to fetch created dataset: %w", err)
replacement in tools/dataset.go at line 158
[3.375068]→[3.375068:375137](∅→∅) − return output, fmt.Errorf("failed to commit transaction: %w", err)
+ return DatasetOutput{}, fmt.Errorf("failed to commit transaction: %w", err)
edit in tools/dataset.go at line 160
[3.375140]→[3.375140:375298](∅→∅) −
− output.Dataset = dataset
− output.Message = fmt.Sprintf("Successfully created dataset '%s' with ID %s (type: %s)",
− dataset.Name, dataset.ID, dataset.Type)
replacement in tools/dataset.go at line 161
[3.375299]→[3.375299:375319](∅→∅) + return DatasetOutput{
+ Dataset: dataset,
+ Message: fmt.Sprintf("Successfully created dataset '%s' with ID %s (type: %s)",
+ dataset.Name, dataset.ID, dataset.Type),
+ }, nil
edit in tools/cluster.go at line 102
[3.382334]→[3.382334:382375](∅→∅),
[3.382375]→[2.2076:2148](∅→∅),
[2.2148]→[3.382749:382766](∅→∅),
[3.382749]→[3.382749:382766](∅→∅),
[3.382766]→[2.2149:2170](∅→∅),
[2.2170]→[3.383062:383065](∅→∅),
[3.383062]→[3.383062:383065](∅→∅) −
− // Verify dataset exists and is active
− datasetName, err := verifyDatasetForCluster(ctx, tx, *input.DatasetID)
− if err != nil {
− return output, err
− }
replacement in tools/cluster.go at line 103
[3.383066]→[3.383066:383142](∅→∅),
[3.383142]→[2.2171:2277](∅→∅) − // Verify location exists, is active, and belongs to the specified dataset
− locationName, err := verifyLocationForCluster(ctx, tx, *input.LocationID, *input.DatasetID, datasetName)
+ // Verify parent references exist and are active
+ datasetName, locationName, err := verifyClusterParentRefs(ctx, tx, input)
edit in tools/cluster.go at line 107
[2.2299]→[3.383742:383745](∅→∅),
[3.383742]→[3.383742:383745](∅→∅),
[3.384243]→[3.384243:384396](∅→∅),
[3.384396]→[2.2300:2410](∅→∅),
[2.2410]→[3.384829:384833](∅→∅),
[3.384829]→[3.384829:384833](∅→∅) − }
−
− // Verify cyclic recording pattern if provided
− if input.CyclicRecordingPatternID != nil && strings.TrimSpace(*input.CyclicRecordingPatternID) != "" {
− if err := verifyPatternExists(ctx, tx, *input.CyclicRecordingPatternID); err != nil {
− return output, err
− }
edit in tools/cluster.go at line 112
[3.385410]→[2.2501:2562](∅→∅) − // Cluster with this name already exists - return existing
replacement in tools/cluster.go at line 120
[3.386387]→[2.2751:2778](∅→∅) − // Generate ID and insert
+ return insertNewCluster(ctx, tx, input, datasetName, locationName)
+ }
+
+ // verifyClusterParentRefs validates that the dataset, location, and optional pattern exist and are active.
+ func verifyClusterParentRefs(ctx context.Context, tx *db.LoggedTx, input ClusterInput) (string, string, error) {
+ datasetName, err := verifyDatasetForCluster(ctx, tx, *input.DatasetID)
+ if err != nil {
+ return "", "", err
+ }
+
+ locationName, err := verifyLocationForCluster(ctx, tx, *input.LocationID, *input.DatasetID, datasetName)
+ if err != nil {
+ return "", "", err
+ }
+
+ if input.CyclicRecordingPatternID != nil && strings.TrimSpace(*input.CyclicRecordingPatternID) != "" {
+ if err := verifyPatternExists(ctx, tx, *input.CyclicRecordingPatternID); err != nil {
+ return "", "", err
+ }
+ }
+
+ return datasetName, locationName, nil
+ }
+
+ // insertNewCluster inserts a new cluster row and returns it within a transaction.
+ func insertNewCluster(ctx context.Context, tx *db.LoggedTx, input ClusterInput, datasetName, locationName string) (ClusterOutput, error) {
replacement in tools/cluster.go at line 148
[3.386456]→[3.386456:386518](∅→∅) − return output, fmt.Errorf("failed to generate ID: %w", err)
+ return ClusterOutput{}, fmt.Errorf("failed to generate ID: %w", err)
replacement in tools/cluster.go at line 156
[3.386941]→[3.386941:387006](∅→∅) − return output, fmt.Errorf("failed to create cluster: %w", err)
+ return ClusterOutput{}, fmt.Errorf("failed to create cluster: %w", err)
edit in tools/cluster.go at line 159
[3.387010]→[3.387010:387040](∅→∅) − // Fetch the created cluster
replacement in tools/cluster.go at line 161
[3.387497]→[3.387497:387569](∅→∅) − return output, fmt.Errorf("failed to fetch created cluster: %w", err)
+ return ClusterOutput{}, fmt.Errorf("failed to fetch created cluster: %w", err)
replacement in tools/cluster.go at line 165
[3.387609]→[3.387609:387678](∅→∅) − return output, fmt.Errorf("failed to commit transaction: %w", err)
+ return ClusterOutput{}, fmt.Errorf("failed to commit transaction: %w", err)
edit in tools/cluster.go at line 167
[3.387681]→[3.387681:387915](∅→∅) −
− output.Cluster = cluster
− output.Message = fmt.Sprintf("Successfully created cluster '%s' with ID %s in location '%s' at dataset '%s' (sample rate: %d Hz)",
− cluster.Name, cluster.ID, locationName, datasetName, cluster.SampleRate)
replacement in tools/cluster.go at line 168
[3.387916]→[3.387916:387936](∅→∅) + return ClusterOutput{
+ Cluster: cluster,
+ Message: fmt.Sprintf("Successfully created cluster '%s' with ID %s in location '%s' at dataset '%s' (sample rate: %d Hz)",
+ cluster.Name, cluster.ID, locationName, datasetName, cluster.SampleRate),
+ }, nil
replacement in db/schema.go at line 158
[3.1004378]→[3.1004378:1004458](∅→∅) − // Use DuckDB's duckdb_constraints() function for accurate FK info
− query := `
+ dependsOnMe, tables, err := buildFKDependencyGraph(db)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := collectAllTables(db, tables); err != nil {
+ return nil, err
+ }
+
+ return topologicalSort(tables, dependsOnMe), nil
+ }
+
+ // buildFKDependencyGraph queries FK constraints and builds a reverse dependency graph.
+ // Returns dependsOnMe (referenced table -> list of tables that depend on it) and
+ // the set of tables seen.
+ func buildFKDependencyGraph(db *sql.DB) (map[string][]string, map[string]bool, error) {
+ dependsOnMe := make(map[string][]string)
+ tables := make(map[string]bool)
+
+ rows, err := db.Query(`
replacement in db/schema.go at line 182
[3.1004599]→[3.1004599:1004633](∅→∅) − `
−
− rows, err := db.Query(query)
replacement in db/schema.go at line 184
[3.1004650]→[3.1004650:1004720](∅→∅) − return nil, fmt.Errorf("failed to query FK relationships: %w", err)
+ return nil, nil, fmt.Errorf("failed to query FK relationships: %w", err)
edit in db/schema.go at line 187
[3.1004743]→[3.1004743:1004945](∅→∅) −
− // Build reverse dependency graph: table -> tables that depend on it
− // dependsOnMe[A] = [B, C] means B and C have FKs to A
− dependsOnMe := make(map[string][]string)
− tables := make(map[string]bool)
replacement in db/schema.go at line 191
[3.1005057]→[3.1005057:1005117](∅→∅) − return nil, fmt.Errorf("failed to scan FK row: %w", err)
+ return nil, nil, fmt.Errorf("failed to scan FK row: %w", err)
edit in db/schema.go at line 193
[3.1005121]→[3.1005121:1005122](∅→∅) edit in db/schema.go at line 195
[3.1005175]→[3.1005175:1005217](∅→∅) −
− // foreignTable is referenced by table
replacement in db/schema.go at line 199
[3.1005328]→[3.1005328:1005389](∅→∅) − return nil, fmt.Errorf("error iterating FK rows: %w", err)
+ return nil, nil, fmt.Errorf("error iterating FK rows: %w", err)
edit in db/schema.go at line 201
+ return dependsOnMe, tables, nil
+ }
replacement in db/schema.go at line 204
[3.1005393]→[3.1005393:1005460](∅→∅) − // Get all tables from the database
− tableRows, err := db.Query(`
+ // collectAllTables adds all base tables from the database schema to the tables set.
+ func collectAllTables(db *sql.DB, tables map[string]bool) error {
+ rows, err := db.Query(`
replacement in db/schema.go at line 213
[3.1005599]→[3.1005599:1005659](∅→∅) − return nil, fmt.Errorf("failed to query tables: %w", err)
+ return fmt.Errorf("failed to query tables: %w", err)
replacement in db/schema.go at line 215
[3.1005662]→[3.1005662:1005687](∅→∅) − defer tableRows.Close()
replacement in db/schema.go at line 217
[3.1005688]→[3.1005688:1005712](∅→∅) replacement in db/schema.go at line 219
[3.1005730]→[3.1005730:1005842](∅→∅) − if err := tableRows.Scan(&name); err != nil {
− return nil, fmt.Errorf("failed to scan table name: %w", err)
+ if err := rows.Scan(&name); err != nil {
+ return fmt.Errorf("failed to scan table name: %w", err)
edit in db/schema.go at line 224
replacement in db/schema.go at line 227
[3.1005872]→[3.1005872:1005966](∅→∅) − // Count how many FKs each table has (tables it depends on)
− fkCount := make(map[string]int)
+ // topologicalSort orders tables so dependencies come first (Kahn's algorithm).
+ // Tables in cycles are appended at the end.
+ func topologicalSort(tables map[string]bool, dependsOnMe map[string][]string) []string {
+ fkCount := make(map[string]int, len(tables))
edit in db/schema.go at line 240
[3.1006134]→[3.1006134:1006230](∅→∅) − // Topological sort (Kahn's algorithm)
− // 1. Start with tables that have no FKs (fkCount = 0)
edit in db/schema.go at line 247
[3.1006346]→[3.1006346:1006367](∅→∅) edit in db/schema.go at line 249
[3.1006410]→[3.1006410:1006433](∅→∅) edit in db/schema.go at line 253
[3.1006511]→[3.1006511:1006578](∅→∅) − // For each table that depends on current, decrease its FK count
replacement in db/schema.go at line 261
[3.1006735]→[3.1006735:1006793](∅→∅) − // If result doesn't contain all tables, there's a cycle
+ // Handle cycles: append remaining tables
edit in db/schema.go at line 263
[3.1006826]→[3.1006826:1006869](∅→∅) − // Add remaining tables (cycle handling)
replacement in db/schema.go at line 264
[3.1006899]→[3.1006899:1006957](∅→∅) − found := slices.Contains(result, table)
− if !found {
+ if !slices.Contains(result, table) {
replacement in db/schema.go at line 269
[3.1007004]→[3.1007004:1007025](∅→∅) edit in Makefile at line 5
[3.12708]→[3.169:265](∅→∅),
[3.169]→[3.169:265](∅→∅) −
− count:
− @find . -name "*.go" ! -name "*_test.go" | xargs wc -l | tail -1
−
− unit:
− go test ./...
edit in Makefile at line 15
+ unit:
+ go test -cover ./...
+
edit in Makefile at line 20
+
+ count:
+ @find . -name "*.go" ! -name "*_test.go" | xargs wc -l | tail -1
replacement in Makefile at line 24
[3.12742]→[3.12742:12778](∅→∅) − test: count unit shell goreportcard
+
+ test: shell unit goreportcard count