137 lines
2.7 KiB
Go
137 lines
2.7 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
|
|
"forge.cadoles.com/arcad/edge/pkg/storage"
|
|
"github.com/pkg/errors"
|
|
"gitlab.com/wpetit/goweb/logger"
|
|
)
|
|
|
|
type BlobStore struct {
|
|
getDB GetDBFunc
|
|
}
|
|
|
|
// DeleteBucket implements storage.BlobStore
|
|
func (s *BlobStore) DeleteBucket(ctx context.Context, name string) error {
|
|
err := s.withTx(ctx, func(tx *sql.Tx) error {
|
|
query := `DELETE FROM blobs WHERE bucket = $1`
|
|
_, err := tx.ExecContext(ctx, query, name)
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ListBuckets implements storage.BlobStore
|
|
func (s *BlobStore) ListBuckets(ctx context.Context) ([]string, error) {
|
|
buckets := make([]string, 0)
|
|
|
|
err := s.withTx(ctx, func(tx *sql.Tx) error {
|
|
query := `SELECT DISTINCT bucket FROM blobs`
|
|
rows, err := tx.QueryContext(ctx, query)
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
defer func() {
|
|
if err := rows.Close(); err != nil {
|
|
logger.Error(ctx, "could not close rows", logger.CapturedE(errors.WithStack(err)))
|
|
}
|
|
}()
|
|
|
|
for rows.Next() {
|
|
var name string
|
|
if err := rows.Scan(&name); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
buckets = append(buckets, name)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
return buckets, nil
|
|
}
|
|
|
|
// OpenBucket implements storage.BlobStore
|
|
func (s *BlobStore) OpenBucket(ctx context.Context, name string) (storage.BlobBucket, error) {
|
|
return &BlobBucket{
|
|
name: name,
|
|
getDB: s.getDB,
|
|
}, nil
|
|
}
|
|
|
|
func ensureBlobTables(ctx context.Context, db *sql.DB) error {
|
|
logger.Debug(ctx, "creating blobs table")
|
|
|
|
err := WithTx(ctx, db, func(tx *sql.Tx) error {
|
|
query := `
|
|
CREATE TABLE IF NOT EXISTS blobs (
|
|
id TEXT,
|
|
bucket TEXT,
|
|
data BLOB,
|
|
content_type TEXT NOT NULL,
|
|
mod_time TIMESTAMP NOT NULL,
|
|
size INTEGER,
|
|
PRIMARY KEY (id, bucket)
|
|
);
|
|
`
|
|
if _, err := tx.ExecContext(ctx, query); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *BlobStore) withTx(ctx context.Context, fn func(tx *sql.Tx) error) error {
|
|
var db *sql.DB
|
|
|
|
db, err := s.getDB(ctx)
|
|
if err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
if err := WithRetry(ctx, db, sqliteBusyMaxRetry, fn); err != nil {
|
|
return errors.WithStack(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewBlobStore(dsn string) *BlobStore {
|
|
getDB := NewGetDBFunc(dsn, ensureBlobTables)
|
|
|
|
return &BlobStore{getDB}
|
|
}
|
|
|
|
func NewBlobStoreWithDB(db *sql.DB) *BlobStore {
|
|
getDB := NewGetDBFuncFromDB(db, ensureBlobTables)
|
|
|
|
return &BlobStore{getDB}
|
|
}
|
|
|
|
var _ storage.BlobStore = &BlobStore{}
|