output

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package output defines the shared response shape every crdb-sql subcommand emits when --output=json, plus the renderer that switches between text and JSON.

The Envelope describes the analysis context — which tier produced the result, which parser version was used, whether a cluster was reached — and carries a uniform Errors list for agent consumption. Subcommand- specific payload lives in Data so the envelope itself never needs to learn about new commands. The schema is derived from the JSON error-output example in docs/design_doc.md (search "Error output"); fields like statement_type that are subcommand-specific live in Data rather than in the envelope itself.

Index

Constants

View Source
const (
	// CodeTargetVersionMismatch is emitted by
	// output.VersionMismatchWarning when the user-declared target
	// version differs from the bundled parser version at the
	// MAJOR.MINOR level.
	CodeTargetVersionMismatch = "target_version_mismatch"

	// CodeFeatureNotYetIntroduced is emitted when the AST inspector
	// recognizes a CockroachDB SQL feature whose Introduced version
	// (per the version registry) is newer than the user's declared
	// target version. The accompanying Error.Context carries the
	// feature_tag, feature_name, introduced, and target keys so
	// agents can branch programmatically without parsing the
	// human-readable Message.
	CodeFeatureNotYetIntroduced = "feature_not_yet_introduced"

	// CodeSafetyViolation is emitted by the Tier 3 safety allowlist
	// (internal/safety) when a statement is rejected before any
	// cluster round-trip. The accompanying Error.Context carries the
	// tag (e.g. "DROP TABLE"), mode (e.g. "read_only"), operation
	// (e.g. "explain"), and reason keys so agents can branch
	// programmatically — for example, prompt the user to escalate the
	// safety mode — without parsing the human-readable Message.
	CodeSafetyViolation = "safety_violation"

	// CodeInputPreprocessed is emitted (severity WARNING) by the MCP
	// and CLI handlers whenever sqlformat.StripShellPromptsWithMap
	// removed cockroach sql REPL prompt bytes from the caller's input
	// before parsing. The accompanying Error.Context carries the
	// bytes_removed integer so agents can confirm the modification
	// programmatically.
	CodeInputPreprocessed = "input_preprocessed"
)

Stable Error.Code values. Agents key off these strings, so renames are breaking changes; new codes are added by appending here. Codes are namespaced by the producing concept (e.g. "target_version_*" for envelope-level version-handling diagnostics).

View Source
const (
	// ContextPositionOmittedReason is the Error.Context key set by the
	// execute handlers when they intentionally drop a cluster error's
	// Position because no honest translation is possible. The value is
	// one of the Reason* constants below; agents should branch on the
	// value, not parse the human-readable Message.
	ContextPositionOmittedReason = "position_omitted_reason"

	// ReasonLimitInjectionRewroteSQL is set as the value of
	// ContextPositionOmittedReason when safety.MaybeInjectLimitParsed
	// re-serialized the AST through tree.AsStringWithFlags. The
	// resulting `rewritten` SQL is canonicalized — comments dropped,
	// whitespace normalized — so pgwire positions index into a buffer
	// the strip map cannot translate. Position is dropped rather than
	// reported in a frame the caller cannot reconstruct.
	ReasonLimitInjectionRewroteSQL = "limit_injection_rewrote_sql"
)

Wire-contract Context keys and values. Promoted here so the MCP and CLI sides cannot drift via a typo in one place.

Variables

View Source
var ErrRendered = errors.New("output: error already rendered as envelope")

ErrRendered is returned by Renderer.RenderError after it has emitted an envelope describing the failure. cmd/crdb-sql/main.go uses errors.Is to recognize this sentinel and suppress its default "Error: ..." stderr print, while still exiting with a non-zero status. This preserves the JSON-mode contract that stdout is the single source of truth and keeps agents from having to parse stderr.

Functions

func ValidateTargetVersion

func ValidateTargetVersion(s string) (string, error)

ValidateTargetVersion parses a user-supplied CockroachDB target version string and returns its canonical form. The accepted shapes are MAJOR.MINOR and MAJOR.MINOR.PATCH, optionally prefixed with a single "v" (so "v25.4.0" and "25.4.0" are both accepted). The canonical form differs from the input in exactly one way: a leading "v" is stripped. Patch presence/absence and exact digits are preserved verbatim, so "25.4" and "25.4.0" remain distinct strings even though they represent the same minor release.

Surrounding whitespace is trimmed before validation so the CLI and MCP entry points behave the same when callers paste a value.

An empty input (after trimming) returns an error; callers that treat empty as "no target version supplied" should check for the empty string before invoking this helper.

Types

type ConnectionStatus

type ConnectionStatus string

ConnectionStatus reports whether the command reached a live cluster. Commands that never connect (version, parse, format) report ConnectionDisconnected.

