Documentation
¶
Overview ¶
Package chserver delivers query results from a ClickHouse server over the synchronous HTTP interface — engine 2 of ADR-0144's three, and the only one that plays all three roles.
A server is the engine with something to say about a run while it runs. It registers the client-minted id in `system.processes` and retains it in `query_log`, so a party that never held the connection can watch the run (queryengine.ObservationI, backed by the E7 poller) and stop it (queryengine.ControlI, by `KILL QUERY`). Both of those address the run by the same id the results are correlated with, which is the point of minting it client-side (R7).
Delivery is a stream of byte frames in the caller's requested FORMAT. Decoding is the consumer's business, deliberately: the engine's job ends at "here are the bytes the server sent, and here is how the run ended".
An engine is one server ¶
An instance is bound to one endpoint, because everything the optional roles do is per-server: `system.processes` is not shared between members, and a `KILL QUERY` only reaches the member that ran the query (R11). Pointing one instance at a load-balanced address would silently observe and cancel on whichever member happened to answer. Construction is cheap — a caller resolving placement per run builds one per run.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseSummary ¶
func ParseSummary(header string) (out queryengine.Summary)
ParseSummary reads the counters ClickHouse reports in `X-ClickHouse-Summary` — a flat JSON object of string-encoded numbers.
The in-band `X-ClickHouse-Progress` header carries the same shape (a prefix of these fields), so one parser serves both the finished run's summary and every live tick.
A header that is absent or malformed yields the zero summary rather than an error. Every field of it means "not reported", and this plane is advisory throughout: a run is not wrong because its counters were unreadable.
Types ¶
type Config ¶
type Config struct {
// Endpoint is the ClickHouse HTTP endpoint of the ONE server this
// engine talks to.
Endpoint string
// User and Password authenticate every request.
User string
Password string
// HTTPClient is the client ordinary requests use; nil means a stock
// one. A request asking for live progress swaps in its own transport
// (see progress.go) and this client is not consulted for it.
HTTPClient *http.Client
// ServesConfined declares that this server may see the plaintext of
// sealed data (ADR-0145 §SD4). Default false: an engine refuses a
// confined run unless the party that built it said otherwise.
//
// This is a discipline gate, not a security boundary — the caller
// asserts it, so it is only as good as the caller. Its value is that
// forgetting is loud and local: a new issuer that never considered
// sealed data gets a refusal naming the reason, rather than a query
// that works until the endpoint moves. The wall a router cannot
// override lives above, at the dispatch seam.
ServesConfined bool
}
Config parameterises an Engine. Endpoint is required.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine delivers results from one ClickHouse server, and cancels runs on it.
func (*Engine) Deliver ¶
func (inst *Engine) Deliver(ctx context.Context, req queryengine.Request) (st queryengine.StreamI, res queryengine.Result, err error)
Deliver POSTs the statement and returns the response body as a frame stream.
The result stream carries the body in chunks and then exactly one terminal frame. A statement the server rejected is a failed terminal, not an error from this call: the run happened and ended badly, which is an outcome the contract already has a shape for.
func (*Engine) Kill ¶
Kill asks the server to stop the run named by runID (R11).
A nil error is not evidence that anything stopped. `KILL QUERY` matching no row is a success, and a run that had already finished, one that never existed, and one this call ended are indistinguishable from here — as is an endpoint that speaks the dialect but has no process list to search, such as the in-process introspection plane, whose one-shot workers are gone before anything could be addressed to them. Terminal truth comes from the result path, never from this.
type ObservationConfig ¶
type ObservationConfig struct {
// Bus receives the ticks. It needs the poller's publish capability
// (queryprogress.PublishFilter).
Bus app.BusI
// Interval is the tick period; zero takes the poller's default. It is
// also the staleness bound — a tick reports what the server said at
// that moment, and the next says nothing until it arrives.
Interval time.Duration
Log zerolog.Logger
}
ObservationConfig parameterises the poller an ObservingEngine carries. Bus is required; the endpoint and credentials come from the engine's own Config, which is the point.
type ObservingEngine ¶
type ObservingEngine struct {
*Engine
// contains filtered or unexported fields
}
ObservingEngine is an Engine whose runs can also be watched by somebody other than the party holding the result path (R8).
func NewObserving ¶
func NewObserving(cfg Config, obs ObservationConfig) (inst *ObservingEngine, err error)
NewObserving returns an engine that delivers, observes and controls one server. The poller it builds watches the same endpoint under the same credentials, so a run this engine issued is a run this engine can observe.
It does not start ticking; call ObservingEngine.Start.
func (*ObservingEngine) Close ¶
func (inst *ObservingEngine) Close() (err error)
Close stops observation and waits for the tick loop to finish. It does not affect delivery.
func (*ObservingEngine) Poller ¶
func (inst *ObservingEngine) Poller() (poller *queryprogress.Poller)
Poller exposes the underlying poller, for a caller that wants to drive the cadence itself (queryprogress.Poller.Tick) or inspect the watch set.
func (*ObservingEngine) Start ¶
func (inst *ObservingEngine) Start()
Start begins observing until Close.
func (*ObservingEngine) Unwatch ¶
func (inst *ObservingEngine) Unwatch(runID string)
Unwatch deregisters a run.
The caller decides when, and the right moment is the terminal frame. This engine will not do it on its own: the only signal it has — the run leaving `system.processes` — cannot tell finished from killed from failed, and acting on it would be inventing an outcome.
func (*ObservingEngine) Watch ¶
func (inst *ObservingEngine) Watch(runID string) (err error)
Watch registers a run for observation. Its ticks are published on queryprogress.Subject(runID), where any holder of the subscribe capability can read them — including a process that did not issue the run.