feat: added repo export/import streaming methods

This commit is contained in:
abelith committed 2026-08-16 21:54:31 +05:00
1 parent c5465fe6f6
commit 4de3218c40
2 files changed
+108 -9

No files matched your search

+105 -9
View File
@@ -3,6 +3,7 @@ package sqlite
import ( import (
"context" "context"
"database/sql" "database/sql"
"fmt"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
"github.com/abelith/spellbook/internal/errors" "github.com/abelith/spellbook/internal/errors"
"github.com/abelith/spellbook/internal/models" "github.com/abelith/spellbook/internal/models"
@@ -45,17 +46,10 @@ func (r *Repo) Init(ctx context.Context) error {
} }
func (r *Repo) Add(ctx context.Context, spell models.Spell) error { func (r *Repo) Add(ctx context.Context, spell models.Spell) error {
tags := strings.Join(spell.Tags, ",") if _, err := insertSpell(ctx, r.db, spell); err != nil {
_, err := sq.Insert("spells").
Columns("name", "spell", "short_desc", "description", "tags").
Values(spell.Name, spell.Spell, spell.ShortDesc, spell.Description, tags).
PlaceholderFormat(sq.Question).
RunWith(r.db).
ExecContext(ctx)
if err != nil {
return err return err
} }
return nil return nil
} }
@@ -148,6 +142,97 @@ func (r *Repo) GetMany(ctx context.Context, query models.SearchQuery) ([]models.
return spells, nil return spells, nil
} }
func (r *Repo) Import(ctx context.Context, ch <-chan models.Spell) error {
return r.runTx(ctx, func(tx *sql.Tx) error {
cache := sq.NewStmtCache(tx)
defer cache.Clear()
for {
select {
case <-ctx.Done():
return ctx.Err()
case spell, ok := <-ch:
if !ok {
return nil
}
_, err := insertSpell(ctx, cache, spell)
if err != nil {
return err
}
}
}
})
}
func (r *Repo) Count(ctx context.Context) (int, error) {
var count int
err := sq.Select("COUNT(*)").
From("spells").
RunWith(r.db).
ScanContext(ctx, &count)
if err != nil {
return 0, err
}
return count, nil
}
func (r *Repo) Export(ctx context.Context) (<-chan models.Spell, <-chan error, error) {
ch := make(chan models.Spell, 1)
errCh := make(chan error, 1)
rows, err := sq.Select("id", "name", "spell", "short_desc", "description", "tags").
From("spells").
RunWith(r.db).
QueryContext(ctx)
if err != nil {
return nil, nil, err
}
go func() {
defer close(errCh)
defer close(ch)
defer rows.Close()
for rows.Next() {
spell, err := scanSpell(rows)
if err != nil {
errCh <- err
return
}
select {
case <-ctx.Done():
errCh <- ctx.Err()
return
case ch <- spell:
}
}
if err := rows.Err(); err != nil {
errCh <- err
}
}()
return ch, errCh, nil
}
func (r *Repo) runTx(ctx context.Context, f func(tx *sql.Tx) error) error {
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
return fmt.Errorf("failed to begin tx: %w", err)
}
if err := f(tx); err != nil {
if rbErr := tx.Rollback(); rbErr != nil {
return fmt.Errorf("err: %v, rollback err: %w", err, rbErr)
}
return err
}
return tx.Commit()
}
func scanSpell(row scanner) (models.Spell, error) { func scanSpell(row scanner) (models.Spell, error) {
spell := models.Spell{} spell := models.Spell{}
tagsString := "" tagsString := ""
@@ -162,3 +247,14 @@ func scanSpell(row scanner) (models.Spell, error) {
return spell, nil return spell, nil
} }
func insertSpell(ctx context.Context, runner sq.BaseRunner, spell models.Spell) (sql.Result, error) {
tags := strings.Join(spell.Tags, ",")
return sq.Insert("spells").
Columns("name", "spell", "short_desc", "description", "tags").
Values(spell.Name, spell.Spell, spell.ShortDesc, spell.Description, tags).
PlaceholderFormat(sq.Question).
RunWith(runner).
ExecContext(ctx)
}
+3
View File
@@ -12,11 +12,14 @@ type SpellCaster interface {
type SpellCommand interface { type SpellCommand interface {
Add(ctx context.Context, spell models.Spell) error Add(ctx context.Context, spell models.Spell) error
Delete(ctx context.Context, spellName string) error Delete(ctx context.Context, spellName string) error
Import(ctx context.Context, ch <-chan models.Spell) error
} }
type SpellQuery interface { type SpellQuery interface {
Get(ctx context.Context, spellName string) (models.Spell, error) Get(ctx context.Context, spellName string) (models.Spell, error)
GetMany(ctx context.Context, query models.SearchQuery) ([]models.Spell, error) GetMany(ctx context.Context, query models.SearchQuery) ([]models.Spell, error)
Count(ctx context.Context) (int, error)
Export(ctx context.Context) (<-chan models.Spell, <-chan error, error)
} }
type Application struct { type Application struct {