github.com/quay/claircore@v1.5.28/rpm/sqlite/sqlite.go (about) 1 // Package sqlite extracts RPM package information from SQLite databases. 2 package sqlite 3 4 import ( 5 "bytes" 6 "context" 7 "database/sql" 8 _ "embed" // embed a sql statement 9 "errors" 10 "fmt" 11 "io" 12 "net/url" 13 "runtime" 14 15 _ "modernc.org/sqlite" // register the sqlite driver 16 ) 17 18 // RPMDB is a handle to a SQLite RPM database. 19 type RPMDB struct { 20 db *sql.DB 21 } 22 23 // Open opens the named SQLite database and interprets it as an RPM 24 // database. 25 // 26 // Must be a file on-disk. This is a limitation of the underlying SQLite 27 // library. 28 // 29 // The returned RPMDB struct must have its Close method called, or the 30 // process may panic. 31 func Open(f string) (*RPMDB, error) { 32 u := url.URL{ 33 Scheme: `file`, 34 Opaque: f, 35 RawQuery: url.Values{ 36 "_pragma": { 37 "foreign_keys(1)", 38 "query_only(1)", 39 }, 40 }.Encode(), 41 } 42 db, err := sql.Open(`sqlite`, u.String()) 43 if err != nil { 44 return nil, err 45 } 46 if err := db.Ping(); err != nil { 47 return nil, err 48 } 49 rdb := RPMDB{db: db} 50 _, file, line, _ := runtime.Caller(1) 51 runtime.SetFinalizer(&rdb, func(rdb *RPMDB) { 52 panic(fmt.Sprintf("%s:%d: RPM db not closed", file, line)) 53 }) 54 return &rdb, nil 55 } 56 57 // Close releases held resources. 58 // 59 // This must be called when the RPMDB is no longer needed, or the 60 // process may panic. 61 func (db *RPMDB) Close() error { 62 runtime.SetFinalizer(db, nil) 63 return db.db.Close() 64 } 65 66 // AllHeaders returns ReaderAts for all RPM headers in the database. 67 func (db *RPMDB) AllHeaders(ctx context.Context) ([]io.ReaderAt, error) { 68 // Keys are sorted coming out of this query. 69 rows, err := db.db.QueryContext(ctx, allpackages) 70 if err != nil { 71 return nil, err 72 } 73 defer rows.Close() 74 var r []io.ReaderAt 75 var hnum int64 76 for rows.Next() { 77 blob := make([]byte, 0, 4*4096) // Eyeballing a good initial capacity; do some profiling. 78 // As an alternative, this function could allocate one large buffer and subslice it for each 79 // Scan call, then use io.SectionReaders for the returned []io.ReaderAt. 80 if err := rows.Scan(&hnum, &blob); err != nil { 81 return nil, fmt.Errorf("sqlite: scan error: %w", err) 82 } 83 r = append(r, bytes.NewReader(blob)) 84 } 85 if err := rows.Err(); err != nil { 86 return nil, fmt.Errorf("sqlite: sql error: %w", err) 87 } 88 89 return r, nil 90 } 91 92 func (db *RPMDB) Validate(ctx context.Context) error { 93 var ignore int64 94 err := db.db.QueryRow(validate).Scan(&ignore) 95 switch { 96 case errors.Is(err, nil): 97 case errors.Is(err, sql.ErrNoRows): 98 return errors.New("not an rpm database") 99 default: 100 return err 101 } 102 return nil 103 } 104 105 //go:embed sql/allpackages.sql 106 var allpackages string 107 108 //go:embed sql/validate.sql 109 var validate string