filter

package
v0.30.0 Latest Latest
Warning

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

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 9 Imported by: 0

README

Memo Filter Engine

This package houses the memo-only filter engine that turns standard CEL syntax into SQL fragments for the subset of expressions supported by the memo schema. The engine follows a three phase pipeline inspired by systems such as Calcite or Prisma:

  1. Parsing – CEL expressions are parsed with cel-go and validated against the memo-specific environment declared in schema.go. Only fields that exist in the schema can surface in the filter, and non-standard legacy coercions are rejected.
  2. Normalization – the raw CEL AST is converted into an intermediate representation (IR) defined in ir.go. The IR is a dialect-agnostic tree of conditions (logical operators, comparisons, list membership, etc.). This step enforces schema rules (e.g. operator compatibility, type checks).
  3. Rendering – the renderer in render.go walks the IR and produces a SQL fragment plus placeholder arguments tailored to a target dialect (sqlite, mysql, or postgres). Dialect differences such as JSON access, boolean semantics, placeholders, and LIKE vs ILIKE are encapsulated in renderer helpers.

The entry point is filter.DefaultEngine() from engine.go. It lazily constructs an Engine configured with the memo schema and exposes:

engine, _ := filter.DefaultEngine()
stmt, _ := engine.CompileToStatement(ctx, `has_task_list && visibility == "PUBLIC"`, filter.RenderOptions{
	Dialect: filter.DialectPostgres,
})
// stmt.SQL  -> "((memo.payload->'property'->>'hasTaskList')::boolean IS TRUE AND memo.visibility = $1)"
// stmt.Args -> ["PUBLIC"]

Core Files

File Responsibility
schema.go Declares memo fields, their types, backing columns, CEL environment options
ir.go IR node definitions used across the pipeline
parser.go Converts CEL Expr into IR while applying schema validation
render.go Translates IR into SQL, handling dialect-specific behavior
engine.go Glue between the phases; exposes Compile, CompileToStatement, and DefaultEngine
helpers.go Convenience helpers for store integration (appending conditions)

SQL Generation Notes

  • Placeholders? is used for SQLite/MySQL, $n for Postgres. The renderer tracks offsets to compose queries with pre-existing arguments.
  • JSON Fields — Memo metadata lives in memo.payload. The renderer handles JSON_EXTRACT/json_extract/->/->> variations and boolean coercion.
  • Time Fieldscreated_ts, updated_ts, and attachment create_time are CEL timestamp values. Express instants with the now variable, duration("…") (e.g. created_ts >= now - duration("24h")), or timestamp("2006-01-02T15:04:05Z") / timestamp(<epoch-seconds>). These fold to epoch seconds at compile time — now is frozen once per compile (injectable for tests via the engine clock) — so the backing columns stay unchanged.
  • Tag Operationstag in [...] and "tag" in tags become JSON array predicates. SQLite uses LIKE patterns, MySQL uses JSON_CONTAINS, and Postgres uses @>.
  • Boolean Flags — Fields such as has_task_list render as IS TRUE equality checks, or comparisons against CAST('true' AS JSON) depending on the dialect.
  • String Matchingcontent.contains(x), content.startsWith(x), and content.endsWith(x) render as case-insensitive LIKE/ILIKE with LIKE metacharacters (%, _, \) escaped. Available on scalar string fields whose schema sets SupportsContains (memo content; attachment filename, mime_type).
  • Regexfield.matches("pattern") renders to ~ (Postgres) or REGEXP (MySQL/SQLite). SQLite uses a Go-backed regexp function registered in store/db/sqlite/functions.go. Patterns are validated at compile time against Go's RE2 via cel.ValidateRegexLiterals(). Caveat: regex syntax differs per engine (Go RE2 on SQLite, POSIX ERE on Postgres, ICU on MySQL 8.0+), so engine-specific patterns may not be portable.
  • Tag all() / exists_one()tags.all(t, <pred>) matches only non-empty tag sets where every element satisfies the predicate; tags.exists_one(t, <pred>) matches when exactly one element does (COUNT(...) = 1). Both iterate per-element (json_each / jsonb_array_elements_text / JSON_TABLE).
  • Timestamp Accessorscreated_ts.getFullYear(), getMonth(), getDate(), getDayOfMonth(), getDayOfWeek(), getDayOfYear(), getHours(), getMinutes(), getSeconds() render to date-part extraction (strftime / EXTRACT / YEAR/MONTH/…). Results are normalized to CEL's base (0-based month, 0-based day-of-week with 0 = Sunday). The same accessors on now fold to literal date parts of the frozen evaluation time (UTC), so saved filters like created_ts.getMonth() == now.getMonth() && created_ts.getDate() == now.getDate() ("on this day") re-resolve on every compile. Extraction is UTC on SQLite/Postgres (epoch columns); on MySQL the TIMESTAMP column is read in the session time zone. A timezone argument is not supported.
  • Set Operationsext.Sets(): sets.contains(tags, [...]), sets.intersects(tags, [...]), and sets.equivalent(tags, [...]) desugar to exact-membership checks (AND / OR of "v" in tags); equivalent adds a size(tags) length check (relies on tags being a set).
  • size()size(tags) renders to JSON array length; size(content) (and other string fields) render to LENGTH / CHAR_LENGTH (MySQL) for code-point counts.
  • Arithmetic+, -, *, /, % constant-fold on literal/now/duration operands (division and modulo guard against a zero divisor).

