Skip to main content

Lance tables

A generic lakehouse catalog manages Lance tables: tables whose format property is lance. Gravitino records each table's columns and location, and creates, alters, and deletes the Lance dataset at that location. Lance clients such as lance-spark and lance-ray reach the same tables through the Lance REST service. How the format stores a table, and how to fill one with embeddings, is covered in How Lance works.

Quick start​

The example creates the Lance table orders in the schema sales of the generic lakehouse catalog lakehouse_catalog, set up as in the generic lakehouse catalog Quick start. Gravitino writes an empty Lance dataset under the schema's location.

  1. Create the table. format is required. Without a location, the table lands at {catalog_location}/sales/orders.

    Create a Lance table
    curl -sS -X POST "https://{gravitino_host}/api/metalakes/{metalake}/catalogs/lakehouse_catalog/schemas/sales/tables" \
    -H "Accept: application/vnd.gravitino.v1+json" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $TOKEN" \
    -d '{
    "name": "orders",
    "comment": "Orders",
    "columns": [
    {"name": "id", "type": "long", "nullable": false},
    {"name": "amount", "type": "double", "nullable": true}
    ],
    "properties": {"format": "lance"}
    }'
  2. Load the table. The response carries the resolved location, which Lance clients use to read and write the dataset.

    Load a Lance table
    curl -sS "https://{gravitino_host}/api/metalakes/{metalake}/catalogs/lakehouse_catalog/schemas/sales/tables/orders" \
    -H "Accept: application/vnd.gravitino.v1+json" \
    -H "Authorization: Bearer $TOKEN"

To catalog a Lance dataset that already exists instead, see Register an existing dataset.

Supported operations​

OperationSupportedNotes
CreateYesWrites an empty Lance dataset at the table location.
RegisterYesRecords an existing dataset without writing to it. See Register an existing dataset.
ListYes
LoadYesRereads columns from the dataset as set by Schema refresh.
AlterPartlyDrop a column, rename a column, and add an index. Every other change is rejected, including a column type change, renaming the table, changing its comment, and setting or removing a property.
DropYesDeletes the dataset as well unless the table is external.
PurgeYesDeletes the metadata and the dataset, external or not.

Partitioning, sort orders, and distributions are not supported.

Table properties​

PropertyDescriptionDefaultRequired
formatTable format. Must be lance.(none)Yes
locationPath of the Lance dataset, on S3, GCS, Azure storage, Aliyun OSS, or a local file system. Without it, the location comes from the schema or catalog; see Storage location.(none)No
externalWhen true, dropping the table removes only its metadata and leaves the dataset; purging still deletes both. When false, dropping deletes the dataset too.falseNo
lance.creation-modeWhat happens when the table already exists. See Creation modes.CREATENo
lance.registerWhen true, the request records an existing dataset at location instead of writing a new one.falseNo
lance.storage.*Lance storage options for this table, such as lance.storage.aws_access_key_id, overriding the catalog's lance.storage.* values. See the Lance storage options.(none)No

Creation modes​

ModeBehavior
CREATECreates the table and fails if it already exists.
EXIST_OKCreates the table, or returns the existing one unchanged. Not allowed when registering.
OVERWRITEReplaces an existing table. For a created table, the existing dataset is deleted first; for a registered table, only the old metadata is removed.

Data type mappings​

Lance uses Apache Arrow for table schemas. The following table shows type mappings between Gravitino and Arrow:

Gravitino TypeArrow Type
StructStruct
MapNot supported by Lance
ListArray
BooleanBoolean
ByteInt8
ShortInt16
IntegerInt32
LongInt64
FloatFloat
DoubleDouble
StringUtf8
BinaryBinary
Decimal(p, s)Decimal(p, s) (128-bit)
DateDate
Timestamp/Timestamp(6)TimestampType withoutZone
Timestamp(0)TimestampType Second withoutZone
Timestamp(3)TimestampType Millisecond withoutZone
Timestamp(9)TimestampType Nanosecond withoutZone
Timestamp_tz/Timestamp_tz(6)TimestampType Microsecond withUtc
Timestamp_tz(0)TimestampType Second withUtc
Timestamp_tz(3)TimestampType Millisecond withUtc
Timestamp_tz(9)TimestampType Nanosecond withUtc
Time/Time(9)Time Nanosecond
NullNull
Fixed(n)Fixed-Size Binary(n)
Interval_yearNot supported by Lance
Interval_dayDuration(Microsecond)
External(arrow_field_json_str)Any Arrow Field

External types​

For Arrow types not natively mapped in Gravitino, use the External(arrow_field_json_str) type, which accepts a JSON string representation of an Arrow Field.

The JSON must meet these rules:

  • It follows the Apache Arrow Field specification
  • name matches the column name exactly.
  • nullable matches the column's nullability.
  • children is empty for primitive types and holds the child fields for complex types such as Struct and List.

Examples:

