FANNG1 opened a new issue, #12468:
URL: https://github.com/apache/gravitino/issues/12468

   ### Version
   
   main branch
   
   Verified on `main` @ `58e0e17a9`, and reproduced against a deployed 1.3.0 
server.
   
   ### Describe what's wrong
   
   `ListNamespaces` on the Lance REST service returns a successful, empty 
response for **any** two-level identifier, without checking that the identifier 
refers to anything that exists.
   
   `GravitinoLanceNameSpaceOperations.listNamespaces` short-circuits the 
deepest level:
   
   ```java
   // 
lance/lance-common/src/main/java/org/apache/gravitino/lance/common/ops/gravitino/GravitinoLanceNameSpaceOperations.java
   switch (nsId.levels()) {
     case 0:
       namespaces = Arrays.stream(namespaceWrapper.listCatalogsInfo())
           .filter(namespaceWrapper::isLakehouseCatalog)
           .map(Catalog::name).collect(Collectors.toList());
       break;
   
     case 1:
       Catalog catalog = 
namespaceWrapper.loadAndValidateLakehouseCatalog(nsId.levelAtListPos(0));
       namespaces = Lists.newArrayList(namespaceWrapper.listSchemas(catalog));
       break;
   
     case 2:
       namespaces = Lists.newArrayList();   // <-- returns empty without 
validating anything
       break;
   ```
   
   Returning an empty list at level 2 is correct — a schema has no child 
namespaces, only tables. The bug is that it never validates the catalog or the 
schema first, so a nonexistent path is reported as an existing-but-empty 
namespace.
   
   This produces a self-contradictory result: **a two-level identifier succeeds 
while its own one-level prefix fails.**
   
   ```
   GET /lance/v1/namespace/BOGUS_CATALOG%24BOGUS_SCHEMA/list?delimiter=%24
   {"namespaces":[]}                                    <-- success
   
   GET /lance/v1/namespace/BOGUS_CATALOG/list?delimiter=%24
   {"error":"Catalog not found: BOGUS_CATALOG", ...}    <-- correctly fails
   ```
   
   `listNamespaces` is also the **only** namespace operation that behaves this 
way. Every sibling validates at level 2:
   
   | Operation | Level-2 behaviour on a nonexistent schema |
   |---|---|
   | `describeNamespace` | `loadAndValidateLakehouseCatalog` + `loadSchema` → 
`NoSuchSchemaException` |
   | `namespaceExists` | `loadAndValidateLakehouseCatalog` + `schemaExists` → 
`NamespaceNotFoundException` |
   | `dropNamespace` | delegates to `dropSchema` → validates |
   | `createNamespace` | delegates to `createOrUpdateSchema` → validates |
   | **`listNamespaces`** | **returns `{"namespaces":[]}`** |
   
   Since the level-1 branch already resolves the catalog with 
`loadAndValidateLakehouseCatalog`, the level-2 branch skipping it looks like an 
oversight rather than a deliberate choice.
   
   #### Why it matters
   
   `ListNamespaces` is the natural operation for a client to validate a 
configured namespace path — it is cheap, read-only, and available at every 
depth. A client that points its configuration at a mistyped schema gets a clean 
success back and only discovers the mistake much later, on the first query.
   
   Concretely: Apache Doris's Lance REST catalog takes a 
`lance.namespace.parent` property and probes it at catalog-creation time. When 
that parent is two levels deep (`<catalog>$<schema>` — the configuration that 
maps a Doris database onto a Gravitino schema), a typo in the schema name 
passes catalog creation silently. The catalog is created, `SHOW DATABASES` 
returns a single empty entry, and the error only surfaces when someone tries to 
read.
   
   ### Error message and/or stacktrace
   
   There is no error — that is the bug. The request returns HTTP 200 with an 
