Skip to content

Add exact-match uri filter to GET /assets endpoint#69489

Open
seanmuth wants to merge 2 commits into
apache:mainfrom
seanmuth:restore-asset-uri-exact-lookup
Open

Add exact-match uri filter to GET /assets endpoint#69489
seanmuth wants to merge 2 commits into
apache:mainfrom
seanmuth:restore-asset-uri-exact-lookup

Conversation

@seanmuth

@seanmuth seanmuth commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What

Adds a uri query parameter to GET /api/v2/assets that matches an asset by its exact URI, backed by a new single-column index on asset.uri.

Why

Today, resolving a single asset when you already know its full URI (for example, discovering an asset_id before posting to /assets/{asset_id}/events for cross-deployment dependencies) requires uri_pattern. That parameter compiles to ILIKE '%...%', which cannot use a B-tree index and degrades to a full table scan on large asset tables — the API docs themselves warn about this.

The Airflow 2 REST API offered a fast exact lookup via GET /datasets/{uri} (later GET /assets/{uri}), but it was not carried forward when the endpoint migrated to FastAPI under AIP-84 — the single-resource route became GET /assets/{asset_id} (integer PK only). This PR restores the exact-URI lookup as a query parameter.

Passing the URI as a query parameter rather than a path segment also avoids the URI-encoding problems path parameters have with the /, :, and %2F characters that asset URIs contain.

What changed

  • New uri exact-match filter on GET /assets (equality comparison via the existing filter_param_factory).
  • New non-unique index idx_asset_uri on asset.uri (the existing unique index leads with name and cannot serve uri-only lookups), with an Alembic migration.
  • Regenerated OpenAPI spec + UI client, updated REVISION_HEADS_MAP and migration ref doc.
  • Tests: exact-match hits plus negative cases confirming a substring does not match (unlike uri_pattern).

Was generative AI tooling used to co-author this PR?
  • Yes — Claude Code (Opus 4.8, 1M context)

Generated-by: Claude Code (Opus 4.8, 1M context) following the guidelines


Drafted-by: Claude Code (Opus 4.8, 1M context) (no human review before posting)

Resolving a single asset by its full URI currently requires uri_pattern,
which compiles to ILIKE '%...%' and cannot use a database index, so it
degrades to a full table scan on large asset tables. This is the fast,
index-backed exact-URI lookup that the Airflow 2 REST API exposed via
GET /datasets/{uri} but that was not carried forward when the endpoint
moved to FastAPI under AIP-84.

The uri query parameter matches an asset by its exact URI using an
equality comparison, backed by a new single-column index on asset.uri
(the existing unique index leads with name and cannot serve uri-only
lookups). Passing it as a query parameter rather than a path segment
avoids the URI-encoding problems that path parameters have with the
'/' and ':' characters in asset URIs.
@seanmuth seanmuth force-pushed the restore-asset-uri-exact-lookup branch from d549e9b to 5416302 Compare July 6, 2026 18:54

@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.

Thanks for the PR, makes sense!

A few suggestions bellow :)

Comment thread airflow-core/docs/migrations-ref.rst Outdated
| Revision ID | Revises ID | Airflow Version | Description |
+=========================+==================+===================+==============================================================+
| ``d2f4e1b3c5a7`` (head) | ``9ff64e1c35d3`` | ``3.3.0`` | Add partition_date to asset_partition_dag_run. |
| ``c4e7a1f9b2d0`` (head) | ``d2f4e1b3c5a7`` | ``3.3.0`` | Add index on asset.uri. |

@pierrejeambrun pierrejeambrun Jul 7, 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.

Target version needs to be updated, 3.3.0 was just released, that would be for 3.4.0

@@ -0,0 +1 @@
Add an indexed exact-match ``uri`` query parameter to ``GET /api/v2/assets`` for fast single-asset lookup by URI (much faster than ``uri_pattern``, which uses an unindexed ``ILIKE '%...%'``).

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.

