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