Visitar URL original
[Bug report] Lance REST rejects the spec's PascalCase `ExistOk` mode on CreateNamespace and CreateTable · Issue #13680 · apache/gravitino · GitHub
Skip to content

[Bug report] Lance REST rejects the spec's PascalCase ExistOk mode on CreateNamespace and CreateTable #13680

Closed

Description

@FANNG1

Version

1.3.0, and main — CommonUtil.normalizeToken is unchanged on origin/main.

Describe what's wrong

The Lance REST server rejects the ExistOk mode value that the lance-namespace spec documents, on both CreateNamespace and CreateTable.

The spec declares these mode fields as "Case insensitive, supports both PascalCase and snake_case", with valid values Create / ExistOk / Overwrite. CommonUtil.normalizeToken only trims and upper-cases:

public static String normalizeToken(String value) {
  return value == null ? "" : value.trim().toUpperCase(Locale.ROOT);
}

so ExistOk becomes EXISTOK, which does not match the EXIST_OK enum constant that parseEnumToken resolves against:

static <E extends Enum<E>> E parseEnumToken(
    Class<E> enumClass, String value, String errorMessagePrefix, String instance) {
  try {
    return Enum.valueOf(enumClass, normalizeToken(value));
  } catch (IllegalArgumentException e) {
    throw new InvalidInputException(
        errorMessagePrefix + value, formatCurrentStackTrace(), instance);
  }
}

snake_case works (exist_ok → EXIST_OK), so the "supports both" promise holds for exactly one of the two spellings.

ExistOk is the only multi-word constant in the spec's mode/behavior vocabulary, which is why nothing else is affected:

Request field Spec values Status
CreateNamespaceRequest.mode Create / ExistOk / Overwrite ExistOk rejected
CreateTableRequest.mode Create / ExistOk / Overwrite ExistOk rejected
RegisterTableRequest.mode Create / Overwrite ok
DropNamespaceRequest.mode Fail / Skip ok
DropNamespaceRequest.behavior Restrict / Cascade ok

The five parseEnumToken call sites are GravitinoLanceNameSpaceOperations.java:449,457,465 and GravitinoLanceTableOperations.java:444,452.

For reference, the dir namespace implementation in lancedb/lance parses the same field with eq_ignore_ascii_case("existok") || eq_ignore_ascii_case("exist_ok") (rust/lance-namespace-impls/src/dir/manifest.rs), i.e. it accepts both spellings.

Error message and/or stacktrace

lance_namespace.errors.InvalidInputError: Invalid input: Unknown create namespace mode: ExistOk

Server side (includeErrorStackTrace = true):

java.lang.RuntimeException: Captured stacktrace
	at org.apache.gravitino.lance.common.ops.gravitino.CommonUtil.formatCurrentStackTrace(CommonUtil.java:31)
	at org.apache.gravitino.lance.common.ops.gravitino.CommonUtil.parseEnumToken(CommonUtil.java:54)
	at org.apache.gravitino.lance.common.ops.gravitino.GravitinoLanceNameSpaceOperations.parseCreateMode(GravitinoLanceNameSpaceOperations.java:449)
	at org.apache.gravitino.lance.service.rest.LanceNamespaceOperations.createNamespace(LanceNamespaceOperations.java:130)

The table path produces the same thing with Unknown create table mode: ExistOk.

How to reproduce

Gravitino 1.3.0 with the lance-rest auxiliary service on port 9101 and a lakehouse-generic catalog named lance_catalog:

gravitino.auxService.names = iceberg-rest,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
# pip install lance-namespace==0.8.6 pyarrow
import io, uuid, pyarrow as pa, lance_namespace as ln

ns = ln.connect("rest", {"uri": "http://127.0.0.1:9101/lance"})
nsid = ["lance_catalog", f"modeprobe_{uuid.uuid4().hex[:6]}"]

def probe(label, fn):
    try:
        fn()
        print(f"  {label:36} -> OK")
    except Exception as e:
        print(f"  {label:36} -> {type(e).__name__}: {str(e).splitlines()[0]}")

ns.create_namespace(ln.CreateNamespaceRequest(id=nsid, mode="EXIST_OK"))

print("CreateNamespace, namespace already exists:")
for mode in ["ExistOk", "exist_ok", "EXIST_OK", "Overwrite"]:
    probe(f"mode={mode!r}", lambda m=mode: ns.create_namespace(ln.CreateNamespaceRequest(id=nsid, mode=m)))

sink = io.BytesIO()
with pa.ipc.new_stream(sink, pa.schema([pa.field("id", pa.int64())])):
    pass
schema_ipc = sink.getvalue()
tid = nsid + ["t1"]
ns.create_table(ln.CreateTableRequest(id=tid), schema_ipc)

print("CreateTable, table already exists:")
for mode in ["ExistOk", "exist_ok", "EXIST_OK"]:
    probe(f"mode={mode!r}", lambda m=mode: ns.create_table(ln.CreateTableRequest(id=tid, mode=m), schema_ipc))

ns.drop_namespace(ln.DropNamespaceRequest(id=nsid, mode="Skip", behavior="Cascade"))

Observed:

CreateNamespace, namespace already exists:
  mode='ExistOk'                       -> InvalidInputError: Invalid input: Unknown create namespace mode: ExistOk
  mode='exist_ok'                      -> OK
  mode='EXIST_OK'                      -> OK
  mode='Overwrite'                     -> OK
CreateTable, table already exists:
  mode='ExistOk'                       -> InvalidInputError: Invalid input: Unknown create table mode: ExistOk
  mode='exist_ok'                      -> OK
  mode='EXIST_OK'                      -> OK

Expected: ExistOk is accepted wherever exist_ok is.

Additional context

Found while building a Daft × Gravitino Lance REST × MinIO end-to-end suite. A client that follows the spec's own example spelling cannot create a namespace or a table idempotently and has to discover the snake_case spelling by trial.

TestGravitinoLanceModeParsing already covers the snake_case half:

Assertions.assertEquals("EXIST_OK", CommonUtil.normalizeToken("exist_ok"));
Assertions.assertEquals(
    TestMode.EXIST_OK,
    CommonUtil.parseEnumToken(TestMode.class, "exist_ok", "Unknown mode: ", "table"));

There is no corresponding PascalCase assertion, which is presumably why this went unnoticed.

normalizeToken has one other caller, LanceMetadataAuthorizationMethodInterceptor.java:223, which compares against OVERWRITE. That token is single-word, so it is unaffected.

Activity

  1. added a commit that references this issue on Oct 8, 2026
  2. FANNG1 commented on Oct 9, 2026

    @FANNG1
    ContributorAuthor

    @kalayciburak could you comment on this issue, so I could assign to you, thanks!

  3. added a commit that references this issue on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    2.0.0Release v2.0.0bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions