This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 0d9dd822d3a Add exact-match uri filter to GET /assets endpoint (#69489)
0d9dd822d3a is described below

commit 0d9dd822d3ac0cb44380836c455baecdf25e2eff
Author: Sean Muth <[email protected]>
AuthorDate: Thu Jul 9 09:31:20 2026 -0500

    Add exact-match uri filter to GET /assets endpoint (#69489)
    
    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 restores 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 assets by exact URI using an equality
    comparison and accepts repeated values (?uri=a&uri=b) to resolve several
    assets in one call, 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.
---
 airflow-core/docs/migrations-ref.rst               |  4 +-
 .../src/airflow/api_fastapi/common/parameters.py   | 17 ++++++++
 .../core_api/openapi/v2-rest-api-generated.yaml    | 14 +++++++
 .../api_fastapi/core_api/routes/public/assets.py   | 12 +++++-
 .../versions/0126_3_4_0_add_index_on_asset_uri.py  | 47 ++++++++++++++++++++++
 airflow-core/src/airflow/models/asset.py           |  1 +
 .../src/airflow/ui/openapi-gen/queries/common.ts   |  5 ++-
 .../ui/openapi-gen/queries/ensureQueryData.ts      |  6 ++-
 .../src/airflow/ui/openapi-gen/queries/prefetch.ts |  6 ++-
 .../src/airflow/ui/openapi-gen/queries/queries.ts  |  6 ++-
 .../src/airflow/ui/openapi-gen/queries/suspense.ts |  6 ++-
 .../ui/openapi-gen/requests/services.gen.ts        |  2 +
 .../airflow/ui/openapi-gen/requests/types.gen.ts   |  4 ++
 airflow-core/src/airflow/utils/db.py               |  2 +-
 .../core_api/routes/public/test_assets.py          |  8 ++++
 15 files changed, 127 insertions(+), 13 deletions(-)

diff --git a/airflow-core/docs/migrations-ref.rst 
b/airflow-core/docs/migrations-ref.rst
index 6419525c8ab..3dd6e96a7d0 100644
--- a/airflow-core/docs/migrations-ref.rst
+++ b/airflow-core/docs/migrations-ref.rst
@@ -39,7 +39,9 @@ Here's the list of all the Database Migrations that are 
executed via when you ru
 
+-------------------------+------------------+-------------------+--------------------------------------------------------------+
 | Revision ID             | Revises ID       | Airflow Version   | Description 
                                                 |
 
+=========================+==================+===================+==============================================================+
-| ``436dc127462c`` (head) | ``5a5d3253e946`` | ``3.4.0``         | Drop 
span_status column.                                     |
+| ``c4e7a1f9b2d0`` (head) | ``436dc127462c`` | ``3.4.0``         | Add index 
on asset.uri.                                      |
++-------------------------+------------------+-------------------+--------------------------------------------------------------+
+| ``436dc127462c``        | ``5a5d3253e946`` | ``3.4.0``         | Drop 
span_status column.                                     |
 
+-------------------------+------------------+-------------------+--------------------------------------------------------------+
 | ``5a5d3253e946``        | ``d2f4e1b3c5a7`` | ``3.4.0``         | Add GIN 
index on asset_event.extra for PostgreSQL.           |
 
+-------------------------+------------------+-------------------+--------------------------------------------------------------+
diff --git a/airflow-core/src/airflow/api_fastapi/common/parameters.py 
b/airflow-core/src/airflow/api_fastapi/common/parameters.py
index c3a543a2b90..060d9478a39 100644
--- a/airflow-core/src/airflow/api_fastapi/common/parameters.py
+++ b/airflow-core/src/airflow/api_fastapi/common/parameters.py
@@ -1664,6 +1664,23 @@ QueryUriPatternSearch = Annotated[_SearchParam, 
Depends(search_param_factory(Ass
 QueryUriPrefixPatternSearch = Annotated[
     _PrefixSearchParam, Depends(prefix_search_param_factory(AssetModel.uri, 
"uri_prefix_pattern"))
 ]
+QueryUriExactMatch = Annotated[
+    FilterParam[list[str]],
+    Depends(
+        filter_param_factory(
+            AssetModel.uri,
+            list[str],
+            FilterOptionEnum.ANY_EQUAL,
+            filter_name="uri",
+            default_factory=list,
+            description=(
+                "Exact-match filter on the full asset URI. Compiles to an 
indexed equality "
+                "comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``) to match "
+                "multiple assets."
+            ),
+        )
+    ),
+]
 QueryAssetAliasNamePatternSearch = Annotated[
     _SearchParam, Depends(search_param_factory(AssetAliasModel.name, 
"name_pattern"))
 ]
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
index b848b996298..8173fd5fe9c 100644
--- 
a/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
+++ 
b/airflow-core/src/airflow/api_fastapi/core_api/openapi/v2-rest-api-generated.yaml
@@ -79,6 +79,20 @@ paths:
           \ stays index-compatible under locale-aware collations \u2014 e.g. 
`test_`\
           \ effectively matches items starting with `test`, and `s3://` 
matches items\
           \ starting with `s3`."
+      - name: uri
+        in: query
+        required: false
+        schema:
+          type: array
+          items:
+            type: string
+          description: Exact-match filter on the full asset URI. Compiles to 
an indexed
+            equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``)
+            to match multiple assets.
+          title: Uri
+        description: Exact-match filter on the full asset URI. Compiles to an 
indexed
+          equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``)
+          to match multiple assets.
       - name: uri_pattern
         in: query
         required: false
diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
index d21b1c1cbb9..d83ed8ec8d8 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/assets.py
@@ -42,6 +42,7 @@ from airflow.api_fastapi.common.parameters import (
     QueryAssetNamePrefixPatternSearch,
     QueryLimit,
     QueryOffset,
+    QueryUriExactMatch,
     QueryUriPatternSearch,
     QueryUriPrefixPatternSearch,
     RangeFilter,
@@ -140,6 +141,7 @@ def get_assets(
     offset: QueryOffset,
     name_pattern: QueryAssetNamePatternSearch,
     name_prefix_pattern: QueryAssetNamePrefixPatternSearch,
+    uri: QueryUriExactMatch,
     uri_pattern: QueryUriPatternSearch,
     uri_prefix_pattern: QueryUriPrefixPatternSearch,
     dag_ids: QueryAssetDagIdPatternSearch,
@@ -185,7 +187,15 @@ def get_assets(
 
     assets_select, total_entries = paginated_select(
         statement=assets_select_statement,
-        filters=[only_active, name_pattern, name_prefix_pattern, uri_pattern, 
uri_prefix_pattern, dag_ids],
+        filters=[
+            only_active,
+            name_pattern,
+            name_prefix_pattern,
+            uri,
+            uri_pattern,
+            uri_prefix_pattern,
+            dag_ids,
+        ],
         order_by=order_by,
         offset=offset,
         limit=limit,
diff --git 
a/airflow-core/src/airflow/migrations/versions/0126_3_4_0_add_index_on_asset_uri.py
 
b/airflow-core/src/airflow/migrations/versions/0126_3_4_0_add_index_on_asset_uri.py
new file mode 100644
index 00000000000..068bfda9add
--- /dev/null
+++ 
b/airflow-core/src/airflow/migrations/versions/0126_3_4_0_add_index_on_asset_uri.py
@@ -0,0 +1,47 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Add index on asset.uri.
+
+Revision ID: c4e7a1f9b2d0
+Revises: 436dc127462c
+Create Date: 2026-07-06 00:00:00.000000
+"""
+
+from __future__ import annotations
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "c4e7a1f9b2d0"
+down_revision = "436dc127462c"
+branch_labels = None
+depends_on = None
+airflow_version = "3.4.0"
+
+
+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")
diff --git a/airflow-core/src/airflow/models/asset.py 
b/airflow-core/src/airflow/models/asset.py
index 60256f9cd88..4354034e649 100644
--- a/airflow-core/src/airflow/models/asset.py
+++ b/airflow-core/src/airflow/models/asset.py
@@ -333,6 +333,7 @@ class AssetModel(Base):
     __tablename__ = "asset"
     __table_args__ = (
         Index("idx_asset_name_uri_unique", name, uri, unique=True),
+        Index("idx_asset_uri", uri),
         {"sqlite_autoincrement": True},  # ensures PK values not reused
     )
 
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
index 2d6dc7ffe7d..a8f08d3df78 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/common.ts
@@ -6,7 +6,7 @@ import { DagRunState, DagWarningType } from 
"../requests/types.gen";
 export type AssetServiceGetAssetsDefaultResponse = Awaited<ReturnType<typeof 
AssetService.getAssets>>;
 export type AssetServiceGetAssetsQueryResult<TData = 
AssetServiceGetAssetsDefaultResponse, TError = unknown> = UseQueryResult<TData, 
TError>;
 export const useAssetServiceGetAssetsKey = "AssetServiceGetAssets";
-export const UseAssetServiceGetAssetsKeyFn = ({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }: 
{
+export const UseAssetServiceGetAssetsKeyFn = ({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }: {
   dagIds?: string[];
   limit?: number;
   namePattern?: string;
@@ -14,9 +14,10 @@ export const UseAssetServiceGetAssetsKeyFn = ({ dagIds, 
limit, namePattern, name
   offset?: number;
   onlyActive?: boolean;
   orderBy?: string[];
+  uri?: string[];
   uriPattern?: string;
   uriPrefixPattern?: string;
-} = {}, queryKey?: Array<unknown>) => [useAssetServiceGetAssetsKey, 
...(queryKey ?? [{ dagIds, limit, namePattern, namePrefixPattern, offset, 
onlyActive, orderBy, uriPattern, uriPrefixPattern }])];
+} = {}, queryKey?: Array<unknown>) => [useAssetServiceGetAssetsKey, 
...(queryKey ?? [{ dagIds, limit, namePattern, namePrefixPattern, offset, 
onlyActive, orderBy, uri, uriPattern, uriPrefixPattern }])];
 export type AssetServiceGetAssetAliasesDefaultResponse = 
Awaited<ReturnType<typeof AssetService.getAssetAliases>>;
 export type AssetServiceGetAssetAliasesQueryResult<TData = 
AssetServiceGetAssetAliasesDefaultResponse, TError = unknown> = 
UseQueryResult<TData, TError>;
 export const useAssetServiceGetAssetAliasesKey = "AssetServiceGetAssetAliases";
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
index 6eba7e9a9db..34eff203140 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/ensureQueryData.ts
@@ -14,6 +14,7 @@ import * as Common from "./common";
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``name_prefix_pattern`` parameter when possible.
 * @param data.namePrefixPattern Prefix match — returns items whose value 
starts with the given string (case-sensitive, index-friendly). Use the pipe `|` 
operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard 
characters (`%`, `_`) are treated as literal characters. Trailing 
non-alphanumeric characters in the prefix are stripped before matching so the 
range scan stays index-compatible under locale-aware collations — e.g. `test_` 
effectively matches items starting with `te [...]
+* @param data.uri Exact-match filter on the full asset URI. Compiles to an 
indexed equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``) to match multiple assets.
 * @param data.uriPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. 
`%customer_%`). Use the pipe `|` operator for OR logic (e.g. `dag1 | dag2`). 
Regular expressions are **not** supported.
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``uri_prefix_pattern`` parameter when possible.
@@ -24,7 +25,7 @@ import * as Common from "./common";
 * @returns AssetCollectionResponse Successful Response
 * @throws ApiError
 */
-export const ensureUseAssetServiceGetAssetsData = (queryClient: QueryClient, { 
dagIds, limit, namePattern, namePrefixPattern, offset, onlyActive, orderBy, 
uriPattern, uriPrefixPattern }: {
+export const ensureUseAssetServiceGetAssetsData = (queryClient: QueryClient, { 
dagIds, limit, namePattern, namePrefixPattern, offset, onlyActive, orderBy, 
uri, uriPattern, uriPrefixPattern }: {
   dagIds?: string[];
   limit?: number;
   namePattern?: string;
@@ -32,9 +33,10 @@ export const ensureUseAssetServiceGetAssetsData = 
(queryClient: QueryClient, { d
   offset?: number;
   onlyActive?: boolean;
   orderBy?: string[];
+  uri?: string[];
   uriPattern?: string;
   uriPrefixPattern?: string;
-} = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern 
}), queryFn: () => AssetService.getAssets({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }) 
});
+} = {}) => queryClient.ensureQueryData({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }), queryFn: () => AssetService.getAssets({ dagIds, limit, 
namePattern, namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }) });
 /**
 * Get Asset Aliases
 * Get asset aliases.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
index 037f6d01a39..170a17f9e58 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -14,6 +14,7 @@ import * as Common from "./common";
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``name_prefix_pattern`` parameter when possible.
 * @param data.namePrefixPattern Prefix match — returns items whose value 
starts with the given string (case-sensitive, index-friendly). Use the pipe `|` 
operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard 
characters (`%`, `_`) are treated as literal characters. Trailing 
non-alphanumeric characters in the prefix are stripped before matching so the 
range scan stays index-compatible under locale-aware collations — e.g. `test_` 
effectively matches items starting with `te [...]
+* @param data.uri Exact-match filter on the full asset URI. Compiles to an 
indexed equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``) to match multiple assets.
 * @param data.uriPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. 
`%customer_%`). Use the pipe `|` operator for OR logic (e.g. `dag1 | dag2`). 
Regular expressions are **not** supported.
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``uri_prefix_pattern`` parameter when possible.
@@ -24,7 +25,7 @@ import * as Common from "./common";
 * @returns AssetCollectionResponse Successful Response
 * @throws ApiError
 */
-export const prefetchUseAssetServiceGetAssets = (queryClient: QueryClient, { 
dagIds, limit, namePattern, namePrefixPattern, offset, onlyActive, orderBy, 
uriPattern, uriPrefixPattern }: {
+export const prefetchUseAssetServiceGetAssets = (queryClient: QueryClient, { 
dagIds, limit, namePattern, namePrefixPattern, offset, onlyActive, orderBy, 
uri, uriPattern, uriPrefixPattern }: {
   dagIds?: string[];
   limit?: number;
   namePattern?: string;
@@ -32,9 +33,10 @@ export const prefetchUseAssetServiceGetAssets = 
(queryClient: QueryClient, { dag
   offset?: number;
   onlyActive?: boolean;
   orderBy?: string[];
+  uri?: string[];
   uriPattern?: string;
   uriPrefixPattern?: string;
-} = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern 
}), queryFn: () => AssetService.getAssets({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }) 
});
+} = {}) => queryClient.prefetchQuery({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }), queryFn: () => AssetService.getAssets({ dagIds, limit, 
namePattern, namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }) });
 /**
 * Get Asset Aliases
 * Get asset aliases.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
index 09ddcdd3837..7efadc2e4f8 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/queries.ts
@@ -14,6 +14,7 @@ import * as Common from "./common";
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``name_prefix_pattern`` parameter when possible.
 * @param data.namePrefixPattern Prefix match — returns items whose value 
starts with the given string (case-sensitive, index-friendly). Use the pipe `|` 
operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard 
characters (`%`, `_`) are treated as literal characters. Trailing 
non-alphanumeric characters in the prefix are stripped before matching so the 
range scan stays index-compatible under locale-aware collations — e.g. `test_` 
effectively matches items starting with `te [...]
+* @param data.uri Exact-match filter on the full asset URI. Compiles to an 
indexed equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``) to match multiple assets.
 * @param data.uriPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. 
`%customer_%`). Use the pipe `|` operator for OR logic (e.g. `dag1 | dag2`). 
Regular expressions are **not** supported.
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``uri_prefix_pattern`` parameter when possible.
@@ -24,7 +25,7 @@ import * as Common from "./common";
 * @returns AssetCollectionResponse Successful Response
 * @throws ApiError
 */
-export const useAssetServiceGetAssets = <TData = 
Common.AssetServiceGetAssetsDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }: 
{
+export const useAssetServiceGetAssets = <TData = 
Common.AssetServiceGetAssetsDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }: {
   dagIds?: string[];
   limit?: number;
   namePattern?: string;
@@ -32,9 +33,10 @@ export const useAssetServiceGetAssets = <TData = 
Common.AssetServiceGetAssetsDef
   offset?: number;
   onlyActive?: boolean;
   orderBy?: string[];
+  uri?: string[];
   uriPattern?: string;
   uriPrefixPattern?: string;
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }, 
queryKey), queryFn: () => AssetService.getAssets({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }) 
as TData, ...options });
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }, queryKey), queryFn: () => AssetService.getAssets({ dagIds, 
limit, namePattern, namePrefixPattern, offset, onlyActive, orderBy, uri, 
uriPattern, uriPrefixPattern }) as TData, ...options });
 /**
 * Get Asset Aliases
 * Get asset aliases.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
index e3ec8836309..d4e694554d0 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/queries/suspense.ts
@@ -14,6 +14,7 @@ import * as Common from "./common";
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``name_prefix_pattern`` parameter when possible.
 * @param data.namePrefixPattern Prefix match — returns items whose value 
starts with the given string (case-sensitive, index-friendly). Use the pipe `|` 
operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard 
characters (`%`, `_`) are treated as literal characters. Trailing 
non-alphanumeric characters in the prefix are stripped before matching so the 
range scan stays index-compatible under locale-aware collations — e.g. `test_` 
effectively matches items starting with `te [...]
+* @param data.uri Exact-match filter on the full asset URI. Compiles to an 
indexed equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``) to match multiple assets.
 * @param data.uriPattern SQL LIKE expression — use `%` / `_` wildcards (e.g. 
`%customer_%`). Use the pipe `|` operator for OR logic (e.g. `dag1 | dag2`). 
Regular expressions are **not** supported.
 *
 * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``uri_prefix_pattern`` parameter when possible.
@@ -24,7 +25,7 @@ import * as Common from "./common";
 * @returns AssetCollectionResponse Successful Response
 * @throws ApiError
 */
-export const useAssetServiceGetAssetsSuspense = <TData = 
Common.AssetServiceGetAssetsDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }: 
{
+export const useAssetServiceGetAssetsSuspense = <TData = 
Common.AssetServiceGetAssetsDefaultResponse, TError = unknown, TQueryKey 
extends Array<unknown> = unknown[]>({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }: {
   dagIds?: string[];
   limit?: number;
   namePattern?: string;
@@ -32,9 +33,10 @@ export const useAssetServiceGetAssetsSuspense = <TData = 
Common.AssetServiceGetA
   offset?: number;
   onlyActive?: boolean;
   orderBy?: string[];
+  uri?: string[];
   uriPattern?: string;
   uriPrefixPattern?: string;
-} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }, 
queryKey), queryFn: () => AssetService.getAssets({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uriPattern, uriPrefixPattern }) 
as TData, ...options });
+} = {}, queryKey?: TQueryKey, options?: Omit<UseQueryOptions<TData, TError>, 
"queryKey" | "queryFn">) => useSuspenseQuery<TData, TError>({ queryKey: 
Common.UseAssetServiceGetAssetsKeyFn({ dagIds, limit, namePattern, 
namePrefixPattern, offset, onlyActive, orderBy, uri, uriPattern, 
uriPrefixPattern }, queryKey), queryFn: () => AssetService.getAssets({ dagIds, 
limit, namePattern, namePrefixPattern, offset, onlyActive, orderBy, uri, 
uriPattern, uriPrefixPattern }) as TData, ...options });
 /**
 * Get Asset Aliases
 * Get asset aliases.
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
index b674a72ccbd..0356a4a3641 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -16,6 +16,7 @@ export class AssetService {
      *
      * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``name_prefix_pattern`` parameter when possible.
      * @param data.namePrefixPattern Prefix match — returns items whose value 
starts with the given string (case-sensitive, index-friendly). Use the pipe `|` 
operator for OR logic (e.g. `dag1|dag2`). Use `~` to match all. Wildcard 
characters (`%`, `_`) are treated as literal characters. Trailing 
non-alphanumeric characters in the prefix are stripped before matching so the 
range scan stays index-compatible under locale-aware collations — e.g. `test_` 
effectively matches items starting wit [...]
+     * @param data.uri Exact-match filter on the full asset URI. Compiles to 
an indexed equality comparison (``uri = ...``). Repeat the parameter 
(``?uri=a&uri=b``) to match multiple assets.
      * @param data.uriPattern SQL LIKE expression — use `%` / `_` wildcards 
(e.g. `%customer_%`). Use the pipe `|` operator for OR logic (e.g. `dag1 | 
dag2`). Regular expressions are **not** supported.
      *
      * **Performance note:** this full-match pattern is evaluated as ``ILIKE 
'%term%'`` and most of the time prevents the database from using B-tree 
indexes, which can be very slow on large tables. Prefer the equivalent 
``uri_prefix_pattern`` parameter when possible.
@@ -35,6 +36,7 @@ export class AssetService {
                 offset: data.offset,
                 name_pattern: data.namePattern,
                 name_prefix_pattern: data.namePrefixPattern,
+                uri: data.uri,
                 uri_pattern: data.uriPattern,
                 uri_prefix_pattern: data.uriPrefixPattern,
                 dag_ids: data.dagIds,
diff --git a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
index d1a91b68a69..acec153fd1f 100644
--- a/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow-core/src/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -2808,6 +2808,10 @@ export type GetAssetsData = {
      * Attributes to order by, multi criteria sort is supported. Prefix with 
`-` for descending order. Supported attributes: `id, name, uri, created_at, 
updated_at`
      */
     orderBy?: Array<(string)>;
+    /**
+     * Exact-match filter on the full asset URI. Compiles to an indexed 
equality comparison (``uri = ...``). Repeat the parameter (``?uri=a&uri=b``) to 
match multiple assets.
+     */
+    uri?: Array<(string)>;
     /**
      * SQL LIKE expression — use `%` / `_` wildcards (e.g. `%customer_%`). Use 
the pipe `|` operator for OR logic (e.g. `dag1 | dag2`). Regular expressions 
are **not** supported.
      *
diff --git a/airflow-core/src/airflow/utils/db.py 
b/airflow-core/src/airflow/utils/db.py
index ee10c008f9b..ce4ef036d20 100644
--- a/airflow-core/src/airflow/utils/db.py
+++ b/airflow-core/src/airflow/utils/db.py
@@ -117,7 +117,7 @@ _REVISION_HEADS_MAP: dict[str, str] = {
     "3.1.8": "509b94a1042d",
     "3.2.0": "1d6611b6ab7c",
     "3.3.0": "d2f4e1b3c5a7",
-    "3.4.0": "436dc127462c",
+    "3.4.0": "c4e7a1f9b2d0",
 }
 
 # Prefix used to identify tables holding data moved during migration.
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
index 1b452924ffe..1ffa8552b84 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_assets.py
@@ -577,6 +577,14 @@ class TestGetAssets(TestAssets):
                     "wasb://some_asset_bucket_/key",
                 },
             ),
+            # Exact-match ``uri`` filter: only the asset whose full URI 
matches is returned.
+            ({"uri": "s3://folder/key"}, {"s3://folder/key"}),
+            ({"uri": "gcp://bucket/key"}, {"gcp://bucket/key"}),
+            # Repeated ``uri`` params match any of the given URIs.
+            ({"uri": ["s3://folder/key", "gcp://bucket/key"]}, 
{"s3://folder/key", "gcp://bucket/key"}),
+            # A substring of an existing URI must NOT match (unlike 
uri_pattern).
+            ({"uri": "s3://folder"}, set()),
+            ({"uri": "does-not-exist://key"}, set()),
         ],
     )
     @provide_session

Reply via email to