planner

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Nov 10, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package planner compiles the validated GQL AST to parameterized PostgreSQL SQL against the search_index union view (SPEC-04 §6, §8).

M9 (landed): the three-branch MR-6 projection (witness | any-union | asset-all), the mode filter (ModeService default / ModeAsset DISTINCT ON asset_id), tokenized/substring/element-substring matching (==, IN, CONTAINS-ILIKE on strings, CONTAINS @> ARRAY on lists), IN_CIDR (ip::inet <<= cidr), IN_DOMAIN (domain = v OR domain ILIKE '%.' || v), sort rules (NULLS LAST/FIRST per direction; row_id ASC tiebreaker always appended), cursor pagination (disjunctive strict-advancement predicate correct for any primary direction + ASC row_id tiebreaker), limit/timeout wiring (Plan.Timeout carried as a Go duration and applied separately via Plan.ApplyStatementTimeoutSQL — PG's SET LOCAL does not accept a $N placeholder, so the timeout value never appears literally in any SQL text, AC-2.8), parameterized SQL only ($N placeholders, never interpolated user values, AC-2.8), and the explain output (Plan.Explain: mode, branch set, limit, timeout, sort, cursor, projection, SQL, args).

The three branches classify a compiled Plan for EXPLAIN output:

  • witness: a flat per-row predicate evaluated directly against search_index (the common case — port == 443, http.status == 200);
  • any-union: an ANY(host){ body } block compiled to a correlated EXISTS subquery against a fresh alias of search_index (the host's services) — the outer row matches if any inner service row of the same asset_id satisfies body;
  • any-service-inline: an ANY(service){ body } block folded into the outer predicate (search_index is already per-service);
  • asset-all: mode=asset wraps the projection in a WITH page AS (…) DISTINCT ON (asset_id) subquery preferring last_seen DESC, so the result is one row per asset.

A single Plan can carry several branches at once (e.g. a witness predicate ANDed with an any-union, run in mode=asset).

Layering: the planner is a pure compiler. It imports internal/gql/ast only (plus the Go standard library). It does NOT import pgx or any execution package — Plan.ApplyStatementTimeoutSQL returns the parameterized SQL the M10 SearchService runs; the planner itself executes nothing. The planner never imports any collector or egress package (ADR-0010 Search ≠ Scan; AC-3.1 import-graph test).

Index

Constants

View Source
const (
	// CodePlanEmpty is returned when Compile is called with a nil root.
	CodePlanEmpty = "GQL_E_PLAN_EMPTY"

	// CodePlanSortField is returned when the requested sort field is not
	// in the sortable registry (or is a list/bool field, which cannot
	// sort meaningfully).
	CodePlanSortField = "GQL_E_PLAN_SORT_FIELD"

	// CodePlanSortDir is returned when the sort direction is not one of
	// "asc" / "desc" (case-insensitive).
	CodePlanSortDir = "GQL_E_PLAN_SORT_DIR"

	// CodePlanCursor is returned when the cursor cannot be decoded or
	// does not match the current sort field/direction.
	CodePlanCursor = "GQL_E_PLAN_CURSOR"

	// CodePlanLimit is returned when the requested limit is out of range.
	CodePlanLimit = "GQL_E_PLAN_LIMIT"

	// CodePlanTimeout is returned when the requested timeout is out of
	// range (must be > 0 and <= the 30s hard cap, PD-1).
	CodePlanTimeout = "GQL_E_PLAN_TIMEOUT"

	// CodePlanMode is returned when the mode is not ModeService or
	// ModeAsset.
	CodePlanMode = "GQL_E_PLAN_MODE"

	// CodePlanProjection is returned when a requested projection field
	// is not a known search_index column.
	CodePlanProjection = "GQL_E_PLAN_PROJECTION"
)

Stable planner error codes.

View Source
const (
	DefaultLimit = 100
	MaxLimit     = 1000
	MinTimeout   = 100 * time.Millisecond
	MaxTimeout   = 30 * time.Second
)

Defaults and hard caps (PD-1, SPEC-03 §3).

Variables

This section is empty.

Functions

func SortedFieldNames

func SortedFieldNames() []string

SortedFieldNames returns the sortable field set, sorted (used by the search command's help text).

Types

type Branch

type Branch struct {
	Witness    bool // a flat per-row predicate (no quantifier)
	AnyUnion   bool // an ANY(host){…} block compiled to an EXISTS subquery
	AssetAll   bool // mode=asset DISTINCT ON (asset_id) projection
	AnyService bool // an ANY(service){…} block compiled inline (folded to witness)
}

Branch classifies the compiled Plan for EXPLAIN output. A single Plan can carry several branches (e.g., a flat predicate wrapped in mode=asset AND containing an ANY(host){…} subquery).

func CompileWhere

func CompileWhere(root ast.Node) (where string, args []any, branch Branch, err error)

CompileWhere compiles a validated AST into ONLY the WHERE clause over search_index plus its positional args — no SELECT, ORDER BY, or LIMIT. It is the facet engine's entry point (internal/search/facets.go): facet aggregates must filter over exactly the rows the search would return, so they reuse the planner's WHERE compilation verbatim (same operator lowering, same alias qualification, same literal handling). The returned SQL is safe to embed in `... FROM search_index WHERE (<where>) ...`.

func (Branch) String

func (b Branch) String() string

String renders the active branch set, comma-separated.

type CursorPayload

type CursorPayload struct {
	SortField string
	RowID     int64
	// RowKind completes the row key: search_index.row_id is the
	// service id on 'service' rows and the ASSET id on 'asset-only'
	// rows — two independent id sequences that can collide numerically,
	// so (row_kind, row_id) is the true unique row key and the
	// tiebreaker must advance over BOTH.
	RowKind string
	Value   string // stringified sort-key value at the cursor row
	// Null is true when the cursor row's sort value was SQL NULL (the
	// value is "").  NULL rows sort at the ASC-tail / DESC-head (NULLS
	// LAST / NULLS FIRST), so the advancement predicate needs the
	// NULL-aware forms in renderCursorPredicate.
	Null bool
}

CursorPayload is the decoded form of the opaque pagination cursor.

func DecodeCursor

func DecodeCursor(s string) (*CursorPayload, error)

DecodeCursor parses an opaque base64 cursor.

func (CursorPayload) Encode

func (c CursorPayload) Encode() string

Encode renders an opaque base64 cursor from a cursor payload.

type Direction

type Direction int

Direction is a sort direction.

const (
	// DirAsc sorts ascending; DirDesc sorts descending.
	DirAsc Direction = iota
	DirDesc
)

func ParseDirection

func ParseDirection(s string) (Direction, error)

ParseDirection parses "asc" / "desc" (case-insensitive).

func (Direction) String

func (d Direction) String() string

String renders the SQL keyword.

type Error

type Error struct {
	Code   string
	Detail string
	Why    string
	Next   string
}

Error is a planner error.

func (*Error) Error

func (e *Error) Error() string

Error renders per the SPEC-03 §7 style guide.

type Mode

type Mode int

Mode is the search mode (PD-1; default service).

const (
	// ModeService returns one row per (asset, service) pair plus orphan
	// assets — the raw search_index projection.
	ModeService Mode = iota

	// ModeAsset collapses to one row per asset (DISTINCT ON asset_id,
	// preferring the row with the most recent last_seen).
	ModeAsset
)

func ParseMode

func ParseMode(s string) (Mode, error)

ParseMode parses "service" / "asset" (case-insensitive).

func (Mode) String

func (m Mode) String() string

String renders the canonical mode name (used in EXPLAIN).

type Options

type Options struct {
	Mode    Mode
	Sort    []SortSpec // empty = default (last_seen DESC, row_id ASC)
	Limit   int        // 0 = server default (DefaultLimit)
	Timeout time.Duration
	Cursor  string // opaque base64 from a prior Result.NextCursor
	Fields  []string
}

Options controls the compiled Plan.

type Plan

type Plan struct {
	SQL     string
	Args    []any
	Branch  Branch
	Mode    Mode
	Sort    []SortSpec
	Limit   int
	Timeout time.Duration

	// CursorDecoded holds the (sort_key, row_id) decoded from
	// Options.Cursor, for EXPLAIN.  Nil if no cursor was supplied.
	CursorDecoded *CursorPayload

	// ProjectionFields is the field set the SQL projects (after mode
	// collapse if ModeAsset).  Empty = default = all search_index cols.
	ProjectionFields []string
}

Plan is the compiled output.

func Compile

func Compile(root ast.Node, opts Options) (*Plan, error)

func (*Plan) ApplyStatementTimeoutSQL

func (p *Plan) ApplyStatementTimeoutSQL() (string, []any)

ApplyStatementTimeoutSQL returns a parameterized SQL statement that applies Plan.Timeout as a transaction-local statement_timeout when executed in the same transaction as Plan.SQL. M10's SearchService uses this so the timeout value never appears literally in any SQL text (AC-2.8): the value is bound through the args slice.

The returned (sql, args) pair is `SELECT set_config('statement_timeout', $1::text, true)` with the timeout in milliseconds as a string. PG's `SET LOCAL` does not accept a $N placeholder, so set_config() is the parameterized form. Returns ("", nil) when the plan has no timeout.

func (*Plan) Explain

func (p *Plan) Explain() string

Explain renders a human-readable description of the Plan (AST shape, branch classification, mode, sort, cursor, limit, timeout, SQL). Used by `gseer search --explain`.

type SortSpec

type SortSpec struct {
	Field     string
	Direction Direction
}

SortSpec is one sort field + direction.

Jump to

Keyboard shortcuts

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