GCC Code Coverage Report


Directory: src/
File: src/index/posting_convert.c
Date: 2026-10-23 09:17:32
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 12 times.
✓ Branch 1 taken 3848 times.
✓ Branch 2 taken 319 times.
4179 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.
3858 prism_posting_entry_at(content, i, ctx->dim);
59
60 /*
61 * Leave behind the entries VACUUM has marked dead. Nothing here
62 * carries a dead AoS entry's state into the fresh fastscan chain --
63 * the destination's tombstone_mask words start at zero, same as any
64 * freshly built page -- so copying the entry would resurrect it as
65 * live. The head's live_count, which the builder stamps from what it
66 * was given, would be wrong by the same number.
67 */
68
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 3845 times.
3858 if (src->meta.flags & PRISM_POSTING_FLAG_DELETED)
69 13 continue;
70
71
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3845 times.
3845 if (ctx->total_entries >= ctx->entries_cap)
72 {
73 ✗ ctx->entries_cap *= 2;
74 ✗ ctx->staged = vs_realloc(
75 ✗ ctx->staged, ctx->entries_cap * sizeof(StagedEntry));
76 ✗ ctx->all_bits = vs_realloc(
77 ✗ ctx->all_bits,
78 ✗ ctx->entries_cap * (size_t)ctx->packed_bytes);
79 }
80
81 3845 StagedEntry *dst = &ctx->staged[ctx->total_entries];
82
83 3845 dst->tid = src->meta.tid;
84 3845 dst->f_add = src->f_add;
85 3845 dst->f_rescale = src->f_rescale;
86 3845 dst->f_error = src->f_error;
87 3845 memcpy(ctx->all_bits + (size_t)ctx->total_entries * ctx->packed_bytes,
88 3845 src->bits,
89 3845 ctx->packed_bytes);
90 3845 ctx->total_entries++;
91 }
92
93 321 return true;
94 }
95
96 BlockNumber
97 321 prism_posting_convert_to_fastscan(
98 VsStorage *storage, BlockNumber aos_head, Dimension dim)
99 {
100
2/2
✓ Branch 0 taken 319 times.
✓ Branch 1 taken 2 times.
321 if (aos_head == InvalidBlockNumber)
101 ✗ return InvalidBlockNumber;
102
103 321 uint32_t packed_bytes = VS_RABITQ_BYTES(dim);
104
105 /* Read pt_centroid from AoS first page */
106 321 Page first_page = vs_storage_read_page(storage, aos_head);
107 321 float *pt_centroid = vs_alloc(dim * sizeof(float));
108 323 memcpy(pt_centroid,
109 321 prism_posting_pt_centroid(first_page),
110 dim * sizeof(float));
111
112 321 PrismPostingPageOpaque *first_op = prism_posting_opaque(first_page);
113 321 uint32_t cluster_id = first_op->cluster_id;
114 321 vs_storage_release_page(storage, aos_head);
115
116 319 PrismPostingBuilder builder;
117 321 prism_posting_builder_init_fastscan(
118 &builder, storage, NULL, dim, cluster_id, NULL, pt_centroid);
119
120 321 StageCtx ctx = {
121 .dim = dim,
122 .packed_bytes = packed_bytes,
123 .entries_cap = 256,
124 };
125 321 ctx.staged = vs_alloc(ctx.entries_cap * sizeof(StagedEntry));
126 321 ctx.all_bits = vs_alloc(ctx.entries_cap * (size_t)packed_bytes);
127
128 321 prism_posting_chain_walk(storage, aos_head, stage_page, &ctx);
129
130 /* Feed staged entries to the fastscan builder */
131
3/3
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3835 times.
✓ Branch 2 taken 319 times.
4166 for (uint32_t i = 0; i < ctx.total_entries; i++)
132 {
133 3845 prism_posting_builder_add_encoded(
134 &builder,
135 12 ctx.staged[i].tid,
136 12 ctx.staged[i].f_add,
137 12 ctx.staged[i].f_rescale,
138 3845 ctx.staged[i].f_error,
139 3845 ctx.all_bits + (size_t)i * packed_bytes);
140 }
141
142 321 vs_free(ctx.staged);
143 321 vs_free(ctx.all_bits);
144
145 321 BlockNumber result = prism_posting_builder_finish(&builder);
146 321 prism_posting_builder_cleanup(&builder);
147 321 vs_free(pt_centroid);
148
149 321 return result;
150 }
151