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
/*
* Copyright (c) 2025-2026 Anton Kundenko <singaraiona@gmail.com>
* All rights reserved.
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*
* idxop.h -- Per-vector accelerator indices.
*
* A vector with RAY_ATTR_HAS_INDEX set carries a child ray_t of type
* RAY_INDEX in its aux[0..7] slot. The index ray_t holds:
* - the kind (hash / sort / zone / bloom)
* - kind-specific payload (keys vec, perm vec, min/max, bloom bits)
* - a snapshot of the parent's original 16-byte aux union plus
* the relevant attrs bits, so detach can restore the vector to its
* pre-attach state byte-for-byte.
*
* Attach precondition: parent must not be a slice, must not already
* carry an index, must be COW'd to rc==1 by the caller's path.
*
* Mutation invalidates: any in-place write to the parent vector must
* call ray_index_drop() first AND clear RAY_ATTR_SORTED — a stale index
* or a stale sorted marker is a wrong-answer bug (e.g. an asof-join that
* trusts a false ordering claim). Operators that build a fresh result
* vector get this for free (fresh vectors start at attrs==0, no index).
*/
/* Maximum key columns a RAY_IDX_UKEY map covers. Chosen so the arm fits
* the existing union (see the static assert below); a wider key simply gets
* no index and falls back to the per-call build. */
/* Index kinds. Stored in ray_index_t.kind. */
typedef enum ray_idx_kind_t;
/* Marker bits stored in ray_index_t.markers (block-resident attributes
* that have no dedicated attrs bit). sorted lives in attrs (RAY_ATTR_SORTED),
* not here. */
/* Set when this index and its child vecs are passengers in the parent column's
* file mapping (restored in-place from the on-disk inline index region). The
* parent's single munmap frees them; ray_release_owned_refs must NOT free a
* passenger index by pointer. Clear = heap-resident index (freed normally),
* including a runtime-built index attached to an mmap'd column. */
/* Hash index whose slot table takes a key's home slot from the TOP bits of
* its mixed hash (the partitioned builder); clear = the low bits, as every
* table built before it. Fixed for the life of the table: a rebuild picks
* its own. */
/* The payload stored inside data[] of a RAY_INDEX ray_t. */
typedef struct ray_index_t;
/* The ukey arm is sized to land exactly on the hash arm's 64 bytes so
* ray_index_t does not grow: it is stored inline in persisted column files,
* and a larger payload would change that on-disk layout for every kind. */
;
/* On-disk index persistence stores the RAY_INDEX object and its child vecs as
* contiguous 32-byte-aligned ray_t blocks mmap'd in place (no serialization).
* For the trailing child blocks to stay 32-aligned, the index payload must be a
* 32-byte multiple — enforce it so the layout invariant can't silently break. */
;
/* Inline accessor — returns ray_index_t* for a RAY_INDEX block. */
static inline ray_index_t*
/* The 64-bit mix every hash index uses (builder, probes, and the
* generation-2 validator). */
static inline uint64_t
/* ── Routing observability: per-site consult/hit counters ──
* Diagnostic, unsynchronized (same caveat as ray_expr_bail_counts). */
typedef enum idx_site_t;
extern uint64_t ray_idx_consults;
extern uint64_t ray_idx_hits;
void ; /* atexit dump when RAY_IDX_STATS set */
/* Per-index use attribution (diagnostic, for the index policy
* measurement). Counts one use of col->index; read and reset through the
* `.idx.uses` builtin. No-op for vectors without an index. */
void ;
/* A consult answered from encv's directory (zone class or exclusion): counts
* under the RAY_ENCCOL object itself. */
void ;
ray_t* ;
/* ===== Lazy in-memory HASH (index policy) =====
*
* The converters persist a CHUNK_ZONE, not a HASH. Point lookups (equality
* or `in` filters) on an 8-byte integer or timestamp column whose zone cannot
* prune (the cost test admits no narrower type) build a HASH in memory once
* they have been repeated enough, and reuse it in the same process.
* The index lives in a process-wide cache keyed by the plain column object
* (generation 0: the table's column; generation 2: the encoded object's
* materialization), never in a table slot and never attached to the column,
* so no save can see it. An entry holds one reference to the column and an
* indexed heap copy (ray_idx_hash_fn, copy-on-write); an entry whose column
* only the cache still holds is dropped on the next probe. The bytes held stay
* within a quarter of ray_sys_ram_limit(); least recently used entries are
* evicted to make room. A column the budget or a failed build declines is
* remembered, so later probes do not retry it.
*
* The build waits for proven repetition: the scan answers a wanted column's
* probes, and the rows they read are counted in a pending entry, until that
* scan work pays for one build. Measured on 10M unclustered I64 rows (8
* cores, release, page cache warm): the parallel in-memory build, column copy
* included, takes 615 ms (about 61 ns/row); the equality scan it replaces
* reads the mapped column in 1.9 ms (about 0.19 ns/row). 61 / 0.19 rounds
* to 320 full-column probes: the 320th probe builds, the first 319 scan.
* A cold scan costs more and pays sooner, so the warm ratio is the safe one.
*
* Threads: the cache takes its own lock, but its entries share column
* objects with the caller's tables, so a caller outside the pool's workers
* that probes while another thread uses the same objects must run with
* ray_rc_sync set (atomic reference counts), as for any shared object. */
/* The random-shape test the converters used: a fresh, integer CHUNK_ZONE of
* at least 4 non-empty chunks whose mean chunk span exceeds half the global
* range, and an index costing at most 5x the column's payload. */
bool ;
/* True when col's fresh integer chunk zone proves that no row equals key
* (key outside its whole-column [min, max]), or, for _set, no row equals any
* element of the integer vector set. The caller has ruled out nulls. */
bool ;
bool ;
/* Directory twins for a single-file generation-2 column (encv a
* RAY_ENCCOL): the answer the zone its materialization would synthesize
* (enc_synth_zone / enc_synth_chunk_zone, src/store/enccol.c) gives,
* from the same directory entries, never decoding. UNKNOWN / false for a
* parted column, another recorded kind, or a type the zone does not cover. */
bool ;
bool ;
bool ;
/* For a probe on `col`: NULL with *out = a retained vector equal to col
* carrying a HASH (from the cache, or built now), or NULL with *out = NULL
* when no lazy index applies (not wanted, over budget, declined before, or
* still pending: the caller scans and this probe's work is counted); an
* owned error on failure to build (the caller falls back to its scan). */
ray_t* ;
/* ray_idx_lazy_hash for a single-file generation-2 column (encv its
* RAY_ENCCOL): the probes are counted on the encoded payload (any header
* copy), and the column is materialized only when the build fires; the
* built entry is keyed by that materialization, and *out is its indexed
* copy. ray_idx_lazy_hash / _lookup / _work on the published
* materialization meet the same entry. */
ray_t* ;
/* The cached indexed copy for col, retained (the caller releases it), or NULL. */
ray_t* ;
/* Drop the entry of a column object that is going away (the generation-2
* object frees its materialization). */
void ;
/* An encoded column's payload is freed: its memo (a probe count kept
* before the column was materialized) goes with it. */
;
void ;
/* Tests: rows the scans of col's pending probes read (0 once built), or -1
* when col has no entry or was declined. */
int64_t ;
/* Test hook: the next lazy build fails with `code` ("oom" or "cancel");
* NULL disarms. */
void ;
/* Tests and runtime teardown: drop every entry; indexed entry count; every
* entry (built, pending, declined); bytes held. */
void ;
int64_t ;
int64_t ;
int64_t ;
/* == */
/* Build an accelerator and attach. Numeric types are supported by every
* index kind below; hash and part also support SYM domain ids.
* On success, *vp is the (possibly new) parent vector with HAS_INDEX set.
* On failure, *vp is unchanged and a RAY_ERROR is returned. */
ray_t* ;
ray_t* ;
ray_t* ;
ray_t* ;
ray_t* ;
/* Build per-chunk min/max + null bit at chunk_size = 1 << chunk_log2.
* Passing 0 picks the default (16 → 64 K rows / chunk). Only valid on
* numeric and temporal vectors; SYM/STR/GUID return RAY_ERR_NYI. */
ray_t* ;
/* Build a chunk-zone index WITHOUT attaching it — returns a standalone
* RAY_INDEX object (caller releases). Used by the splayed-store builder to
* compute an index for persistence without COWing a shared column. */
ray_t* ;
/* == */
typedef struct ray_zone_acc_t;
/* true when `type` gets a chunk zone (the ten numeric/temporal types). */
bool ;
/* Start accumulating rows of `type` from global row `start_row`. */
ray_err_t ;
/* Rows [next_row, next_row + v->len) are v; v->type must equal a->type. */
/* A non-OK return (OOM, CANCEL) leaves the accumulator partial: free/discard it. */
ray_err_t ;
/* dst += src, chunk by chunk; both must have the same type. src is untouched. */
ray_err_t ;
/* The standalone RAY_IDX_CHUNK_ZONE index for a column of `len` rows
* (chunk_log2 16), identical to ray_index_chunk_zone_compute(col, 16);
* NULL when len < 65536. Frees the accumulator either way. */
ray_t* ;
void ;
/* 128-bit two's-complement accumulation of int64 values: (hi, lo) += v.
* The engine's integer avg sums this way — exact for any column, and the
* same bits whatever the morsel split — and the chunk-zone metadata keeps
* the per-chunk (hi, lo) so it can answer the same value. */
static inline void
static inline void
/* Double nearest the 128-bit value (hi, lo): the magnitude is converted
* (high word scaled by 2^64 plus the low word) and the sign reapplied, so
* a small negative total is not lost in 2^64 - |s|. One formula
* everywhere so every path agrees bit for bit. */
static inline double
/* Whole-column sum (int64 wraparound) and non-null count of an integer
* column from its chunk-zone per-chunk aggregates; false when the column
* carries none for its current length. */
bool ;
/* The exact 128-bit sum (hi, lo) and non-null count from the same
* metadata: true when the per-chunk high words are stored, or when no
* partial sum can wrap int64 (then the wrapped sum is the exact one). */
bool ;
/* OP_SUM / OP_AVG / OP_MIN / OP_MAX of a single-file generation-2 column
* whose recorded index is CHUNK_ZONE, answered as ray_sum_fn / ray_avg_fn /
* ray_min_fn / ray_max_fn answer the generation-0 column from its
* CHUNK_ZONE (the zone the materialization would synthesize), from the
* directory; NULL when that zone path would not answer. */
ray_t* ;
/* ray_zone_int_sum128's twin for the same columns: the exact 128-bit sum
* and non-null count from the directory; false when the zone the
* materialization would synthesize carries no integer sums. */
bool ;
/* ray_zone_dir_answer's answer for every single-file stored generation-2
* column, with or without a recorded zone: the execution-time reduction
* (ray_reduce_src) takes it. Planning-time answers stay on
* ray_zone_dir_answer, so they do not widen past generation 0's. */
ray_t* ;
/* The directory totals (a ray_enc_col_totals_t, store/enccol.h) of one
* parted segment for an integer or temporal SUM / AVG / MIN / MAX: true
* when the segment is a single-file stored generation-2 column the
* directory answers `opcode` for, false for a plain, synthetic or float
* segment (the caller reduces its rows). */
bool ;
/* Build a RAY_IDX_DICT (codes + distinct values) for STR vector `v` WITHOUT
* attaching it — standalone RAY_INDEX object (caller releases / attaches).
* Returns RAY_ERR_NYI for non-STR. Used at splayed save to persist the dict
* and by ray_index_attach_dict for the runtime path. */
ray_t* ;
ray_t* ;
/* == */
typedef struct ray_dict_acc_s ray_dict_acc_t;
ray_err_t ;
/* Appends every row of STR vector v. There is no cardinality cap: the
* result matches ray_index_dict_compute for any number of distinct strings.
* Only a row count past INT32_MAX (codes are int32) frees the accumulator,
* sets dead and returns RAY_OK; further adds are no-ops. A non-OK return
* (OOM, CANCEL) leaves the accumulator partial: free/discard it. */
ray_err_t ;
/* Appends n raw STR descriptors whose pooled bytes are relative to `pool`
* (ray_str_t_ptr): the same codes ray_dict_acc_add assigns to the same
* strings, with the same dead / error rules. */
ray_err_t ;
/* The fewest rows a saved generation-0 STR column carries its DICT index at. */
/* The standalone RAY_IDX_DICT for a column of `len` rows, identical to
* ray_index_dict_compute; NULL when dead or len < RAY_DICT_MIN_ROWS. Frees `a`. */
ray_t* ;
void ;
/* Attach an already-built standalone RAY_INDEX object (zero-copy on rc=1). */
ray_t* ;
/* Attach a heap-built root to a fresh plain vector. Consumes `root` on
* success and on error; refuses (corrupt) a pair the matrix does not
* admit, a root whose parent_type or built_for_len disagree, a slice, or
* a vector already indexed. */
ray_t* ;
/* A zeroed RAY_INDEX block of `kind` for a parent (public wrapper of the
* allocator used by every builder). */
ray_t* ;
/* Spec "Materialization" step 3 admission matrix:
* numeric and temporal types carry HASH, SORT, BLOOM, PART, ZONE,
* CHUNK_ZONE and a marker-only root; SYM carries HASH, PART and a
* marker-only root; STR carries HASH, DICT (persisted for generation 2
* since P6b) and a marker-only root; GUID carries nothing. */
bool ;
/* ── Inline on-disk index region (zero-copy mmap) ──
* ray_index_inline_size: bytes the region occupies (32-aligned ray_t blocks).
* ray_index_inline_write: serialize `ix` into dst (child ptrs → region offsets).
* ray_index_inline_map: patch an mmap'd region's child offsets → absolute ptrs
* in place and return the RAY_INDEX object (flagged RAY_MARK_MMAP). */
int64_t ;
void ;
/* The same bytes as ray_index_inline_write, streamed to `f` block by block
* straight from the child vecs: no region-sized staging buffer (a 100 M-row
* hash region is ~4.5 GB) and one copy fewer. false on a short write. */
bool ;
ray_t* ;
/* ── Hash-index build trace (RAY_CSV_TRACE) ──
* Per phase of one build: wall time, major faults, the bytes the process
* read and wrote (/proc/self/io; zero off Linux), and `moved`, the bytes the
* phase's own passes read and write by design (each array a pass reads or
* writes in full, once per pass) — the I/O it costs when nothing fits in
* memory. count: the column read for the partition histogram; bucket: the
* column read again into key words and row ids; distinct: the groups of
* each partition counted; groups: deduped and stored, and the slot table
* filled alongside when its size is known; table: the slot table after the
* groups, and the arrays cut to size; append: the region into the file. */
;
typedef struct ray_hash_trace_t;
typedef struct ray_hash_mark_t;
ray_hash_mark_t ;
/* Phase `ph` ran since `since` and moved `moved` bytes. */
void ;
/* One line: each phase that ran, `moved` also as a multiple of `col_bytes`. */
void ;
/* Build the hash index of numeric or SYM vector `v` straight into its
* persisted inline region, without attaching it and without staging the
* arrays in memory: once the region's size is known, `map(ctx, bytes)`
* returns `bytes` of zero-filled writable memory (NULL when it cannot),
* and the build writes there exactly the bytes ray_index_inline_write would
* write for the index ray_index_attach_hash builds on `v`. RAY_ERR_NYI for
* a type the partitioned builder does not take (STR) — nothing was mapped;
* RAY_ERR_IO when map failed, RAY_ERR_OOM / RAY_ERR_CANCEL when the build
* did: the mapped memory, if any, holds no complete region. `tr` (may be
* NULL) collects the phases. */
typedef uint8_t* ;
/* The arrays of HASH payload `ix` in the generation-1 layout a
* generation-2 index region stores: every array I64, the groups numbered by
* first occurrence (row order of their first rows), each group in the first
* free slot from mix64(key) & mask (the low-bits home rule): the bytes the
* generation-1 builders made. Shared (retained) when ix is that layout
* already; fresh otherwise. False on OOM (outputs NULL). */
bool ;
ray_err_t ;
/* Drop any attached index from *vp. No-op if none. Restores the
* pre-attach aux state byte-for-byte. Returns *vp. */
ray_t* ;
/* ===== Table-resident key index (RAY_IDX_UKEY) =====
* Attached to a RAY_TABLE, not to a vector, so the generic attach path does
* not apply: a table's aux is zero-init, there is nothing to snapshot and
* restore. Retain/release ride the type-agnostic RAY_ATTR_HAS_INDEX arms in
* heap.c, so the index dies with the table. */
/* Build a standalone RAY_IDX_UKEY sized for `entries` rows (caller attaches
* or releases). The slot vector is allocated zeroed. */
ray_t* ;
/* Attach `idx` to `tbl`, taking ownership of the caller's reference.
* Returns false (and keeps the caller's reference) if `tbl` already carries
* an index — the caller drops first. */
bool ;
/* The attached ukey payload, or NULL when the table carries no index or one
* of another kind. */
ray_index_t* ;
/* Detach and release any index on `tbl`. Safe on a table with none. */
void ;
/* == */
static inline bool
/* Returns RAY_IDX_NONE if no index is attached. */
static inline ray_idx_kind_t
/* --- Semantic attribute markers (see mem/heap.h: RAY_ATTR_SORTED) --- */
/* True iff v is a vector flagged sorted (non-descending). */
static inline bool
/* Returns a fresh RAY_DICT with {kind, length, ...kind-specific...}
* or RAY_NULL_OBJ when no index is attached. */
ray_t* ;
/* ===== Zone-index all/none classification =====
*
* O(1) pre-check: given the zone index on `col` and a comparison
* (col cmp_op key), decide whether the predicate is definitely true for
* ALL rows, definitely false for ALL rows (NONE), or UNKNOWN (need scan).
*
* cmp_op is the OP_EQ..OP_GE opcode. key_i is used for integer/temporal
* column families; key_f for float families. NaN key_f → UNKNOWN.
* Returns UNKNOWN when the column is not eligible (no zone index, stale,
* null-bearing, unsupported type). */
typedef enum ray_zone_class_t;
ray_zone_class_t ;
/* Its directory twin for a generation-2 column recording ZONE (see
* ray_enc_zone_excludes): UNKNOWN unless encv is a single-file RAY_ENCCOL
* without nulls. */
ray_zone_class_t ;
/* Build an all-NONE rowsel of `n` rows. Wraps rowsel_from_sorted_ids(n, NULL, 0).
* Returns NULL on OOM; returns a valid zero-match rowsel on success. */
ray_t* ;
/* ===== Bloom-index definite-absent probe =====
*
* Returns true ONLY when the bloom filter PROVES the key is absent
* (all k probe bits are clear). Returns false when the key may be
* present OR when the column is not eligible — caller proceeds to
* other consults / the scan either way.
*
* Integer-family columns only in v1 (mirrors the hash path's
* conservatism on float equality: F32/F64 NaN/-0 semantics).
* No range validation is performed: an out-of-range key hashes to
* some bit positions; if they are set we fall through — the absent
* proof is still sound (no false absent is possible). */
bool ;
/* ===== Hash-index point-lookup probe =====
*
* Build a ray_rowsel directly from a hash probe on `col`'s
* RAY_IDX_HASH for rows where the payload equals `key`. Bypasses
* the intermediate BOOL pred vec entirely — touches O(matches)
* memory instead of O(rows), which is the whole reason to ship
* this fast path.
*
* Returns:
* - A fresh rowsel block (rc=1) on success — install on
* g->selection. The block carries per-segment NONE/MIX/ALL
* flags and the morsel-local indices for matching rows.
* Pure NONE blocks (no matches) are returned as a valid empty
* rowsel rather than NULL — NULL is the "all-pass" sentinel
* in the consumer and would let every row through.
* - NULL when the column is not eligible: no index, wrong kind,
* built_for_len mismatch (stale), type mismatch, or out-of-
* range key. Caller must fall back to the full scan path.
*
* Eligibility (and the canonical hashing used) match
* ray_index_attach_hash: BOOL/U8/I16/I32/I64/DATE/TIME/TIMESTAMP.
* Floats are intentionally not supported — equality on F32/F64
* has NaN / -0 semantics the unfused compare kernel handles. */
ray_t* ;
/* ray_index_hash_eq_rowsel for a single-file generation-2 column whose
* recorded HASH region is probed where it is mapped, without materializing
* the column or copying the region: the root's layout is checked against
* the region, every slot, group and offset the probe reads is bounded, and
* the group's rows are checked ascending, inside the column, and holding
* `key` (read back by range: a region that disagrees with the column is
* `corrupt`, naming it). `key` as for ray_index_hash_eq_rowsel (a SYM key
* a nonzero domain id). 1: *sel the selection (an empty one for an absent
* key); 0 declined (no such region, a key or type it does not cover, or a
* group dense enough for the scan, as ray_index_hash_eq_rowsel declines);
* -1: *err owned. */
int ;
/* Test hook: probes ray_enc_hash_eq_rowsel answered since process start. */
uint64_t ;
/* Direct equality selection for a fresh part index. `key` is a numeric
* value or a column-domain SYM id. Dense matches remain eligible because
* the part payload already stores one contiguous physical span. */
ray_t* ;
/* ===== Hash/part-index IN probe =====
*
* Build a rowsel of all rows whose value is in set_vec via per-element hash
* probes or direct physical spans from a part index. Integer-family and SYM
* columns (same conservatism as the hash-eq path: F32/F64 NaN/-0 semantics
* belong to the scan kernel).
*
* set_vec must match the column family: an integer-family typed vec for an
* integer column, a SYM vec for a SYM column (each set symbol is resolved
* through the COLUMN's domain; domain-absent symbols match no rows).
* Other combinations → NULL.
*
* Returns:
* - A fresh rowsel block (rc=1) on success — install on g->selection.
* An empty set yields a valid all-NONE rowsel (NOT NULL).
* - NULL when the column is not eligible (no hash/part index, stale,
* float-family column, unsupported set type, OOM) or a hash-index union
* is too dense. Part-index unions retain dense full morsels efficiently.
* Caller falls back to the scan path. */
ray_t* ;
/* ===== Hash-index find (point lookup) =====
*
* Returns the minimum row id where the column value equals `key`, or
* -1 when the index proves the key is absent (provably-no-match).
* Returns -2 when the column is not eligible: no hash index, stale,
* float-family column, or out-of-eligibility condition — caller must
* fall back to the linear scan.
*
* Contract:
* >= 0 → key found; this is the FIRST (minimum) row id match.
* -1 → key provably absent; caller returns find's miss value.
* -2 → not eligible; caller falls back to the scan.
*
* Uses idx_fresh_nonull: null-bearing columns fall back (-2) so the
* scan correctly surfaces null-equality searches. */
int64_t ;
/* ===== Hash-index find: one atom / a vector of needles =====
*
* ray_index_find_atom: like ray_index_find_row but keyed by an ATOM, so it
* also serves SYM columns (the symbol re-expressed in the column's domain)
* and STR columns (the bytes). Same contract: >= 0 first row, -1 provably
* absent, -2 not eligible (no fresh null-free hash index, null / float /
* cross-family needle) — caller falls back to the scan.
*
* ray_index_find_vec: one probe per needle of the typed vector `needles`
* into `out[needles->len]` (first matching row, NULL_I64 on a miss; a null
* needle misses — the index is only consulted on null-free columns).
* Returns 1 when handled, 0 when not eligible (out is then unspecified and
* the caller builds its own hash). Integer-family needles for an
* integer-family column, SYM for SYM (any domain), STR for STR. */
int64_t ;
int ;
/* ===== Hash-index carry across an append =====
*
* `dst` is a fresh (rc=1, unsliced, unindexed, null-free) vector whose first
* src->len rows are src's rows and whose tail is appended rows. When src
* carries a fresh null-free hash index and every appended row is a NEW key,
* dst gets an equivalent index in O(groups + appended) without re-hashing the
* old rows; markers (`unique`) and the within-group order symbol carry over.
* A repeated key, a float column, a SYM domain change or OOM leave dst
* unindexed (the caller re-attaches if it wants one). Best-effort: never
* fails the append. */
void ;
/* Row ids borrowed from a hash index: 64-bit, or unsigned 32-bit when the
* index was built narrow (any column below 2^32 rows). Read with
* ray_idx_rows_at. */
typedef struct ray_idx_rows_t;
static inline int64_t
/* The ids from position `off` on. */
static inline ray_idx_rows_t
/* ===== Hash-index group slice (CSR accessor) =====
*
* Resolve `key` to its group's contiguous ascending row-id slice.
* SYM columns take the COLUMN-DOMAIN id as key; numeric columns take the
* value (canonicalized through the storage width like the eq probe).
*
* Returns:
* 1 → hit: *rows_out holds the ascending row ids, *n_out the size.
* The slice borrows the index payload — valid while the index is.
* 0 → key provably absent (rows_out->p NULL, *n_out 0).
* -1 → not eligible (no fresh hash index / bad key) — caller falls back. */
int ;
/* Build a rowsel from ASCENDING row ids (empty n=0 is valid). NULL on OOM. */
ray_t* ;
/* Same, from row ids borrowed from an index. */
ray_t* ;
/* ===== Slice-group resolution (FILTER + GROUP-by-key fusion) =====
*
* One surviving group of the fused shape: the key's column-domain id and
* its CSR row slice (ascending row ids, borrowed from the index payload —
* valid while the index is). */
typedef struct ray_idx_slice_t;
/* Resolve a SYM eq key (sym ATOM, global intern id) or IN set (SYM vec,
* any domain) against `col`'s fresh hash or part index into per-key slices,
* canonicalized ascending by column-domain id (duplicates collapsed;
* absent / rowless keys dropped). Hash slices borrow CSR row ids; part
* slices use rows==NULL plus their contiguous physical first row. On success
* returns K >= 0 and, for
* K > 0, *out points into a fresh alloc block returned via *hdr_out
* (release with ray_free). K == 0 → provably no surviving rows (*out
* and *hdr_out NULL). Returns -1 when ineligible: stale or missing
* hash/part index, null-bearing or non-SYM column, wrong key shape. */
int64_t ;
/* ===== Sort-index range probe =====
*
* Build a rowsel from a binary search over the sort-index permutation for
* rows satisfying (col cmp_op key). Supports EQ/LT/LE/GT/GE. NE returns
* NULL (two disjoint spans — unsupported).
*
* Returns:
* - A fresh rowsel block on success — install on g->selection.
* span == 0 yields a valid all-NONE rowsel (NOT NULL).
* - NULL when not eligible: no sort index, stale, type mismatch,
* span > col->len / IDX_RANGE_MAX_FRAC (selectivity guard), OOM,
* or NE operator. Caller falls back to the scan.
*
* key_i is used for integer-family columns; key_f for float-family.
* is_float must match the column family (mirror of idx_filter_decode
* contract); mismatch returns NULL defensively. */
ray_t* ;
/* Build a rowsel by binary-searching a physically non-descending column.
* Unlike the sort-index route above, matching row ids are already one
* contiguous interval, so dense spans are eligible and the rowsel stores
* only the at-most-two partial boundary morsels. Requires the verified
* RAY_ATTR_SORTED marker and a null-free numeric column. */
ray_t* ;
/* ===== Sort-index ORDER BY permutation probe =====
*
* Returns a BORROWED pointer to the ascending permutation vector stored in
* the sort index on `col` when the column carries a fresh, null-free sort
* index; NULL otherwise. The caller MUST NOT release the returned pointer
* (it is owned by the index payload). To use it as an owned reference,
* call ray_retain() immediately after. */
ray_t* ;
/* ===== Sort-index distinct fast path =====
*
* Walk the sort permutation once to collect the first-occurrence row id of
* each distinct value (the first element of each equal-value run, which is
* the minimum row id of the run because the sort is stable with iota
* tie-breaking). Returns an OWNED I64 vector of those row ids, sorted
* ascending by VALUE (matching distinct_vec_eager's qsort-of-indices
* contract), and sets *out_count to the number of distinct values.
*
* Returns NULL if not eligible (no sort index, stale, null-bearing) or on
* OOM. The caller is responsible for releasing the returned vector. */
ray_t* ;
/* ===== Internal helpers (used by retain/release/detach in heap.c
* and by mutation paths in vec.c) ===== */
/* Release the saved-aux pointers carried by a RAY_INDEX ray_t.
* Invoked from ray_release_owned_refs when the index ray_t is freed. */
void ;
/* Retain the saved-aux pointers carried by a RAY_INDEX ray_t.
* Invoked from ray_retain_owned_refs after a copy of the index ray_t. */
void ;
/* Release per-kind payload children (keys/table/perm/bits...). */
void ;
/* The payload child blocks of an index (the same set retain/release walk),
* written to out[0..cap) — returns the count (at most 4). Footprint
* accounting (ray_shallow_bytes / ray_retained_bytes) sums them; a
* RAY_MARK_MMAP index's children live inside the column's mapping and are
* charged there, so callers skip them. */
int ;
/* Retain per-kind payload children. */
void ;
/* == */
ray_t* ; /* (.idx.zone v) -> v with zone attached */
ray_t* ; /* (.idx.hash v) -> v with hash attached */
ray_t* ; /* (.idx.sort v) -> v with sort attached */
ray_t* ; /* (.idx.bloom v) -> v with bloom attached */
ray_t* ; /* (.idx.drop v) -> v with index removed */
ray_t* ; /* (.idx.has? v) -> 0b/1b */
ray_t* ; /* (.idx.info v) -> dict of metadata */
ray_t* ; /* (.idx.info col) | (.idx.info table 'col) */
ray_t* ; /* (.attr.set 'name v) */
ray_t* ; /* (.attr.get v) -> sym vec */
ray_t* ; /* (.attr.drop v) -> v cleared */
/* RAY_IDXOP_H */