Typical Integration

  1. Fetch the engine with filter.DefaultEngine().
  2. Call CompileToStatement using the appropriate dialect enum.
  3. Append the emitted SQL fragment/args to the existing WHERE clause.
  4. Execute the resulting query through the store driver.

The helpers.AppendConditions helper encapsulates steps 2–3 when a driver needs to process an array of filters.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AppendConditions

func AppendConditions(ctx context.Context, engine *Engine, filters []string, dialect DialectName, where *[]string, args *[]any) error

AppendConditions compiles the provided filters and appends the resulting SQL fragments and args.

Types

type Column

type Column struct {
	Table string
	Name  string
}

Column identifies the backing table column.

type ComparisonCondition

type ComparisonCondition struct {
	Left     ValueExpr
	Operator ComparisonOperator
	Right    ValueExpr
}

ComparisonCondition represents a binary comparison.

type ComparisonOperator

type ComparisonOperator string

ComparisonOperator lists supported comparison operators.

const (
	CompareEq  ComparisonOperator = "="
	CompareNeq ComparisonOperator = "!="
	CompareLt  ComparisonOperator = "<"
	CompareLte ComparisonOperator = "<="
	CompareGt  ComparisonOperator = ">"
	CompareGte ComparisonOperator = ">="
)

type ComprehensionKind

type ComprehensionKind string

ComprehensionKind enumerates the types of list comprehensions.

const (
	ComprehensionExists    ComprehensionKind = "exists"
	ComprehensionAll       ComprehensionKind = "all"
	ComprehensionExistsOne ComprehensionKind = "exists_one"
)

type Condition

type Condition interface {
	// contains filtered or unexported methods
}

Condition represents a boolean expression derived from the CEL filter.

type ConstantCondition

type ConstantCondition struct {
	Value bool
}

ConstantCondition captures a literal boolean outcome.

type ContainsPredicate

type ContainsPredicate struct {
	Substring string
}

ContainsPredicate represents t.contains("substring").

type DialectName

type DialectName string

DialectName enumerates supported SQL dialects.

const (
	DialectSQLite   DialectName = "sqlite"
	DialectMySQL    DialectName = "mysql"
	DialectPostgres DialectName = "postgres"
)

type ElementInCondition

type ElementInCondition struct {
	Element ValueExpr
	Field   string
}

ElementInCondition represents the CEL syntax `"value" in field`.

type EndsWithPredicate

type EndsWithPredicate struct {
	Suffix string
}

EndsWithPredicate represents t.endsWith("suffix").

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine parses CEL filters into a dialect-agnostic condition tree.

func DefaultAttachmentEngine

func DefaultAttachmentEngine() (*Engine, error)

DefaultAttachmentEngine returns the process-wide attachment filter engine.

func DefaultEngine

func DefaultEngine() (*Engine, error)

DefaultEngine returns the process-wide memo filter engine.

func NewEngine

func NewEngine(schema Schema) (*Engine, error)

NewEngine builds a new Engine for the provided schema.

func (*Engine) Compile

func (e *Engine) Compile(_ context.Context, filter string) (*Program, error)

Compile parses the filter string into an executable program.

func (*Engine) CompileToStatement

func (e *Engine) CompileToStatement(ctx context.Context, filter string, opts RenderOptions) (Statement, error)

CompileToStatement compiles and renders the filter in a single step.

type EqualsPredicate

type EqualsPredicate struct {
	Value string
}

EqualsPredicate represents t == "value".

type Field

type Field struct {
	Name                 string
	Kind                 FieldKind
	Type                 FieldType
	Column               Column
	JSONPath             []string
	AliasFor             string
	SupportsContains     bool
	Expressions          map[DialectName]string
	AllowedComparisonOps map[ComparisonOperator]bool
}

Field captures the schema metadata for an exposed CEL identifier.

type FieldAccessorValue added in v0.30.0

type FieldAccessorValue struct {
	Field    string
	Accessor string // e.g. "getFullYear", "getMonth"
}

FieldAccessorValue captures a CEL timestamp accessor on a field, such as created_ts.getMonth(). It renders to a dialect-specific date-part extraction.

