package sqlc import ( "context" "database/sql" "errors" "fmt" "sync" ) const writeBufferSize = 100 type GenericQuery func(*Queries, context.Context, any) (any, error) type WriteTx struct { ErrChan chan error ReturnChan chan any Query GenericQuery Args any } type Queue struct { Queries *Queries Db *sql.DB WriteQueue chan WriteTx wg sync.WaitGroup ctx context.Context cancel context.CancelFunc } func NewQueue(ctx context.Context, sqlDb *sql.DB) (*Queue, error) { ctx, cancel := context.WithCancel(context.Background()) sqldb := &Queue{ Queries: New(sqlDb), Db: sqlDb, WriteQueue: make(chan WriteTx, writeBufferSize), ctx: ctx, cancel: cancel, } return sqldb, nil } func (d *Queue) Start() { d.wg.Add(1) go func() { // TODO: I think this might be a naive approach. defer d.wg.Done() for { select { case <-d.ctx.Done(): return case writeTx := <-d.WriteQueue: ret, err := writeTx.Query(d.Queries, d.ctx, writeTx.Args) writeTx.ErrChan <- err writeTx.ReturnChan <- ret close(writeTx.ErrChan) close(writeTx.ReturnChan) } } }() } func (d *Queue) Stop() error { d.cancel() d.wg.Wait() close(d.WriteQueue) return d.Db.Close() } func (d *Queue) EnqueueWriteTx(queryFunc GenericQuery, args any) (any, error) { select { case <-d.ctx.Done(): return nil, errors.New("database is shutting down") default: } errChan := make(chan error, 1) retChan := make(chan any, 1) writeTx := WriteTx{ Query: queryFunc, Args: args, ErrChan: errChan, ReturnChan: retChan, } d.WriteQueue <- writeTx err := <-errChan val := <-retChan return val, err } func OpenSqliteDb(dbPath string) (*sql.DB, error) { sqliteDb, err := sql.Open("sqlite3", dbPath) if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) } if err := sqliteDb.Ping(); err != nil { return nil, fmt.Errorf("failed to connect to database: %w", err) } return sqliteDb, nil }