internal/programs/programs.go (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | package programs import ( "context" "database/sql" "fmt" "os/exec" "strings" "alin.ovh/searchix/internal/config" "alin.ovh/x/log" "github.com/Southclaws/fault" "github.com/Southclaws/fault/fmsg" _ "modernc.org/sqlite" //nolint:blank-imports // sqlite driver needed for database/sql ) type DB struct { Path string Source *config.Source logger *log.Logger db *sql.DB stmt *sql.Stmt } func Instantiate( ctx context.Context, source *config.Source, logger *log.Logger, ) (*DB, error) { // nix-instantiate --eval --json -I nixpkgs=channel:nixos-unstable --expr 'toString <nixpkgs/programs.sqlite>' args := []string{ "--eval", "--json", "-I", fmt.Sprintf("%s=channel:%s", source.Key, source.Channel), "--expr", fmt.Sprintf("toString <%s/%s>", source.Key, source.Programs.Attribute), } logger.Debug("nix-instantiate command", "args", args) cmd := exec.CommandContext(ctx, "nix-instantiate", args...) out, err := cmd.Output() if err != nil { return nil, fault.Wrap(err, fmsg.With("failed to run nix-instantiate")) } outPath := strings.Trim(strings.TrimSpace(string(out)), "\"") logger.Debug("got output path", "outputPath", outPath) return &DB{ Source: source, Path: outPath, logger: logger, }, nil } func (p *DB) Open() error { var err error p.db, err = sql.Open("sqlite", p.Path) if err != nil { return fault.Wrap(err, fmsg.With("failed to open sqlite database")) } p.logger.Debug("opened sqlite database") _, err = p.db.Exec("ATTACH DATABASE ':memory:' AS mem") if err != nil { return fault.Wrap(err, fmsg.With("failed to attach in-memory database")) } _, err = p.db.Exec(` CREATE TABLE mem.programs AS SELECT name, package FROM main.Programs GROUP BY name, package `) if err != nil { return fault.Wrap(err, fmsg.With("failed to create programs table")) } p.logger.Debug("created programs table") _, err = p.db.Exec(`CREATE INDEX mem.idx_package ON programs(package)`) if err != nil { return fault.Wrap(err, fmsg.With("failed to create idx_package index")) } p.logger.Debug("created idx_package index") p.stmt, err = p.db.Prepare(` SELECT name FROM mem.programs WHERE package = ? `) if err != nil { return fault.Wrap(err, fmsg.With("failed to prepare statement")) } p.logger.Debug("prepared statement") return nil } func (p *DB) Close() error { if err := p.db.Close(); err != nil { return fault.Wrap(err, fmsg.With("failed to close sqlite database")) } return nil } func (p *DB) GetPackagePrograms(ctx context.Context, pkg string) ([]string, error) { var programs []string if p.db == nil { return nil, fault.New("database not open") } rows, err := p.stmt.QueryContext(ctx, pkg) if err != nil { return nil, fault.Wrap(err, fmsg.With("failed to execute query")) } defer rows.Close() for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, fault.Wrap(err, fmsg.With("failed to scan row")) } programs = append(programs, name) } rerr := rows.Close() if rerr != nil { return nil, fault.Wrap(rerr, fmsg.With("sql error")) } return programs, nil } |