package db import ( "context" "database/sql" "errors" "fmt" "strconv" "time" "github.com/jmoiron/sqlx" "golang.org/x/sync/errgroup" ) type Game struct { ID uint CreatedAt time.Time DemoMode bool IdleLogout bool Paused bool UserID string Addresses Addresses Files Files Options GameOptions Release Release World *World System *System CoreUpdate CoreUpdate SystemUpdate SystemUpdate ActiveUsers []string Modules []*Module PackageWarnings []*PackageWarning Packs []*Pack Messages []*Message Combats []*Combat CardDeck []*CardDeck Users []*User Macros []*Macro Folders []*WorldFolder Items []*Item Settings []*Setting Journals []*Journal Tables []*Table Playlists []*Playlist Actors []*Actor // Scenes []Scene } func (g *Game) InsertObjects(db *sqlx.DB) error { relData := InsertId[uint]{id: g.ID, fieldName: "game_id"} err := func() error { tx := db.MustBegin() defer tx.Rollback() groupFunc, _ := errgroup.WithContext(context.Background()) InsertWithCtxParallel(groupFunc, tx, &g.Addresses, relData) InsertWithCtxParallel(groupFunc, tx, &g.Files, relData) InsertWithCtxParallel(groupFunc, tx, &g.Options, relData) InsertWithCtxParallel(groupFunc, tx, &g.Release, relData) InsertWithCtxParallel(groupFunc, tx, &g.CoreUpdate, relData) InsertWithCtxParallel(groupFunc, tx, &g.SystemUpdate, relData) relDataString := InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"} InsertWithCtxParallel(groupFunc, tx, g.World, relDataString) InsertWithCtxParallel(groupFunc, tx, g.System, relDataString) InsertSimpleSliceParallel(groupFunc, tx, g.ActiveUsers, &InsertId[uint]{id: g.ID, fieldName: "game_id", tableName: "active_users"}) InsertSliceParallel(groupFunc, tx, g.Modules, relData) InsertSliceParallel(groupFunc, tx, g.PackageWarnings, relData) InsertSliceParallel(groupFunc, tx, g.Packs, relDataString) InsertSliceParallel(groupFunc, tx, g.Messages, relData) InsertSliceParallel(groupFunc, tx, g.Combats, relData) InsertSliceParallel(groupFunc, tx, g.CardDeck, relData) InsertSliceParallel(groupFunc, tx, g.Users, relData) InsertSliceParallel(groupFunc, tx, g.Macros, relData) InsertSliceParallel(groupFunc, tx, g.Folders, relData) errFunc := groupFunc.Wait() if errFunc != nil && !errors.Is(errFunc, sql.ErrNoRows) { return errFunc } return tx.Commit() }() err = func() error { tx := db.MustBegin() defer tx.Rollback() groupFunc, _ := errgroup.WithContext(context.Background()) InsertSliceParallel(groupFunc, tx, g.Settings, relData) InsertSliceParallel(groupFunc, tx, g.Journals, relData) InsertSliceParallel(groupFunc, tx, g.Tables, relData) InsertSliceParallel(groupFunc, tx, g.Playlists, relData) errFunc := groupFunc.Wait() if errFunc != nil && !errors.Is(errFunc, sql.ErrNoRows) { return errFunc } return tx.Commit() }() err = func() error { tx := db.MustBegin() defer tx.Rollback() groupFunc, _ := errgroup.WithContext(context.Background()) InsertSliceParallelTimeout(groupFunc, tx, g.Items, InsertId[string]{id: strconv.FormatUint(uint64(g.ID), 10), fieldName: "game_id"}, 15*time.Second) InsertSliceParallelTimeout(groupFunc, tx, g.Actors, relData, 15*time.Second) errFunc := groupFunc.Wait() if errFunc != nil && !errors.Is(errFunc, sql.ErrNoRows) { return errFunc } return tx.Commit() }() return err } func (g *Game) Insert(db *sqlx.DB) error { tx := db.MustBegin() defer tx.Rollback() const query = ` INSERT INTO game (demo_mode, idle_logout, paused, user_id) VALUES ($1, $2, $3, $4) RETURNING id, created_at` args := []any{g.DemoMode, g.IdleLogout, g.Paused, g.UserID} ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() err := tx.QueryRowxContext(ctx, query, args...).Scan(&g.ID, &g.CreatedAt) if err != nil { return err } err = tx.Commit() if err != nil { return err } err = g.InsertObjects(db) if err != nil { fmt.Printf("%v\n", err) } return err }