GCC Code Coverage Report


Directory: src/
File: src/index/posting_convert.c
Date: 2026-09-30 11:11:31
Exec Total Coverage
Lines: 50 57 87.7%
Functions: 2 2 100.0%
Branches: 13 14 92.9%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2026 Tiger Data, Inc.
3 * Licensed under the PostgreSQL License. See LICENSE for details.
4 *
5 * posting_convert.c - Convert AoS posting pages to fastscan format
6 *
7 * Walks an AoS posting chain, reads pre-encoded entries, and feeds
8 * them to PrismPostingBuilder via _add_encoded(). The builder
9 * handles group packing, page layout, and chain linking.
10 */
11
12 #include <string.h>
13
14 #include "core/memory.h"
15 #include "index/posting_build.h"
16 #include "index/posting_convert.h"
17 #include "quant/fastscan.h"
18 #include "quant/rabitq.h"
19
20 /*
21 * Entries staged out of the AoS chain before any of them is written back.
22 *
23 * The PG storage layer holds one pinned buffer at a time, so reads and
24 * writes cannot interleave: appending to the builder mid-walk would take
25 * over the buffer the walk is reading from. Staging everything first is what
26 * keeps the two apart.
27 */
28 typedef struct StagedEntry
29 {
30 ItemPointerData tid;
31 float f_add;
32 float f_rescale;
33 float f_error;
34 } StagedEntry;
35
36 typedef struct StageCtx
37 {
38 Dimension dim;
39 uint32_t packed_bytes;
40 uint32_t total_entries;
41 uint32_t entries_cap;
42 StagedEntry *staged;
43 uint8_t *all_bits;
44 } StageCtx;
45
46 static bool
47 321 stage_page(PrismPostingChainPos *pos, void *state)
48 {
49 321 StageCtx *ctx = state;
50 321 const PrismPostingPageOpaque *op = prism_posting_opaque(pos->page);
51 321 char *content = prism_posting_page_content(pos->page, ctx->dim);
52
53 321 prism_posting_check_count(pos->blkno, op, ctx->dim);
54
55
3/3
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 3848 times.
✓ Branch 2 taken 319 times.
4211 for (uint32_t i = 0; i < op->entry_count; i++)
56 {
57 3846 PrismPostingEntryHeader *src =
58
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3833 times.
3890 prism_posting_entry_at(content, i, ctx->dim);
59
60 /*
61 * Leave behind the entries VACUUM has marked dead. A fastscan page
62 * packs codes with no per-entry flag -- deletion there is
63 * page-granular -- so a dead entry copied into one comes back as
64 * live and can never be marked again: a later VACUUM can only
65 * tombstone the page once *every* entry on it is dead, which a page
66 * holding live entries never is. The head's live_count, which the
67 * builder stamps from what it was given, would be wrong by the same
68 * number.
69 */
70
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3877 times.
3890 if (src->meta.flags & PRISM_POSTING_FLAG_DELETED)
71 13 continue;
72
73
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3877 times.
3877 if (ctx->total_entries >= ctx->entries_cap)
74 {
75 ✗ ctx->entries_cap *= 2;
76 ✗ ctx->staged = vs_realloc(
77 ✗ ctx->staged, ctx->entries_cap * sizeof(StagedEntry));
78 ✗ ctx->all_bits = vs_realloc(
79 ✗ ctx->all_bits,
80 ✗ ctx->entries_cap * (size_t)ctx->packed_bytes);
81 }
82
83 3877 StagedEntry *dst = &ctx->staged[ctx->total_entries];
84
85 3877 dst->tid = src->meta.tid;
86 3877 dst->f_add = src->f_add;
87 3877 dst->f_rescale = src->f_rescale;
88 3877 dst->f_error = src->f_error;
89 3877 memcpy(ctx->all_bits + (size_t)ctx->total_entries * ctx->packed_bytes,
90 3877 src->bits,
91 3877 ctx->packed_bytes);
92 3877 ctx->total_entries++;
93 }
94
95 321 return true;
96 }
97
98 BlockNumber
99 321 prism_posting_convert_to_fastscan(
100 VsStorage *storage, BlockNumber aos_head, Dimension dim)
101 {
102
2/2
✓ Branch 0 taken 319 times.
✓ Branch 1 taken 2 times.
321 if (aos_head == InvalidBlockNumber)
103 ✗ return InvalidBlockNumber;
104
105 321 uint32_t packed_bytes = VS_RABITQ_BYTES(dim);
106
107 /* Read pt_centroid from AoS first page */
108 321 Page first_page = vs_storage_read_page(storage, aos_head);
109 321 float *pt_centroid = vs_alloc(dim * sizeof(float));
110 323 memcpy(pt_centroid,
111 321 prism_posting_pt_centroid(first_page),
112 dim * sizeof(float));
113
114 321 PrismPostingPageOpaque *first_op = prism_posting_opaque(first_page);
115 321 uint32_t cluster_id = first_op->cluster_id;
116 321 vs_storage_release_page(storage, aos_head);
117
118 319 PrismPostingBuilder builder;
119 321 prism_posting_builder_init_fastscan(
120 &builder, storage, NULL, dim, cluster_id, NULL, pt_centroid);
121
122 321 StageCtx ctx = {
123 .dim = dim,
124 .packed_bytes = packed_bytes,
125 .entries_cap = 256,
126 };
127 321 ctx.staged = vs_alloc(ctx.entries_cap * sizeof(StagedEntry));
128 321 ctx.all_bits = vs_alloc(ctx.entries_cap * (size_t)packed_bytes);
129
130 321 prism_posting_chain_walk(storage, aos_head, stage_page, &ctx);
131
132 /* Feed staged entries to the fastscan builder */
133
3/3
✓ Branch 0 taken 44 times.
✓ Branch 1 taken 3835 times.
✓ Branch 2 taken 319 times.
4198 for (uint32_t i = 0; i < ctx.total_entries; i++)
134 {
135 3877 prism_posting_builder_add_encoded(
136 &builder,
137 44 ctx.staged[i].tid,
138 44 ctx.staged[i].f_add,
139 44 ctx.staged[i].f_rescale,
140 3877 ctx.staged[i].f_error,
141 3877 ctx.all_bits + (size_t)i * packed_bytes);
142 }
143
144 321 vs_free(ctx.staged);
145 321 vs_free(ctx.all_bits);
146
147 321 BlockNumber result = prism_posting_builder_finish(&builder);
148 321 prism_posting_builder_cleanup(&builder);
149 321 vs_free(pt_centroid);
150
151 321 return result;
152 }
153