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 | package programs import ( "context" "database/sql" "fmt" "os/exec" "strings" "gitlab.com/tozd/go/errors" "go.alanpearce.eu/searchix/internal/config" "go.alanpearce.eu/x/log" _ "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, errors.E) { // 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, errors.WithMessage(err, "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() errors.E { var err error p.db, err = sql.Open("sqlite", p.Path) if err != nil { return errors.WithMessage(err, "failed to open sqlite database") } p.logger.Debug("opened sqlite database") _, err = p.db.Exec("ATTACH DATABASE ':memory:' AS mem") if err != nil { return errors.WithMessage(err, "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 errors.WithMessage(err, "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 errors.WithMessage(err, "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 errors.WithMessage(err, "failed to prepare statement") } p.logger.Debug("prepared statement") return nil } func (p *DB) Close() errors.E { if err := p.db.Close(); err != nil { return errors.WithMessage(err, "failed to close sqlite database") } return nil } func (p *DB) GetPackagePrograms(ctx context.Context, pkg string) ([]string, errors.E) { programs := make([]string, 10) if p.db == nil { return nil, errors.New("database not open") } rows, err := p.stmt.QueryContext(ctx, pkg) if err != nil { return nil, errors.WithMessage(err, "failed to execute query") } defer rows.Close() for rows.Next() { var name string if err := rows.Scan(&name); err != nil { return nil, errors.WithMessage(err, "failed to scan row") } programs = append(programs, name) } rerr := rows.Close() if rerr != nil { return nil, errors.WithMessage(rerr, "sql error") } return programs, nil } |