Skip to content

Add partition_key and partition_key_pattern filters for asset events#64610

Open
hussein-awala wants to merge 13 commits into
apache:mainfrom
hussein-awala:feature/partition-key-regex-filter
Open

Add partition_key and partition_key_pattern filters for asset events#64610
hussein-awala wants to merge 13 commits into
apache:mainfrom
hussein-awala:feature/partition-key-regex-filter

Conversation

@hussein-awala

@hussein-awala hussein-awala commented Apr 1, 2026

Copy link
Copy Markdown
Member

Summary

Add two query parameters for filtering asset events by partition key across the full stack:

  • partition_key — exact-match filter using == equality, leverages the B-tree
    composite index (asset_id, partition_key) for fast lookups.
  • partition_key_pattern — regex filter using SQLAlchemy's regexp_match, which maps
    to database-native regex on all backends (PostgreSQL ~, MySQL REGEXP, SQLite re.match).

The two parameters are mutually exclusive — providing both returns a 400 error.

Also includes:

  • Composite database index (asset_id, partition_key) on asset_event table with Alembic migration.
  • Documentation in assets.rst with usage examples for both parameters.
  • InletEventsAccessor exposes .partition_key(value) for exact match and
    .partition_key_pattern(pattern) for regex within tasks.

Details

Exact match (partition_key)

Uses a simple WHERE partition_key = ? equality check. This is the preferred option
when you know the full partition key — it leverages the B-tree index directly with
zero regex overhead.

Regex filter (partition_key_pattern)

Uses regexp_match which maps to database-native regex on all supported backends:
PostgreSQL (~ operator), MySQL (REGEXP), and SQLite (via Python re.match).
Note that SQLite's re.match is anchored at the start of the string, so patterns
should use ^-anchored regex for consistent cross-backend behavior.

Invalid regex patterns are validated with re.compile() before hitting the database,
returning a 400 error with a descriptive message.

A _RegexParam class and regex_param_factory were added to the common FastAPI
parameters module for reuse (the factory accepts a customizable description).

Performance note

Currently, partition_key_pattern performs a full table scan (filtered by any other
WHERE clauses) since standard B-tree indexes cannot accelerate regex matching. For
exact matches, the composite B-tree index (asset_id, partition_key) is used directly.

For high-volume deployments where regex filtering on partition_key becomes a bottleneck,
a trigram (n-gram) index can significantly improve performance on PostgreSQL. This
requires the pg_trgm extension and can be created manually by a database administrator
without any application-level changes:

CREATE EXTENSION IF NOT EXISTS pg_trgm;

CREATE INDEX CONCURRENTLY idx_asset_event_partition_key_trgm
ON asset_event USING gin (partition_key gin_trgm_ops);

PostgreSQL's query planner will automatically use the trigram index for ~ (regex)
queries emitted by regexp_match — no Airflow configuration changes needed.

When it helps:

  • Non-prefix patterns where the B-tree index cannot assist: .*sales$, .*\|2024-.*, us.*west
  • Substring or suffix matching across large tables

When it doesn't help (and what to use instead):

  • Simple prefix patterns like ^us\|2024- — for these, use partition_key (exact match)
    which leverages the B-tree index directly with zero overhead
  • Tables with very high write throughput — trigram GIN indexes are larger and slower to
    update than B-tree, adding write-time cost on every INSERT into asset_event

Shipping this as an Alembic migration would require CREATE EXTENSION privileges
(typically superuser) and a cross-backend strategy, since MySQL and SQLite have no
equivalent. This can be discussed separately if there is demand.

Files changed

Core API layer:

  • parameters.py — new _RegexParam, regex_param_factory, QueryAssetEventPartitionKeyFilter
    (exact match), QueryAssetEventPartitionKeyRegex (regex)
  • assets.py — accept both partition_key and partition_key_pattern filters in
    GET /api/v2/assets/events with mutual-exclusion validation

Execution API layer:

  • asset_events.py — accept both params in /by-asset and /by-asset-alias endpoints,
    with mutual-exclusion and regex validation

