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

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


The following commit(s) were added to refs/heads/main by this push:
     new 6fc1fef8fb [#14377] docs: Add Semantic Model source lookup design 
(#14378)
6fc1fef8fb is described below

commit 6fc1fef8fbe589978a224023ca9af4ffee498c9f
Author: mchades <[email protected]>
AuthorDate: Wed Nov 4 19:37:37 2026 +0800

    [#14377] docs: Add Semantic Model source lookup design (#14378)
    
    ### What changes were proposed in this pull request?
    
    Add the design for looking up Semantic Model datasets from a source
    table or View. Cover REST and client APIs, authorization, transactional
    association storage, lifecycle behavior, known limitations, and the
    implementation plan.
    
    ### Why are the changes needed?
    
    Users and agents need to discover models built from a source without
    scanning model definitions. This establishes the lookup contract as a
    foundation for #13334.
    
    Fixes #14377.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No runtime changes. The APIs described in the document are proposed and
    are not implemented by this PR.
    
    ### How was this patch tested?
    
    Checked Markdown parsing, local links and anchors, JSON examples, and
    whitespace. No code tests were run for this documentation-only change.
---
 ...ravitino-semantic-model-source-lookup-design.md | 558 +++++++++++++++++++++
 1 file changed, 558 insertions(+)

diff --git a/design-docs/gravitino-semantic-model-source-lookup-design.md 
b/design-docs/gravitino-semantic-model-source-lookup-design.md
new file mode 100644
index 0000000000..64c7081a85
--- /dev/null
+++ b/design-docs/gravitino-semantic-model-source-lookup-design.md
@@ -0,0 +1,558 @@
+<!--
+  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 observed during
+source validation for 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',
+    `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'association 
retired at',
+    PRIMARY KEY (`id`),
+    KEY `idx_smsr_model` (`semantic_model_id`, `deleted_at`),
+    KEY `idx_smsr_source` (`metalake_id`, `source_type`, `source_id`, 
`deleted_at`)
+) 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` and `deleted_at` supports 
replacing or retiring
+  a model's complete live association set.
+- **Retirement.** `deleted_at = 0` identifies a live association. Replacement 
or model deletion
+  soft-deletes old rows by recording their retirement time in epoch 
milliseconds. Only the
+  legacy-timeline garbage collector physically deletes retired rows.
+- **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. Retired 
association rows are
+  temporary cleanup state and are excluded from lookup. Snapshot retention 
neither keeps retired
+  associations nor retires current ones.
+
+The table contains one live row per dataset, including separate rows when 
datasets share a source.
+This is a write-path invariant maintained by dataset-name validation and 
complete-set replacement
+under the existing model transaction and concurrency checks. The DDL does not 
define a unique key
+on `(semantic_model_id, dataset_name)`.
+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 registered `TableEntity` or 
`ViewEntity` ID and type
+  observed by that validation. These IDs come from internal registered 
metadata, not the public
+  `Table` or `View` contract. Complete any required source registration before 
model persistence;
+  association creation must not depend on a later lookup request. 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.** Choose one mode for the entire alter request: 
`replace` if any change is
+  `ReplaceDefinition`, otherwise `preserve`. Carry this explicit instruction 
through the internal
+  dispatcher and storage write path into the model transaction. `replace` 
carries the complete
+  validated association set for the final definition after applying all 
changes; `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. Soft-delete the model's previous live association 
rows and insert the
+  complete new set with `deleted_at = 0` in that transaction. 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
+  only a metric expression changes or the submitted definition is identical. 
After a source rename
+  and reuse of its old name by another object, a successful replacement 
records the new object's ID
+  for unchanged dataset source references.
+- **Metadata-only updates.** Requests containing only model rename, comment, 
or property changes
+  preserve association rows and do not resolve dataset sources again. If the 
same request includes
+  `ReplaceDefinition`, the whole request uses `replace` regardless of the 
order of metadata changes.
+
+Any write failure rolls back both the model changes and the association 
changes. Update association
+rows inside the model transaction; do not defer these writes to post-write 
event listeners.
+
+**Reads**
+
+- **Source lookup.** Resolve the requested source to its current ID and type, 
then query
+  `idx_smsr_source` using the metalake ID, source type, source ID, and 
`deleted_at = 0`.
+- **Relation query.** Add `SEMANTIC_MODEL_SOURCE_REL` to 
`SupportsRelationOperations.Type` and query
+  it through `RelationQuery` with a table or View as the anchor. A dedicated 
relational service and
+  mapper serve the read through the existing EntityStore relation query 
abstraction.
+- **Consistent metadata.** Join associations with current model, schema, 
catalog, and metalake
+  metadata, excluding deleted objects. Use one statement or a consistent read 
snapshot to read each
+  matching dataset's exact name and its model's current identifier. No 
definition JSON or separate
+  per-model loads are needed.
+- **Internal query result.** Use a lightweight model object implementing 
`Entity` and
+  `HasIdentifier`. It contains the model's ID, current identifier, 
`SEMANTIC_MODEL` type, and the
+  matching dataset names.
+- **Result identifiers.** Deduplicate matches by model ID and exact, 
case-sensitive dataset name;
+  same-named datasets in different models remain distinct. Construct each 
five-level dataset
+  identifier from the model's current identifier components and the recorded 
dataset name. No
+  dataset entity type is required. After a model rename, returned dataset 
namespaces contain the
+  new model name.
+
+#### Lifecycle Behavior
+
+| Change                                       | Association and lookup 
behavior                                                                        
 |
+| -------------------------------------------- | 
-------------------------------------------------------------------------------------------------------
 |
+| Source renamed with the same ID              | Keep the stored source ID and 
type; lookup through the new name returns the same dataset identifiers.   |
+| Source deleted                               | Preserve the model's 
association; lookup through a missing source fails.                             
   |
+| Source name reused by a different object     | Lookup resolves the reused 
name to the new ID and excludes datasets associated with the old ID.         |
+| Definition explicitly replaced               | Resolve sources again and 
replace the association set atomically.                                       |
+| Dataset renamed                              | Store the new dataset name 
and return its new identifier; the old identifier does not track the rename. |
+| Dataset removed                              | Retire its row and omit its 
identifier; other matching dataset results remain.                          |
+| Dataset source changed                       | Store the newly resolved 
source ID and type; lookup returns this dataset for that source.               |
+| Datasets reordered                           | Replace the association set; 
order alone does not change the returned identifiers.                      |
+| Model renamed without definition replacement | Keep association rows; 
returned dataset namespaces contain the new model name.                         
 |
+| Model deleted directly                       | Retire its live associations 
in the same model deletion transaction.                                    |
+| Model deleted by parent deletion             | Retire its live associations 
in the deleting parent's cascade transaction.                              |
+| Model dropped and recreated                  | Create associations for the 
new model ID.                                                               |
+
+Source rename or deletion does not rewrite definitions, block the source 
operation, or grant
+additional privileges. Models remain loadable for inspection and explicit 
repair through the normal
+model update API, following the [existing Semantic Model 
design][semantic-design].
+
+Retire associations in 
`SemanticModelMetaService.deleteSemanticModelWithVersion` and in the bulk
+deletion transactions in `SchemaMetaService`, `CatalogMetaService`, and 
`MetalakeMetaService`.
+Parent cascades do not call the direct model-delete method. Identify affected 
model IDs from the
+models' own schema, catalog, or metalake within the parent deletion 
transaction, then retire their
+live associations atomically with model deletion. Cleanup follows model 
ownership, including
+descendant schemas, rather than the source's namespace.
+
+Extend `SemanticModelMetaService.deleteSemanticModelMetasByLegacyTimeline` to 
purge association
+rows with `0 < deleted_at < legacyTimeline`, using the existing per-table 
deletion limit. This
+cleanup must depend only on the association's retirement timestamp, so it 
still works after the
+model or its parents have been physically removed. Snapshot retention must not 
retire current
+association rows.
+
+Deleting a source, directly or through its parents, must not retire 
associations for models that
+still exist. Those associations remain live until the model definition is 
replaced or the model is
+deleted. Lookup excludes deleted models and models whose schema, catalog, or 
metalake has been
+deleted. Do not add a source foreign key or register source IDs for cleanup 
through
+`OrphanedMetadataObjectRelationService`.
+
+Update the 2.0.0 schema and 1.3.0-to-2.0.0 upgrade scripts for all supported 
databases, following the
+existing Semantic Model storage release boundary. Released versions have no 
Semantic Model data
+to migrate.
+
+## Known Limitations
+
+These limitations describe observable consequences of the association and 
lookup contracts. They do
+not relax the requirement to commit model definitions and their dataset 
associations atomically.
+Each item includes **Current scope** handling through the proposed lookup and 
existing model
+operations, plus optional **Extension** suggestions requiring a separate 
design. These extensions
+are not included in the Development Plan.
+
+1. **Source name drift and validity.**
+
+   - **Source rename.** A source rename can leave `Dataset.source` pointing to 
its old name while the
+     association still records the same source ID. For example, after renaming 
`hr.public.employees`
+     to `hr.public.employees_v2`, lookup through the new name still returns 
identifiers for datasets
+     `employee` and `manager`, while their stored source references can remain 
`hr.public.employees`.
+   - **Validity.** Source column changes can also invalidate a model without 
changing the source ID
+     in its association. Lookup does not revalidate definitions, detect 
outdated references, or
+     verify that a model can compile or execute.
+
+   - **Current scope:** Use lookup through the source's current name to locate 
matching datasets,
+     load the current model, and have a caller with model modification 
permission repair references
+     and affected fields through `replaceDefinition`. Applications that 
compile or execute the model
+     must still validate it against current source metadata.
+
+   - **Extension:** First add read-only checks for differences between stored 
associations, declared
+     source names, and current source metadata. Automatic repair needs 
separate rules for who may
+     edit affected models, how to handle concurrent edits, and what happens 
when a repair fails.
+
+2. **Source name reuse can make execution use a different source.** If a 
different object takes an
+   old source name, an application resolving `Dataset.source` by name may read 
the new object, while
+   the stored association still contains the original source ID. Lookup for 
the new object therefore
+   excludes those datasets. Explicitly replacing the definition resolves 
sources again and updates
+   the associations; lookup itself does not change compilation or execution 
behavior.
+
+   Source deletion or same-name recreation can also occur after source 
validation but before the
+   model transaction commits. A successful write can retain the previously 
resolved source ID even
+   if that source has since been deleted or replaced. Model definitions and 
associations still
+   commit atomically, but the write does not serialize source changes or 
guarantee that the source
+   remains live through commit. Lookup of a replacement source excludes 
datasets bound to the old ID.
+
+   - **Current scope:** Confirm the intended source before replacing a 
definition. Replacement can
+     change stored source IDs even when the submitted names are unchanged. Do 
not use replacement
+     merely to check whether source references are outdated. After concurrent 
source changes, verify
+     the intended source and explicitly replace the definition again if the 
binding needs updating.
+
+   - **Extension:** Add a server-side check before compilation or use: resolve 
`Dataset.source`
+     again and compare the resolved source ID with the stored source ID. 
Report mismatches and
+     require explicit rebinding (updating the stored source association) 
rather than silently
+     switching objects. Coordinating source changes with model commits 
requires a separate design
+     if a concrete use case needs that guarantee.
+
+3. **Registered IDs and external source changes.** Associations identify 
sources by their registered
+   Gravitino IDs. A rename preserves an association only if it preserves that 
ID. How changes made
+   directly in an external catalog affect registered IDs depends on the 
connector and metadata
+   synchronization. The transaction that stores the model does not include 
external catalog changes.
+
+   - **Current scope:** Prefer operations that preserve registered IDs where 
supported. After
+     changes outside Gravitino, refresh metadata through supported connector 
mechanisms and verify
+     the intended source before repairing models. Refreshing metadata alone 
does not guarantee that
+     Gravitino recognizes the source as the same registered object.
+
+   - **Extension:** Use connector-provided stable source IDs or reliable 
change events to reconcile
+     associations with source changes. Report changes for review when it is 
unclear whether the
+     source is still the same object.
+
+4. **Lookup cannot recover associations for a deleted source.** Source 
deletion preserves
+   association rows for models that still exist, but lookup requires an 
existing source. The API
+   accepts neither historical source IDs nor requests to list associations 
whose sources are
+   missing.
+
+   - **Current scope:** Before a planned deletion, record the returned dataset 
identifiers for
+     follow-up repairs. The list includes only datasets visible to the caller 
and does not prove
+     that deletion is safe. After deletion, model APIs can still load affected 
models that exist,
+     but source lookup cannot rediscover all of them.
+
+   - **Extension:** Add an authorized API for inspecting a model's stored 
associations, including
+     those whose sources no longer exist. Define who can read these 
associations and how long they
+     are retained.
+
+5. **Separate requests can observe different model states.** Lookup and a 
subsequent model load are
+   independent requests. If the model is edited between them, the returned 
identifier may name a
+   dataset that has been removed, renamed, or changed to use another source. 
The lookup response has
+   no model revision token with which to verify that a later load returns the 
same definition
+   version.
+
+   - **Current scope:** Load the current definition for inspection and repeat 
source lookup if the
+     model or dataset name is absent. The model can change again between the 
repeated lookup and
+     load.
+
+   - **Extension:** Return opaque model revision tokens from lookup and allow 
model-load requests
+     to require a matching token. Reject the load if the model revision has 
changed, so callers can
+     repeat lookup instead of combining results from different model versions.
+
+6. **Lookup does not list all dependencies.** Results include only directly 
associated datasets
+   from models the caller can load. Dependencies through Views and 
inaccessible models are omitted.
+   A dataset-to-source association does not identify the metrics that depend 
on that dataset. An
+   empty result therefore cannot establish that a source is unused or safe to 
change or delete.
+
+   - **Current scope:** Use results for discovery and inspection; do not infer 
that deletion is safe
+     from an empty list.
+
+   - **Extension:** If source changes must be blocked when models depend on 
them, design a separate
+     impact check and specify which dependencies it checks, including 
dependencies through Views.
+     Account for models hidden from the caller without revealing their 
identities. An incomplete
+     check must not conclude that no dependencies exist.
+
+7. **Dots in source names cannot be represented reliably.** The endpoint uses 
dots to separate the
+   catalog, schema, and source name in `fullName`. A literal dot inside any of 
those names is
+   indistinguishable from a separator; URL encoding does not provide a way to 
quote individual name
+   components. Returned dataset identifiers preserve their separate 
`namespace` and `name` fields.
+
+   - **Current scope:** There is no reliable workaround for literal dots 
through this route;
+     additional URL encoding does not resolve the ambiguity.
+
+   - **Extension:** Introduce a shared structured identifier representation 
for object relationship
+     APIs, preserving catalog, schema, and object name as separate components 
through REST and both
+     SDKs.
+
+8. **Authorization evaluation failures can hide results.** Per-model 
authorization evaluation
+   exceptions exclude the affected models, so the request can succeed with 
incomplete or empty
+   results. This follows the existing per-object authorization filtering 
behavior of list and
+   relationship APIs, including table listing, Tag relationship lookup, and 
metric discovery.
+
+   - **Current scope:** Evaluation exceptions are recorded in server logs. The 
response does not
+     indicate that evaluation failed or that a retry may be needed. 
Request-level authorization and
+     storage failures still fail the request.
+
+   - **Extension:** Distinguishing operational authorization failures from 
ordinary denials would
+     require a shared decision across listing APIs.
+
+9. **Lookup processing and response size are unbounded.** This endpoint is 
unpaginated, consistent
+   with existing relationship and metric discovery APIs. The source index does 
not cap matching
+   datasets or models. Large match sets increase memory use, per-model 
authorization work, and
+   global result sorting; the response includes every dataset that passes 
visibility filtering.
+
+   - **Current scope:** No result-count limit or silent truncation is 
introduced. This proposal
+     does not define a supported maximum cardinality.
+
+   - **Extension:** Define pagination and processing bounds, including 
permission filtering,
+     ordering, and concurrent edits. See the broader REST pagination proposal 
in
+     [#14453](https://github.com/apache/gravitino/issues/14453).
+
+## Development Plan
+
+1. Add the relation table, source resolution result, and transactional 
maintenance for every model
+   definition write and model deletion, including model deletion caused by 
dropping its schema,
+   catalog, or metalake. Cover datasets sharing a source, dataset changes and 
reordering, and
+   transaction rollback. Verify that replacing an identical definition updates 
bindings after
+   same-name source recreation. Also cover a metric-only replacement after 
source rename and name
+   reuse: unchanged dataset source references bind to the new source if 
validation succeeds.
+   Metadata-only changes must preserve bindings after source rename or 
deletion.
+   Cover mixed requests with rename, comment, or property changes before and 
after
+   `ReplaceDefinition`, verifying that associations match the final definition.
+   Cover association retirement in direct and all parent deletion 
transactions, rollback on
+   retirement failure, and legacy-timeline GC with its deletion limit after 
parent metadata is
+   purged. Verify that source deletion, including deletion through its 
parents, and old snapshot
+   retention preserve live associations of models that still exist.
+2. Add the indexed relation query, REST endpoint using `EntityListResponse`, 
source access checks,
+   and full model load authorization checks. Emit pre-operation, success, and 
failure events using
+   `LIST_SEMANTIC_MODEL_DATASETS_BY_SOURCE`, with the requested source table 
or View as the subject
+   and its fully qualified identifier as the event and audit identifier. Add 
this operation to the
+   listener API's `OperationType` and `AuditLog.Operation`, wiring both 
`CompatibilityUtils` and the
+   legacy `AuditLog.Operation.fromEvent` conversion. Verify the source 
identifier, operation name,
+   and success/failure audit mappings in implementation tests.
+3. Add `listSemanticModelDatasets` and its Python equivalent to the table/View 
capability, OpenAPI
+   definitions, and usage documentation, with fully qualified five-level 
dataset identifiers.
+4. Validate cross-catalog lookup, direct View references, model renames, 
source renames with unchanged
+   definition references, source deletion and name reuse, one result per 
matching dataset, and parent
+   model identifier construction from dataset identifiers. Cover structured 
identifier round-trips,
+   dataset names containing dots or long names, hidden models, empty results, 
and lifecycle behavior
+   across supported databases.
+
+[semantic-design]: gravitino-semantic-model-design.md#validation

Reply via email to