Skip to content

fix(analytics,driver-sql): coerce datetime filter comparands to storage form#2034

Merged
os-zhuang merged 2 commits into
mainfrom
fix/analytics-datetime-epoch-filter
Jun 18, 2026
Merged

fix(analytics,driver-sql): coerce datetime filter comparands to storage form#2034
os-zhuang merged 2 commits into
mainfrom
fix/analytics-datetime-epoch-filter

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Problem

Dashboard time-series charts and "last N months/days" KPIs that filter or group by a Field.datetime column silently returned 0 rows on SQLite, even though the data existed (charts on Field.date columns worked).

Root cause: the analytics NativeSQLStrategy builds raw SQL and runs it via engine.execute, which bypasses the driver's dialect-aware filter coercion (coerceFilterValue in driver-sql). Dashboard relative-date tokens ({12_months_ago}, …) resolve to ISO date strings ("2025-06-18"); Field.datetime under better-sqlite3 stores an integer epoch (ms), and in SQLite an INTEGER always sorts before any TEXT, so epoch >= 'ISO' is always false. This is the datetime analogue of the date write/filter asymmetry that ADR-0053 Phase 1 fixed — on the analytics-raw-SQL path ADR-0053 didn't reach (same bug family #1874).

Fix

The driver is the single source of dialect truth for filter-value coercion; the analytics raw-SQL path now routes through it instead of re-deriving types from value shape.

  • driver-sql: gate the datetime→epoch coercion behind isSqlite (native-timestamp dialects leave the ISO/Date comparand intact → Postgres/MySQL correct), and expose temporalFilterValue(object, field, value) delegating to the existing coerceFilterValue (one code path).
  • service-analytics: NativeSQLStrategy resolves the real (object, column) each member binds against and coerces temporal filter values via the driver, threaded into buildFilterClause (gte/lte/gt/lt/equals, in/notIn) and the dateRange BETWEEN path. New StrategyContext.coerceTemporalFilterValue hook wired from the data engine.
  • spec: added the coerceTemporalFilterValue contract hook.

No CAST(col …) is emitted (indexes preserved). When the hook is absent, behaviour is byte-for-byte identical to before.

Verification

  • Real-SQLite E2E: assessed_at >= '2025-06-18' (datetime) 0 → 4 after driver coercion; Field.date text path unchanged (4).
  • New regression tests assert SQLite is epoch-coerced and Postgres/MySQL/date are not (no regression).
  • Post-rebase onto current main: driver-sql 169 passed, service-analytics 134 passed; spec/driver-sql/service-analytics build green.

ADR

Records this as the first increment in a new Addendum to ADR-0053 (driver-as-single-source-of-dialect-truth; formalize temporalFilterValue onto the IDataDriver contract; add a type × operator × token × driver conformance matrix as the runtime regression backstop).

🤖 Generated with Claude Code

os-zhuang and others added 2 commits June 18, 2026 20:22
…ge form

Dashboard time-series charts and "last N months" KPIs that filter/group by a
`Field.datetime` column silently returned "No rows" even though data existed;
`Field.date` columns worked.

Root cause: `NativeSQLStrategy` expands dashboard relative-date tokens
(`{12_months_ago}`, `{today}`, …) to ISO date strings and binds them directly
into raw SQL, bypassing the driver's CRUD filter coercion. Under better-sqlite3
a `Field.datetime` column is stored as an INTEGER epoch (ms), so
`assessed_at >= '2025-06-18'` is a TEXT-vs-INTEGER affinity compare that is
always false → empty result. `Field.date` stores ISO TEXT and compared fine.

Fix: make the comparand coercion type- and storage-aware via a new optional
`StrategyContext.coerceTemporalFilterValue` hook, wired by the analytics plugin
to the driver's public `SqlDriver.temporalFilterValue` — the single source of
truth for the storage convention (reuses the existing `coerceFilterValue`).
Coercion is dialect-correct: SQLite `Field.datetime` → epoch ms; `Field.date`
text and native-timestamp dialects (Postgres/MySQL) are left unchanged, so
Postgres is never handed an epoch integer. Also gated the driver's existing
datetime→epoch coercion on `isSqlite` to make the native-timestamp path correct.
Applied to gte/lte/gt/lt/equals, in/notIn, and the dateRange/timeDimension path.

Tests:
- service-analytics: NativeSQLStrategy binds epoch for a datetime `gte`/range/in,
  leaves ISO text unchanged when the hook reports no coercion (Postgres/date),
  and is backward-compatible with no hook.
- driver-sql: E2E SQLite repro proving 0→4 rows once coerced; dialect-gating unit
  test asserting Postgres/MySQL are NOT epoch-coerced (no regression).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…+ follow-ups

Append an addendum to ADR-0053 covering the gap it did not reach: the analytics
NativeSQLStrategy raw-SQL path bypasses the driver's dialect-aware filter
coercion, producing the datetime analogue of Phase 1's date equality miss
(epoch-vs-ISO-string → 0 rows). Records the hotfix increment (6f4cf856e) and two
follow-ups: formalize temporalFilterValue onto the IDataDriver contract, and add
a cross-driver temporal conformance matrix as the runtime regression backstop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 18, 2026 12:34pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jun 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/driver-sql, packages/services, @objectstack/spec.

95 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/cloud-artifact-api.mdx (via packages/spec)
  • content/docs/concepts/cluster-semantics.mdx (via @objectstack/spec)
  • content/docs/concepts/core/plugins.mdx (via @objectstack/driver-sql)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/concepts/packages.mdx (via @objectstack/spec)
  • content/docs/concepts/setup-app.mdx (via @objectstack/spec)
  • content/docs/concepts/skills.mdx (via @objectstack/spec)
  • content/docs/concepts/terminology.mdx (via @objectstack/driver-sql)
  • content/docs/concepts/webhook-delivery.mdx (via @objectstack/spec)
  • content/docs/getting-started/architecture.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/core-concepts.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/guides/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/guides/ai-capabilities.mdx (via @objectstack/spec)
  • content/docs/guides/airtable-dashboard-analysis.mdx (via @objectstack/spec)
  • content/docs/guides/analytics-datasets.mdx (via @objectstack/spec)
  • content/docs/guides/api-reference.mdx (via @objectstack/spec)
  • content/docs/guides/business-logic.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/error-catalog.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-type-gallery.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-validation-rules.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/protocol-diagram.mdx (via packages/spec)
  • content/docs/guides/cheatsheets/query-cheat-sheet.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/quick-reference.mdx (via @objectstack/spec)
  • content/docs/guides/client-sdk.mdx (via @objectstack/spec)
  • content/docs/guides/common-patterns.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/auth-service.mdx (via packages/spec)
  • content/docs/guides/contracts/cache-service.mdx (via packages/spec)
  • content/docs/guides/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/index.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/guides/contracts/storage-service.mdx (via packages/spec)
  • content/docs/guides/data-modeling.mdx (via @objectstack/spec)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/spec)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/guides/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/guides/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/guides/formula.mdx (via @objectstack/spec)
  • content/docs/guides/hook-bodies.mdx (via packages/spec)
  • content/docs/guides/kernel-services.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/dashboard.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/field.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/flow.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/index.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/object.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/validation.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/workflow.mdx (via @objectstack/spec)
  • content/docs/guides/packages.mdx (via @objectstack/driver-sql, packages/services, @objectstack/spec)
  • content/docs/guides/plugin-development.mdx (via @objectstack/spec)
  • content/docs/guides/plugins.mdx (via @objectstack/spec)
  • content/docs/guides/project-scoping.mdx (via @objectstack/spec)
  • content/docs/guides/public-forms.mdx (via @objectstack/spec)
  • content/docs/guides/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/guides/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/index.mdx (via packages/services, packages/spec)
  • content/docs/guides/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/guides/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/guides/security.mdx (via @objectstack/spec)
  • content/docs/guides/seed-data.mdx (via @objectstack/spec)
  • content/docs/guides/skills.mdx (via @objectstack/spec)
  • content/docs/guides/standards.mdx (via @objectstack/spec)
  • content/docs/guides/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via packages/services, @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit db02bd5 into main Jun 18, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the fix/analytics-datetime-epoch-filter branch June 18, 2026 12:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant