refactor: migrate to xorm
This commit is contained in:
		| @@ -23,41 +23,39 @@ import ( | ||||
| 	"context" | ||||
| 	"log/slog" | ||||
|  | ||||
| 	"github.com/jmoiron/sqlx" | ||||
| 	"github.com/leonelquinteros/gotext" | ||||
| 	_ "modernc.org/sqlite" | ||||
| 	"xorm.io/xorm" | ||||
|  | ||||
| 	"gitea.plemya-x.ru/Plemya-x/ALR/internal/config" | ||||
| ) | ||||
|  | ||||
| // CurrentVersion is the current version of the database. | ||||
| // The database is reset if its version doesn't match this. | ||||
| const CurrentVersion = 4 | ||||
| const CurrentVersion = 5 | ||||
|  | ||||
| // Package is a ALR package's database representation | ||||
| type Package struct { | ||||
| 	BasePkgName   string                    `sh:"base" db:"basepkg_name"` | ||||
| 	Name          string                    `sh:"name,required" db:"name"` | ||||
| 	Version       string                    `sh:"version,required" db:"version"` | ||||
| 	Release       int                       `sh:"release,required" db:"release"` | ||||
| 	Epoch         uint                      `sh:"epoch" db:"epoch"` | ||||
| 	Summary       JSON[map[string]string]   `db:"summary"` | ||||
| 	Description   JSON[map[string]string]   `db:"description"` | ||||
| 	Group         JSON[map[string]string]   `db:"group_name"` | ||||
| 	Homepage      JSON[map[string]string]   `db:"homepage"` | ||||
| 	Maintainer    JSON[map[string]string]   `db:"maintainer"` | ||||
| 	Architectures JSON[[]string]            `sh:"architectures" db:"architectures"` | ||||
| 	Licenses      JSON[[]string]            `sh:"license" db:"licenses"` | ||||
| 	Provides      JSON[[]string]            `sh:"provides" db:"provides"` | ||||
| 	Conflicts     JSON[[]string]            `sh:"conflicts" db:"conflicts"` | ||||
| 	Replaces      JSON[[]string]            `sh:"replaces" db:"replaces"` | ||||
| 	Depends       JSON[map[string][]string] `db:"depends"` | ||||
| 	BuildDepends  JSON[map[string][]string] `db:"builddepends"` | ||||
| 	OptDepends    JSON[map[string][]string] `db:"optdepends"` | ||||
| 	Repository    string                    `db:"repository"` | ||||
| 	BasePkgName   string              `sh:"basepkg_name" xorm:"notnull 'basepkg_name'"` | ||||
| 	Name          string              `sh:"name,required" xorm:"notnull unique(name_repo) 'name'"` | ||||
| 	Version       string              `sh:"version,required" xorm:"notnull 'version'"` | ||||
| 	Release       int                 `sh:"release" xorm:"notnull 'release'"` | ||||
| 	Epoch         uint                `sh:"epoch" xorm:"'epoch'"` | ||||
| 	Summary       map[string]string   `xorm:"json 'summary'"` | ||||
| 	Description   map[string]string   `xorm:"json 'description'"` | ||||
| 	Group         map[string]string   `xorm:"json 'group_name'"` | ||||
| 	Homepage      map[string]string   `xorm:"json 'homepage'"` | ||||
| 	Maintainer    map[string]string   `xorm:"json 'maintainer'"` | ||||
| 	Architectures []string            `sh:"architectures" xorm:"json 'architectures'"` | ||||
| 	Licenses      []string            `sh:"license" xorm:"json 'licenses'"` | ||||
| 	Provides      []string            `sh:"provides" xorm:"json 'provides'"` | ||||
| 	Conflicts     []string            `sh:"conflicts" xorm:"json 'conflicts'"` | ||||
| 	Replaces      []string            `sh:"replaces" xorm:"json 'replaces'"` | ||||
| 	Depends       map[string][]string `xorm:"json 'depends'"` | ||||
| 	BuildDepends  map[string][]string `xorm:"json 'builddepends'"` | ||||
| 	OptDepends    map[string][]string `xorm:"json 'optdepends'"` | ||||
| 	Repository    string              `xorm:"notnull unique(name_repo) 'repository'"` | ||||
| } | ||||
|  | ||||
| type version struct { | ||||
| 	Version int `db:"version"` | ||||
| type Version struct { | ||||
| 	Version int `xorm:"'version'"` | ||||
| } | ||||
|  | ||||
| type Config interface { | ||||
| @@ -65,7 +63,7 @@ type Config interface { | ||||
| } | ||||
|  | ||||
| type Database struct { | ||||
| 	conn   *sqlx.DB | ||||
| 	engine *xorm.Engine | ||||
| 	config Config | ||||
| } | ||||
|  | ||||
| @@ -75,181 +73,98 @@ func New(config Config) *Database { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (d *Database) Init(ctx context.Context) error { | ||||
| 	err := d.Connect(ctx) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return d.initDB(ctx) | ||||
| } | ||||
|  | ||||
| func (d *Database) Connect(ctx context.Context) error { | ||||
| func (d *Database) Connect() error { | ||||
| 	dsn := d.config.GetPaths().DBPath | ||||
| 	db, err := sqlx.Open("sqlite", dsn) | ||||
| 	engine, err := xorm.NewEngine("sqlite", dsn) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	d.conn = db | ||||
| 	d.engine = engine | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (d *Database) GetConn() *sqlx.DB { | ||||
| 	return d.conn | ||||
| } | ||||
|  | ||||
| func (d *Database) initDB(ctx context.Context) error { | ||||
| 	d.conn = d.conn.Unsafe() | ||||
| 	conn := d.conn | ||||
| 	_, err := conn.ExecContext(ctx, ` | ||||
| 		CREATE TABLE IF NOT EXISTS pkgs ( | ||||
| 			basepkg_name  TEXT NOT NULL, | ||||
| 			name          TEXT NOT NULL, | ||||
| 			repository    TEXT NOT NULL, | ||||
| 			version       TEXT NOT NULL, | ||||
| 			release       INT  NOT NULL, | ||||
| 			epoch         INT, | ||||
| 			summary       TEXT CHECK(summary = 'null' OR (JSON_VALID(summary) AND JSON_TYPE(summary) = 'object')), | ||||
| 			description   TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')), | ||||
| 			group_name    TEXT CHECK(group_name = 'null' OR (JSON_VALID(group_name) AND JSON_TYPE(group_name) = 'object')), | ||||
| 			homepage      TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')), | ||||
| 			maintainer    TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')), | ||||
| 			architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')), | ||||
| 			licenses      TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')), | ||||
| 			provides      TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')), | ||||
| 			conflicts     TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')), | ||||
| 			replaces      TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')), | ||||
| 			depends       TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')), | ||||
| 			builddepends  TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')), | ||||
| 			optdepends    TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')), | ||||
| 			UNIQUE(name, repository) | ||||
| 		); | ||||
|  | ||||
| 		CREATE TABLE IF NOT EXISTS alr_db_version ( | ||||
| 			version INT NOT NULL | ||||
| 		); | ||||
| 	`) | ||||
| 	if err != nil { | ||||
| func (d *Database) Init(ctx context.Context) error { | ||||
| 	if err := d.Connect(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := d.engine.Sync2(new(Package), new(Version)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	ver, ok := d.GetVersion(ctx) | ||||
| 	if ok && ver != CurrentVersion { | ||||
| 		slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion) | ||||
| 		err = d.reset(ctx) | ||||
| 		if err != nil { | ||||
| 		if err := d.reset(); err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		return d.initDB(ctx) | ||||
| 		return d.Init(ctx) | ||||
| 	} else if !ok { | ||||
| 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."), "version", ver, "expected", CurrentVersion) | ||||
| 		return d.addVersion(ctx, CurrentVersion) | ||||
| 		slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working.")) | ||||
| 		return d.addVersion(CurrentVersion) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (d *Database) GetVersion(ctx context.Context) (int, bool) { | ||||
| 	var ver version | ||||
| 	err := d.conn.GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;") | ||||
| 	if err != nil { | ||||
| 	var v Version | ||||
| 	has, err := d.engine.Get(&v) | ||||
| 	if err != nil || !has { | ||||
| 		return 0, false | ||||
| 	} | ||||
| 	return ver.Version, true | ||||
| 	return v.Version, true | ||||
| } | ||||
|  | ||||
| func (d *Database) addVersion(ctx context.Context, ver int) error { | ||||
| 	_, err := d.conn.ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver) | ||||
| func (d *Database) addVersion(ver int) error { | ||||
| 	_, err := d.engine.Insert(&Version{Version: ver}) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (d *Database) reset(ctx context.Context) error { | ||||
| 	_, err := d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	_, err = d.conn.ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;") | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (d *Database) GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) { | ||||
| 	stream, err := d.conn.QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return stream, nil | ||||
| } | ||||
|  | ||||
| func (d *Database) GetPkg(ctx context.Context, where string, args ...any) (*Package, error) { | ||||
| 	out := &Package{} | ||||
| 	err := d.conn.GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...) | ||||
| 	return out, err | ||||
| } | ||||
|  | ||||
| func (d *Database) DeletePkgs(ctx context.Context, where string, args ...any) error { | ||||
| 	_, err := d.conn.ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (d *Database) IsEmpty(ctx context.Context) bool { | ||||
| 	var count int | ||||
| 	err := d.conn.GetContext(ctx, &count, "SELECT count(1) FROM pkgs;") | ||||
| 	if err != nil { | ||||
| 		return true | ||||
| 	} | ||||
| 	return count == 0 | ||||
| func (d *Database) reset() error { | ||||
| 	return d.engine.DropTables(new(Package), new(Version)) | ||||
| } | ||||
|  | ||||
| func (d *Database) InsertPackage(ctx context.Context, pkg Package) error { | ||||
| 	_, err := d.conn.NamedExecContext(ctx, ` | ||||
| 		INSERT OR REPLACE INTO pkgs ( | ||||
| 			basepkg_name, | ||||
| 			name, | ||||
| 			repository, | ||||
| 			version, | ||||
| 			release, | ||||
| 			epoch, | ||||
| 			summary, | ||||
| 			description, | ||||
| 			group_name, | ||||
| 			homepage, | ||||
| 			maintainer, | ||||
| 			architectures, | ||||
| 			licenses, | ||||
| 			provides, | ||||
| 			conflicts, | ||||
| 			replaces, | ||||
| 			depends, | ||||
| 			builddepends, | ||||
| 			optdepends | ||||
| 		) VALUES ( | ||||
| 		 	:basepkg_name, | ||||
| 			:name, | ||||
| 			:repository, | ||||
| 			:version, | ||||
| 			:release, | ||||
| 			:epoch, | ||||
| 			:summary, | ||||
| 			:description, | ||||
| 			:group_name, | ||||
| 			:homepage, | ||||
| 			:maintainer, | ||||
| 			:architectures, | ||||
| 			:licenses, | ||||
| 			:provides, | ||||
| 			:conflicts, | ||||
| 			:replaces, | ||||
| 			:depends, | ||||
| 			:builddepends, | ||||
| 			:optdepends | ||||
| 		); | ||||
| 	`, pkg) | ||||
| 	session := d.engine.Context(ctx) | ||||
|  | ||||
| 	affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if affected == 0 { | ||||
| 		_, err = session.Insert(&pkg) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]Package, error) { | ||||
| 	var pkgs []Package | ||||
| 	err := d.engine.Where(where, args...).Find(&pkgs) | ||||
| 	return pkgs, err | ||||
| } | ||||
|  | ||||
| func (d *Database) GetPkg(where string, args ...any) (*Package, error) { | ||||
| 	var pkg Package | ||||
| 	has, err := d.engine.Where(where, args...).Get(&pkg) | ||||
| 	if err != nil || !has { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &pkg, nil | ||||
| } | ||||
|  | ||||
| func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error { | ||||
| 	_, err := d.engine.Where(where, args...).Delete(&Package{}) | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (d *Database) Close() error { | ||||
| 	if d.conn != nil { | ||||
| 		return d.conn.Close() | ||||
| 	} else { | ||||
| 		return nil | ||||
| 	} | ||||
| func (d *Database) IsEmpty() bool { | ||||
| 	count, err := d.engine.Count(new(Package)) | ||||
| 	return err != nil || count == 0 | ||||
| } | ||||
|  | ||||
| func (d *Database) Close() error { | ||||
| 	return d.engine.Close() | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user