-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathlance.h
More file actions
2317 lines (2124 loc) · 90.4 KB
/
Copy pathlance.h
File metadata and controls
2317 lines (2124 loc) · 90.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* SPDX-License-Identifier: Apache-2.0 */
/* SPDX-FileCopyrightText: Copyright The Lance Authors */
/**
* @file lance.h
* @brief C API for the Lance columnar data format.
*
* All data crosses this boundary via the Arrow C Data Interface
* (ArrowSchema, ArrowArray, ArrowArrayStream).
* For Arrow structures written to caller-provided output storage, the caller
* retains ownership of the outer structure and must invoke its non-NULL
* `release` callback exactly once to release the contents. APIs that allocate
* the outer structure as well document a separate matching free function.
*
* Error handling uses thread-local storage: after any function returns its
* documented error sentinel (for example NULL, -1, or 0 for selected scalar
* accessors), call lance_last_error_code() and lance_last_error_message() to
* get details.
*/
#ifndef LANCE_H
#define LANCE_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* ─── Arrow C Data Interface forward declarations ─── */
/* These match the canonical Arrow spec structs. If you already include
arrow/c/abi.h, guard with ARROW_C_DATA_INTERFACE. */
#ifndef ARROW_C_DATA_INTERFACE
#define ARROW_C_DATA_INTERFACE
struct ArrowSchema {
const char* format;
const char* name;
const char* metadata;
int64_t flags;
int64_t n_children;
struct ArrowSchema** children;
struct ArrowSchema* dictionary;
void (*release)(struct ArrowSchema*);
void* private_data;
};
struct ArrowArray {
int64_t length;
int64_t null_count;
int64_t offset;
int64_t n_buffers;
int64_t n_children;
const void** buffers;
struct ArrowArray** children;
struct ArrowArray* dictionary;
void (*release)(struct ArrowArray*);
void* private_data;
};
struct ArrowArrayStream {
int (*get_schema)(struct ArrowArrayStream*, struct ArrowSchema* out);
int (*get_next)(struct ArrowArrayStream*, struct ArrowArray* out);
const char* (*get_last_error)(struct ArrowArrayStream*);
void (*release)(struct ArrowArrayStream*);
void* private_data;
};
#endif /* ARROW_C_DATA_INTERFACE */
/* ─── Error handling ─── */
typedef enum {
LANCE_OK = 0,
LANCE_ERR_INVALID_ARGUMENT = 1,
LANCE_ERR_IO = 2,
LANCE_ERR_NOT_FOUND = 3,
LANCE_ERR_DATASET_ALREADY_EXISTS = 4,
LANCE_ERR_INDEX = 5,
LANCE_ERR_INTERNAL = 6,
LANCE_ERR_NOT_SUPPORTED = 7,
LANCE_ERR_COMMIT_CONFLICT = 8,
/* An unexpected panic was caught at the FFI boundary. */
LANCE_ERR_PANIC = 9,
} LanceErrorCode;
/**
* Panic handling (issue lance-format/lance-c#61).
*
* A panic raised inside Lance/Arrow code is caught at the FFI boundary and
* reported as LANCE_ERR_PANIC through the usual thread-local error channel
* (lance_last_error_code / lance_last_error_message) instead of unwinding
* into the host. After a panic:
*
* - A LanceScanner handle is poisoned: every later call on it fails with
* LANCE_ERR_PANIC ("scanner is poisoned by an earlier panic"). Close it
* and build a fresh scanner; do not retry the poisoned handle.
* - A LanceDataset handle remains usable: commits are atomic manifest
* swaps, and a mutation that panics before commit rolls back in memory,
* leaving the last committed snapshot intact.
*
* Honest limits: a double panic, a panic in a destructor while unwinding, a
* stack overflow, or an allocation failure still aborts the process. A
* panic caught inside a close/free call (lance_*_close, lance_batch_free,
* lance_free_string, lance_scanner_async_stream_free, or the release callback
* of an exported ArrowArrayStream) is logged and the remainder of the value
* may leak — close is best-effort by design. Post-panic process state is
* best-effort: hosts should fail the in-flight query rather than retry a
* poisoned handle.
*
* Callbacks passed INTO the library (LanceCallback, LanceWaker, and
* LanceScanStatisticsCallback) are the reverse direction and are NOT covered
* by this contract: their ABI is non-unwinding, so a callback that throws or
* unwinds can abort the host process before the library can contain it.
* Callbacks must return normally.
*
* This contract requires Rust's `panic = "unwind"` strategy. The crate
* rejects `panic = "abort"` builds at compile time because catch_unwind
* cannot provide this API contract in such a build.
*/
/* ─── Index types (Phase 2) ─── */
typedef enum {
LANCE_INDEX_IVF_FLAT = 101,
LANCE_INDEX_IVF_SQ = 102,
LANCE_INDEX_IVF_PQ = 103,
LANCE_INDEX_IVF_HNSW_SQ = 104,
LANCE_INDEX_IVF_HNSW_PQ = 105,
LANCE_INDEX_IVF_HNSW_FLAT = 106,
} LanceVectorIndexType;
typedef enum {
LANCE_SCALAR_BTREE = 1,
LANCE_SCALAR_BITMAP = 2,
LANCE_SCALAR_LABEL_LIST = 3,
LANCE_SCALAR_INVERTED = 4,
} LanceScalarIndexType;
typedef enum {
LANCE_METRIC_L2 = 0,
LANCE_METRIC_COSINE = 1,
LANCE_METRIC_DOT = 2,
LANCE_METRIC_HAMMING = 3,
} LanceMetricType;
/** Speed / accuracy tradeoff for approximate vector search. */
typedef enum {
LANCE_APPROX_MODE_FAST = 0,
LANCE_APPROX_MODE_NORMAL = 1,
LANCE_APPROX_MODE_ACCURATE = 2,
} LanceApproxMode;
typedef enum {
LANCE_DTYPE_FLOAT32 = 0,
LANCE_DTYPE_FLOAT16 = 1,
LANCE_DTYPE_FLOAT64 = 2,
LANCE_DTYPE_UINT8 = 3,
LANCE_DTYPE_INT8 = 4,
} LanceDataType;
typedef struct {
LanceVectorIndexType index_type;
LanceMetricType metric;
uint32_t num_partitions; /* IVF; required, must be > 0 */
uint32_t num_sub_vectors; /* PQ; required, must be > 0 */
uint32_t num_bits; /* PQ: 0 (default 8), 4, or 8; SQ: 0 or 8 */
uint32_t max_iterations; /* IVF kmeans; 0 = 50 */
uint32_t hnsw_m; /* HNSW; required, must be > 0 */
uint32_t hnsw_ef_construction; /* HNSW; 0 = default */
uint32_t sample_rate; /* IVF; 0 = 256 */
} LanceVectorIndexParams;
/** Return the error code from the last failed operation on this thread. */
LanceErrorCode lance_last_error_code(void);
/** Return the error message. Caller must free with lance_free_string(). */
const char* lance_last_error_message(void);
/** Free a string returned by lance_last_error_message(). */
void lance_free_string(const char* s);
/* ─── Opaque handles ─── */
typedef struct LanceDataset LanceDataset;
typedef struct LanceScanner LanceScanner;
typedef struct LanceBatch LanceBatch;
typedef struct LanceSession LanceSession;
typedef struct LanceVersions LanceVersions;
typedef struct LanceDataStatistics LanceDataStatistics;
typedef struct LanceIndexSegmentBuilder LanceIndexSegmentBuilder;
typedef struct LanceIndexSegmentMetadata LanceIndexSegmentMetadata;
typedef struct LanceFtsQueryContext LanceFtsQueryContext;
typedef struct LanceBlobFile LanceBlobFile;
/* ─── Shared session ─── */
/**
* Snapshot of a shared session's cache statistics.
*
* Cache sizes are the bytes currently retained, not their configured limits.
*/
typedef struct LanceSessionCacheStats {
uint64_t index_cache_hits;
uint64_t index_cache_misses;
uint64_t index_cache_entries;
uint64_t index_cache_size_bytes;
uint64_t metadata_cache_hits;
uint64_t metadata_cache_misses;
uint64_t metadata_cache_entries;
uint64_t metadata_cache_size_bytes;
} LanceSessionCacheStats;
/**
* Create a session that can share metadata and index caches across datasets.
*
* Cache limits are specified in bytes. Pass 0 to request zero capacity.
* @return Session handle, or NULL on error
*/
LanceSession* lance_session_new(
uint64_t index_cache_size_bytes,
uint64_t metadata_cache_size_bytes
);
/**
* Close a session handle. Safe to call with NULL. Datasets previously opened
* with the session remain valid and retain the shared cache state.
*/
void lance_session_close(LanceSession* session);
/**
* Copy current cache statistics to `out_stats`.
*
* @return 0 on success, -1 on error
*/
int32_t lance_session_get_cache_stats(
const LanceSession* session,
LanceSessionCacheStats* out_stats
);
/* ─── Dataset lifecycle ─── */
/**
* Open a Lance dataset.
*
* Pass `version` = 0 to open the latest, or a specific version id (e.g. one
* returned by `lance_dataset_versions`) to check out that version:
*
* LanceDataset* ds = lance_dataset_open("data.lance", NULL, 42);
*
* @param uri Dataset path (file://, s3://, memory://, etc.)
* @param storage_opts NULL-terminated key-value pairs ["k1","v1",NULL], or NULL
* @param version Version to open (0 = latest)
* @return Dataset handle, or NULL on error
*/
LanceDataset* lance_dataset_open(
const char* uri,
const char* const* storage_opts,
uint64_t version
);
/**
* Open a Lance dataset using a shared session.
*
* The dataset retains the shared session state and remains valid if the caller
* subsequently closes `session`.
*
* @param uri Dataset path (file://, s3://, memory://, etc.)
* @param storage_opts NULL-terminated key-value pairs ["k1","v1",NULL], or NULL
* @param version Version to open (0 = latest)
* @param session Shared session; must not be NULL
* @return Dataset handle, or NULL on error
*/
LanceDataset* lance_dataset_open_with_session(
const char* uri,
const char* const* storage_opts,
uint64_t version,
const LanceSession* session
);
/** Close and free a dataset handle. Safe to call with NULL. */
void lance_dataset_close(LanceDataset* dataset);
/* ─── Dataset metadata (sync, in-memory) ─── */
/**
* Return the version number of this dataset snapshot.
* @return version on success, or 0 on error (check lance_last_error_code())
*/
uint64_t lance_dataset_version(const LanceDataset* dataset);
/**
* Return the number of rows. Returns 0 on error; an empty dataset also returns
* 0, so check lance_last_error_code().
*/
uint64_t lance_dataset_count_rows(const LanceDataset* dataset);
/**
* Return the latest version ID (I/O), or 0 on error (check
* lance_last_error_code()).
*/
uint64_t lance_dataset_latest_version(const LanceDataset* dataset);
/* ─── Version history ─── */
/**
* Snapshot the dataset's version history. Caller frees the returned handle
* with lance_versions_close().
* @return handle on success, or NULL on error
*/
LanceVersions* lance_dataset_versions(const LanceDataset* dataset);
/**
* Number of versions in the snapshot, or 0 on error (check
* lance_last_error_code()).
*/
uint64_t lance_versions_count(const LanceVersions* versions);
/**
* Monotonic version id at `index` (0 <= index < count).
* Returns 0 on error (NULL handle or out-of-range index) — check
* lance_last_error_code().
*/
uint64_t lance_versions_id_at(const LanceVersions* versions, size_t index);
/**
* Version timestamp at `index`, as Unix epoch milliseconds.
* Returns 0 on error (NULL handle or out-of-range index) — check
* lance_last_error_code().
*/
int64_t lance_versions_timestamp_ms_at(const LanceVersions* versions, size_t index);
/** Close and free a versions handle. Safe to call with NULL. */
void lance_versions_close(LanceVersions* versions);
/* ─── Data statistics ─── */
/**
* Compute per-field data statistics (compressed on-disk byte size) for query
* planning. Walks every fragment, so this performs I/O. Caller frees the
* returned handle with lance_data_statistics_close().
*
* Entries are ordered by schema field id, one per field (including nested
* struct/list children).
* @return handle on success, or NULL on error
*/
LanceDataStatistics* lance_dataset_calculate_data_stats(const LanceDataset* dataset);
/**
* Number of fields in the statistics snapshot. Clears the thread-local error
* on success. Returns 0 and sets LANCE_ERR_INVALID_ARGUMENT on a NULL handle;
* a dataset with an empty schema also yields 0 with no error set, so check
* lance_last_error_code() to distinguish the error case from an empty result.
*/
uint64_t lance_data_statistics_count(const LanceDataStatistics* stats);
/**
* Schema field id at `index` (0 <= index < count).
* Returns 0 on error (NULL handle or out-of-range index), setting
* LANCE_ERR_INVALID_ARGUMENT. Because 0 is itself a valid field id, check
* lance_last_error_code() when passing an untrusted index; iterating
* `0..count` never errors.
*/
uint32_t lance_data_statistics_field_id_at(const LanceDataStatistics* stats, size_t index);
/**
* Compressed on-disk byte size of the field at `index`.
* Returns 0 on error (NULL handle or out-of-range index), setting
* LANCE_ERR_INVALID_ARGUMENT. A field written with the legacy (v1) storage
* format also reports 0 but sets no error, so check lance_last_error_code() to
* distinguish a genuine 0 from the error sentinel.
*/
uint64_t lance_data_statistics_bytes_on_disk_at(const LanceDataStatistics* stats, size_t index);
/** Close and free a data statistics handle. Safe to call with NULL. */
void lance_data_statistics_close(LanceDataStatistics* stats);
/**
* Restore the dataset to an older version by committing a new manifest that
* carries the fragments of `version`. If `version` is already the latest,
* succeeds as a no-op without writing a new manifest.
*
* @param dataset Open dataset (not consumed). Must not be NULL.
* @param version Target version id (>= 1). `0` is rejected since it is the
* "latest" sentinel used by lance_dataset_open.
* @return Fresh LanceDataset* positioned at the target version (caller closes
* with lance_dataset_close), or NULL on error. Possible error codes
* include LANCE_ERR_INVALID_ARGUMENT (NULL handle or version == 0),
* LANCE_ERR_NOT_FOUND (unknown version),
* LANCE_ERR_COMMIT_CONFLICT (concurrent writer).
*/
LanceDataset* lance_dataset_restore(const LanceDataset* dataset, uint64_t version);
/**
* Delete rows matching the SQL `predicate`, committing a new manifest.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight against this dataset
* keep their pre-delete snapshot view.
*
* @param dataset Open dataset (not consumed). Must not be NULL.
* @param predicate SQL filter, e.g. "id > 100" or "name = 'alice'".
* Must not be NULL or empty.
* @param out_num_deleted Optional. If non-NULL, on success receives the
* number of rows that were deleted (0 if the
* predicate matched nothing). On error the slot is
* left unchanged — do not read it.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL/empty args (validated at this
* boundary) and for malformed SQL or unknown columns (surfaced from
* the upstream parser since Lance 9.1; previously LANCE_ERR_INTERNAL),
* and LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_delete(
LanceDataset* dataset,
const char* predicate,
uint64_t* out_num_deleted
);
/**
* Update rows matching the SQL `predicate` by applying per-column SQL
* expressions, committing a new manifest.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight against this dataset
* keep their pre-update snapshot view.
*
* @param dataset Open dataset (not consumed). Must not be NULL.
* @param predicate SQL filter, e.g. "id > 100". Pass NULL to update
* every row. An explicit empty string is rejected.
* @param columns Column names to update. Length = `num_updates`.
* Must not be NULL when `num_updates > 0`; each
* entry must be a non-NULL, non-empty C string.
* @param values SQL scalar expressions, evaluated per row, one
* per `columns[i]` (e.g. `"100"`, `"price * 2"`,
* `"CASE WHEN ... END"`). Same NULL/length rules.
* @param num_updates Length of `columns` and `values`. Must be >= 1.
* @param out_num_updated Optional. If non-NULL, on success receives the
* number of rows that were updated (0 if the
* predicate matched nothing). On error the slot is
* left unchanged — do not read it.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL/empty args, `num_updates == 0`,
* malformed SQL, and unknown columns; LANCE_ERR_COMMIT_CONFLICT for
* a concurrent writer.
*/
int32_t lance_dataset_update(
LanceDataset* dataset,
const char* predicate,
const char* const* columns,
const char* const* values,
size_t num_updates,
uint64_t* out_num_updated
);
/* ─── lance_dataset_merge_insert ──────────────────────────────────────────── */
/**
* Behavior when a target row matches a source row on the join keys.
* Defaults are zero-valued so a zero-initialized LanceMergeInsertParams is a
* valid find-or-create configuration.
*/
typedef enum {
/* Keep the target row unchanged (find-or-create). Default. */
LANCE_MERGE_WHEN_MATCHED_DO_NOTHING = 0,
/* Replace the target row with the source row (upsert). */
LANCE_MERGE_WHEN_MATCHED_UPDATE_ALL = 1,
/* Replace only when an SQL filter evaluates true; requires
when_matched_expr. */
LANCE_MERGE_WHEN_MATCHED_UPDATE_IF = 2,
/* Fail the operation on any match. */
LANCE_MERGE_WHEN_MATCHED_FAIL = 3,
/* Drop the matching target row without inserting anything. */
LANCE_MERGE_WHEN_MATCHED_DELETE = 4,
} LanceMergeWhenMatched;
/** Behavior when a source row has no matching target row. */
typedef enum {
/* Insert the source row. Default. */
LANCE_MERGE_WHEN_NOT_MATCHED_INSERT_ALL = 0,
/* Discard the source row. */
LANCE_MERGE_WHEN_NOT_MATCHED_DO_NOTHING = 1,
} LanceMergeWhenNotMatched;
/** Behavior when a target row has no matching source row. */
typedef enum {
/* Keep the target row. Default. */
LANCE_MERGE_WHEN_NOT_MATCHED_BY_SOURCE_KEEP = 0,
/* Delete every unmatched target row. */
LANCE_MERGE_WHEN_NOT_MATCHED_BY_SOURCE_DELETE = 1,
/* Delete unmatched target rows that satisfy an SQL filter; requires
when_not_matched_by_source_expr. */
LANCE_MERGE_WHEN_NOT_MATCHED_BY_SOURCE_DELETE_IF = 2,
} LanceMergeWhenNotMatchedBySource;
/**
* Tunable parameters for lance_dataset_merge_insert. Pass NULL to use the
* find-or-create defaults (DO_NOTHING / INSERT_ALL / KEEP).
*
* Expression strings are read only when the corresponding mode requires
* them; spurious non-NULL pointers on other modes are rejected so the
* contract is unambiguous.
*/
typedef struct LanceMergeInsertParams {
/* LanceMergeWhenMatched discriminant. */
int32_t when_matched;
/* SQL filter for UPDATE_IF; NULL otherwise. Empty string is rejected. */
const char* when_matched_expr;
/* LanceMergeWhenNotMatched discriminant. */
int32_t when_not_matched;
/* LanceMergeWhenNotMatchedBySource discriminant. */
int32_t when_not_matched_by_source;
/* SQL filter for DELETE_IF; NULL otherwise. Empty string is rejected. */
const char* when_not_matched_by_source_expr;
} LanceMergeInsertParams;
/** Per-call merge statistics returned via the optional out parameter. */
typedef struct LanceMergeInsertResult {
uint64_t num_inserted_rows;
uint64_t num_updated_rows;
uint64_t num_deleted_rows;
} LanceMergeInsertResult;
/**
* Merge `source` into `dataset` keyed on `on_columns`, committing a new
* manifest. Mirrors SQL MERGE; the default parameters yield a find-or-create
* (insert rows that do not match an existing key).
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight against this dataset
* keep their pre-merge snapshot view.
*
* @param dataset Open dataset (not consumed). Must not be NULL.
* @param on_columns Join keys. Length = `num_on_columns`. Must be
* non-NULL when `num_on_columns > 0`; each entry
* must be a non-NULL, non-empty C string. Column
* names are matched case-insensitively (upstream).
* @param num_on_columns Length of `on_columns`. Must be >= 1.
* @param source Arrow C Data Interface stream of source rows.
* Consumed by this call. Its schema must be
* compatible with the dataset schema (full match or
* a subschema).
* @param params Tunable parameters. Pass NULL for find-or-create
* defaults.
* @param out_result Optional. If non-NULL, on success receives the
* per-call insert/update/delete counts. On error the
* slot is left unchanged — do not read it.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL/empty args, out-of-range mode
* discriminants, missing or extraneous expression strings, malformed
* SQL, unknown columns, schema incompatibility, and no-op
* configurations; LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_merge_insert(
LanceDataset* dataset,
const char* const* on_columns,
size_t num_on_columns,
struct ArrowArrayStream* source,
const LanceMergeInsertParams* params,
LanceMergeInsertResult* out_result
);
/* ─── lance_dataset_compact_files ─────────────────────────────────────────── */
/**
* Tunable parameters for lance_dataset_compact_files. Pass NULL to use the
* upstream defaults. Each numeric field uses 0 as a "keep upstream default"
* sentinel; non-zero values are forwarded after a usize range check so the
* API does not silently truncate on 32-bit hosts.
*/
typedef struct LanceCompactionOptions {
/* Target row count per output fragment. Fragments below this size are
candidates for being merged with neighbors. 0 = default (~1Mi rows). */
uint64_t target_rows_per_fragment;
/* Soft cap on rows per row group within an output fragment. 0 = default. */
uint64_t max_rows_per_group;
/* Soft cap on bytes per output fragment file. 0 = default (writer cap). */
uint64_t max_bytes_per_file;
/* Compute parallelism for compaction tasks. 0 = default
(number of compute-intensive CPUs). */
uint64_t num_threads;
/* Scanner batch size for reading input fragments. 0 = default. */
uint64_t batch_size;
} LanceCompactionOptions;
/** Per-call compaction metrics returned via the optional out parameter. */
typedef struct LanceCompactionMetrics {
/* Number of input fragments that were rewritten and dropped. */
uint64_t fragments_removed;
/* Number of new fragments produced by the rewrite. */
uint64_t fragments_added;
/* Total files removed across the operation, including deletion files. */
uint64_t files_removed;
/* Total files added across the operation; one per new fragment. */
uint64_t files_added;
} LanceCompactionMetrics;
/**
* Compact the dataset's fragments, committing a new manifest if anything
* changed. Each compaction task merges adjacent small fragments and
* materializes any deletion files in the process. A clean dataset (no
* fragment under the target size, no deletions worth materializing) is a
* no-op: the function returns success with all-zero metrics and the
* dataset's version is unchanged.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight against this dataset
* keep their pre-compaction snapshot view.
*
* @param dataset Open dataset (not consumed). Must not be NULL.
* @param options Tunable parameters. Pass NULL for upstream defaults.
* @param out_metrics Optional. If non-NULL, on success receives the per-call
* compaction metrics. On error the slot is left unchanged
* — do not read it.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL `dataset` or for numeric
* overrides that exceed usize::MAX on the running target;
* LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_compact_files(
LanceDataset* dataset,
const LanceCompactionOptions* options,
LanceCompactionMetrics* out_metrics
);
/* ─── lance_dataset_drop_columns ──────────────────────────────────────────── */
/**
* Drop one or more columns from the dataset's schema, committing a new
* manifest. This is a metadata-only operation: the data files on storage
* are not rewritten until a later `lance_dataset_compact_files` call
* materializes the projection (after which the previous version's files
* can be removed by a future cleanup operation).
*
* Mutates `dataset` in place — the same handle remains valid afterward
* and sees the new version. Scanners already in flight against this
* dataset keep their pre-drop schema view.
*
* @param dataset Open dataset (not consumed). Mutated in place to
* see the new version. Must not be NULL.
* @param columns Array of NUL-terminated UTF-8 column names to drop.
* Must not be NULL; entries must be non-NULL and
* non-empty.
* @param num_columns Length of `columns`. Must be > 0.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL/empty inputs, NULL or empty
* entries, non-UTF-8 column names, unknown columns, or an attempt
* to drop every column;
* LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_drop_columns(
LanceDataset* dataset,
const char* const* columns,
size_t num_columns
);
/* ─── lance_dataset_alter_columns ─────────────────────────────────────────── */
/**
* Tri-state nullability override for `LanceColumnAlteration`. The
* `UNCHANGED` discriminant is zero so a zero-initialised
* `LanceColumnAlteration` leaves nullability alone by default.
*
* Discriminants are pinned for ABI stability. Out-of-range values are
* rejected with `LANCE_ERR_INVALID_ARGUMENT` — that's why the field on
* `LanceColumnAlteration` is `int32_t`, not this enum directly.
*/
typedef enum {
/* Do not touch the column's existing nullability. */
LANCE_COLUMN_NULLABLE_UNCHANGED = 0,
/* Set the column to nullable. */
LANCE_COLUMN_NULLABLE_TRUE = 1,
/* Set the column to non-nullable. Upstream verifies via a scan that no
row holds a NULL — the call fails if any do. */
LANCE_COLUMN_NULLABLE_FALSE = 2,
} LanceColumnNullableMode;
/**
* A single alteration applied to one column. Every non-`path` field is
* optional via a sentinel:
*
* - `rename = NULL` keeps the current name.
* - `nullable_mode = LANCE_COLUMN_NULLABLE_UNCHANGED` keeps current nullability.
* - `data_type = NULL` keeps the current data type.
*
* At least one of `rename`, `nullable_mode`, or `data_type` must request a
* change; an alteration that touches nothing is rejected at the FFI boundary.
*
* `data_type`, when non-NULL, borrows an Arrow C Data Interface `ArrowSchema`
* describing the target type. The struct is read by shared reference for the
* duration of the call; its `release` callback is never invoked.
*/
typedef struct LanceColumnAlteration {
/* Path to the existing column. Required, non-empty UTF-8. */
const char* path;
/* New column name, or NULL to keep the current name. */
const char* rename;
/* LanceColumnNullableMode discriminant. */
int32_t nullable_mode;
/* New data type, or NULL to keep the current type. */
const struct ArrowSchema* data_type;
} LanceColumnAlteration;
/**
* Apply one or more column alterations and commit a new manifest. Rename and
* nullability-only changes are zero-copy and preserve any indices on the
* affected columns. A type change rewrites the column's data files and drops
* any indices that referenced it, mirroring upstream behaviour.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight against this dataset keep
* their pre-alteration view.
*
* @param dataset Open dataset (not consumed). Mutated in place to
* see the new version. Must not be NULL.
* @param alterations Array of `LanceColumnAlteration`. Must not be NULL.
* @param num_alterations Length of `alterations`. Must be > 0.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL/empty inputs, NULL or empty
* `path`, non-UTF-8 strings, no-op alterations (all three optional
* fields left at their sentinels), invalid `nullable_mode`
* discriminant, unknown columns, type changes that aren't a valid
* cast, or tightening nullability when existing rows hold NULLs;
* LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_alter_columns(
LanceDataset* dataset,
const LanceColumnAlteration* alterations,
size_t num_alterations
);
/* ─── lance_dataset_add_columns ───────────────────────────────────────────── */
/**
* A single new column defined by a SQL expression over the dataset's existing
* columns, e.g. { .name = "doubled", .expression = "x * 2" }. Both fields are
* required, non-empty UTF-8, and are read by shared reference for the duration
* of the call.
*/
typedef struct LanceSqlColumn {
/* Name of the new column. Required, non-empty UTF-8. */
const char* name;
/* SQL expression evaluated against existing columns. Required, non-empty. */
const char* expression;
} LanceSqlColumn;
/**
* Add one or more columns computed from SQL expressions over the dataset's
* existing columns, committing a new manifest. Each fragment is scanned, the
* expressions are evaluated, and the results are written as new column files.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight keep their pre-add view.
*
* @param dataset Open dataset (not consumed). Mutated in place. Must not
* be NULL.
* @param columns Array of `LanceSqlColumn`. Must not be NULL; each entry's
* `name` and `expression` must be non-NULL and non-empty.
* @param num_columns Length of `columns`. Must be > 0.
* @param batch_size Rows per scan batch while evaluating expressions.
* 0 = upstream default.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for NULL/empty inputs, NULL or empty
* `name` / `expression`, non-UTF-8 strings, malformed SQL *syntax*, a
* new column name that collides with an existing column, an
* expression that references a non-existent column (an upstream
* schema error reclassified in Lance 9.1; previously
* LANCE_ERR_INTERNAL), or a `batch_size` beyond UINT32_MAX.
* LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_add_columns_sql(
LanceDataset* dataset,
const LanceSqlColumn* columns,
size_t num_columns,
uint64_t batch_size
);
/**
* Add one or more all-null columns described by an Arrow C Data Interface
* schema, committing a new manifest. On non-legacy datasets this is a
* metadata-only operation — no data files are rewritten. Every field in the
* schema must be nullable.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight keep their pre-add view.
*
* @param dataset Open dataset (not consumed). Mutated in place. Must not be
* NULL.
* @param schema Arrow C `ArrowSchema` describing the new columns. Read by
* shared reference; its `release` callback is never invoked.
* Must not be NULL. Only the top-level schema is validated
* before it is handed to arrow-rs; the caller is responsible for
* providing fully-initialised child fields.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for a NULL dataset/schema, an
* uninitialised or already-released schema, an invalid Arrow schema, a
* non-nullable field, or a name that collides with an existing column.
* LANCE_ERR_NOT_SUPPORTED for a legacy-format dataset (which cannot take
* all-null columns as a metadata-only change).
* LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_add_columns_nulls(
LanceDataset* dataset,
const struct ArrowSchema* schema
);
/**
* Add columns by splicing precomputed data from an Arrow C Data Interface
* stream into the dataset, committing a new manifest. The stream's batches are
* consumed in order and aligned positionally to the dataset's existing rows;
* the total row count must match the dataset exactly.
*
* Mutates `dataset` in place — the same handle remains valid afterward and
* sees the new version. Scanners already in flight keep their pre-add view.
*
* @param dataset Open dataset (not consumed). Mutated in place. Must not
* be NULL.
* @param stream Arrow C stream of new column data. When non-NULL it is
* consumed (released) on every return path, including error
* returns — the caller must not use it again. (A NULL stream
* is rejected before anything is consumed.) Its schema
* defines the new columns and must not collide with existing
* column names.
* @param batch_size Rows per write batch while aligning the stream to
* fragments. 0 = upstream default.
* @return 0 on success, -1 on error. Error codes:
* LANCE_ERR_INVALID_ARGUMENT for a NULL dataset/stream, a stream missing
* a mandatory get_schema/get_next/release callback, a stream whose total
* row count does not match the dataset, a new column name that collides
* with an existing column, or a `batch_size` beyond UINT32_MAX.
* LANCE_ERR_COMMIT_CONFLICT for a concurrent writer.
*/
int32_t lance_dataset_add_columns_stream(
LanceDataset* dataset,
struct ArrowArrayStream* stream,
uint64_t batch_size
);
/**
* Export the dataset schema via Arrow C Data Interface.
* @param out Pointer to caller-allocated ArrowSchema struct
* @return 0 on success, -1 on error
*/
int32_t lance_dataset_schema(
const LanceDataset* dataset,
struct ArrowSchema* out
);
/* ─── Fragment enumeration ─── */
/**
* Return the number of fragments in the dataset. Returns 0 on error; a
* dataset with no fragments also returns 0, so check lance_last_error_code().
*/
uint64_t lance_dataset_fragment_count(const LanceDataset* dataset);
/**
* Fill out_ids with the fragment IDs of the dataset.
* Caller must allocate out_ids with at least lance_dataset_fragment_count() elements.
* @return 0 on success, -1 on error
*/
int32_t lance_dataset_fragment_ids(const LanceDataset* dataset, uint64_t* out_ids);
/* ─── Random access ─── */
/**
* Take rows by indices.
*
* On success, `out` is initialized in caller-owned storage; the caller must
* eventually invoke its non-NULL `release` callback exactly once. The schema
* is validated before the stream callbacks are exposed. A deferred iteration
* failure, including a caught panic in `get_next`, is reported through the
* Arrow C stream contract (nonzero `get_next` plus `get_last_error`). A panic
* during `release` cleanup is contained and logged; cleanup remains
* best-effort.
*
* @param indices Array of 0-based row offsets
* @param num_indices Length of indices array
* @param columns NULL-terminated column names, or NULL for all
* @param out Pointer to caller-allocated ArrowArrayStream
* @return 0 on success, -1 on error
*/
int32_t lance_dataset_take(
const LanceDataset* dataset,
const uint64_t* indices,
size_t num_indices,
const char* const* columns,
struct ArrowArrayStream* out
);
/**
* Take rows by dataset row IDs.
*
* Row IDs are values from the `_rowid` scanner column, not zero-based row
* offsets. They must belong to the same dataset snapshot used for this read.
* Missing or deleted row IDs may be omitted from the result. For found rows,
* input order and duplicates are preserved.
*
* On success, `out` is initialized in caller-owned storage; the caller must
* eventually invoke its non-NULL `release` callback exactly once. The schema
* is validated before the stream callbacks are exposed. A deferred iteration
* failure, including a caught panic in `get_next`, is reported through the
* Arrow C stream contract (nonzero `get_next` plus `get_last_error`). A panic
* during `release` cleanup is contained and logged; cleanup remains
* best-effort.
*
* @param dataset Open dataset snapshot.
* @param row_ids Array of dataset row IDs. May be NULL only when
* `num_row_ids` is zero.
* @param num_row_ids Length of `row_ids`.
* @param columns NULL-terminated column names, or NULL for all. The
* system column `_rowid` may be requested explicitly.
* @param out Pointer to caller-allocated ArrowArrayStream.
* @return 0 on success, -1 on error.
*/
int32_t lance_dataset_take_rows(
const LanceDataset* dataset,
const uint64_t* row_ids,
size_t num_row_ids,
const char* const* columns,
struct ArrowArrayStream* out
);
/* ─── Blob v2 random access ─── */
/*
* A LanceBlobFile is a file-like handle over one value of a Blob v2 column,
* returned by lance_dataset_take_blobs() / lance_dataset_take_blobs_by_indices()
* and released with lance_blob_file_close(). It owns what it needs to read,
* so it stays valid after the dataset is closed. Not thread-safe per handle;
* distinct handles are independent.
*
* Reads are cursor-based: the cursor starts at 0, lance_blob_file_read() and
* lance_blob_file_read_up_to() advance it, lance_blob_file_read_range() does
* not, lance_blob_file_seek() sets it.
*/
/**
* Take blob handles by dataset row ID.
*
* Row IDs are values from the `_rowid` scanner column, not zero-based row
* offsets. They must belong to the same dataset snapshot used for this read.
*
* On success `out[i]` holds the handle for `row_ids[i]`, or NULL when that
* blob value is null (an empty blob is a handle of size 0). The caller closes
* every non-NULL handle exactly once. On failure `out` is left untouched; a
* row ID that does not resolve fails the whole call.
*
* @param dataset Open dataset snapshot.
* @param row_ids Array of dataset row IDs. May be NULL only when
* `num_row_ids` is zero.
* @param num_row_ids Length of `row_ids`. Zero is a no-op that succeeds
* without writing to `out`.
* @param column Name of a Blob v2 column. Must not be NULL. A missing
* column, or a column that is not a blob column, is an
* error.
* @param out Caller-allocated array of at least `num_row_ids`
* handle pointers. Must not be NULL.
* @return 0 on success, -1 on error
*/
int32_t lance_dataset_take_blobs(
const LanceDataset* dataset,
const uint64_t* row_ids,
size_t num_row_ids,
const char* column,
LanceBlobFile** out
);
/**
* Take blob handles by row index.
*
* Row indices are 0-based offsets in the dataset, as used by
* lance_dataset_take(). Ownership, ordering, NULL slots, and failure
* behavior are identical to lance_dataset_take_blobs().
*
* @param dataset Open dataset snapshot.
* @param indices Array of 0-based row offsets. May be NULL only when
* `num_indices` is zero.
* @param num_indices Length of `indices`. Zero is a no-op that succeeds
* without writing to `out`.
* @param column Name of a Blob v2 column. Must not be NULL.
* @param out Caller-allocated array of at least `num_indices`
* handle pointers. Must not be NULL.
* @return 0 on success, -1 on error
*/
int32_t lance_dataset_take_blobs_by_indices(
const LanceDataset* dataset,
const uint64_t* indices,
size_t num_indices,
const char* column,
LanceBlobFile** out
);
/**
* Return the size of the blob in bytes.
*
* Metadata carried by the handle: no storage access, independent of the