Arrow TypeExternal Type Definition
Large Utf8External("{\"name\":\"col_name\",\"nullable\":true,\"type\":{\"name\":\"largeutf8\"},\"children\":[]}")
Large BinaryExternal("{\"name\":\"col_name\",\"nullable\":true,\"type\":{\"name\":\"largebinary\"},\"children\":[]}")
Large ListExternal("{\"name\":\"col_name\",\"nullable\":true,\"type\":{\"name\":\"largelist\"},\"children\":[{\"name\":\"element\",\"nullable\":true,\"type\":{\"name\":\"int\",\"bitWidth\":32,\"isSigned\":true},\"children\":[]}]}")
Fixed-Size ListExternal("{\"name\":\"col_name\",\"nullable\":true,\"type\":{\"name\":\"fixedsizelist\",\"listSize\":10},\"children\":[{\"name\":\"element\",\"nullable\":true,\"type\":{\"name\":\"int\",\"bitWidth\":32,\"isSigned\":true},\"children\":[]}]}")

Schema refresh​

Gravitino stores a Lance table's columns in its own metadata store, but Lance writers can also change the dataset directly at its location. The catalog property lance.schema-refresh-mode decides when Gravitino rereads the columns from the dataset:

ModeBehavior
DECLARED_AND_EMPTYDefault. Refreshes schema from the Lance dataset for two cases: (1) declared tables (lance.declared=true) whose schema has not yet been written to Gravitino; (2) tables whose Gravitino column list is empty, for example tables registered before their schema was captured.
VERSION_CHECKOpens the Lance dataset on every loadTable, compares the dataset version with lance.version, and refreshes columns when the version has changed.

Use VERSION_CHECK only when tables may be modified directly through the Lance path outside Gravitino. It adds a dataset version check to every loadTable call.

If a Lance dataset has no columns, DECLARED_AND_EMPTY mode records the checked dataset version (lance.version) on the first loadTable call. Subsequent loads skip opening the dataset as long as the stored version is unchanged. Once columns are written to the dataset, the next VERSION_CHECK load or an explicit alterTable will detect the change and repair the schema.

Create a table​

Create a Lance table
curl -sS -X POST "https://{gravitino_host}/api/metalakes/{metalake}/catalogs/lakehouse_catalog/schemas/sales/tables" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "lance_table",
"comment": "Example Lance table",
"columns": [
{"name": "id", "type": "integer", "comment": "Primary identifier", "nullable": false}
],
"properties": {
"format": "lance",
"location": "s3://{bucket}/lakehouse/sales/lance_table"
}
}'

Load, list, alter, and drop work as for other tables; see Tables and views.

Register an existing dataset​

Registering records a Lance dataset that already exists, without moving or copying it. Send no columns; Gravitino reads them from the dataset the first time the table is loaded. Set external to true as well, or dropping the table later deletes the dataset.

Register an existing Lance dataset
curl -sS -X POST "https://{gravitino_host}/api/metalakes/{metalake}/catalogs/lakehouse_catalog/schemas/sales/tables" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "existing_orders",
"comment": "Registered Lance dataset",
"columns": [],
"properties": {
"format": "lance",
"lance.register": "true",
"external": "true",
"location": "s3://{bucket}/datasets/orders.lance"
}
}'

Register many datasets​

The script registers a list of existing datasets, one request each. Fill in the list with the schema, table name, and dataset location of each one; the schemas must already exist.

Register existing Lance datasets in bulk
datasets=(
"sales orders s3://{bucket}/datasets/orders.lance"
"sales customers s3://{bucket}/datasets/customers.lance"
"inventory products s3://{bucket}/datasets/products.lance"
)

for entry in "${datasets[@]}"; do
read -r schema table location <<< "$entry"
curl -sS -X POST "https://{gravitino_host}/api/metalakes/{metalake}/catalogs/lakehouse_catalog/schemas/${schema}/tables" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d "{
\"name\": \"${table}\",
\"columns\": [],
\"properties\": {
\"format\": \"lance\",
\"lance.register\": \"true\",
\"external\": \"true\",
\"location\": \"${location}\"
}
}"
echo "Registered ${schema}.${table}"
done

After registering, point Lance clients at the Lance REST service instead of the dataset paths, so reads and writes go through Gravitino.

S3-compatible storage​

For Lance tables on MinIO or another S3-compatible store, set the storage options once on the catalog. Gravitino hands them to Lance clients with each table, so clients do not repeat them.

Create a catalog for Lance tables on MinIO
curl -sS -X POST "https://{gravitino_host}/api/metalakes/{metalake}/catalogs" \
-H "Accept: application/vnd.gravitino.v1+json" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "lance_catalog",
"type": "RELATIONAL",
"provider": "lakehouse-generic",
"comment": "Lance tables on MinIO",
"properties": {
"location": "s3://{bucket}/lance",
"lance.storage.endpoint": "https://{minio_host}:9000",
"lance.storage.access_key_id": "{access_key_id}",
"lance.storage.secret_access_key": "{secret_access_key}",
"lance.storage.region": "us-east-1"
}
}'

For a MinIO endpoint on plain HTTP, add "lance.storage.allow_http": "true". A single table can override any of these with its own lance.storage.* properties.

Troubleshooting​

SymptomCause and fix
'location' property is neither set in table properties...None of the table, schema, or catalog sets location. Set it at one of the three levels.
Permission denied from storageThe storage credentials in lance.storage.* lack access to the location. Check them and the bucket policy.
A registered table has no columnslocation does not point at a Lance dataset directory. Check the path, which usually ends in .lance.
Unsupported changes to lance tableThe alter request changes something other than dropping or renaming a column or adding an index.