Documentation
¶
Overview ¶
Package recordresults serves record streams through the profile engine: one read-only `sql` profile per result type, over the sqlite index a recordstore.Indexer keeps caught up, addressed by a stream param.
One profile per type rather than per stream is the point. Every profile is a catalog entry, an OpenAPI path and a sidebar item, so a profile per stream would grow all three with every capture; a stream param grows none of them. Paging, column filters, filter value lookups, sort and export are the engine's own, applied to the index by SQL.
Index ¶
- Constants
- func RegisterResultType[T any](registry *Registry, resultType ResultType[T]) error
- type OpenOptions
- type RegisteredResultType
- type Registry
- func (r *Registry) BeforeExecute(ctx context.Context, reads []profiles.ReadRequest) (func(), error)
- func (r *Registry) BeforeRead(ctx context.Context, p query.Profile, params map[string]any) (func(), error)
- func (r *Registry) Close() error
- func (r *Registry) Delete(context.Context, string) error
- func (r *Registry) Get(_ context.Context, name string) (query.Profile, error)
- func (r *Registry) IsVirtual(name string) bool
- func (r *Registry) List(context.Context) ([]query.Profile, error)
- func (r *Registry) Peek(ctx context.Context, name string) (query.Profile, error)
- func (r *Registry) ResolveConnection(reference string) (*models.Connection, error)
- func (r *Registry) ResultTypes() []RegisteredResultType
- func (r *Registry) Save(context.Context, query.Profile) error
- func (r *Registry) Update(context.Context, string, query.Profile, profiles.UpdateOptions) error
- type RegistryOptions
- type ResultType
- type Results
Constants ¶
const FollowRecheckInterval = 5 * time.Second
FollowRecheckInterval is how often a follow confirms, while nothing is appended, that its stream still exists. It bounds how long a follow of an expired or removed stream runs on; rows appended through Results.Backend arrive without waiting for it.
const MaxExportRows = 1_000_000
MaxExportRows is where an all-row export of a result stops. It is well above the engine default because a capture is read whole far more often than a table is: a trace of a busy run is tens of thousands of rows.
const ( // ProviderType is the provider a followable result type's profile reads // through (ResultType.Follow). It pages, filters, looks values up and exports // exactly as the sqlite provider it delegates to, and it also streams: a // session started with follow=true tails the stream it names. ProviderType = "recordstore" )
Variables ¶
This section is empty.
Functions ¶
func RegisterResultType ¶
func RegisterResultType[T any](registry *Registry, resultType ResultType[T]) error
RegisterResultType declares T's kind: its columns (reflected through query.ColumnsFor) become the kind's schema and index table, and a profile <prefix>/<kind> serves its streams.
Types ¶
type OpenOptions ¶
type OpenOptions struct {
// Prefix and ConnectionName name the result profiles and the index
// connection, as RegistryOptions does.
Prefix string
ConnectionName string
// Settings say where the files live and how long a stream is kept. With no
// Source, Settings.Backend is the local store to open, sqlite or ndjson —
// resolve it first (Settings.Resolve).
Settings recordstore.Settings
// Source is a backend the caller routes itself, typically a
// recordstore.Router over a kv store per tenant, or nil to open
// Settings.Backend locally. A Source is always mirrored into a derived
// index of its own, never into a file a route could also write, so one
// route's streams cannot be read through another. Open owns Source: Close,
// or a failed Open, closes it.
Source recordstore.Backend
// Schemas is the kind catalog the index and Register share. Pass one when
// Source opens stores of its own that need it — a route's local sqlite
// file resolves kinds the registry declares; nil makes a fresh one.
Schemas *recordstore.Schemas
// Register declares the result types the registry serves.
Register func(*Registry) error
}
OpenOptions configure Open.
type RegisteredResultType ¶
type RegisteredResultType struct {
Kind string `json:"kind"`
Title string `json:"title"`
Profile string `json:"profile"`
}
RegisteredResultType is one registered result type as a caller finds it.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is the result types a server serves: a profiles.VirtualStore of their profiles, the resolver of the index connection those profiles read, and the BeforeExecute hook that catches the index up first.
func NewRegistry ¶
func NewRegistry(options RegistryOptions) (*Registry, error)
NewRegistry validates options and returns an empty registry.
func (*Registry) BeforeExecute ¶
BeforeExecute is the profiles.BeforeExecuteFunc for result profiles: it catches every requested stream up, then holds one lease across the whole read batch. Revalidation under that lease closes the gap in which a sweep can remove an index stream after Ensure returns but before its query starts. A stream that does not exist, or that holds another result type, is profiles.ErrProfileDataNotFound — an empty page would say "nothing matched" about a stream nobody wrote. Every other profile passes through untouched.
func (*Registry) BeforeRead ¶
func (r *Registry) BeforeRead(ctx context.Context, p query.Profile, params map[string]any) (func(), error)
BeforeRead is the query.RegistryOptions.BeforeRead hook for a session registry serving result profiles. A host mounts the sessions API over result profiles as:
registry := query.NewSessionRegistry(query.RegistryOptions{BeforeRead: results.Registry.BeforeRead})
service, err := sessions.New(sessions.Options{Profiles: ..., Context: ..., Registry: registry})
handler, err := service.Handler("/api/v1", profileHandler)
where Profiles overlays results.Registry on the profile store and Context carries results.Registry.ResolveConnection, as for the profile service.
It catches the index up and checks the stream exactly as BeforeExecute does for a page. A top session keeps the lease for its sample. A trace — a follow — gives it back at once: it reads for as long as it lasts, a lease held that long would hold every append off, and the follow provider takes its own lease around each read.
func (*Registry) Close ¶
Close stops the registry answering follows. It closes nothing it was given: the index and source belong to whoever opened them.
func (*Registry) ResolveConnection ¶
func (r *Registry) ResolveConnection(reference string) (*models.Connection, error)
ResolveConnection is the dbcontext.ConnectionResolver for the index connection. Any other reference is not the registry's to answer, so it reports none and resolution carries on.
func (*Registry) ResultTypes ¶
func (r *Registry) ResultTypes() []RegisteredResultType
ResultTypes lists the registered result types by kind.
type RegistryOptions ¶
type RegistryOptions struct {
// Prefix names every result profile <prefix>/<kind>, and namespaces the
// index connection.
Prefix string
// Schemas is the kind catalog Index resolves kinds through.
// RegisterResultType fills it.
Schemas *recordstore.Schemas
// Index is the sqlite file result profiles read. It must have been opened
// with Schemas.Kind as its schema.
Index *sqlite.Backend
// Source is the authoritative backend result streams are written to. The
// registry constructs the Indexer that mirrors it into Index, so preparation
// and profile reads cannot accidentally name different indexes. A result
// type that follows its streams (ResultType.Follow) needs a
// *recordstore.Notifier here, which captures must append through.
Source recordstore.Backend
// ConnectionName names the index's virtual connection,
// connection://<prefix>/<name>.
ConnectionName string
}
RegistryOptions configure a Registry.
type ResultType ¶
type ResultType[T any] struct { // Kind is the stream kind, and the last segment of the profile name. Kind string // Title is the human name of the result type. Title string // TimeColumn, when set, names T's datetime column: it becomes the table's // timestamp, the rows are ordered newest first, and the profile takes a // from/to time window over it in place of the column's own filter. Without // it rows are in seq order and there is no time window. TimeColumn string // DefaultFrom is where the time window starts when a request names no // from — date math such as now-12h, or RFC3339. Empty leaves the window // open, so a request with no from reads the whole stream. It needs a // TimeColumn. DefaultFrom string // KeyColumn, when set, names T's string column identifying a row within a // stream: a stream holds each key once, and appending a row whose key it // already holds skips the row (recordstore.KindOptions.Key). KeyColumn string // Retention says how long a stream of the type keeps its rows // (recordstore.KindOptions.Retention). Retention recordstore.Retention // Follow lets a session tail the type's streams: its profile reads through // the ProviderType provider, which streams each appended row, rather than // plain sqlite. Only a type whose rows each stand alone should follow — one // listed as the latest row per id would stream superseded rows. Follow bool }
ResultType declares one kind of record a stream holds, and the profile that serves it.
type Results ¶
type Results struct {
// Backend is what a capture appends its rows to. Appending through it wakes
// every session following the stream.
Backend *recordstore.Notifier
// Registry serves the result types over the index.
Registry *Registry
// contains filtered or unexported fields
}
Results is an open result store: the backend captures append to, and the registry that serves what they appended through the profile engine.
func Open ¶
func Open(options OpenOptions) (*Results, error)
Open opens the store options describe:
Source Settings.Backend backend index nil sqlite records.sqlite the same file nil ndjson ndjson/ index.sqlite, derived a Router (any) the Router index.sqlite, derived