feat(eventcore-types): glob pattern matching for subscriptions (#246) #410

Merged
jwilger merged 1 commit from feat/246-glob-pattern-matching into main 2026-06-13 08:11:50 -07:00
Owner

Summary

Implements POSIX glob pattern matching for event subscription filtering (issue #246) — the distinct pattern type ADR-017 reserved metacharacters for.

  • New StreamPattern domain type (nutype) validated to compile as a glob::Pattern at construction (parse-don't-validate), with a matches() method and proptest-covered validation. The glob crate's default Pattern::matches treats / as an ordinary character, so * crosses /.
  • EventFilter gains a mutually-exclusive stream_pattern field with a pattern() constructor and stream_pattern() accessor; StreamPattern is re-exported from eventcore-types.
  • Per-backend pushdown, applied before the pagination LIMIT (same correctness reasoning as the event_type filter, #372): Postgres uses stream_id ~ $n with an anchored, injection-safe glob→POSIX-regex translation; SQLite uses the native GLOB operator; memory and fs filter in-process. Postgres and SQLite read_events queries are refactored into dynamic builders so prefix XOR pattern + cursor + event_type compose without combinatorial query strings.
  • Closes the ADR-017 gap: StreamPrefix now rejects glob metacharacters via the no_glob_metacharacters predicate (blast radius minimal — existing prefixes are all literal).

Tests

New cross-backend contract tests wired into backend_contract_tests! (run by all four backends): * wildcard (crossing the page-limit boundary to prove pre-LIMIT filtering), ? single-char, [0-9] character class. Plus unit tests for the glob→regex translation including injection cases. Full workspace suite: 269 tests pass, clippy clean.

Decision record

ADR-0047 records the matching approach (glob crate, / semantics, per-backend pushdown, validation). projection-system and store-backends blueprints document pattern filtering.

Acceptance criteria

  • StreamPattern type created with wildcard support
  • Single-character wildcard (?) works correctly
  • Character class ([0-9]) works correctly
  • StreamPattern distinct from StreamPrefix (glob chars forbidden in prefix)
  • Subscription filtering with patterns works for InMemoryEventStore
  • Subscription filtering with patterns works for PostgresEventStore (and SQLite + fs)

Closes #246

## Summary Implements POSIX glob pattern matching for event subscription filtering (issue #246) — the distinct pattern type ADR-017 reserved metacharacters for. - **New `StreamPattern` domain type** (nutype) validated to compile as a `glob::Pattern` at construction (parse-don't-validate), with a `matches()` method and proptest-covered validation. The `glob` crate's default `Pattern::matches` treats `/` as an ordinary character, so `*` crosses `/`. - **`EventFilter`** gains a mutually-exclusive `stream_pattern` field with a `pattern()` constructor and `stream_pattern()` accessor; `StreamPattern` is re-exported from `eventcore-types`. - **Per-backend pushdown, applied before the pagination `LIMIT`** (same correctness reasoning as the `event_type` filter, #372): Postgres uses `stream_id ~ $n` with an anchored, injection-safe glob→POSIX-regex translation; SQLite uses the native `GLOB` operator; memory and fs filter in-process. Postgres and SQLite `read_events` queries are refactored into dynamic builders so prefix XOR pattern + cursor + event_type compose without combinatorial query strings. - **Closes the ADR-017 gap**: `StreamPrefix` now rejects glob metacharacters via the `no_glob_metacharacters` predicate (blast radius minimal — existing prefixes are all literal). ## Tests New cross-backend contract tests wired into `backend_contract_tests!` (run by all four backends): `*` wildcard (crossing the page-limit boundary to prove pre-`LIMIT` filtering), `?` single-char, `[0-9]` character class. Plus unit tests for the glob→regex translation including injection cases. Full workspace suite: **269 tests pass**, clippy clean. ## Decision record ADR-0047 records the matching approach (glob crate, `/` semantics, per-backend pushdown, validation). `projection-system` and `store-backends` blueprints document pattern filtering. ## Acceptance criteria - [x] StreamPattern type created with wildcard support - [x] Single-character wildcard (?) works correctly - [x] Character class ([0-9]) works correctly - [x] StreamPattern distinct from StreamPrefix (glob chars forbidden in prefix) - [x] Subscription filtering with patterns works for InMemoryEventStore - [x] Subscription filtering with patterns works for PostgresEventStore (and SQLite + fs) Closes #246
feat(eventcore-types): glob pattern matching for subscriptions (#246)
All checks were successful
CI / Detect Changes (pull_request) Successful in 3s
CI / Request auto_review semantic review (pull_request) Successful in 2s
CI / Format (pull_request) Successful in 15s
CI / Clippy (pull_request) Successful in 2m4s
auto_review auto_review: no findings
CI / Test (pull_request) Successful in 3m5s
CI / Security Audit (pull_request) Successful in 26s
CI / Mutation (pull_request) Has been skipped
CI / CI Gate (pull_request) Successful in 2s
403c930e0e
Add POSIX glob pattern matching for event subscription filtering, the
distinct pattern type ADR-017 reserved metacharacters for.

- New StreamPattern domain type (nutype) validated to compile as a
  glob::Pattern at construction (parse-don't-validate), with a matches()
  method and proptest-covered validation. The glob crate's default
  Pattern::matches treats / as an ordinary character, so * crosses /.
- EventFilter gains a mutually-exclusive stream_pattern field with a
  pattern() constructor and stream_pattern() accessor; StreamPattern is
  re-exported from eventcore-types.
- Per-backend pushdown, applied before the pagination LIMIT (same
  correctness reasoning as the event_type filter, issue #372): Postgres
  uses stream_id ~ $n with an anchored, injection-safe glob->POSIX-regex
  translation; SQLite uses the native GLOB operator; memory and fs filter
  in-process. Postgres and SQLite read_events queries are refactored into
  dynamic builders so prefix XOR pattern + cursor + event_type compose
  without combinatorial query strings.
- Close the ADR-017 gap: StreamPrefix now rejects glob metacharacters via
  the no_glob_metacharacters predicate (blast radius minimal; existing
  prefixes are all literal).
- New cross-backend contract tests (* wildcard crossing the page limit,
  ? single-char, [0-9] character class) wired into backend_contract_tests!.
- ADR-0047 records the decision; projection-system and store-backends
  blueprints document pattern filtering and backend pushdown.

Closes #246
auto-review left a comment

This PR introduces POSIX glob pattern matching for event subscription filtering, enhancing the flexibility of event stream selection. The changes appear well-implemented with comprehensive testing across backends, ensuring correctness and performance. The PR is ready to merge.

LLM usage and cost

This PR introduces POSIX glob pattern matching for event subscription filtering, enhancing the flexibility of event stream selection. The changes appear well-implemented with comprehensive testing across backends, ensuring correctness and performance. The PR is ready to merge. ## LLM usage and cost - Reasoning (gpt-4o) in=46980 out=17431 cost=$0.496365 - Cheap (gpt-4o-mini) in=795 out=47 cost=$0.000147 Estimated total USD: $0.496512 via https://api.openai.com and https://api.openai.com
jwilger deleted branch feat/246-glob-pattern-matching 2026-06-13 08:11:50 -07:00
Sign in to join this conversation.
No description provided.