Task SDK:

  • client.py — send partition_key and partition_key_pattern as separate query params,
    with client-side ValueError if both are provided
  • comms.py — add both fields to request models
  • supervisor.py — forward both fields to API client
  • context.pyInletEventsAccessor exposes .partition_key() and .partition_key_pattern()
    (mutually exclusive — setting one clears the other)

Database:

  • asset.py — composite index idx_asset_event_asset_id_partition_key
  • New migration 0112_3_3_0_add_idx_asset_event_partition_key.py

Auto-generated:

  • OpenAPI specs (core API, private UI) — new query parameters
  • UI TypeScript clients (openapi-gen/queries/*, openapi-gen/requests/*)

Tests:

  • Core API and Execution API tests for exact match, regex, mutual-exclusion 400 error
  • Task SDK client tests for param forwarding and mutual exclusivity
  • Supervisor tests for forwarding both params

Docs:

  • assets.rst — usage examples for both parameters via REST API and InletEventsAccessor

Test plan

  • Unit tests for Core REST API partition key exact match and regex filtering
  • Unit tests for Execution API partition key filtering (both by-asset and by-alias)
  • Mutual-exclusion validation returns HTTP 400 when both params are provided
  • Invalid regex patterns return HTTP 400 with descriptive message
  • Task SDK client raises ValueError when both params are provided
  • InletEventsAccessor clears the other field when setting one
  • All tests run on all backends (PostgreSQL, MySQL, SQLite)

@boring-cyborg boring-cyborg Bot added area:API Airflow's REST/HTTP API area:db-migrations PRs with DB migration area:task-sdk area:UI Related to UI/UX. For Frontend Developers. kind:documentation labels Apr 1, 2026
@kaxil kaxil requested a review from Copilot April 2, 2026 00:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a regex-based partition key filter for asset events end-to-end (public REST API, execution API, Task SDK/supervisor), plus a supporting DB index/migration and documentation updates.

Changes:

  • Introduces partition_key_pattern (REST) / partition_key (execution API + SDK) regex filtering and wires it through API ↔ supervisor ↔ Task SDK accessor.
  • Adds (asset_id, partition_key) composite index on asset_event with Alembic migration + updates migration head mapping/docs.
  • Regenerates OpenAPI artifacts (YAML + UI TS clients) and adds tests/docs for the new filter.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
task-sdk/tests/task_sdk/execution_time/test_supervisor.py Supervisor request forwarding test updates + new partition_key cases
task-sdk/tests/task_sdk/api/test_client.py SDK client query param forwarding tests for partition_key
task-sdk/src/airflow/sdk/execution_time/supervisor.py Forwards partition_key from supervisor messages to API client
task-sdk/src/airflow/sdk/execution_time/context.py Adds .partition_key(pattern) chaining to InletEventsAccessor
task-sdk/src/airflow/sdk/execution_time/comms.py Adds partition_key to supervisor request message models
task-sdk/src/airflow/sdk/api/client.py Adds partition_key query param forwarding in asset_events.get()
airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_asset_events.py Adds execution API regex filter tests (postgres-only)
airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py Adds REST API partition_key_pattern filtering tests (postgres-only)
airflow-core/src/airflow/utils/db.py Updates migration head mapping for 3.3.0
airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts Regenerates UI request types incl. partitionKeyPattern
airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts Regenerates request service to pass partition_key_pattern
airflow-core/src/airflow/ui/openapi-gen/requests/schemas.gen.ts Regenerates request schemas (incl. ValidationError schema changes)
airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts Regenerates suspense query hook with partitionKeyPattern
airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts Regenerates query hook with partitionKeyPattern
airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts Regenerates prefetch helper with partitionKeyPattern
airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts Regenerates ensureQueryData helper with partitionKeyPattern
airflow-core/src/airflow/ui/openapi-gen/queries/common.ts Regenerates query key fn to include partitionKeyPattern
airflow-core/src/airflow/models/asset.py Adds ORM index idx_asset_event_asset_id_partition_key
airflow-core/src/airflow/migrations/versions/0111_3_3_0_add_idx_asset_event_partition_key.py New migration to create/drop the composite index
airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Adds execution API regex filter param + query clause
airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py Adds REST partition_key_pattern filter dependency to /assets/events
airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml Regenerates REST OpenAPI (adds param + changes ValidationError schema)
airflow-core/src/airflow/api_fastapi/core_api/openapi/_private_ui.yaml Regenerates private UI OpenAPI (ValidationError schema changes)
airflow-core/src/airflow/api_fastapi/common/parameters.py Adds reusable _RegexParam + regex_param_factory + query alias type
airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/openapi-gen/requests/types.gen.ts Regenerates simple auth manager UI request types
airflow-core/src/airflow/api_fastapi/auth/managers/simple/ui/openapi-gen/requests/schemas.gen.ts Regenerates simple auth manager UI request schemas
airflow-core/src/airflow/api_fastapi/auth/managers/simple/openapi/v2-simple-auth-manager-generated.yaml Regenerates simple auth manager OpenAPI (ValidationError schema changes)
airflow-core/docs/migrations-ref.rst Updates auto-generated migration reference table (new head)
airflow-core/docs/authoring-and-scheduling/assets.rst Documents Task SDK accessor + REST query usage for partition key regex
Comments suppressed due to low confidence (1)

airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py:341

  • If partition_key_pattern contains an invalid regex, the underlying DB will raise during query execution (e.g. PostgreSQL error), which currently propagates as a 500. Consider catching the relevant SQLAlchemy/DBAPI exception around the query execution and returning a 400 with a clear message that the regex pattern is invalid for the current backend.
    assets_event_select, total_entries = paginated_select(
        statement=base_statement,
        filters=[
            asset_id,
            source_dag_id,
            source_task_id,
            source_run_id,
            source_map_index,
            partition_key_pattern,
            name_pattern,
            timestamp_range,
        ],
        order_by=order_by,
        offset=offset,
        limit=limit,
        session=session,
    )

Comment thread task-sdk/src/airflow/sdk/api/client.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py
Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py
Comment thread task-sdk/src/airflow/sdk/api/client.py
Comment thread airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml Outdated
Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Outdated
@hussein-awala hussein-awala force-pushed the feature/partition-key-regex-filter branch 2 times, most recently from 371aac1 to 5dbfe96 Compare April 9, 2026 19:24
@hussein-awala hussein-awala changed the title Add regex-based partition_key filtering for asset events Add partition_key filtering for asset events (exact match + regex) Apr 9, 2026
@kaxil kaxil requested a review from Copilot April 10, 2026 19:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 7 comments.

Comment thread task-sdk/tests/task_sdk/execution_time/test_supervisor.py
Comment thread task-sdk/src/airflow/sdk/execution_time/context.py Outdated
Comment thread airflow-core/docs/authoring-and-scheduling/assets.rst Outdated
Comment thread task-sdk/tests/task_sdk/api/test_client.py Outdated
Comment thread task-sdk/src/airflow/sdk/api/client.py Outdated
Comment thread airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 3 comments.

Comment thread airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Outdated
Comment thread task-sdk/src/airflow/sdk/api/client.py
Comment thread task-sdk/src/airflow/sdk/execution_time/supervisor.py
@hussein-awala hussein-awala force-pushed the feature/partition-key-regex-filter branch from 165ec20 to 7ec7341 Compare April 27, 2026 10:58
@Lee-W Lee-W moved this to In Review in AIP-76 Asset Partitioning Apr 28, 2026

@pierrejeambrun pierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is regexp required here? We have a lot of pattern and prefix_pattern filters doing both substring match and prefix match. (supporting logical OR, some special character too "~" etc....)

That introduce an inconsistency for that specific pattern param as well as introducing complexity handling regexp.

Would it be possible to implement that search similarly to what we already have on other APIs and discuss on a separate issue/PR specifically the introduction of Regexp matching.

@Leondon9 Leondon9 mentioned this pull request Jun 3, 2026
1 task
@Lee-W Lee-W moved this from In Review to Backlog in AIP-76 Asset Partitioning Jun 5, 2026
@hussein-awala

Copy link
Copy Markdown
Member Author

Sorry for the late reply @pierrejeambrun, and thanks for the review.

You raise a fair point about consistency, and I agree the regex introduction deserves a dedicated discussion. Let me explain the concrete gap so we can decide together.

The existing pattern (substring ILIKE '%term%') and prefix_pattern (prefix range scan) filters are great for free-text columns like dag_id, but they fall short for partition keys, which are typically structured composite strings (e.g. us|2024-01-15, region=us/date=2024-01-15). A few queries that are common for partition keys and that pattern/prefix_pattern cannot express:

1. The | delimiter collides with the OR operator

Both filters treat | as logical OR (val_str.split("|")). But partition keys very frequently use | as a field separator (see this PR's own examples like us|2026-03-10). So:

  • prefix_pattern=us|2024-01-15 is parsed as OR(prefix "us", prefix "2024-01-15") — not a match on the literal key.
  • With regex you escape it: partition_key_pattern=^us\|2024-01-15$.

2. Suffix / "ends with" matching

Find all partitions for a given date regardless of the region prefix:

  • regex: partition_key_pattern=\|2024-01-15$
  • pattern can only do unanchored substring (%2024-01-15%), which also matches 2024-01-15T09:00; prefix_pattern only matches from the start. Neither can anchor to the end.

3. Character-class / format constraints

Match only well-formed date partitions for a region:

  • regex: partition_key_pattern=^us\|\d{4}-\d{2}-\d{2}$
  • LIKE's _ matches any character, so us|2024-__-__ would also match us|2024-ab-cd. There's no way to require digits.

4. Structured mid-string alternation

Match either region, but only for a specific month:

  • regex: partition_key_pattern=^(us|eu)\|2024-03
  • The | OR in pattern/prefix_pattern splits the whole term, so it can't express "us OR eu, each followed by |2024-03".

Add two new query parameters to the asset events API endpoints:

- partition_key: exact-match filter leveraging the new composite
  B-tree index on (asset_id, partition_key)
- partition_key_pattern: regex-based filter using database-native
  regexp_match for flexible pattern matching

Includes Core API, Execution API, Task SDK (InletEventsAccessor),
client, documentation, migration for the composite index, and tests.

Made-with: Cursor
@hussein-awala hussein-awala force-pushed the feature/partition-key-regex-filter branch from da1abd9 to 02b4b83 Compare June 9, 2026 08:07
@Lee-W Lee-W moved this from In Review to Backlog in AIP-76 Asset Partitioning Jun 16, 2026
…-regex-filter

# Conflicts:
#	airflow-core/docs/authoring-and-scheduling/assets.rst
#	airflow-core/docs/migrations-ref.rst
#	airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
#	airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
#	airflow-core/src/airflow/utils/db.py
@hussein-awala hussein-awala requested a review from henry3260 as a code owner July 6, 2026 19:06
The pattern is evaluated by the database's regex engine, so a pathological
pattern could consume DB CPU (ReDoS). Mitigations:

- Cap the pattern length (MAX_REGEX_PATTERN_LENGTH) in both the Core and
  Execution API validators, returning 400 for over-long patterns.
- Bound the regex query with a transaction-local statement_timeout on
  PostgreSQL (apply_regex_query_timeout); no-op elsewhere. MySQL bounds
  regex evaluation via its built-in regexp_time_limit; SQLite has no
  server-side regex and MariaDB is unsupported.
- Document the safeguards in assets.rst.
Add two [api] configs (Airflow 3.4.0):

- enable_regexp_query_filters (bool, default False): the partition_key_pattern
  filter passes a user-supplied regex to the database engine, which is a ReDoS
  vector, so it is disabled by default and must be explicitly enabled. Exact-match
  partition_key filtering is unaffected.
- regexp_query_timeout (int seconds, default 30): the primary runtime mitigation,
  enforced as a PostgreSQL transaction-local statement_timeout; 0 disables it.

When the flag is off, partition_key_pattern requests return HTTP 400 on both the
Core and Execution APIs. Config descriptions document the security rationale.
Adds tests for the disabled path and the timeout helper.
Drop MAX_REGEX_PATTERN_LENGTH; the opt-in flag and the PostgreSQL
statement_timeout are the meaningful ReDoS safeguards, and a length cap
does not stop short catastrophic patterns while risking false rejections
of legitimate ones.
@hussein-awala

Copy link
Copy Markdown
Member Author

Hey @pierrejeambrun,

I did the security deep-dive on the regex filtering. Summary and what I've done:

The risk is real, and it's DB-side

partition_key_pattern is only syntax-checked with Python re.compile(), then handed to the database's own regex engine via SQLAlchemy regexp_match (~ on PostgreSQL, REGEXP on MySQL). So a malicious pattern burns database CPU, not app CPU, same impact profile as our prior ReDoS, CVE-2023-36543 (authenticated user hangs a request), which was fixed by switching to google-re2.

Per-engine picture (only the backends we support)

  • PostgreSQL (primary): Henry Spencer's engine -> more resistant to classic "evil regex" than PCRE, but not immune; PostgreSQL's own guidance is to bound it with statement_timeout.
  • MySQL 8.0+: ICU engine with built-in regexp_time_limit/regexp_stack_limit -> protected by default.
  • MariaDB (PCRE, the classic backtracking engine): explicitly not supported by Airflow -> so out of scope.
  • SQLite: dev-only, and Airflow doesn't even register a REGEXP function.

Important subtlety about re2

re2 fixes ReDoS when we do the matching (like CVE-2023-36543's Python-side fix). Here the database matches, so a Python-side re2 can't protect the DB engine. The SQL-world equivalent of "an engine that stops backtracking" is a statement timeout.

What I implemented

  1. Opt-in feature flag: [api] enable_regexp_query_filters, default False. Regex filtering is off out of the box; a request using partition_key_pattern while disabled gets a 400. So there's zero attack surface unless an operator knowingly turns it on. Exact-match partition_key (B-tree indexed) is always available and unaffected.
  2. Bounded query timeout: [api] regexp_query_timeout (seconds, default 30), enforced as a transaction-local statement_timeout on PostgreSQL so a pathological pattern is aborted instead of pinning a backend. MySQL relies on its built-in limit. 0 disables it.
  3. Docs explain the security rationale for both settings; tests cover the disabled path and the timeout helper.

(I initially also added a pattern-length cap but dropped it, it doesn't stop short catastrophic patterns like (a+)+$ and risks rejecting legitimate ones, so it added little over the flag + timeout.)

Net: the feature is safe-by-default (off), and when enabled it's bounded by a configurable timeout on the one backend that needs it. Happy to adjust the default timeout or add anything else you'd like.

- Add the enable_regexp_query_filters conf_vars fixture to
  TestGetAssetEventByAssetAliasPartitionKey (was missed), fixing the 4
  by-alias regex tests that now hit the default-off flag; add a
  disabled->400 test for the alias endpoint too.
- Regenerate ts-sdk/src/generated/supervisor.ts for the partition_key /
  partition_key_pattern comms fields (check-ts-sdk-supervisor-schema).

@pierrejeambrun pierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks a lot for the investigation. This makes sense.

A few suggestions, feel free to disregards if this doesn't apply.

Would be great to have another pair of eyes especially for the task sdk part.

Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py Outdated
Comment on lines +179 to +190
partition_key: Annotated[
str | None,
Query(description="Exact partition key filter."),
] = None,
partition_key_pattern: Annotated[
str | None,
Query(
description="Partition key regex filter. Uses database-native regex "
"(PostgreSQL ~ operator, MySQL REGEXP, SQLite re.match). "
"Note: on SQLite, matching is anchored at the start of the string."
),
] = None,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use QueryAssetEventPartitionKeyRegex and the other one?

This create duplicated validation code in the view

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, they were different when I started the implementation but it is not the case now.

The execution routes now depend on the shared QueryAssetEventPartitionKeyFilter / QueryAssetEventPartitionKeyRegex, applied via a filters argument on the SQL helper. The inline Query(...) params and the _validate_partition_key_params helper are gone, so validation (gate + regex compile) lives in one place.

Comment thread airflow-core/src/airflow/config_templates/config.yml Outdated
Comment thread airflow-core/src/airflow/config_templates/config.yml Outdated
Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/common/parameters.py Outdated
Comment thread airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml Outdated
…-regex-filter

# Conflicts:
#	airflow-core/docs/authoring-and-scheduling/assets.rst
#	airflow-core/docs/migrations-ref.rst
#	airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
#	airflow-core/src/airflow/api_fastapi/execution_api/routes/asset_events.py
#	airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
#	airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
#	airflow-core/src/airflow/utils/db.py
#	airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
#	airflow-core/tests/unit/api_fastapi/execution_api/versions/head/test_asset_events.py
#	task-sdk/src/airflow/sdk/api/client.py
#	task-sdk/src/airflow/sdk/execution_time/comms.py
#	task-sdk/src/airflow/sdk/execution_time/context.py
#	task-sdk/src/airflow/sdk/execution_time/schema/schema.json
#	task-sdk/src/airflow/sdk/execution_time/supervisor.py
#	task-sdk/tests/task_sdk/api/test_client.py
#	task-sdk/tests/task_sdk/execution_time/test_supervisor.py
- Rename the public/SDK parameter partition_key_pattern ->
  partition_key_regexp_pattern to distinguish regexp filtering from the
  substring "*_pattern" filters on other APIs.
- Move the enable_regexp_query_filters gate into _RegexParam construction
  and the regex compile check into depends_regex, so a regexp filter can
  never be instantiated without the setting being enabled.
- Drop the mutually-exclusive 400 between partition_key and
  partition_key_regexp_pattern (both now combine with AND) on the Core and
  Execution APIs and in the Task SDK client/accessor.
- Reuse the shared QueryAssetEventPartitionKeyFilter/Regex params in the
  Execution API asset-event routes instead of duplicating the query params
  and validation.
- Keep backend implementation details out of the public parameter
  description; simplify the config docs; revert an unrelated limit check.
- Regenerate OpenAPI specs, UI TS clients, and the supervisor schema
  snapshots.
Address further review feedback:

- Replace the enable_regexp_query_filters + regexp_query_timeout pair with a
  single [api] regexp_query_timeout: 0 (default) disables regexp filtering
  entirely, any positive value enables it and bounds the query runtime. This
  removes the "enabled but unbounded" combination.
- Make apply_regex_query_timeout a context manager that sets a transaction-local
  statement_timeout on enter and resets it on exit, so the bound is scoped to
  the regexp query and does not leak to other statements in the request's
  transaction. Both asset-event routes apply it automatically around query
  execution instead of relying on a manual call in the view.
@hussein-awala

Copy link
Copy Markdown
Member Author

Thanks @pierrejeambrun! I've applied all of these in the last two commits and I updated the PR after #64611 is merged.

I will try to test all the new changes before merging

@hussein-awala hussein-awala moved this from Backlog to In Review in AIP-76 Asset Partitioning Jul 7, 2026
The hand-applied rename missed the type-block declarations and JSDoc in the
generated query hooks (they were not on lines matching the AssetEvents hook
name). Regenerate with `pnpm codegen` so the UI client matches the OpenAPI
spec, fixing the "Compile / format / lint UI" static check.
…-regex-filter

# Conflicts:
#	ts-sdk/src/generated/supervisor.ts
The check-no-new-provide-session-positional hook flagged the partition-key
regexp tests. The nine test methods never used the injected session, so drop
their @provide_session decorator and unused session parameter; make session
keyword-only in the _create_partition_key_test_data helper that does use it.

@pierrejeambrun pierrejeambrun left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, we might want a second pair of eyes as this is a significant change.

finally:
# Reset to the default for the remainder of the transaction so the timeout only bounds the
# regexp query and not unrelated statements in the same request.
session.execute(text("SELECT set_config('statement_timeout', '0', true)"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This restores 0, but if there was a global timeout on the db side, this will override it. We should restore to the db side default.

@pierrejeambrun pierrejeambrun Jul 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe capture the value before and restore it after?

Comment on lines +1698 to +1704
QueryAssetEventPartitionKeyFilter = Annotated[
FilterParam[str | None],
Depends(filter_param_factory(AssetEvent.partition_key, str | None, filter_name="partition_key")),
]
QueryAssetEventPartitionKeyRegex = Annotated[
_RegexParam, Depends(regex_param_factory(AssetEvent.partition_key, "partition_key_regexp_pattern"))
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing I wanted to prevent by moving the timeout inside the regex_param_factory or the filter class directly, is because if someone add a filter in here using the factory, and forget to call the timeout in the view -> CVE.

So I was trying to be defensive for a future bug.

@pierrejeambrun pierrejeambrun Jul 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe in paginated_select, add a check. If one of the filters is an instance of _RegexpParam and there is no timeout set for the request -> big server failure, refusing to process.

That doesn't cover 'all cases' someone could still use a bare _RegexpParam without using the paginated_select helper but that will cover 'most cases' of using filters.

And maybe something in the _RegexParam docstring, explaining that the caller query using this should time bound calling apply_regex_query_timeout

@ashb ashb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly looks okay - not having a db query timeout on MySQL feels like something we need to fix

for event in us_events:
print(event.extra, event.partition_key)

For an exact partition key match, use ``.partition_key(value)`` instead. Regexp filtering is opt-in: it is enabled only by setting ``[api] regexp_query_timeout`` to a positive number of seconds, which also bounds the query runtime; see the config for the security trade-off.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do a direct link to the config via

:conf:`api__regexp_query__timeout`

Something like this exists

on/off switch with the timeout intentionally makes it impossible to enable regexp
filtering without a runtime bound in place.
version_added: 3.4.0
type: integer

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Float maybe? To allow 0.5 etc?

configured timeout is ``0`` (which also means regexp filtering is disabled).
"""
timeout_seconds = conf.getint("api", "regexp_query_timeout")
if timeout_seconds <= 0 or get_dialect_name(session) != "postgresql":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this timeout config doesn't apply to mysql? That feels risky?

def test_partition_key_regexp_pattern_filtering(
self, test_client, partition_key_regexp_pattern, expected_count
):
self._create_partition_key_test_data()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: it seems wasteful to create the same rows for each parameter set? Class scoped fixture perhaps? That could also delete/clean on teardown that way

Comment on lines +1213 to +1235
def test_partition_key_exact_match_via_regex(self, test_client):
self._create_partition_key_test_data()
response = test_client.get("/assets/events", params={"partition_key_regexp_pattern": "^2024-01-01$"})
assert response.status_code == 200
assert response.json()["total_entries"] == 1

def test_partition_key_exact_match(self, test_client):
self._create_partition_key_test_data()
response = test_client.get("/assets/events", params={"partition_key": "2024-01-01"})
assert response.status_code == 200
assert response.json()["total_entries"] == 1

def test_partition_key_exact_match_composite(self, test_client):
self._create_partition_key_test_data()
response = test_client.get("/assets/events", params={"partition_key": "us|2024-01-01"})
assert response.status_code == 200
assert response.json()["total_entries"] == 1

def test_partition_key_exact_match_no_match(self, test_client):
self._create_partition_key_test_data()
response = test_client.get("/assets/events", params={"partition_key": "nonexistent"})
assert response.status_code == 200
assert response.json()["total_entries"] == 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be 4 cases in a single parameteized test i think?

"/assets/events",
params={"partition_key": "2024-01-01", "partition_key_regexp_pattern": "^2025-"},
)
assert response.status_code == 200

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected providing both to return a 400?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More Parameteization here too?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:API Airflow's REST/HTTP API area:db-migrations PRs with DB migration area:task-sdk area:UI Related to UI/UX. For Frontend Developers. kind:documentation

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

5 participants