empty namespace list:
   
   ```console
   $ curl -s 
'http://127.0.0.1:9101/lance/v1/namespace/BOGUS_CATALOG%24BOGUS_SCHEMA/list?delimiter=%24'
   {"namespaces":[]}
   ```
   
   For contrast, the sibling operations on the same nonexistent path do report 
the problem:
   
   ```console
   $ curl -s -X POST 
'http://127.0.0.1:9101/lance/v1/namespace/lance_catalog%24BOGUS_SCHEMA/exists?delimiter=%24'
 \
       -H 'Content-Type: application/json' -d '{}'
   {"error":"Schema not found: BOGUS_SCHEMA","code":1, ...}
   
   $ curl -s -X POST 
'http://127.0.0.1:9101/lance/v1/namespace/lance_catalog%24BOGUS_SCHEMA/describe?delimiter=%24'
 \
       -H 'Content-Type: application/json' -d '{}'
   {"error":"Failed to operate schema(s) [BOGUS_SCHEMA] operation [LOAD] under 
catalog [lance_catalog],
     reason [Schema test.lance_catalog.BOGUS_SCHEMA does not exist]", ...}
   ```
   
   ### How to reproduce
   
   Gravitino 1.3.0 or `main`, with the Lance REST service enabled as an 
auxiliary service:
   
   ```properties
   # conf/gravitino.conf
   gravitino.auxService.names              = lance-rest
   gravitino.lance-rest.classpath          = lance-rest-server/libs
   gravitino.lance-rest.httpPort           = 9101
   gravitino.lance-rest.namespace-backend  = gravitino
   gravitino.lance-rest.gravitino-uri      = http://127.0.0.1:8090
   gravitino.lance-rest.gravitino-metalake = test
   ```
   
   Any `lakehouse-generic` catalog will do; no schema or table needs to exist.
   
   ```bash
   # 1. fully fabricated two-level identifier -> succeeds
   curl -s 
'http://127.0.0.1:9101/lance/v1/namespace/BOGUS_CATALOG%24BOGUS_SCHEMA/list?delimiter=%24'
   # {"namespaces":[]}
   
   # 2. its own one-level prefix -> correctly fails
   curl -s 
'http://127.0.0.1:9101/lance/v1/namespace/BOGUS_CATALOG/list?delimiter=%24'
   # {"error":"Catalog not found: BOGUS_CATALOG", ...}
   
   # 3. real catalog, fabricated schema -> still succeeds
   curl -s 
'http://127.0.0.1:9101/lance/v1/namespace/<real_catalog>%24BOGUS_SCHEMA/list?delimiter=%24'
   # {"namespaces":[]}
   ```
   
   Step 1 succeeding while step 2 fails is the clearest statement of the bug: 
the child of a nonexistent parent is reported to exist.
   
   ### Additional context
   
   Suggested fix — validate before returning the empty list, reusing the 
helpers the sibling operations already use:
   
   ```java
   case 2:
     Catalog parent = 
namespaceWrapper.loadAndValidateLakehouseCatalog(nsId.levelAtListPos(0));
     String schemaName = nsId.levelAtListPos(1);
     if (!namespaceWrapper.schemaExists(parent, schemaName)) {
       throw new NamespaceNotFoundException(
           "Schema not found: " + schemaName, 
CommonUtil.formatCurrentStackTrace(), schemaName);
     }
     namespaces = Lists.newArrayList();
     break;
   ```
   
   This costs one metadata lookup on a path that is currently a no-op, and 
makes `listNamespaces` consistent with `namespaceExists`, which already raises 
exactly this exception for exactly this case.
   
   Worth checking whether `ListTables` has the mirror-image gap at other depths 
while someone is in this code.
   
   Found while testing Apache Doris's Lance REST catalog against Gravitino — 
apache/doris#66772 covers the Doris-side findings from that run. This issue is 
independent of that work and reproducible with `curl` alone.
   
   Related: #8889 (EPIC: add Lance REST Service supports).
   


-- 
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]

Reply via email to