laserninja commented on code in PR #14378: URL: https://github.com/apache/gravitino/pull/14378#discussion_r4429949653
########## design-docs/gravitino-semantic-model-source-lookup-design.md: ########## @@ -0,0 +1,484 @@ +<!-- + 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. +--> + +# Design of Semantic Model Dataset Lookup by Source in Gravitino + +## Background + +A Semantic Model dataset references a table or logical View through `Dataset.source`. Sources may +belong to another catalog or schema in the same metalake. Users browsing a source cannot currently +list its associated Semantic Models without collecting and inspecting model definitions themselves. + +Gravitino should provide this lookup through its existing object relationship API pattern. The +result helps users and agents discover models built from a source and identify the datasets to +inspect when that source changes. + +## Goals + +1. **Discovery.** List fully qualified identifiers of Semantic Model datasets directly associated + with a table or View through REST, Java, and Python, across catalogs and schemas in the same + metalake. +2. **Visibility.** Require access to the source metadata and return datasets only from models the + caller can load. +3. **Consistency.** Maintain source associations in the same transaction as model definition writes. +4. **Integration.** Follow existing object relationship APIs, client capability interfaces, and + listener and audit conventions. + +## Non-Goals + +1. Transitive View dependencies, column or metric impact analysis, and validation of whether a + model can currently compile or execute. These checks require additional lineage or compiler + analysis beyond the source references explicitly declared by datasets. +2. Automatically changing model definitions, blocking source changes because models reference the + source, detecting mismatches between model references and current sources, and sending + notifications. These need separate decisions about detecting source changes, permissions to + modify affected models, and handling update or notification failures. +3. Independent association management, exposing internal source IDs, and historical association + queries. Associations are derived from model definitions and updated when those definitions are + written. Editing associations separately could make them disagree with the definitions. Discovery + of current datasets needs neither exposed source IDs nor retained association history. +4. Stable dataset IDs, dataset-specific privileges, and independent dataset lifecycle APIs. Dataset + identifiers locate datasets in the current model definition. Tracking the same dataset across + model versions or authorizing it separately requires rules for dataset identity and lifecycle. + +## Proposal + +### Association Contract + +Each dataset has one association to the registered Gravitino source ID and type resolved during the +latest successful model definition write (create, import, overwrite, or replacement). The model ID +and exact dataset name identify which dataset an association belongs to. Only tables or Views named +in `Dataset.source` are indexed; lookup does not follow the dependencies of a referenced View. + +`Dataset.name` is a case-sensitive alias unique within the model and need not match the table or +View name in `Dataset.source`. + +Definition updates and association updates must commit in the same transaction. Each association's +`dataset_name` must match the corresponding `Dataset.name` in that committed definition. + +Lookup returns one identifier per matching dataset whose model has not been deleted and can be +loaded by the caller. For example, model `analytics.semantic.employee_analysis` in metalake `example` +has two datasets referencing the same source: + +| Model | Dataset | Source | +| ------------------- | ---------- | --------------------- | +| `employee_analysis` | `employee` | `hr.public.employees` | +| `employee_analysis` | `manager` | `hr.public.employees` | + +Storing this model creates two association rows; looking up `hr.public.employees` returns both +dataset identifiers. Callers needing only models derive the containing model identifiers and remove +duplicates. Updates follow [Lifecycle Behavior](#lifecycle-behavior); drift and other constraints +are covered under [Known Limitations](#known-limitations). + +### API Contract + +#### Java API + +Add a read-only capability in the semantic API package: + +```java +/** Lists Semantic Model datasets associated with a source metadata object. */ +public interface SupportsSemanticModels { + + /** + * Lists datasets directly associated with this source from visible Semantic Models. + * + * @return The fully qualified dataset identifiers, or an empty array. + * @throws NoSuchMetadataObjectException If the source does not exist. + */ + NameIdentifier[] listSemanticModelDatasets() throws NoSuchMetadataObjectException; +} +``` + +Each result has five levels in total: namespace `[metalake, catalog, schema, semanticModel]` and the +exact dataset name as `name`, following the existing metric identifier convention. This identifies +a member of the current model definition and does not create a separate dataset metadata object. + +Construct identifiers from components with `NameIdentifier.of(namespace, datasetName)` and preserve +the structured `namespace` and `name` during serialization. Dataset names may contain literal dots. +Joining components with `.` and splitting that string again would treat dots in dataset names as +namespace separators. + +`Table` and `View` add the following default capability accessor, following `supportsTags()`: + +```java +/** + * Returns the Semantic Model dataset lookup operations for this source. + * + * @return The Semantic Model dataset lookup operations. + * @throws UnsupportedOperationException If the implementation does not support lookup. + */ +default SupportsSemanticModels supportsSemanticModels() { + throw new UnsupportedOperationException("Semantic Model dataset lookup is not supported"); +} +``` + +The Gravitino Java client's table and View implementations provide this capability through shared +object relationship request handling. Underlying catalog connectors do not implement the lookup. + +```java +NameIdentifier[] datasets = table.supportsSemanticModels().listSemanticModelDatasets(); +``` + +Results can belong to a different catalog from the source. Java and Python therefore return the same +fully qualified five-level identifiers as REST, including metalake and catalog in the namespace. +For a result `dataset`, `NameIdentifier.of(dataset.namespace().levels())` gives its containing model +identifier. Remove duplicates from these model identifiers to obtain the associated model list. + +#### REST API + +Add a route alongside the existing object tag and policy routes: + +```http +GET /api/metalakes/{metalake}/objects/{type}/{fullName}/semantic-model-datasets +``` + +`type` accepts `table` or `view`. `fullName` contains the source's catalog, schema, and name using the +existing `MetadataObject` format, such as `hr.public.employees`, with normal URL path encoding. +Limits on names containing literal dots are described under [Known Limitations](#known-limitations). + +The endpoint implements `listSemanticModelDatasets` and always returns the existing +`EntityListResponse`, with one identifier per matching dataset. For the employee example above: + +```json +{ + "code": 0, + "identifiers": [ + { + "namespace": ["example", "analytics", "semantic", "employee_analysis"], + "name": "employee" + }, + { + "namespace": ["example", "analytics", "semantic", "employee_analysis"], + "name": "manager" + } + ] +} +``` + +- **Uniqueness and ordering.** Return each dataset identifier once. Sort lexicographically by the + model namespace components (`metalake`, `catalog`, `schema`, `semanticModel`), then by the + case-sensitive dataset name. This order is independent of dataset order in the definition. + Compare every component case-sensitively by UTF-16 code units (Java `String.compareTo` order), + independent of locale and database collation. +- **Empty results.** Return `identifiers: []` when the source exists but no matching datasets are + visible. +- **Errors.** Unsupported source types return `400`. Missing or inaccessible sources follow the + existing object relationship API error behavior, rather than becoming empty successful results. + Storage and source lookup failures fail the request without partial results. + +The response contains identifiers only. Callers select the result's catalog and use the existing +model load API to inspect the containing model and its datasets. No internal IDs, validity status, +hidden-model counts, or association mutation APIs are added. + +#### Python API + +The Python table and View APIs add `supports_semantic_models()`, returning a capability with +`list_semantic_model_datasets() -> list[NameIdentifier]`. It uses the same REST endpoint and full +four-level namespace plus dataset name as Java. Java and Python clients preserve identifier +components and server ordering and do not scan catalogs or load all models to perform lookup. + +### Authorization + +- **Source access.** The REST entry point uses `CAN_ACCESS_METADATA` for the source, as existing + object relationship APIs do. Source type, name normalization, existence checks, and internal ID + resolution reuse the existing table and View paths. Source existence is still checked when + authorization is disabled. +- **Model visibility.** For each distinct candidate model, apply + `LOAD_SEMANTIC_MODEL_AUTHORIZATION_EXPRESSION` in the caller's context using the containing model + identifier, not the dataset identifier. Include or exclude all matching datasets from that model + together. Apply this filtering in REST before constructing the response. +- **Catalog and schema access.** Apply the model load rules for ownership and use or modify + privileges, including access to that model's catalog and schema. Schema listing checks the schema + named in its request before filtering models. This lookup can return models from several schemas + and catalogs, so it must check each model's own parents using the full load expression. +- **Failure handling.** Reuse `MetadataAuthzHelper.filterByExpression`: exclude a model if its + authorization check denies access or throws an exception, and log evaluation exceptions. If the Review Comment: **Distinguish authorization denial from authorization failure.** An authorization service outage could produce a successful empty or incomplete result here, leaving clients unaware that they should retry. Permission denials should continue to omit the model, but operational authorization failures should fail the request without exposing model identities. Could we distinguish those cases and reconcile this with the REST contract stating that storage failures fail the request without partial results? ########## design-docs/gravitino-semantic-model-source-lookup-design.md: ########## @@ -0,0 +1,484 @@ +<!-- + 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. +--> + +# Design of Semantic Model Dataset Lookup by Source in Gravitino + +## Background + +A Semantic Model dataset references a table or logical View through `Dataset.source`. Sources may +belong to another catalog or schema in the same metalake. Users browsing a source cannot currently +list its associated Semantic Models without collecting and inspecting model definitions themselves. + +Gravitino should provide this lookup through its existing object relationship API pattern. The +result helps users and agents discover models built from a source and identify the datasets to +inspect when that source changes. + +## Goals + +1. **Discovery.** List fully qualified identifiers of Semantic Model datasets directly associated + with a table or View through REST, Java, and Python, across catalogs and schemas in the same + metalake. +2. **Visibility.** Require access to the source metadata and return datasets only from models the + caller can load. +3. **Consistency.** Maintain source associations in the same transaction as model definition writes. +4. **Integration.** Follow existing object relationship APIs, client capability interfaces, and + listener and audit conventions. + +## Non-Goals + +1. Transitive View dependencies, column or metric impact analysis, and validation of whether a + model can currently compile or execute. These checks require additional lineage or compiler + analysis beyond the source references explicitly declared by datasets. +2. Automatically changing model definitions, blocking source changes because models reference the + source, detecting mismatches between model references and current sources, and sending + notifications. These need separate decisions about detecting source changes, permissions to + modify affected models, and handling update or notification failures. +3. Independent association management, exposing internal source IDs, and historical association + queries. Associations are derived from model definitions and updated when those definitions are + written. Editing associations separately could make them disagree with the definitions. Discovery + of current datasets needs neither exposed source IDs nor retained association history. +4. Stable dataset IDs, dataset-specific privileges, and independent dataset lifecycle APIs. Dataset + identifiers locate datasets in the current model definition. Tracking the same dataset across + model versions or authorizing it separately requires rules for dataset identity and lifecycle. + +## Proposal + +### Association Contract + +Each dataset has one association to the registered Gravitino source ID and type resolved during the +latest successful model definition write (create, import, overwrite, or replacement). The model ID +and exact dataset name identify which dataset an association belongs to. Only tables or Views named +in `Dataset.source` are indexed; lookup does not follow the dependencies of a referenced View. + +`Dataset.name` is a case-sensitive alias unique within the model and need not match the table or +View name in `Dataset.source`. + +Definition updates and association updates must commit in the same transaction. Each association's +`dataset_name` must match the corresponding `Dataset.name` in that committed definition. + +Lookup returns one identifier per matching dataset whose model has not been deleted and can be +loaded by the caller. For example, model `analytics.semantic.employee_analysis` in metalake `example` +has two datasets referencing the same source: + +| Model | Dataset | Source | +| ------------------- | ---------- | --------------------- | +| `employee_analysis` | `employee` | `hr.public.employees` | +| `employee_analysis` | `manager` | `hr.public.employees` | + +Storing this model creates two association rows; looking up `hr.public.employees` returns both +dataset identifiers. Callers needing only models derive the containing model identifiers and remove +duplicates. Updates follow [Lifecycle Behavior](#lifecycle-behavior); drift and other constraints +are covered under [Known Limitations](#known-limitations). + +### API Contract + +#### Java API + +Add a read-only capability in the semantic API package: + +```java +/** Lists Semantic Model datasets associated with a source metadata object. */ +public interface SupportsSemanticModels { + + /** + * Lists datasets directly associated with this source from visible Semantic Models. + * + * @return The fully qualified dataset identifiers, or an empty array. + * @throws NoSuchMetadataObjectException If the source does not exist. + */ + NameIdentifier[] listSemanticModelDatasets() throws NoSuchMetadataObjectException; +} +``` + +Each result has five levels in total: namespace `[metalake, catalog, schema, semanticModel]` and the +exact dataset name as `name`, following the existing metric identifier convention. This identifies +a member of the current model definition and does not create a separate dataset metadata object. + +Construct identifiers from components with `NameIdentifier.of(namespace, datasetName)` and preserve +the structured `namespace` and `name` during serialization. Dataset names may contain literal dots. +Joining components with `.` and splitting that string again would treat dots in dataset names as +namespace separators. + +`Table` and `View` add the following default capability accessor, following `supportsTags()`: + +```java +/** + * Returns the Semantic Model dataset lookup operations for this source. + * + * @return The Semantic Model dataset lookup operations. + * @throws UnsupportedOperationException If the implementation does not support lookup. + */ +default SupportsSemanticModels supportsSemanticModels() { + throw new UnsupportedOperationException("Semantic Model dataset lookup is not supported"); +} +``` + +The Gravitino Java client's table and View implementations provide this capability through shared +object relationship request handling. Underlying catalog connectors do not implement the lookup. + +```java +NameIdentifier[] datasets = table.supportsSemanticModels().listSemanticModelDatasets(); +``` + +Results can belong to a different catalog from the source. Java and Python therefore return the same +fully qualified five-level identifiers as REST, including metalake and catalog in the namespace. +For a result `dataset`, `NameIdentifier.of(dataset.namespace().levels())` gives its containing model +identifier. Remove duplicates from these model identifiers to obtain the associated model list. + +#### REST API + +Add a route alongside the existing object tag and policy routes: + +```http +GET /api/metalakes/{metalake}/objects/{type}/{fullName}/semantic-model-datasets +``` + +`type` accepts `table` or `view`. `fullName` contains the source's catalog, schema, and name using the +existing `MetadataObject` format, such as `hr.public.employees`, with normal URL path encoding. +Limits on names containing literal dots are described under [Known Limitations](#known-limitations). + +The endpoint implements `listSemanticModelDatasets` and always returns the existing +`EntityListResponse`, with one identifier per matching dataset. For the employee example above: + +```json +{ + "code": 0, + "identifiers": [ + { + "namespace": ["example", "analytics", "semantic", "employee_analysis"], + "name": "employee" + }, + { + "namespace": ["example", "analytics", "semantic", "employee_analysis"], + "name": "manager" + } + ] +} +``` + +- **Uniqueness and ordering.** Return each dataset identifier once. Sort lexicographically by the + model namespace components (`metalake`, `catalog`, `schema`, `semanticModel`), then by the + case-sensitive dataset name. This order is independent of dataset order in the definition. + Compare every component case-sensitively by UTF-16 code units (Java `String.compareTo` order), + independent of locale and database collation. +- **Empty results.** Return `identifiers: []` when the source exists but no matching datasets are + visible. +- **Errors.** Unsupported source types return `400`. Missing or inaccessible sources follow the + existing object relationship API error behavior, rather than becoming empty successful results. + Storage and source lookup failures fail the request without partial results. + +The response contains identifiers only. Callers select the result's catalog and use the existing +model load API to inspect the containing model and its datasets. No internal IDs, validity status, +hidden-model counts, or association mutation APIs are added. + +#### Python API + +The Python table and View APIs add `supports_semantic_models()`, returning a capability with +`list_semantic_model_datasets() -> list[NameIdentifier]`. It uses the same REST endpoint and full +four-level namespace plus dataset name as Java. Java and Python clients preserve identifier +components and server ordering and do not scan catalogs or load all models to perform lookup. + +### Authorization + +- **Source access.** The REST entry point uses `CAN_ACCESS_METADATA` for the source, as existing + object relationship APIs do. Source type, name normalization, existence checks, and internal ID + resolution reuse the existing table and View paths. Source existence is still checked when + authorization is disabled. +- **Model visibility.** For each distinct candidate model, apply + `LOAD_SEMANTIC_MODEL_AUTHORIZATION_EXPRESSION` in the caller's context using the containing model + identifier, not the dataset identifier. Include or exclude all matching datasets from that model + together. Apply this filtering in REST before constructing the response. +- **Catalog and schema access.** Apply the model load rules for ownership and use or modify + privileges, including access to that model's catalog and schema. Schema listing checks the schema + named in its request before filtering models. This lookup can return models from several schemas + and catalogs, so it must check each model's own parents using the full load expression. +- **Failure handling.** Reuse `MetadataAuthzHelper.filterByExpression`: exclude a model if its + authorization check denies access or throws an exception, and log evaluation exceptions. If the + caller fails the source access check, fail the entire request. When authorization is disabled, + skip model visibility filtering. +- **Association writes.** These are part of an already authorized model definition write and its + source validation. They do not introduce a new privilege or require the source owner to modify + other users' model definitions. + +Dataset-level privileges are not introduced, and owning a source does not grant access to its +associated models. Lookup grants no data access or model modification privileges. + +### Alternatives + +| Approach | Trade-offs | +| -------------------------- | ------------------------------------------------------------------------------------------------------- | +| Scan stored definitions | Avoids a new table, but reads and parses models across the metalake for each lookup. | +| Index source names | Names must be updated after a rename and can refer to different objects after name reuse. | +| Index source IDs and types | Keeps associations across source renames that preserve IDs; requires transactional association updates. | + +This proposal stores source IDs and types in the relation. Model load, export, and compilation +continue to use the source names in the stored definition. The relation records the source objects +resolved when that definition was written; it does not rewrite or override those names. + +A row containing only a model ID and source ID cannot identify which datasets reference that source. +Matching `Dataset.source` against the source's current name is unreliable after a source rename. +Store the dataset name in each association so lookup can return dataset identifiers. Callers can +derive the model list from those identifiers; no second model-level relation table is needed. + +### Internal Storage + +Add `semantic_model_source_rel` without changing the public definition or model snapshot format: + +```sql +CREATE TABLE IF NOT EXISTS `semantic_model_source_rel` ( + `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'auto increment id', + `metalake_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'containing metalake id', + `semantic_model_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'containing semantic model id', + `dataset_name` MEDIUMTEXT NOT NULL COMMENT 'exact dataset name in the current definition', + `source_type` VARCHAR(64) NOT NULL COMMENT 'TABLE or VIEW', + `source_id` BIGINT(20) UNSIGNED NOT NULL COMMENT 'resolved source object id', + PRIMARY KEY (`id`), + KEY `idx_smsr_model` (`semantic_model_id`), + KEY `idx_smsr_source` (`metalake_id`, `source_type`, `source_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin + COMMENT 'current semantic model dataset source associations'; +``` + +Use equivalent auto-generated keys, types, and indexes for H2 and PostgreSQL. + +- **Dataset names.** Existing validation requires non-empty names but sets no maximum name length. + Store the full name as text without truncation or case normalization. Within each model, + validation rejects duplicate dataset names using case-sensitive string comparison. +- **Row identity.** The primary key uses the auto-increment `id`, so `dataset_name` need not fit in + an indexed key. This ID identifies a stored association row and is never returned by public APIs. + Replacing a model's association set allocates new row IDs; these IDs do not track datasets across + definition versions. +- **Model index.** The index on `semantic_model_id` supports replacing or deleting a model's + complete association set. +- **History.** Association rows have no version, audit payload, or copy of the source name. The + model's snapshots and audit records retain the definition history, but replacing or deleting + associations removes the old rows. Keeping an old model snapshot does not keep its association + rows. + +The table contains one row per dataset, including separate rows when datasets share a source. +Resolve each distinct source once per definition write, then reuse that result in each dataset's +row. + +#### Read and Write Paths + +**Writes** + +- **Resolve sources.** Source validation already loads each distinct dataset source under the + caller. Extend this path to retain the actual resolved object type and ID. Reuse the resolved + object for datasets sharing a source. +- **Build associations.** Pass each dataset's exact name and resolved source ID and type into model + persistence. Use the existing source lookup path to normalize names and resolve objects. Storage + must use that lookup result, without guessing TABLE versus VIEW or accepting IDs from public + requests. +- **Pass update intent.** Carry an explicit `replace` or `preserve` instruction through the internal + dispatcher and storage write path into the model transaction. `replace` carries the complete + validated association set; `preserve` carries no replacement set. Do not infer this instruction by + comparing old and new definitions. Keep this write context out of public request DTOs and model + snapshots. +- **Commit together.** Create, import, explicit overwrite, and `replaceDefinition` replace the + complete dataset association set together with the model metadata, complete snapshot, and + current-version pointer. Reuse the existing model transaction and optimistic concurrency checks. + This handles dataset addition, removal, rename, reordering, and source changes without separate + dataset operations. +- **Explicit replacement.** Every explicit definition replacement resolves sources again, even if + the submitted source names or serialized definition are unchanged. This permits an explicit + replacement to record a different source ID when the same name now refers to a new object. Review Comment: **Make rebinding during ordinary definition edits explicit.** Suppose table A is renamed and table B takes its old name. A user then changes only a metric expression through `replaceDefinition`. Saving that definition also moves unchanged dataset associations from A to B. The document acknowledges rebinding, but explicit definition replacement does not necessarily mean the user intended to switch sources. Could we either document and test this ordinary editing scenario as part of the chosen contract, or require explicit rebinding when an unchanged source reference resolves to a different ID? ########## design-docs/gravitino-semantic-model-source-lookup-design.md: ########## @@ -0,0 +1,484 @@ +<!-- + 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. +--> + +# Design of Semantic Model Dataset Lookup by Source in Gravitino + +## Background + +A Semantic Model dataset references a table or logical View through `Dataset.source`. Sources may +belong to another catalog or schema in the same metalake. Users browsing a source cannot currently +list its associated Semantic Models without collecting and inspecting model definitions themselves. + +Gravitino should provide this lookup through its existing object relationship API pattern. The +result helps users and agents discover models built from a source and identify the datasets to +inspect when that source changes. + +## Goals + +1. **Discovery.** List fully qualified identifiers of Semantic Model datasets directly associated + with a table or View through REST, Java, and Python, across catalogs and schemas in the same + metalake. +2. **Visibility.** Require access to the source metadata and return datasets only from models the + caller can load. +3. **Consistency.** Maintain source associations in the same transaction as model definition writes. +4. **Integration.** Follow existing object relationship APIs, client capability interfaces, and + listener and audit conventions. + +## Non-Goals + +1. Transitive View dependencies, column or metric impact analysis, and validation of whether a + model can currently compile or execute. These checks require additional lineage or compiler + analysis beyond the source references explicitly declared by datasets. +2. Automatically changing model definitions, blocking source changes because models reference the + source, detecting mismatches between model references and current sources, and sending + notifications. These need separate decisions about detecting source changes, permissions to + modify affected models, and handling update or notification failures. +3. Independent association management, exposing internal source IDs, and historical association + queries. Associations are derived from model definitions and updated when those definitions are + written. Editing associations separately could make them disagree with the definitions. Discovery + of current datasets needs neither exposed source IDs nor retained association history. +4. Stable dataset IDs, dataset-specific privileges, and independent dataset lifecycle APIs. Dataset + identifiers locate datasets in the current model definition. Tracking the same dataset across + model versions or authorizing it separately requires rules for dataset identity and lifecycle. + +## Proposal + +### Association Contract + +Each dataset has one association to the registered Gravitino source ID and type resolved during the +latest successful model definition write (create, import, overwrite, or replacement). The model ID +and exact dataset name identify which dataset an association belongs to. Only tables or Views named +in `Dataset.source` are indexed; lookup does not follow the dependencies of a referenced View. + +`Dataset.name` is a case-sensitive alias unique within the model and need not match the table or +View name in `Dataset.source`. + +Definition updates and association updates must commit in the same transaction. Each association's +`dataset_name` must match the corresponding `Dataset.name` in that committed definition. + +Lookup returns one identifier per matching dataset whose model has not been deleted and can be +loaded by the caller. For example, model `analytics.semantic.employee_analysis` in metalake `example` +has two datasets referencing the same source: + +| Model | Dataset | Source | +| ------------------- | ---------- | --------------------- | +| `employee_analysis` | `employee` | `hr.public.employees` | +| `employee_analysis` | `manager` | `hr.public.employees` | + +Storing this model creates two association rows; looking up `hr.public.employees` returns both +dataset identifiers. Callers needing only models derive the containing model identifiers and remove +duplicates. Updates follow [Lifecycle Behavior](#lifecycle-behavior); drift and other constraints +are covered under [Known Limitations](#known-limitations). + +### API Contract + +#### Java API + +Add a read-only capability in the semantic API package: + +```java +/** Lists Semantic Model datasets associated with a source metadata object. */ +public interface SupportsSemanticModels { + + /** + * Lists datasets directly associated with this source from visible Semantic Models. + * + * @return The fully qualified dataset identifiers, or an empty array. + * @throws NoSuchMetadataObjectException If the source does not exist. + */ + NameIdentifier[] listSemanticModelDatasets() throws NoSuchMetadataObjectException; +} +``` + +Each result has five levels in total: namespace `[metalake, catalog, schema, semanticModel]` and the +exact dataset name as `name`, following the existing metric identifier convention. This identifies +a member of the current model definition and does not create a separate dataset metadata object. + +Construct identifiers from components with `NameIdentifier.of(namespace, datasetName)` and preserve +the structured `namespace` and `name` during serialization. Dataset names may contain literal dots. +Joining components with `.` and splitting that string again would treat dots in dataset names as +namespace separators. + +`Table` and `View` add the following default capability accessor, following `supportsTags()`: + +```java +/** + * Returns the Semantic Model dataset lookup operations for this source. + * + * @return The Semantic Model dataset lookup operations. + * @throws UnsupportedOperationException If the implementation does not support lookup. + */ +default SupportsSemanticModels supportsSemanticModels() { + throw new UnsupportedOperationException("Semantic Model dataset lookup is not supported"); +} +``` + +The Gravitino Java client's table and View implementations provide this capability through shared +object relationship request handling. Underlying catalog connectors do not implement the lookup. + +```java +NameIdentifier[] datasets = table.supportsSemanticModels().listSemanticModelDatasets(); +``` + +Results can belong to a different catalog from the source. Java and Python therefore return the same +fully qualified five-level identifiers as REST, including metalake and catalog in the namespace. +For a result `dataset`, `NameIdentifier.of(dataset.namespace().levels())` gives its containing model +identifier. Remove duplicates from these model identifiers to obtain the associated model list. + +#### REST API + +Add a route alongside the existing object tag and policy routes: + +```http +GET /api/metalakes/{metalake}/objects/{type}/{fullName}/semantic-model-datasets +``` + +`type` accepts `table` or `view`. `fullName` contains the source's catalog, schema, and name using the +existing `MetadataObject` format, such as `hr.public.employees`, with normal URL path encoding. +Limits on names containing literal dots are described under [Known Limitations](#known-limitations). + +The endpoint implements `listSemanticModelDatasets` and always returns the existing +`EntityListResponse`, with one identifier per matching dataset. For the employee example above: Review Comment: **Define bounded lookup behavior before freezing the API.** One shared source can have many datasets across many models. This contract requires returning every match, checking each model's visibility, and sorting the results globally. The source index helps find matching rows, but does not bound authorization work, memory, or response size. Could we specify cursor pagination and bounded processing, including behavior under concurrent edits and permission filtering? Otherwise, we should document a supported cardinality limit and an explicit overflow error rather than silently truncating results. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
