Skip to content

feat(esgen): Painless queries for field-to-field comparison and recurring() (LYT-391)#56

Draft
junichi-cstk wants to merge 2 commits into
masterfrom
feat/painless-script-field-compare-recurring
Draft

feat(esgen): Painless queries for field-to-field comparison and recurring() (LYT-391)#56
junichi-cstk wants to merge 2 commits into
masterfrom
feat/painless-script-field-compare-recurring

Conversation

@junichi-cstk

Copy link
Copy Markdown

What

Adds Elasticsearch Painless script generation for two new segment filter capabilities, so they can run as ES-backed scans:

  1. recurring(date_field, period[, offsetDays]) — matches documents whose date field lands on a recurrence of itself relative to now. period is yearly/monthly/weekly/daily or a positive integer (every N days). offsetDays shifts the recurrence (yearly + 182 ≈ half-birthday).
  2. Field-to-field comparison (a < b) — ES range queries only compare a field to a constant, so a field-to-field comparison is emitted as a Painless script.

Behavior

Case Generated Painless
recurring(bday, "yearly") getMonthValue() == params.month && getDayOfMonth() == params.day
recurring(bday, "weekly") getDayOfWeek().getValue() == params.dow (ISO 1=Mon..7=Sun)
recurring(bday, 90) epoch-day diff: (todayDay - epochDay - offset) % n == 0
joined < signup (time) toLocalDate().toEpochDay() comparison — day-granular
clicks >= score (numeric) direct doc[a].value >= doc[b].value

Notable semantics:

  • UTC, day-granular throughout. Anchors that don't exist in the target period (Feb 29 in a non-leap year, the 31st in a 30-day month) simply don't match — no clamping.
  • Field-to-field time comparison compares calendar dates (toLocalDate().toEpochDay()), not instants, so a < b matches for the whole day. Floors toward the earlier day, so pre-1970 dates (older birthdates) bucket correctly.
  • Only range operators (< <= > >=) are supported for field-to-field; == / != and non-nested comparable types (time↔time, numeric↔numeric) only.

Why

Backend half of the segment recurring/field-compare feature. The consuming lio changes (in-process compiled evaluator + VM builtin) are paired; the two evaluation paths were parity-audited to agree on the edge cases above.

Sequencing

This PR merges and tags first, then lio bumps its go.mod to the tag and drops a temporary local replace. The lio PR is stacked on this.

Tracked in Jira: LYT-391.

🤖 Generated with Claude Code

Junichi Furukawa and others added 2 commits June 30, 2026 10:26
Add script-query generation to the Elasticsearch generator, which previously
only supported comparisons of a field against a constant.

- ScriptFilter type emitting {"script":{"script":{source,lang,params}}}
- field-to-field comparison (a < b) for non-nested time/numeric fields
- recurring(date_field, period[, offsetDays]) -> script query for
  yearly/monthly/weekly/daily and every-N-days anniversaries

Supports lytics/lio#36194 (compare two date fields) and lytics/lio#34919
(recurring anniversary targeting).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Compare calendar dates via toLocalDate().toEpochDay() instead of instants
(toEpochMilli), so `a < b` between two date fields matches for the whole
UTC day rather than a single instant. Matches the lio in-process compiled
evaluator, keeping the two scan paths semantically in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant