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
// SPDX-License-Identifier: BUSL-1.1
//! Record-boundary admission checks for timeseries ingest.
//!
//! Every check here answers the same question BEFORE the first row of a WAL
//! record is written: can the memtable take this record WHOLE? A "no" means the
//! caller must flush first — never partway through.
//!
//! That ordering is what makes the partition stamp honest.
//! `flush_ts_collection` labels the partition it writes with the collection's
//! max ingested WAL LSN, and boot replay skips every record at or below the
//! highest stamp it finds. A flush that fires between two rows of record L
//! writes a partition holding SOME of L but stamped L-1 (L is recorded only
//! once the record is fully ingested), so replay does not skip L and appends
//! every one of its rows a second time — on an append-only engine nothing
//! masks that.
use HashSet;
use crate;
use crateIlpLine;
use crateilp_ingest;
/// Whether every symbol column's dictionary can absorb this batch's distinct
/// values without hitting `max_tag_cardinality`.
///
/// Cardinality exhaustion is the reason a mid-record stop is otherwise
/// unavoidable, and the reason it cannot be handled by retrying a suffix:
/// `SymbolDictionary::resolve` fails only for values NOT already in the
/// dictionary, so once a dictionary is full the lines carrying new tag values
/// fail while lines reusing existing values keep succeeding. The failures are
/// INTERLEAVED through the batch, not a suffix of it, so the count of accepted
/// lines does not identify the consumed prefix.
///
/// Answering this up front lets the caller flush — which resets the
/// dictionaries — and then take the record whole, preserving exactly the
/// cardinality progress today's behaviour makes with none of the mid-record
/// flush.
///
/// A `false` here does not promise the batch then fits: a single batch with
/// more distinct values than `max_tag_cardinality` cannot fit in any
/// generation, and its excess rows are honestly reported as `rejected`. What
/// it does guarantee is that the decision is made once, before any row lands.
///
/// ## Cost
///
/// The common case is O(1) per symbol column. A batch of `n` lines can add at
/// most `n` new values to a column, so a column whose dictionary plus `n`
/// still fits under the ceiling cannot possibly overflow and is skipped
/// without touching a line. That bound is exact, not a heuristic — it never
/// skips a column that would have overflowed — and it retires every batch that
/// is not genuinely near the ceiling, which is all of them until a collection
/// approaches its cardinality limit.
///
/// Only a column that could overflow pays the full pass: `O(lines)` borrowed
/// `&str` dictionary probes plus one `HashSet<&str>` sized to that column's
/// NEW distinct values (not to the batch). Nothing is allocated per line and
/// no value is copied — the set borrows out of the parsed lines. The pass
/// re-does probes the ingest would do anyway, without the row writes; that
/// repeat is the price of knowing the answer before the first write instead of
/// after a partial one.
pub
/// The value `ingest_batch_with_lvc` would resolve for symbol column
/// `col_name` on `line`.
///
/// Mirrors that function's resolution order exactly — tag first, then string
/// field, else the empty string. Any divergence would make the headroom check
/// answer about a different set of values than the ingest actually inserts.