Trove

Grove Store Layer

Metadata persistence with Grove ORM: models, store, migrations, and multi-database support.

The Trove extension uses Grove for metadata persistence. Object metadata, bucket definitions, upload sessions, CAS index entries, and quotas are stored in a relational database (PostgreSQL, SQLite, or MongoDB) managed by Grove.

Models

All models use Grove struct tags and embed grove.BaseModel.

Bucket

type Bucket struct {
    grove.BaseModel `grove:"table:trove_buckets,alias:b"`

    ID            string            `grove:"id,pk"`
    Name          string            `grove:"name,notnull,unique"`
    Driver        string            `grove:"driver,notnull,default:'local'"`
    Region        string            `grove:"region"`
    Versioning    bool              `grove:"versioning,default:false"`
    CASEnabled    bool              `grove:"cas_enabled,default:false"`
    DefaultMeta   map[string]string `grove:"default_meta,type:jsonb"`
    QuotaMaxBytes int64             `grove:"quota_max_bytes,default:0"`
    TenantKey     string            `grove:"tenant_key"`
    CreatedAt     time.Time         `grove:"created_at,notnull,default:current_timestamp"`
    UpdatedAt     time.Time         `grove:"updated_at,notnull,default:current_timestamp"`
}

Object

type Object struct {
    grove.BaseModel `grove:"table:trove_objects,alias:o"`

    ID               string            `grove:"id,pk"`
    BucketID         string            `grove:"bucket_id,notnull"`
    Key              string            `grove:"key,notnull"`
    Size             int64             `grove:"size,notnull,default:0"`
    ContentType      string            `grove:"content_type"`
    ETag             string            `grove:"etag"`
    ChecksumSHA256   string            `grove:"checksum_sha256"`
    ChecksumBLAKE3   string            `grove:"checksum_blake3"`
    Metadata         map[string]string `grove:"metadata,type:jsonb"`
    Tags             map[string]string `grove:"tags,type:jsonb"`
    StorageDriver    string            `grove:"storage_driver"`
    StorageKey       string            `grove:"storage_key"`
    TenantKey        string            `grove:"tenant_key"`
    DeletedAt        *time.Time        `grove:"deleted_at,soft_delete"`
    CreatedAt        time.Time         `grove:"created_at,notnull,default:current_timestamp"`
    UpdatedAt        time.Time         `grove:"updated_at,notnull,default:current_timestamp"`
}

Upload Session

type UploadSession struct {
    grove.BaseModel `grove:"table:trove_upload_sessions,alias:us"`

    ID          string     `grove:"id,pk"`
    BucketID    string     `grove:"bucket_id,notnull"`
    ObjectKey   string     `grove:"object_key,notnull"`
    Status      string     `grove:"status,notnull,default:'initiated'"`
    TotalParts  int        `grove:"total_parts,default:0"`
    TotalSize   int64      `grove:"total_size,default:0"`
    ExpiresAt   time.Time  `grove:"expires_at,notnull"`
    CreatedAt   time.Time  `grove:"created_at,notnull,default:current_timestamp"`
    UpdatedAt   time.Time  `grove:"updated_at,notnull,default:current_timestamp"`
}

CAS Entry

type CASEntry struct {
    grove.BaseModel `grove:"table:trove_cas_index,alias:cas"`

    Hash      string    `grove:"hash,pk"`
    BucketID  string    `grove:"bucket_id,notnull"`
    ObjectKey string    `grove:"object_key"`
    Size      int64     `grove:"size,notnull,default:0"`
    RefCount  int       `grove:"ref_count,notnull,default:1"`
    Pinned    bool      `grove:"pinned,default:false"`
    CreatedAt time.Time `grove:"created_at,notnull,default:current_timestamp"`
}

Quota

type Quota struct {
    grove.BaseModel `grove:"table:trove_quotas,alias:q"`

    TenantKey    string    `grove:"tenant_key,pk"`
    UsedBytes    int64     `grove:"used_bytes,notnull,default:0"`
    LimitBytes   int64     `grove:"limit_bytes,notnull,default:0"`
    ObjectCount  int64     `grove:"object_count,notnull,default:0"`
    LimitObjects int64     `grove:"limit_objects,notnull,default:0"`
    UpdatedAt    time.Time `grove:"updated_at,notnull,default:current_timestamp"`
}

Store API

The store.Store provides CRUD methods for all models:

// Buckets
store.CreateBucket(ctx, &bucket) error
store.GetBucket(ctx, id) (*Bucket, error)
store.GetBucketByName(ctx, name) (*Bucket, error)
store.ListBuckets(ctx, ...ListOption) ([]*Bucket, error)

// Objects
store.CreateObject(ctx, &obj) error
store.GetObject(ctx, id) (*Object, error)
store.GetObjectByKey(ctx, bucketID, key) (*Object, error)
store.ListObjects(ctx, bucketID, ...ListOption) ([]*Object, error)
store.UpdateObject(ctx, &obj) error
store.SoftDeleteObject(ctx, id) error

// Upload Sessions
store.CreateUploadSession(ctx, &session) error
store.GetUploadSession(ctx, id) (*UploadSession, error)
store.UpdateUploadSession(ctx, &session) error
store.ListExpiredUploads(ctx, before) ([]*UploadSession, error)

// CAS Index
store.PutCASEntry(ctx, &entry) error
store.GetCASEntry(ctx, hash) (*CASEntry, error)
store.IncrementCASRef(ctx, hash) error
store.DecrementCASRef(ctx, hash) error
store.ListUnpinnedCAS(ctx, minRefCount) ([]*CASEntry, error)

// Quotas
store.GetQuota(ctx, tenantKey) (*Quota, error)
store.UpdateQuotaUsage(ctx, tenantKey, deltaBytes, deltaCount) error
store.SetQuota(ctx, &quota) error

List Options

store.ListObjects(ctx, bucketID,
    store.WithPrefix("photos/"),
    store.WithLimit(100),
    store.WithOffset(0),
    store.WithTenantKey("tenant-abc"),
)

Migrations

Migrations run automatically on extension startup (unless disable_migrate: true). They use Grove's modular migration system under the "trove" namespace:

migrate.NewGroup("trove",
    migrate.NewMigration("001", "create trove_buckets", upSQL, downSQL),
    migrate.NewMigration("002", "create trove_objects", upSQL, downSQL),
    migrate.NewMigration("003", "create trove_upload_sessions", upSQL, downSQL),
    migrate.NewMigration("004", "create trove_cas_index", upSQL, downSQL),
    migrate.NewMigration("005", "create trove_quotas", upSQL, downSQL),
)

Tables Created

TablePurposeKey Indexes
trove_bucketsBucket definitionsname (unique), tenant_key
trove_objectsObject metadata(bucket_id, key), tenant_key
trove_upload_sessionsMultipart uploadsexpires_at
trove_cas_indexCAS dedup index(pinned, ref_count)
trove_quotasPer-tenant quotastenant_key (PK)

Multi-Database Support

The store layer works with any Grove-supported database:

DriverBackendNotes
pgdriverPostgreSQLFull feature support, JSONB columns, RETURNING
sqlitedriverSQLiteLightweight, embedded, JSONB via text
mongodriverMongoDBDocument-native, flexible schema

Driver detection is automatic based on the Grove DB's driver name.

On this page