mirror of https://github.com/usememos/memos
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
- Updated memo and reaction filtering logic to use a unified engine for compiling filter expressions into SQL statements. - Removed redundant filter parsing and conversion code from ListMemoRelations, ListReactions, and ListAttachments methods. - Introduced IDList and UIDList fields in FindMemo and FindReaction structs to support filtering by multiple IDs. - Removed old filter test files for reactions and attachments, as the filtering logic has been centralized. - Updated tests for memo filtering to reflect the new SQL statement compilation approach. - Ensured that unsupported user filters return an error in ListUsers method. |
12 hours ago | |
---|---|---|
.. | ||
MAINTENANCE.md | ||
README.md | ||
engine.go | ||
helpers.go | ||
ir.go | ||
parser.go | ||
render.go | ||
schema.go |
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:
- Parsing – CEL expressions are parsed with
cel-go
and validated against the memo-specific environment declared inschema.go
. Only fields that exist in the schema can surface in the filter. - 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). - Rendering – the renderer in
render.go
walks the IR and produces a SQL fragment plus placeholder arguments tailored to a target dialect (sqlite
,mysql
, orpostgres
). Dialect differences such as JSON access, boolean semantics, placeholders, andLIKE
vsILIKE
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 handlesJSON_EXTRACT
/json_extract
/->
/->>
variations and boolean coercion. - Tag Operations —
tag in [...]
and"tag" in tags
become JSON array predicates. SQLite usesLIKE
patterns, MySQL usesJSON_CONTAINS
, and Postgres uses@>
. - Boolean Flags — Fields such as
has_task_list
render asIS TRUE
equality checks, or comparisons againstCAST('true' AS JSON)
depending on the dialect.
Typical Integration
- Fetch the engine with
filter.DefaultEngine()
. - Call
CompileToStatement
using the appropriate dialect enum. - Append the emitted SQL fragment/args to the existing
WHERE
clause. - 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.