type FieldKind

type FieldKind string

FieldKind describes how a field is stored.

const (
	FieldKindScalar       FieldKind = "scalar"
	FieldKindBoolColumn   FieldKind = "bool_column"
	FieldKindJSONBool     FieldKind = "json_bool"
	FieldKindJSONList     FieldKind = "json_list"
	FieldKindVirtualAlias FieldKind = "virtual_alias"
)

type FieldPredicateCondition

type FieldPredicateCondition struct {
	Field string
}

FieldPredicateCondition asserts that a field evaluates to true.

type FieldRef

type FieldRef struct {
	Name string
}

FieldRef references a named schema field.

type FieldType

type FieldType string

FieldType represents the logical type of a field.

const (
	FieldTypeString    FieldType = "string"
	FieldTypeInt       FieldType = "int"
	FieldTypeBool      FieldType = "bool"
	FieldTypeTimestamp FieldType = "timestamp"
)

type FunctionValue

type FunctionValue struct {
	Name string
	Args []ValueExpr
}

FunctionValue captures simple function calls like size(tags).

type InCondition

type InCondition struct {
	Left   ValueExpr
	Values []ValueExpr
}

InCondition represents an IN predicate with literal list values.

type ListComprehensionCondition

type ListComprehensionCondition struct {
	Kind      ComprehensionKind
	Field     string        // The list field to iterate over (e.g., "tags")
	IterVar   string        // The iteration variable name (e.g., "t")
	Predicate PredicateExpr // The predicate to evaluate for each element
}

ListComprehensionCondition represents CEL macros like exists(), all(), filter().

type LiteralValue

type LiteralValue struct {
	Value interface{}
}

LiteralValue holds a literal scalar.

type LogicalCondition

type LogicalCondition struct {
	Operator LogicalOperator
	Left     Condition
	Right    Condition
}

LogicalCondition composes two conditions with a logical operator.

type LogicalOperator

type LogicalOperator string

LogicalOperator enumerates the supported logical operators.

const (
	LogicalAnd LogicalOperator = "AND"
	LogicalOr  LogicalOperator = "OR"
)

type NotCondition

type NotCondition struct {
	Expr Condition
}

NotCondition negates a child condition.

type PredicateExpr

type PredicateExpr interface {
	// contains filtered or unexported methods
}

PredicateExpr represents predicates used in comprehensions.

type Program

type Program struct {
	// contains filtered or unexported fields
}

Program stores a compiled filter condition.

func (*Program) ConditionTree

func (p *Program) ConditionTree() Condition

ConditionTree exposes the underlying condition tree.

func (*Program) Render

func (p *Program) Render(opts RenderOptions) (Statement, error)

Render converts the program into a dialect-specific SQL fragment.

type RegexCondition added in v0.30.0

type RegexCondition struct {
	Field   string
	Pattern string
}

RegexCondition models field.matches("pattern") on a string field.

type RenderOptions

type RenderOptions struct {
	Dialect           DialectName
	PlaceholderOffset int
	DisableNullChecks bool
}

RenderOptions configure SQL rendering.

type Schema

type Schema struct {
	Name       string
	Fields     map[string]Field
	EnvOptions []cel.EnvOption
}

Schema collects CEL environment options and field metadata.

func NewAttachmentSchema

func NewAttachmentSchema() Schema

NewAttachmentSchema constructs the attachment filter schema and CEL environment.

func NewSchema

func NewSchema() Schema

NewSchema constructs the memo filter schema and CEL environment.

func (Schema) Field

func (s Schema) Field(name string) (Field, bool)

Field returns the field metadata if present.

func (Schema) ResolveAlias

func (s Schema) ResolveAlias(name string) (Field, bool)

ResolveAlias resolves a virtual alias to its target field.

type StartsWithPredicate

type StartsWithPredicate struct {
	Prefix string
}

StartsWithPredicate represents t.startsWith("prefix").

type Statement

type Statement struct {
	SQL  string
	Args []any
}

Statement contains the rendered SQL fragment and its args.

type TextMatchCondition added in v0.30.0

type TextMatchCondition struct {
	Field string
	Mode  TextMatchMode
	Value string
}

TextMatchCondition models a case-insensitive LIKE match on a scalar string field (content.contains/startsWith/endsWith).

type TextMatchMode added in v0.30.0

type TextMatchMode string

TextMatchMode enumerates LIKE-based string match modes.

const (
	TextMatchContains TextMatchMode = "contains"
	TextMatchPrefix   TextMatchMode = "prefix"
	TextMatchSuffix   TextMatchMode = "suffix"
)

type ValueExpr

type ValueExpr interface {
	// contains filtered or unexported methods
}

ValueExpr models arithmetic or scalar expressions whose result feeds a comparison.

Jump to

Keyboard shortcuts

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