memos/plugin/filter
majiayu000 6c996016eb
fix(filter): register tag_search and property as CEL variables
Add missing CEL variable declarations for tag_search and property in
the filter environment so the documented filter syntax works.

- schema.go: Add tag_search as VirtualAlias for tags, add property
  as DynType CEL variable
- parser.go: Convert VirtualAlias==value to InCondition for JSONList
  targets, handle property.X selector expressions
- render.go: Generalize hardcoded tag check to support any VirtualAlias
  resolving to JSONList
- Add integration tests for tag_search, property, and combined filters

Fixes #5761

Signed-off-by: majiayu000 <1835304752@qq.com>
2026-03-25 11:11:41 +08:00
..
MAINTENANCE.md refactor: memo filter 2025-10-16 09:22:52 +08:00
README.md refactor: memo filter 2025-10-16 09:22:52 +08:00
engine.go chore(backend): update Go toolchain and dependencies (#5730) 2026-03-16 21:07:52 +08:00
helpers.go refactor: memo filter 2025-10-16 09:22:52 +08:00
ir.go feat(filter): add CEL list comprehension support for tag filtering 2026-01-17 12:36:39 +08:00
parser.go fix(filter): register tag_search and property as CEL variables 2026-03-25 11:11:41 +08:00
render.go fix(filter): register tag_search and property as CEL variables 2026-03-25 11:11:41 +08:00
schema.go fix(filter): register tag_search and property as CEL variables 2026-03-25 11:11:41 +08:00

README.md

Memo Filter Engine

This package houses the memo-only filter engine that turns CEL expressions into SQL fragments. 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.
  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.
  • 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.

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 23 when a driver needs to process an array of filters.