chserver

package
v0.0.17 Latest Latest
Warning

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

Go to latest
Published: Jul 28, 2026 License: MIT Imports: 22 Imported by: 0

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 New

func New(cfg Config) (inst *Engine, err error)

New returns an Engine bound to one endpoint.

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) Endpoint

func (inst *Engine) Endpoint() (endpoint string)

Endpoint reports the server this engine is bound to.

func (*Engine) Kill

func (inst *Engine) Kill(ctx context.Context, runID string) (err error)

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.

Jump to

Keyboard shortcuts

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