const (
	ConnectionDisconnected ConnectionStatus = "disconnected"
	ConnectionConnected    ConnectionStatus = "connected"
)

ConnectionStatus values.

type Envelope

type Envelope struct {
	Tier             Tier             `json:"tier,omitempty"`
	ParserVersion    string           `json:"parser_version"`
	TargetVersion    string           `json:"target_version,omitempty"`
	ConnectionStatus ConnectionStatus `json:"connection_status"`
	Errors           []Error          `json:"errors,omitempty"`
	Data             json.RawMessage  `json:"data,omitempty"`
}

Envelope is the response contract every crdb-sql subcommand returns under --output=json.

Lifecycle: built fresh by the subcommand's RunE, marshalled once by Renderer.Render, then discarded. No long-lived state.

Field discipline:

  • Tier uses omitempty so commands without tier semantics (e.g. version) omit the field rather than emitting a misleading empty string. This relies on TierUnset being the empty-string zero value.
  • ParserVersion and ConnectionStatus are always emitted so agents can rely on their presence.
  • TargetVersion uses omitempty so the field is absent when the user did not pass --target-version (or the equivalent MCP parameter). This preserves byte-identical output for the unflagged path.
  • Errors uses omitempty so a clean run produces no errors key rather than a noisy "errors": null.
  • Data is json.RawMessage so each subcommand controls its own payload marshalling and this package never depends on subcommand types.

type Error

type Error struct {
	Code        string         `json:"code"`
	Severity    Severity       `json:"severity"`
	Message     string         `json:"message"`
	Position    *Position      `json:"position,omitempty"`
	Category    string         `json:"category,omitempty"`
	Context     map[string]any `json:"context,omitempty"`
	Suggestions []Suggestion   `json:"suggestions,omitempty"`
}

Error is the per-error schema agents consume; the fields are derived from the JSON example in docs/design_doc.md (search "Error output"). Code, Severity, and Message are required (every error has them); Position, Category, Context, and Suggestions are optional so early subcommands can populate only what they have. Richer enrichment lands with later issues.

func VersionMismatchWarning

func VersionMismatchWarning(parserVersion, targetVersion string) (Error, bool)

VersionMismatchWarning compares the parser version compiled into the binary against the user-supplied target version and, when the major or minor components differ, returns a warning Error suitable for appending to Envelope.Errors. The bool return indicates whether a warning was produced; callers should append only when it is true.

targetVersion is expected to already be in canonical form (post-ValidateTargetVersion). The silent-skip path is meant to tolerate an unresolvable parserVersion; a parse failure on targetVersion would indicate a caller contract violation but is also tolerated rather than panicked on, since it never makes sense to fail a SQL operation just because the version-mismatch check could not run.

Comparison is on MAJOR.MINOR only — patch-level differences are noise for users who care about feature compatibility, not bug-fix releases. If either input cannot be parsed (e.g. parserVersion resolved to "unknown" in a development build), no warning is produced; callers should not punish users for an unresolvable bundled version.

