You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/src/dir/catalog-spec.md
+45-1Lines changed: 45 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -105,6 +105,8 @@ The `__manifest` table has the following schema:
105
105
|`metadata`| String (nullable) | JSON-encoded metadata/properties (only for namespaces) |
106
106
|`base_objects`| List<String> (nullable) | Reserved for future use (e.g., view dependencies) |
107
107
108
+
**Primary Key**: The `object_id` column is the [unenforced primary key](https://lance.org/format/table/#unenforced-primary-key) for the manifest table. Implementation of this spec must always enforce the primary key uniqueness using features like Lance merge insert with primary key deduplication.
109
+
108
110
**Schema Extensibility**: The `__manifest` table schema may include additional columns beyond those listed above. Extensions like [partitioned namespaces](../partitioning-spec.md) add columns for efficient filtering. Implementations should preserve unrecognized columns during updates.
109
111
110
112
### Root Namespace Properties
@@ -140,6 +142,48 @@ The `object_id` suffix ensures uniqueness and aids debugging.
140
142
141
143
In [compatibility mode](#compatibility-mode), root namespace tables use `<table_name>.lance` naming to remain compatible with V1.
142
144
145
+
146
+
### Table Version Management
147
+
148
+
V2 optionally supports managed table versioning, where table versions are tracked in the `__manifest` table instead of relying on Lance's native version management. When enabled, the directory namespace acts as an [external manifest store](https://lance.org/format/table/transaction/#external-manifest-store). This feature must be enabled for the entire namespace.
149
+
150
+
#### Enabling Table Version Management
151
+
152
+
To enable table version management, store `table_version_management=true` in the `__manifest` Lance table's metadata map. Once enabled, all table version operations must use the namespace APIs (`CreateTableVersion`, `BatchCreateTableVersions`, `DescribeTableVersion`, `ListTableVersions`, `BatchDeleteTableVersions`) instead of the default single-table storage-only version management.
153
+
154
+
#### Table Version Object ID
155
+
156
+
Table versions are stored in the `__manifest` table with `object_id` in the format `<table_id>$<version>`. For example:
157
+
158
+
- Table `users` version 1: `object_id = "users$1"`
159
+
- Table `analytics$events` (in namespace `analytics`) version 5: `object_id = "analytics$events$5"`
160
+
161
+
The `object_type` for table version entries is `"table_version"`.
162
+
163
+
#### Table Version Metadata Schema
164
+
165
+
The `metadata` column for table version entries contains a JSON object with the following schema:
By default, the directory namespace operates in compatibility mode, supporting both V1 and V2 tables simultaneously. This allows gradual migration from V1 to V2 without disrupting existing workflows.
@@ -154,4 +198,4 @@ In compatibility mode:
154
198
155
199
### Migration from V1 to V2
156
200
157
-
To fully migrate from V1 to V2, add all existing V1 table directory paths to the manifest table. Once all tables are registered in the manifest, compatibility mode can be disabled to use only V2 behavior.
201
+
To fully migrate from V1 to V2, add all existing V1 table directory paths to the manifest table. Once all tables are registered in the manifest, compatibility mode can be disabled to use only V2 behavior.
@@ -62,16 +62,17 @@ A valid Lance table directory must be non-empty.
62
62
63
63
This operation is only supported in V2. V1 does not support explicit namespace creation since it uses a flat directory structure.
64
64
65
-
The implementation creates a new namespace by inserting a row into the manifest table:
65
+
The implementation creates a new namespace using a merge-insert operation on the manifest table:
66
66
67
67
1. Validate the parent namespace exists (if not creating at root level)
68
-
2. Check that no namespace with the same identifier already exists
69
-
3. Insert a new row into the manifest table with:
68
+
2. Merge-insert a new row into the manifest table with:
70
69
-`object_id` set to the namespace identifier (e.g., `prod$analytics`)
71
70
-`object_type` set to `"namespace"`
72
71
-`metadata` containing the namespace properties as JSON
73
72
-`created_at` set to the current timestamp
74
73
74
+
Primary-key deduplication on `object_id` ensures no duplicate rows are inserted. If a namespace with the same identifier already exists, the operation fails.
75
+
75
76
**Error Handling:**
76
77
77
78
If a namespace with the same identifier already exists, return error code `2` (NamespaceAlreadyExists).
@@ -120,7 +121,7 @@ The implementation:
120
121
1. Check that the namespace exists in the manifest table
121
122
2. Query for any child namespaces or tables with identifiers starting with this namespace's prefix
122
123
3. If any children exist, the operation fails
123
-
4. Delete the namespace row from the manifest table
124
+
4. Delete the namespace row from the manifest table using the `object_id` primary key
124
125
125
126
**Error Handling:**
126
127
@@ -135,17 +136,18 @@ This operation declares a new Lance table, reserving the table name and location
135
136
The implementation:
136
137
137
138
1. Validate the parent namespace exists (in V2)
138
-
2. Check that no table with the same identifier already exists
139
-
3. Determine the table location:
139
+
2. Determine the table location:
140
140
- In V1: `<root>/<table_name>.lance`
141
141
- In V2 with `dir_listing_enabled=true` at root level: `<root>/<table_name>.lance`
142
142
- In V2 for child namespaces or with `dir_listing_enabled=false`: `<root>/<hash>_<object_id>/`
143
-
4. Create a `.lance-reserved` file at the location to mark the table's existence
144
-
5. In V2, insert a row into the manifest table with:
143
+
3. Create a `.lance-reserved` file at the location to mark the table's existence
144
+
4. In V2, merge-insert a row into the manifest table with:
145
145
-`object_id` set to the table identifier
146
146
-`object_type` set to `"table"`
147
147
-`location` set to the table directory path
148
148
149
+
Primary-key deduplication on `object_id` ensures no duplicate rows are inserted. If a table with the same identifier already exists, the operation fails.
150
+
149
151
**Error Handling:**
150
152
151
153
If the parent namespace does not exist, return error code `1` (NamespaceNotFound).
@@ -204,6 +206,43 @@ If the table does not exist, return error code `4` (TableNotFound).
204
206
205
207
If a specific version is requested and does not exist, return error code `11` (TableVersionNotFound).
206
208
209
+
### DeregisterTable
210
+
211
+
This operation deregisters a table from the namespace while preserving its data on storage. The table files remain at their storage location and can be re-registered later using RegisterTable.
212
+
213
+
In **V1**:
214
+
215
+
1. Locate the table by checking for the `<table_name>.lance` directory
216
+
2. Verify the table exists and is not already deregistered
217
+
3. Create a `.lance-deregistered` marker file inside the table directory
218
+
4. Return the table location for reference
219
+
220
+
The marker file approach ensures that:
221
+
- Table data remains intact at its original location
222
+
- The table is excluded from `ListTables` results
223
+
- The table returns `TableNotFound` for `DescribeTable` and `TableExists` operations
224
+
- The table can be re-registered by removing the marker file and calling `RegisterTable`
225
+
-`DropTable` still works on deregistered tables (removes both data and marker file)
226
+
227
+
In **V2**:
228
+
229
+
1. Locate the table by querying the manifest table for the table location
230
+
2. Remove the table row from the manifest table using the `object_id` primary key
231
+
3. Keep the table files at the storage location
232
+
4. Return the table location and properties for reference
233
+
234
+
When **both V1 and V2 are enabled** (the default [Compatibility Mode](catalog-spec.md#compatibility-mode)),
235
+
first check the manifest table, then fall back to checking the `.lance` directory.
236
+
If found in manifest, follow V2 behavior; otherwise follow V1 behavior.
237
+
238
+
**Error Handling:**
239
+
240
+
If the parent namespace does not exist, return error code `1` (NamespaceNotFound).
241
+
242
+
If the table does not exist or is already deregistered, return error code `4` (TableNotFound).
243
+
244
+
## Additional Operations
245
+
207
246
### DropTable
208
247
209
248
This operation removes a table and its data.
@@ -217,8 +256,8 @@ In **V1**:
217
256
In **V2**:
218
257
219
258
1. Locate the table by querying the manifest table for the table location
220
-
2. Remove the table row from the manifest table first
221
-
3. Delete the table directory and all its contents from storage
259
+
2. Remove the table row from the manifest table using the `object_id` primary key
260
+
3. Delete the table directory and all its contents from storage
222
261
(failure here does not affect the success of the drop since the table is no longer reachable)
223
262
224
263
When **both V1 and V2 are enabled** (the default [Compatibility Mode](catalog-spec.md#compatibility-mode)),
@@ -235,37 +274,134 @@ If there is a file system permission error, return error code `15` (PermissionDe
235
274
236
275
If there is an unexpected I/O error, return error code `18` (Internal).
237
276
238
-
### DeregisterTable
277
+
### CreateTableVersion
239
278
240
-
This operation deregisters a table from the namespace while preserving its data on storage. The table files remain at their storage location and can be re-registered later using RegisterTable.
279
+
This operation creates a new version entry for a table. It supports `put_if_not_exists` semantics.
241
280
242
-
In**V1**:
281
+
When**table version management is not enabled**:
243
282
244
-
1. Locate the table by checking for the `<table_name>.lance` directory
245
-
2. Verify the table exists and is not already deregistered
246
-
3. Create a `.lance-deregistered` marker file inside the table directory
247
-
4. Return the table location for reference
283
+
1. Resolve the table location
284
+
2. Parse the staging manifest path from the request
285
+
3. Determine the final manifest path based on the naming scheme (V1 or V2)
286
+
4. Copy the staging manifest to the final path in the `_versions/` directory using `put_if_not_exists` semantics
287
+
5. Delete the staging manifest file
288
+
6. Return the created version info including the final manifest path
248
289
249
-
The marker file approach ensures that:
250
-
- Table data remains intact at its original location
251
-
- The table is excluded from `ListTables` results
252
-
- The table returns `TableNotFound` for `DescribeTable` and `TableExists` operations
253
-
- The table can be re-registered by removing the marker file and calling `RegisterTable`
254
-
-`DropTable` still works on deregistered tables (removes both data and marker file)
290
+
When **table version management is enabled** (V2 with `table_version_management=true` in `__manifest` metadata), the directory namespace acts as an external manifest store. The commit process follows these steps:
255
291
256
-
In **V2**:
292
+
1.**Stage manifest in object storage**: The caller writes the new manifest to a staging path (e.g., `{table_location}/_versions/{version}.manifest-{uuid}`). This staged manifest is not yet visible to readers.
293
+
2.**Atomically commit to manifest table**: Merge-insert a new row into the `__manifest` table with:
294
+
-`object_id` set to `<table_id>$<version>` (e.g., `users$1` or `ns1$users$1`)
295
+
-`object_type` set to `"table_version"`
296
+
-`metadata` containing the JSON-encoded version metadata including the staging manifest path
257
297
258
-
1. Locate the table by querying the manifest table for the table location
259
-
2. Remove the table row from the manifest table
260
-
3. Keep the table files at the storage location
261
-
4. Return the table location and properties for reference
298
+
Primary-key deduplication on `object_id` ensures no duplicate rows are inserted. The commit is effectively complete after this step. If this fails, another writer has already committed that version.
299
+
3.**Finalize in object storage**: Copy the staged manifest to the standard location (`{table_location}/_versions/{version}.manifest`). This makes it discoverable by readers that do not use the manifest table.
300
+
4.**Update manifest table pointer**: Update the `metadata` in the manifest table row to point to the finalized manifest path, synchronizing both systems.
262
301
263
-
When **both V1 and V2 are enabled** (the default [Compatibility Mode](catalog-spec.md#compatibility-mode)),
264
-
first check the manifest table, then fall back to checking the `.lance` directory.
265
-
If found in manifest, follow V2 behavior; otherwise follow V1 behavior.
302
+
**Error Handling:**
303
+
304
+
If the table does not exist, return error code `4` (TableNotFound).
305
+
306
+
If the version already exists, return error code `12` (TableVersionAlreadyExists).
307
+
308
+
If there is a concurrent creation attempt, return error code `14` (ConcurrentModification).
309
+
310
+
### BatchCreateTableVersions
311
+
312
+
This operation atomically creates version entries for multiple tables.
313
+
314
+
When **table version management is not enabled**, this operation iterates through each entry and calls `CreateTableVersion` for each one. Atomicity is not guaranteed.
315
+
316
+
When **table version management is enabled**, the batch commit process follows these steps:
317
+
318
+
1.**Stage manifests in object storage**: For each entry, the caller writes the new manifest to a staging path (e.g., `{table_location}/_versions/{version}.manifest-{uuid}`).
319
+
2.**Atomically commit to manifest table**: Merge-insert all version rows into the `__manifest` table in a single atomic commit, each with:
320
+
-`object_id` set to `<table_id>$<version>`
321
+
-`object_type` set to `"table_version"`
322
+
-`metadata` containing the JSON-encoded version metadata including the staging manifest path
323
+
324
+
Primary-key deduplication on `object_id` ensures no duplicate rows are inserted. The commit is effectively complete after this step. If any version already exists, the entire batch fails.
325
+
3.**Finalize in object storage**: For each entry, copy the staged manifest to the standard location.
326
+
4.**Update manifest table pointers**: Update the `metadata` in each manifest table row to point to the finalized manifest paths.
266
327
267
328
**Error Handling:**
268
329
269
-
If the parent namespace does not exist, return error code `1` (NamespaceNotFound).
330
+
If any table does not exist, return error code `4` (TableNotFound).
270
331
271
-
If the table does not exist or is already deregistered, return error code `4` (TableNotFound).
332
+
If any version already exists, return error code `12` (TableVersionAlreadyExists).
333
+
334
+
If there is a concurrent modification, return error code `14` (ConcurrentModification).
335
+
336
+
### ListTableVersions
337
+
338
+
This operation lists version entries for a table.
339
+
340
+
When **table version management is not enabled**:
341
+
342
+
1. Resolve the table location
343
+
2. List all files in the `_versions/` directory
344
+
3. Parse version numbers from manifest filenames (handling both V1 and V2 naming schemes)
345
+
4. Extract metadata from file attributes (size, e_tag, last_modified timestamp)
346
+
5. Sort results by version number (descending if `descending=true`)
347
+
6. Apply pagination using `page_token` and `limit`
348
+
349
+
When **table version management is enabled**:
350
+
351
+
1. Query the manifest table for rows where:
352
+
-`object_type = "table_version"`
353
+
-`object_id` starts with `<table_id>$`
354
+
2. Parse the version number from each `object_id`
355
+
3. Parse the `metadata` column as JSON to extract version details
356
+
4. Sort results by version number (descending if `descending=true`)
357
+
5. Apply pagination using `page_token` and `limit`
358
+
359
+
**Error Handling:**
360
+
361
+
If the table does not exist, return error code `4` (TableNotFound).
362
+
363
+
### DescribeTableVersion
364
+
365
+
This operation retrieves details for a specific table version.
366
+
367
+
When **table version management is not enabled**:
368
+
369
+
1. Resolve the table location
370
+
2. Open the Lance dataset at the specified version
371
+
3. Read the manifest file to extract version metadata
372
+
4. Return the version information including manifest_path, manifest_size, e_tag, timestamp_millis, and metadata
373
+
374
+
When **table version management is enabled**, the read process validates and synchronizes the manifest:
375
+
376
+
1.**Query manifest table**: Retrieve the manifest path for the requested version from the row with `object_id = <table_id>$<version>`. If the path matches the expected path based on the naming scheme, synchronization is complete.
377
+
2.**Synchronize to object storage**: If the manifest path does not match the expected path based on the naming scheme (i.e., it is a staging path), copy the staged manifest to its final location (`{table_location}/_versions/{version}.manifest`). This is an idempotent operation.
378
+
3.**Update manifest table**: Update the `metadata` in the manifest table row to reflect the finalized path for future readers.
379
+
4.**Return version information**: Return the version information with the finalized manifest path, or error if synchronization fails.
380
+
381
+
**Error Handling:**
382
+
383
+
If the table does not exist, return error code `4` (TableNotFound).
384
+
385
+
If the version does not exist, return error code `11` (TableVersionNotFound).
386
+
387
+
### BatchDeleteTableVersions
388
+
389
+
This operation deletes multiple version entries for a table.
390
+
391
+
When **table version management is not enabled**:
392
+
393
+
1. Resolve the table location
394
+
2. Delete the manifest files in the `_versions/` directory for each specified version
395
+
3. Return the count of deleted versions
396
+
397
+
When **table version management is enabled**:
398
+
399
+
1. Delete the manifest files in the `_versions/` directory for each specified version
400
+
2. Delete rows from the manifest table using the `object_id` primary key for each specified version
401
+
3. Return the count of deleted versions
402
+
403
+
**Error Handling:**
404
+
405
+
If the table does not exist, return error code `4` (TableNotFound).
406
+
407
+
If any specified version does not exist, the operation may either skip it silently or return error code `11` (TableVersionNotFound), depending on the `ignore_missing` parameter.
0 commit comments