step 2: Add UUID columns with backfill and dual-write support
- Add migration 000007: Add UUID columns to soundtrack and song with backfill - Update InsertSoundtrack and InsertSoundtrackWithExistingId to accept UUID - Update AddSong to accept UUID - Add dual-write: Go code now generates UUIDs for new records - Add uuid and pgtype imports Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -16,6 +16,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/panjf2000/ants/v2"
|
"github.com/panjf2000/ants/v2"
|
||||||
|
|
||||||
"github.com/MShekow/directory-checksum/directory_checksum"
|
"github.com/MShekow/directory-checksum/directory_checksum"
|
||||||
@@ -343,7 +345,8 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = repo.InsertSoundtrackWithExistingId(BackendCtx(), repository.InsertSoundtrackWithExistingIdParams{ID: id, SoundtrackName: file.Name(), Path: gameDir, Hash: dirHash})
|
gameUuid := pgtype.UUID{Bytes: uuid.New(), Valid: true}
|
||||||
|
err = repo.InsertSoundtrackWithExistingId(BackendCtx(), repository.InsertSoundtrackWithExistingIdParams{ID: id, Uuid: gameUuid, SoundtrackName: file.Name(), Path: gameDir, Hash: dirHash})
|
||||||
handleError("InsertSoundtrackWithExistingId", err, "")
|
handleError("InsertSoundtrackWithExistingId", err, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.GetLogger().Debug("Game already exists, removing old ID file",
|
logging.GetLogger().Debug("Game already exists, removing old ID file",
|
||||||
@@ -435,7 +438,8 @@ func syncGameNew(file os.DirEntry, foldersToSkip []string, baseDir string, full
|
|||||||
|
|
||||||
func insertGameNew(name string, path string, hash string) int32 {
|
func insertGameNew(name string, path string, hash string) int32 {
|
||||||
var duplicateError = errors.New("ERROR: duplicate key value violates unique")
|
var duplicateError = errors.New("ERROR: duplicate key value violates unique")
|
||||||
id, err := repo.InsertSoundtrack(BackendCtx(), repository.InsertSoundtrackParams{SoundtrackName: name, Path: path, Hash: hash})
|
gameUuid := pgtype.UUID{Bytes: uuid.New(), Valid: true}
|
||||||
|
id, err := repo.InsertSoundtrack(BackendCtx(), repository.InsertSoundtrackParams{Uuid: gameUuid, SoundtrackName: name, Path: path, Hash: hash})
|
||||||
handleError("InsertSoundtrack", err, "")
|
handleError("InsertSoundtrack", err, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.GetLogger().Warn("ID collision detected, resetting sequence")
|
logging.GetLogger().Warn("ID collision detected, resetting sequence")
|
||||||
@@ -522,7 +526,8 @@ func newCheckSong(entry os.DirEntry, gameDir string, id int32) bool {
|
|||||||
err = repo.AddHashToSong(BackendCtx(), repository.AddHashToSongParams{Hash: songHash, SoundtrackID: id, Path: path})
|
err = repo.AddHashToSong(BackendCtx(), repository.AddHashToSongParams{Hash: songHash, SoundtrackID: id, Path: path})
|
||||||
handleError("AddHashToSong", err, fmt.Sprintf("SoundtrackID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
|
handleError("AddHashToSong", err, fmt.Sprintf("SoundtrackID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
|
||||||
} else {
|
} else {
|
||||||
err = repo.AddSong(BackendCtx(), repository.AddSongParams{SoundtrackID: id, SongName: songName, Path: path, FileName: &fileName, Hash: songHash})
|
songUuid := pgtype.UUID{Bytes: uuid.New(), Valid: true}
|
||||||
|
err = repo.AddSong(BackendCtx(), repository.AddSongParams{Uuid: songUuid, SoundtrackID: id, SongName: songName, Path: path, FileName: &fileName, Hash: songHash})
|
||||||
handleError("AddSong", err, fmt.Sprintf("SoundtrackID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
|
handleError("AddSong", err, fmt.Sprintf("SoundtrackID: %d | Path: %s | SongName: %s | SongHash: %s", id, path, entry.Name(), songHash))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
-- Rollback: Remove UUID columns from soundtrack and song tables
|
||||||
|
|
||||||
|
-- Drop indexes
|
||||||
|
DROP INDEX IF EXISTS idx_soundtrack_uuid;
|
||||||
|
DROP INDEX IF EXISTS idx_song_uuid;
|
||||||
|
|
||||||
|
-- Drop UUID columns
|
||||||
|
ALTER TABLE soundtrack DROP COLUMN IF EXISTS uuid;
|
||||||
|
ALTER TABLE song DROP COLUMN IF EXISTS uuid;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
-- Migration: Add UUID columns to soundtrack and song, then backfill
|
||||||
|
|
||||||
|
-- Add UUID column to soundtrack (nullable for now)
|
||||||
|
ALTER TABLE soundtrack ADD COLUMN uuid UUID NULL UNIQUE;
|
||||||
|
|
||||||
|
-- Create index on uuid for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_soundtrack_uuid ON soundtrack(uuid);
|
||||||
|
|
||||||
|
-- Add UUID column to song (nullable for now)
|
||||||
|
ALTER TABLE song ADD COLUMN uuid UUID NULL UNIQUE;
|
||||||
|
|
||||||
|
-- Create index on uuid for performance
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_song_uuid ON song(uuid);
|
||||||
|
|
||||||
|
-- Backfill existing records immediately
|
||||||
|
UPDATE soundtrack SET uuid = gen_random_uuid() WHERE uuid IS NULL;
|
||||||
|
UPDATE song SET uuid = gen_random_uuid() WHERE uuid IS NULL;
|
||||||
|
|
||||||
|
-- Verify no nulls remain
|
||||||
|
-- SELECT COUNT(*) FROM soundtrack WHERE uuid IS NULL; -- Should be 0
|
||||||
|
-- SELECT COUNT(*) FROM song WHERE uuid IS NULL; -- Should be 0
|
||||||
@@ -5,7 +5,7 @@ DELETE FROM song;
|
|||||||
DELETE FROM song WHERE soundtrack_id = $1;
|
DELETE FROM song WHERE soundtrack_id = $1;
|
||||||
|
|
||||||
-- name: AddSong :exec
|
-- name: AddSong :exec
|
||||||
INSERT INTO song(soundtrack_id, song_name, path, file_name, hash) VALUES ($1, $2, $3, $4, $5);
|
INSERT INTO song(uuid, soundtrack_id, song_name, path, file_name, hash) VALUES ($1, $2, $3, $4, $5, $6);
|
||||||
|
|
||||||
-- name: CheckSong :one
|
-- name: CheckSong :one
|
||||||
SELECT COUNT(*) FROM song WHERE soundtrack_id = $1 AND path = $2;
|
SELECT COUNT(*) FROM song WHERE soundtrack_id = $1 AND path = $2;
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ UPDATE soundtrack SET deleted=NULL WHERE id=$1;
|
|||||||
SELECT id FROM soundtrack WHERE soundtrack_name = $1;
|
SELECT id FROM soundtrack WHERE soundtrack_name = $1;
|
||||||
|
|
||||||
-- name: InsertSoundtrack :one
|
-- name: InsertSoundtrack :one
|
||||||
INSERT INTO soundtrack (soundtrack_name, path, hash, added) VALUES ($1, $2, $3, now()) returning id;
|
INSERT INTO soundtrack (uuid, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, now()) returning id;
|
||||||
|
|
||||||
-- name: InsertSoundtrackWithExistingId :exec
|
-- name: InsertSoundtrackWithExistingId :exec
|
||||||
INSERT INTO soundtrack (id, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, now());
|
INSERT INTO soundtrack (id, uuid, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, $5, now());
|
||||||
|
|
||||||
-- name: FindAllSoundtracks :many
|
-- name: FindAllSoundtracks :many
|
||||||
SELECT *
|
SELECT *
|
||||||
|
|||||||
@@ -10,14 +10,6 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDMigrationStatus struct {
|
|
||||||
TableName string `json:"table_name"`
|
|
||||||
TotalRows int32 `json:"total_rows"`
|
|
||||||
MigratedRows int32 `json:"migrated_rows"`
|
|
||||||
Completed bool `json:"completed"`
|
|
||||||
StartedAt *time.Time `json:"started_at"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Session struct {
|
type Session struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
IpAddress string `json:"ip_address"`
|
IpAddress string `json:"ip_address"`
|
||||||
|
|||||||
@@ -27,19 +27,21 @@ func (q *Queries) AddHashToSong(ctx context.Context, arg AddHashToSongParams) er
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addSong = `-- name: AddSong :exec
|
const addSong = `-- name: AddSong :exec
|
||||||
INSERT INTO song(soundtrack_id, song_name, path, file_name, hash) VALUES ($1, $2, $3, $4, $5)
|
INSERT INTO song(uuid, soundtrack_id, song_name, path, file_name, hash) VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
`
|
`
|
||||||
|
|
||||||
type AddSongParams struct {
|
type AddSongParams struct {
|
||||||
SoundtrackID int32 `json:"soundtrack_id"`
|
Uuid pgtype.UUID `json:"uuid"`
|
||||||
SongName string `json:"song_name"`
|
SoundtrackID int32 `json:"soundtrack_id"`
|
||||||
Path string `json:"path"`
|
SongName string `json:"song_name"`
|
||||||
FileName *string `json:"file_name"`
|
Path string `json:"path"`
|
||||||
Hash string `json:"hash"`
|
FileName *string `json:"file_name"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) AddSong(ctx context.Context, arg AddSongParams) error {
|
func (q *Queries) AddSong(ctx context.Context, arg AddSongParams) error {
|
||||||
_, err := q.db.Exec(ctx, addSong,
|
_, err := q.db.Exec(ctx, addSong,
|
||||||
|
arg.Uuid,
|
||||||
arg.SoundtrackID,
|
arg.SoundtrackID,
|
||||||
arg.SongName,
|
arg.SongName,
|
||||||
arg.Path,
|
arg.Path,
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
const addSoundtrackPlayed = `-- name: AddSoundtrackPlayed :exec
|
const addSoundtrackPlayed = `-- name: AddSoundtrackPlayed :exec
|
||||||
@@ -153,36 +155,44 @@ func (q *Queries) GetSoundtrackNameById(ctx context.Context, id int32) (string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const insertSoundtrack = `-- name: InsertSoundtrack :one
|
const insertSoundtrack = `-- name: InsertSoundtrack :one
|
||||||
INSERT INTO soundtrack (soundtrack_name, path, hash, added) VALUES ($1, $2, $3, now()) returning id
|
INSERT INTO soundtrack (uuid, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, now()) returning id
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertSoundtrackParams struct {
|
type InsertSoundtrackParams struct {
|
||||||
SoundtrackName string `json:"soundtrack_name"`
|
Uuid pgtype.UUID `json:"uuid"`
|
||||||
Path string `json:"path"`
|
SoundtrackName string `json:"soundtrack_name"`
|
||||||
Hash string `json:"hash"`
|
Path string `json:"path"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertSoundtrack(ctx context.Context, arg InsertSoundtrackParams) (int32, error) {
|
func (q *Queries) InsertSoundtrack(ctx context.Context, arg InsertSoundtrackParams) (int32, error) {
|
||||||
row := q.db.QueryRow(ctx, insertSoundtrack, arg.SoundtrackName, arg.Path, arg.Hash)
|
row := q.db.QueryRow(ctx, insertSoundtrack,
|
||||||
|
arg.Uuid,
|
||||||
|
arg.SoundtrackName,
|
||||||
|
arg.Path,
|
||||||
|
arg.Hash,
|
||||||
|
)
|
||||||
var id int32
|
var id int32
|
||||||
err := row.Scan(&id)
|
err := row.Scan(&id)
|
||||||
return id, err
|
return id, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertSoundtrackWithExistingId = `-- name: InsertSoundtrackWithExistingId :exec
|
const insertSoundtrackWithExistingId = `-- name: InsertSoundtrackWithExistingId :exec
|
||||||
INSERT INTO soundtrack (id, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, now())
|
INSERT INTO soundtrack (id, uuid, soundtrack_name, path, hash, added) VALUES ($1, $2, $3, $4, $5, now())
|
||||||
`
|
`
|
||||||
|
|
||||||
type InsertSoundtrackWithExistingIdParams struct {
|
type InsertSoundtrackWithExistingIdParams struct {
|
||||||
ID int32 `json:"id"`
|
ID int32 `json:"id"`
|
||||||
SoundtrackName string `json:"soundtrack_name"`
|
Uuid pgtype.UUID `json:"uuid"`
|
||||||
Path string `json:"path"`
|
SoundtrackName string `json:"soundtrack_name"`
|
||||||
Hash string `json:"hash"`
|
Path string `json:"path"`
|
||||||
|
Hash string `json:"hash"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InsertSoundtrackWithExistingId(ctx context.Context, arg InsertSoundtrackWithExistingIdParams) error {
|
func (q *Queries) InsertSoundtrackWithExistingId(ctx context.Context, arg InsertSoundtrackWithExistingIdParams) error {
|
||||||
_, err := q.db.Exec(ctx, insertSoundtrackWithExistingId,
|
_, err := q.db.Exec(ctx, insertSoundtrackWithExistingId,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
|
arg.Uuid,
|
||||||
arg.SoundtrackName,
|
arg.SoundtrackName,
|
||||||
arg.Path,
|
arg.Path,
|
||||||
arg.Hash,
|
arg.Hash,
|
||||||
|
|||||||
Reference in New Issue
Block a user