Known limitation (tracked separately): parserVersion today is the cockroachdb-parser Go-module version (e.g. "v0.26.2"), not the CockroachDB release version it implements. Until the parser ships a CRDB-version mapping, a user supplying --target-version 25.4.0 will see "parser is v0.26 but target is v25.4" — technically correct under the implemented contract, but semantically a false positive. Stage 2/3 (#83/#85) will revisit how versions compare.

type Format

type Format string

Format selects how a subcommand serializes its result.

text — human-readable, subcommand-defined layout (default).
json — Envelope marshalled as indented JSON.
const (
	FormatText Format = "text"
	FormatJSON Format = "json"
)

Format values.

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat validates a user-supplied --output value. Only "text" and "json" are accepted; any other value (including empty) produces an error that names the valid choices, so cobra's surfaced message is actionable.

type Position

type Position struct {
	Line       int `json:"line"`
	Column     int `json:"column"`
	ByteOffset int `json:"byte_offset"`
}

Position is a 1-based line/column with a 0-based byte offset, matching the convention used by the CockroachDB parser.

type Range

type Range struct {
	Start int `json:"start"`
	End   int `json:"end"`
}

Range is a half-open byte range [Start, End) into the original input.

type Renderer

type Renderer struct {
	Format Format
	Out    io.Writer
}

Renderer writes a subcommand's result in the selected format.

For FormatText, textFn owns the output: it receives the configured writer and produces whatever lines the subcommand wants (the envelope is ignored). For FormatJSON, the envelope is marshalled as indented JSON so piped output stays human-readable; agents that want compact form can pipe through `jq -c`. A trailing newline is appended so the output is a well-formed line on terminals.

EPIPE handling: write failures with errno EPIPE are suppressed (return nil) because they are the normal outcome when a downstream consumer closes stdout early (e.g. piping into `head -n1`). Surfacing them as a non-zero exit with a "broken pipe" message is hostile for a tool meant to be piped, and partial output is acceptable. Marshal errors and the unsupported-format error are NOT subject to EPIPE filtering — they are propagated unchanged. On Windows the native broken-pipe error has a different value (ERROR_BROKEN_PIPE), so this check would not fire there; Windows is not a supported target, so we don't add a separate branch.

func (Renderer) Render

func (r Renderer) Render(env Envelope, textFn func(io.Writer) error) error

Render dispatches on r.Format. textFn must be non-nil for FormatText; it is ignored for FormatJSON.

textFn contract (FormatText only): Render passes textFn's returned error through suppressEPIPE, so any error that wraps syscall.EPIPE will be silently dropped — even if it did not originate from a write to r.Out. To avoid accidental suppression, textFn should perform only writes (fmt.Fprintf, w.Write, etc.) and return their errors directly. Pre-write work (formatting, lookups) should happen before calling Render so that textFn's error space is limited to write failures.

func (Renderer) RenderError

func (r Renderer) RenderError(env Envelope, failure error) error

RenderError is the JSON-mode error path. It appends a single Error describing failure to env, renders the envelope, and returns ErrRendered so the caller (and cmd/crdb-sql/main.go) know the failure has already been surfaced as structured output and should not be reprinted to stderr. In text mode it returns failure unchanged so cobra's existing "Error: ..." path runs.

The envelope's Data field is cleared because the partial result that motivated env (parser version, connection status) is still meaningful context, but any subcommand payload would be misleading on the error path. ParserVersion and ConnectionStatus may be empty if the failure happened before they were resolved; that is acceptable, since the errors entry is the load-bearing field for agents in this case.

If marshalling the error envelope itself fails, both the original failure and the marshal/write error are joined (via errors.Join) so cmd/crdb-sql/main.go's stderr fallback shows both causes. The joined error is NOT wrapped in ErrRendered so cmd/crdb-sql/main.go prints it.

EPIPE edge case: if the downstream consumer has already closed the pipe, Render succeeds (EPIPE is suppressed) and RenderError returns ErrRendered even though nothing reached stdout. This is intentional: the consumer abandoned the pipe, so no output channel remains and the best we can do is exit non-zero.

func (Renderer) RenderErrorEntry

func (r Renderer) RenderErrorEntry(env Envelope, failure error, entry Error) error

RenderErrorEntry is the structured-error variant of RenderError for callers that have already constructed an Error themselves. The supplied entry is appended verbatim to env.Errors instead of the generic internal_error shape RenderError synthesizes; this lets callers preserve enriched fields (e.g. SQLSTATE Code, Category, Position) that the generic synthesis would not populate.

failure is still required so the text-mode return path stays consistent: text mode bypasses the envelope and returns failure unchanged. entry.Message and failure.Error() are not required to match — JSON consumers see entry, text consumers see failure. All other contract points (ErrRendered sentinel, Data clearing, append semantics on pre-existing errors, errors.Join on render failure) match RenderError.

type Severity

type Severity string

Severity is the severity level of a structured Error. Values use the PostgreSQL frontend/backend protocol severity strings (uppercase) so wire output matches what agents see from a live cluster. Defining the set as named constants prevents subcommands from drifting between "ERROR"/"error"/"Error" as more error producers come online.

const (
	SeverityError   Severity = "ERROR"
	SeverityWarning Severity = "WARNING"
	SeverityNotice  Severity = "NOTICE"
	SeverityFatal   Severity = "FATAL"
	SeverityPanic   Severity = "PANIC"
)

Severity values.

type Suggestion

type Suggestion struct {
	Replacement string  `json:"replacement"`
	Range       Range   `json:"range"`
	Confidence  float64 `json:"confidence"`
	Reason      string  `json:"reason"`
}

Suggestion is a structured fix proposal: replace bytes [Range.Start, Range.End) with Replacement. Confidence is in [0, 1]; Reason is a machine-readable label (e.g. "levenshtein_distance_1").

type Tier

type Tier string

Tier identifies which analysis tier produced a response. The names and order match the "Three analysis tiers" section in docs/design_doc.md:

zero_config — Tier 1, expression type checking (zero-config).
schema_file — Tier 2, name resolution (requires schema files).
connected   — Tier 3, connected validation.

TierUnset is the zero value and indicates the command has no tier semantics (e.g. version).

const (
	TierUnset      Tier = ""
	TierZeroConfig Tier = "zero_config"
	TierSchemaFile Tier = "schema_file"
	TierConnected  Tier = "connected"
)

Tier values.

Jump to

Keyboard shortcuts

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