This needs to be removed. (I know doc mentions that user facing change should have a fragment, but that's only true for significant / behavior change that needs a user warning basically).

Comment on lines +1581 to +1596
QueryUriExactMatch = Annotated[
FilterParam[str | None],
Depends(
filter_param_factory(
AssetModel.uri,
str | None,
filter_name="uri",
description=(
"Exact-match filter on the full asset URI. Compiles to an indexed equality "
"comparison (``uri = ...``), so it is far faster than ``uri_pattern`` (which uses "
"``ILIKE '%...%'`` and cannot use an index) for resolving a single asset by its "
"known URI."
),
)
),
]

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 remove the piece that compares to search params. They have their own doc explaining the performance pitfalls, this can stay about 'exact match'.

Suggested change
QueryUriExactMatch = Annotated[
FilterParam[str | None],
Depends(
filter_param_factory(
AssetModel.uri,
str | None,
filter_name="uri",
description=(
"Exact-match filter on the full asset URI. Compiles to an indexed equality "
"comparison (``uri = ...``), so it is far faster than ``uri_pattern`` (which uses "
"``ILIKE '%...%'`` and cannot use an index) for resolving a single asset by its "
"known URI."
),
)
),
]
QueryUriExactMatch = Annotated[
FilterParam[str | None],
Depends(
filter_param_factory(
AssetModel.uri,
str | None,
filter_name="uri",
description=(
"Exact-match filter on the full asset URI. Compiles to an indexed equality "
"comparison (``uri = ...``).
),
)
),
]

Comment on lines +1581 to +1589
QueryUriExactMatch = Annotated[
FilterParam[str | None],
Depends(
filter_param_factory(
AssetModel.uri,
str | None,
filter_name="uri",
description=(
"Exact-match filter on the full asset URI. Compiles to an indexed equality "

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.

We could make this filter accept multiple values.

To allow ?uri=some_uri1&uri=some_uri2 to get both assets with those URI. (And it makes more sense to me for a list endpoint to accept multiple values, otherwise it's really close to a single GET endpoint and maybe we should instead have GET assets/{uri}, but lets not go there)

It's really a simple change, as an exemple for implementation:

QueryTIStateFilter = Annotated[
    FilterParam[list[str]],
    Depends(
        filter_param_factory(
            TaskInstance.state,
            list[str],
            FilterOptionEnum.ANY_EQUAL,
            default_factory=list,
            transform_callable=_transform_ti_states,
        )
    ),
]

Comment on lines +37 to +47

def upgrade():
"""Apply Add index on asset.uri."""
with op.batch_alter_table("asset", schema=None) as batch_op:
batch_op.create_index("idx_asset_uri", ["uri"], unique=False)


def downgrade():
"""Unapply Add index on asset.uri."""
with op.batch_alter_table("asset", schema=None) as batch_op:
batch_op.drop_index("idx_asset_uri")

@pierrejeambrun pierrejeambrun Jul 7, 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.

It's fine to have a 'filter/order_by' in the API that isn't backed by an index. Indeed it will be slow if the table grows out of control but does the table grows out of control in most common cases? I would avoid adding an index (costly) just for a single filter of a single endpoint.
I'm not sure here but I would say the table isn't huge most of the time. (millions of assets) and no index is probably fine for most? (the index probably wasn't there in 2.x as well)

If someone happen to have a huge asset table they can add their own index to improve performance following https://github.com/apache/airflow/blob/main/airflow-core/docs/howto/performance.rst

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 just checked, in 2.x there was an index indeed, so the table was worth indexing. Disregard my previous comment, this makes sense.

Comment on lines +336 to +337
# Single-column index so exact-match lookups by URI (GET /assets?uri=...) can use an
# index; the composite index above leads with ``name`` and cannot serve uri-only queries.

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 remove this, to not mention the API layer in ORM models.

@pierrejeambrun pierrejeambrun added this to the Airflow 3.4.0 milestone Jul 7, 2026
Address review feedback: let the exact-match uri filter accept repeated
values (?uri=a&uri=b) so the list endpoint can resolve several assets in
one call, target the migration at 3.4.0 (3.3.0 is already released), and
drop the newsfragment since this is not a behaviour change that needs a
user warning.
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:UI Related to UI/UX. For Frontend Developers. kind:documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants