appdata

package
v0.60.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package appdata is an app's own data: a key-value store per app, kept BESIDE filex and never in filex's own database (docs/APP-PLUGINS-API.md → An app's own data). An app reaches it only through the data_* host functions; it cannot name a table, see one of filex's, or run a query.

Two places it can live:

  • a file per app under the data directory (SQLite, the default). Fast (a point read is single-digit microseconds) and simple, but the data directory is per node (docs/DEPLOYMENT.md → Scaling): behind several filex replicas each node keeps its own, which is why NodeLocal says so;
  • a PostgreSQL database of its own (FILEX_APP_DATA_DSN), which every replica shares. Never filex's own database: an app's rows have no business beside filex's tables, and an operator who points this at filex's DSN is refused.

Every key is in a scope the HOST chooses (`app`, `t:<tenant>`), so an app cannot read another tenant's keys by spelling them. Keys are printable ASCII, so every engine orders and compares them byte for byte.

Index

Constants

View Source
const (
	MaxKeyBytes   = 512
	MaxValueBytes = 64 << 10
	MaxList       = 500
	// DefaultMaxBytes is an app's store when the operator says nothing
	// (FILEX_APP_DATA_MAX_MB).
	DefaultMaxBytes = 256 << 20
)

Limits (the wire contract's, repeated here so the store holds them whoever calls it).

View Source
const AppScope = "app"

AppScope is the scope every tenant shares.

Variables

View Source
var (
	// ErrConflict: a condition did not hold (busy).
	ErrConflict = errors.New("appdata: the condition does not hold")
	// ErrQuota: the app's store is full (too_large).
	ErrQuota = errors.New("appdata: the app's data is at its size limit")
	// ErrCounter: a value written to a counter key, or a counter added to a
	// value key (invalid).
	ErrCounter = errors.New("appdata: a counter key is written by incr only")
	// ErrKey / ErrScope / ErrValue: what was asked cannot be stored (invalid).
	ErrKey   = errors.New("appdata: a key is 1 to 512 printable ASCII characters")
	ErrScope = errors.New("appdata: no such scope")
	ErrValue = errors.New("appdata: a value is valid UTF-8, at most 64 KiB")
	// ErrApp: not an app name.
	ErrApp = errors.New("appdata: not an app name")
)

Errors the host functions answer with their wire codes.

View Source
var ErrSameDatabase = errors.New("appdata: FILEX_APP_DATA_DSN names filex's own database; give apps a database of their own")

ErrSameDatabase: FILEX_APP_DATA_DSN names filex's own database.

Functions

func CheckKey

func CheckKey(key string) error

CheckKey refuses a key that is empty, longer than MaxKeyBytes or holds a byte outside printable ASCII (0x21-0x7E; no space).

func TenantScope

func TenantScope(tenant int64) string

TenantScope is the scope of a tenant's keys (0: the platform's own).

Types

type Cond

type Cond struct {
	// IfAbsent: only when the key does not exist.
	IfAbsent bool
	// IfRev: only when the key is at this revision (0 asks nothing).
	IfRev int64
}

Cond makes a write conditional.

type Entry

type Entry struct {
	Key     string
	Value   string
	Rev     int64
	Counter bool
}

Entry is one key.

type Postgres

type Postgres struct {
	// contains filtered or unexported fields
}

Postgres keeps every app's data in a database of the apps' own.

func OpenPostgres

func OpenPostgres(ctx context.Context, dsn, filexDSN string, maxBytes int64) (*Postgres, error)

OpenPostgres connects and creates the two tables. filexDSN is filex's own database's DSN when it is PostgreSQL ("" otherwise): naming the same database (host, port and database name) is refused.

func (*Postgres) Close

func (p *Postgres) Close() error

func (*Postgres) Delete

func (p *Postgres) Delete(ctx context.Context, app, scope, key string, ifRev int64) (bool, error)

func (*Postgres) Driver

func (p *Postgres) Driver() string

func (*Postgres) Drop

func (p *Postgres) Drop(ctx context.Context, app string) error

func (*Postgres) Get

func (p *Postgres) Get(ctx context.Context, app, scope, key string) (Entry, bool, error)

func (*Postgres) Incr

func (p *Postgres) Incr(ctx context.Context, app, scope, key string, by int64) (int64, error)

func (*Postgres) List

func (p *Postgres) List(ctx context.Context, app, scope, prefix, after string, limit int) ([]Entry, string, error)

func (*Postgres) NodeLocal

func (p *Postgres) NodeLocal() bool

func (*Postgres) Put

func (p *Postgres) Put(ctx context.Context, app, scope, key, value string, c Cond) (int64, error)

func (*Postgres) Usage

func (p *Postgres) Usage(ctx context.Context, app string) (int64, int64, error)

type SQLite

type SQLite struct {
	// contains filtered or unexported fields
}

SQLite keeps every app's data in a file of its own.

func OpenSQLite

func OpenSQLite(dir string, maxBytes int64) (*SQLite, error)

OpenSQLite stores apps' data under dir (created 0700). maxBytes is each app's limit (0: DefaultMaxBytes).

func (*SQLite) Close

func (s *SQLite) Close() error

func (*SQLite) Delete

func (s *SQLite) Delete(ctx context.Context, app, scope, key string, ifRev int64) (bool, error)

func (*SQLite) Driver

func (s *SQLite) Driver() string

func (*SQLite) Drop

func (s *SQLite) Drop(_ context.Context, app string) error

Drop closes the app's file and removes it. The path is built from a validated app name under the store's own directory, never from input.

func (*SQLite) Get

func (s *SQLite) Get(ctx context.Context, app, scope, key string) (Entry, bool, error)

func (*SQLite) Incr

func (s *SQLite) Incr(ctx context.Context, app, scope, key string, by int64) (int64, error)

func (*SQLite) List

func (s *SQLite) List(ctx context.Context, app, scope, prefix, after string, limit int) ([]Entry, string, error)

func (*SQLite) NodeLocal

func (s *SQLite) NodeLocal() bool

func (*SQLite) Put

func (s *SQLite) Put(ctx context.Context, app, scope, key, value string, c Cond) (int64, error)

func (*SQLite) Usage

func (s *SQLite) Usage(ctx context.Context, app string) (int64, int64, error)

type Store

type Store interface {
	Get(ctx context.Context, app, scope, key string) (Entry, bool, error)
	Put(ctx context.Context, app, scope, key, value string, c Cond) (int64, error)
	Delete(ctx context.Context, app, scope, key string, ifRev int64) (bool, error)
	// List answers the keys that start with prefix, after `after`, in byte
	// order, at most limit; next is "" on the last page.
	List(ctx context.Context, app, scope, prefix, after string, limit int) ([]Entry, string, error)
	Incr(ctx context.Context, app, scope, key string, by int64) (int64, error)
	// Usage is how many keys an app keeps and their bytes (keys + values).
	Usage(ctx context.Context, app string) (keys, bytes int64, err error)
	// Drop erases everything an app keeps.
	Drop(ctx context.Context, app string) error
	// Driver is "sqlite" or "postgres"; NodeLocal is true when the data is
	// this node's alone.
	Driver() string
	NodeLocal() bool
	Close() error
}

Store is where every app's data lives.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL