1use crate::ids::{OwnerKind, TenantId};
2use std::fmt::Write as _;
3
4#[must_use]
8pub fn tenant_hex(tenant: TenantId) -> String {
9 let mut out = String::with_capacity(8);
10 for byte in tenant.to_be_bytes() {
11 let _ = write!(out, "{byte:02x}");
12 }
13 out
14}
15
16pub const SYSTEM_NS: u8 = 0x00;
18pub const TENANT_NS: u8 = 0x01;
20pub const TENANT_PREFIX_LEN: usize = 5;
22
23pub const SYS_TENANT_BY_SCOPE: u8 = 0x02;
25pub const SYS_TENANT_BY_ORD: u8 = 0x03;
27pub const SYS_TENANT_SEQ: u8 = 0x04;
29
30#[must_use]
32pub fn tenant_prefix(tenant: TenantId) -> [u8; TENANT_PREFIX_LEN] {
33 let [a, b, c, d] = tenant.to_be_bytes();
34 [TENANT_NS, a, b, c, d]
35}
36
37#[must_use]
39pub fn parse_tenant(key: &[u8]) -> Option<TenantId> {
40 if key.first() != Some(&TENANT_NS) {
41 return None;
42 }
43 let bytes: [u8; 4] = key.get(1..TENANT_PREFIX_LEN)?.try_into().ok()?;
44 Some(TenantId(u32::from_be_bytes(bytes)))
45}
46
47#[must_use]
49pub fn tenant_by_scope_key(scope: &crate::ids::TenantScope) -> Vec<u8> {
50 let mut out = Vec::with_capacity(34);
51 out.extend_from_slice(&[SYSTEM_NS, SYS_TENANT_BY_SCOPE]);
52 out.extend_from_slice(&scope.0);
53 out
54}
55
56#[must_use]
58pub fn tenant_by_ord_key(tenant: TenantId) -> Vec<u8> {
59 let mut out = Vec::with_capacity(6);
60 out.extend_from_slice(&[SYSTEM_NS, SYS_TENANT_BY_ORD]);
61 out.extend_from_slice(&tenant.to_be_bytes());
62 out
63}
64
65#[must_use]
67pub fn tenant_by_ord_prefix() -> [u8; 2] {
68 [SYSTEM_NS, SYS_TENANT_BY_ORD]
69}
70
71#[must_use]
73pub fn tenant_seq_key() -> [u8; 2] {
74 [SYSTEM_NS, SYS_TENANT_SEQ]
75}
76
77#[must_use]
79pub fn vector_index_meta_prefix(tenant: TenantId) -> String {
80 format!("vector/index/{}/", tenant_hex(tenant))
81}
82
83#[must_use]
85pub fn ann_manifest_meta_prefix(tenant: TenantId) -> String {
86 format!("vector/ann-manifest/{}/", tenant_hex(tenant))
87}
88
89pub const P_META: &[u8] = b"meta";
91pub const P_REL: &[u8] = b"relationship";
93pub const P_REL_BY_ORD: &[u8] = b"relationship-ordinal";
95pub const P_REL_GROUP: &[u8] = b"rel-group";
97pub const P_FAMILY_SIG: &[u8] = b"property-family-signature";
99pub const P_FAMILY: &[u8] = b"property-family";
101pub const P_DEFINITION: &[u8] = b"property-definition";
103#[must_use]
106pub fn key(tenant: TenantId, segments: &[&[u8]]) -> Vec<u8> {
107 let mut out =
108 Vec::with_capacity(TENANT_PREFIX_LEN + segments.iter().map(|s| s.len() + 1).sum::<usize>());
109 out.extend_from_slice(&tenant_prefix(tenant));
110 for s in segments {
111 out.push(b'/');
112 out.extend_from_slice(s);
113 }
114 out
115}
116
117#[must_use]
129pub fn prefix_range(prefix: &[u8]) -> (Vec<u8>, Vec<u8>) {
130 let mut end = prefix.to_vec();
131 loop {
132 match end.last_mut() {
133 Some(last) if *last != 0xFF => {
134 *last += 1;
135 return (prefix.to_vec(), end);
136 }
137 Some(_) => {
138 end.pop();
139 }
140 None => return (prefix.to_vec(), vec![]),
141 }
142 }
143}
144
145#[must_use]
147pub fn u64be(v: u64) -> [u8; 8] {
148 v.to_be_bytes()
149}
150
151#[must_use]
153pub fn owner_prefix(kind: OwnerKind) -> &'static [u8] {
154 match kind {
155 OwnerKind::Record => b"record",
156 OwnerKind::Relationship => b"rel",
157 }
158}
159
160pub const P_EMB_SPACE: &[u8] = b"emb-space";
162pub const P_EMB_ALIAS: &[u8] = b"emb-alias";
164pub const P_EMB_BINDING: &[u8] = b"emb-binding";
166pub const P_EMB_VECTOR: &[u8] = b"vec";
168
169#[must_use]
171pub fn emb_space_key(tenant: TenantId, sid: u64) -> Vec<u8> {
172 key(tenant, &[P_EMB_SPACE, &u64be(sid)])
173}
174
175#[must_use]
177pub fn emb_alias_key(tenant: TenantId, alias: &str) -> Vec<u8> {
178 key(tenant, &[P_EMB_ALIAS, alias.as_bytes()])
179}
180
181#[must_use]
183pub fn emb_binding_key(
184 tenant: TenantId,
185 space: u64,
186 kind: OwnerKind,
187 owner_ord: u64,
188 pid: u64,
189 elem: u64,
190) -> Vec<u8> {
191 key(
192 tenant,
193 &[
194 P_EMB_BINDING,
195 &u64be(space),
196 &[kind.as_byte()],
197 &u64be(owner_ord),
198 &u64be(pid),
199 &u64be(elem),
200 ],
201 )
202}
203
204#[must_use]
206pub fn emb_bindings_prefix(tenant: TenantId, space: u64) -> Vec<u8> {
207 key(tenant, &[P_EMB_BINDING, &u64be(space)])
208}
209
210#[must_use]
212pub fn emb_vector_key(tenant: TenantId, space: u64, slot: u64) -> Vec<u8> {
213 key(tenant, &[P_EMB_VECTOR, &u64be(space), &u64be(slot)])
214}
215
216pub const P_EMB_VERSION: &[u8] = b"emb-version";
218
219#[must_use]
225pub fn emb_space_version_key(tenant: TenantId, space: u64) -> Vec<u8> {
226 key(tenant, &[P_EMB_VERSION, &u64be(space)])
227}
228
229#[must_use]
231pub fn emb_space_version_value(prev_ci: u64, ci: u64) -> Vec<u8> {
232 let mut out = Vec::with_capacity(16);
233 out.extend_from_slice(&u64be(prev_ci));
234 out.extend_from_slice(&u64be(ci));
235 out
236}
237
238#[must_use]
240pub fn parse_emb_space_version(raw: &[u8]) -> Option<(u64, u64)> {
241 let prev = u64::from_be_bytes(raw.get(..8)?.try_into().ok()?);
242 let ci = u64::from_be_bytes(raw.get(8..16)?.try_into().ok()?);
243 (raw.len() == 16).then_some((prev, ci))
244}
245
246pub const P_EMB_READY: &[u8] = b"emb-ready";
248
249#[must_use]
253pub fn emb_ready_count_key(tenant: TenantId, space: u64) -> Vec<u8> {
254 key(tenant, &[P_EMB_READY, &u64be(space)])
255}
256
257pub const P_EMB_RECLAIM: &[u8] = b"emb-reclaim";
259
260#[must_use]
265pub fn emb_reclaim_key(tenant: TenantId, after_ci: u64, space: u64, slot: u64) -> Vec<u8> {
266 key(
267 tenant,
268 &[P_EMB_RECLAIM, &u64be(after_ci), &u64be(space), &u64be(slot)],
269 )
270}
271
272#[must_use]
274pub fn emb_reclaim_prefix(tenant: TenantId) -> Vec<u8> {
275 key(tenant, &[P_EMB_RECLAIM])
276}
277
278#[must_use]
281pub fn emb_reclaim_value(after_ci: u64, space: u64, slot: u64) -> Vec<u8> {
282 let mut out = Vec::with_capacity(24);
283 out.extend_from_slice(&u64be(after_ci));
284 out.extend_from_slice(&u64be(space));
285 out.extend_from_slice(&u64be(slot));
286 out
287}
288
289#[must_use]
291pub fn parse_emb_reclaim_value(value: &[u8]) -> Option<(u64, u64, u64)> {
292 if value.len() != 24 {
293 return None;
294 }
295 let after_ci = u64::from_be_bytes(value[0..8].try_into().ok()?);
296 let space = u64::from_be_bytes(value[8..16].try_into().ok()?);
297 let slot = u64::from_be_bytes(value[16..24].try_into().ok()?);
298 Some((after_ci, space, slot))
299}
300
301pub const SECURITY_GENERATION_KEY: &str = "security/generation";
304
305pub const SECURITY_POLICY_ACTIVE_KEY: &str = "security/policy/active";
307
308#[must_use]
310pub fn security_prefix(tenant: TenantId) -> Vec<u8> {
311 key(tenant, &[b"security", b""])
312}
313
314#[must_use]
316pub fn security_generation_key(tenant: TenantId) -> Vec<u8> {
317 key(tenant, &[b"security", b"generation"])
318}
319
320#[must_use]
322pub fn security_policy_key(tenant: TenantId) -> Vec<u8> {
323 key(tenant, &[b"security", b"policy", b"active"])
324}
325
326pub const F_RECORD_ID: u8 = 0x03;
335pub const F_RECORD_HDR: u8 = 0x04;
337pub const F_LABEL: u8 = 0x05;
339pub const F_COUNTER: u8 = 0x09;
341pub const F_VALUE_DICT: u8 = 0x0A;
343pub const F_MARKER: u8 = 0x11;
345pub const F_OCC: u8 = 0x12;
347pub const F_PRESENCE: u8 = 0x13;
349pub const F_INDEX_DEF: u8 = 0x14;
351pub const F_INDEX_NAME: u8 = 0x15;
353pub const F_INDEX_ENTRY: u8 = 0x16;
355pub const F_INDEX_PENDING: u8 = 0x17;
358pub const F_USAGE_BYTES: u8 = 0x18;
362
363#[must_use]
368pub fn family_label(key: &[u8]) -> String {
369 match key.first() {
370 Some(&TENANT_NS) if key.len() > TENANT_PREFIX_LEN => {
371 let rest = &key[TENANT_PREFIX_LEN..];
372 match rest[0] {
373 F_RECORD_ID => "record-id".into(),
374 F_RECORD_HDR => "record-header".into(),
375 F_LABEL => "label-member".into(),
376 F_COUNTER => format!("counter/{:02x}", rest.get(1).copied().unwrap_or(0)),
377 F_VALUE_DICT => "value-dictionary".into(),
378 F_MARKER => "marker".into(),
379 F_OCC => "occurrence".into(),
380 F_PRESENCE => "presence".into(),
381 F_INDEX_DEF => "index-def".into(),
382 F_INDEX_NAME => "index-name".into(),
383 F_INDEX_ENTRY => "index-entry".into(),
384 F_INDEX_PENDING => "index-pending".into(),
385 F_USAGE_BYTES => "usage-bytes".into(),
386 b'/' => {
387 let segment = rest[1..].split(|byte| *byte == b'/').next().unwrap_or(&[]);
388 format!("legacy/{}", String::from_utf8_lossy(segment))
389 }
390 other => format!("tenant-family/{other:02x}"),
391 }
392 }
393 Some(&SYSTEM_NS) => format!("system/{:02x}", key.get(1).copied().unwrap_or(0)),
394 _ => {
395 let segment = key.split(|byte| *byte == b'/').next().unwrap_or(&[]);
396 format!("global/{}", String::from_utf8_lossy(segment))
397 }
398 }
399}
400
401#[must_use]
405pub fn describe_key(key: &[u8]) -> String {
406 use std::fmt::Write as _;
407 let Some(tenant) = parse_tenant(key) else {
408 return key.escape_ascii().to_string();
409 };
410 let rest = &key[TENANT_PREFIX_LEN..];
411 let family = match rest.first() {
412 Some(&F_RECORD_ID) => "record-id",
413 Some(&F_RECORD_HDR) => "record-header",
414 Some(&F_LABEL) => "label-member",
415 Some(&F_COUNTER) => "counter",
416 Some(&F_VALUE_DICT) => "value-dictionary",
417 Some(&F_MARKER) => "marker",
418 Some(&F_OCC) => "occurrence",
419 Some(&F_PRESENCE) => "presence",
420 Some(&F_INDEX_DEF) => "index-def",
421 Some(&F_INDEX_NAME) => "index-name",
422 Some(&F_INDEX_ENTRY) => "index-entry",
423 Some(&F_INDEX_PENDING) => "index-pending",
424 Some(&F_USAGE_BYTES) => "usage-bytes",
425 _ => return format!("tenant {tenant} {}", rest.escape_ascii()),
426 };
427 let mut out = format!("tenant {tenant} {family} ");
428 for byte in &rest[1..] {
429 let _ = write!(out, "{byte:02x}");
430 }
431 out
432}
433
434pub const C_LABEL: u8 = 0x01;
436pub const C_SUPPORT_TOTAL: u8 = 0x02;
438pub const C_SUPPORT_BY_PID: u8 = 0x03;
440pub const C_SUPPORT_BY_LABEL: u8 = 0x04;
442pub const C_NULL: u8 = 0x05;
444pub const C_DEGREE: u8 = 0x06;
446pub const C_DISTINCT: u8 = 0x07;
448pub const C_REL_CATALOG: u8 = 0x08;
450pub const C_LONG_VALUES: u8 = 0x09;
452pub const C_REL_SHAPE_PROP: u8 = 0x0A;
455pub const C_USAGE: u8 = 0x0B;
457pub const C_LONG_VALUE: u8 = 0x0C;
461pub const C_LONG_DISTINCT: u8 = 0x0D;
464
465pub const U_RECORDS: u8 = 0x01;
467pub const U_RELATIONSHIPS: u8 = 0x02;
469pub const U_LABELS: u8 = 0x03;
471pub const U_PROPERTIES: u8 = 0x04;
473
474pub const M_ARRAY_LABEL: u8 = 0x02;
476
477pub const NULLNESS_VALUE: u8 = 0;
479pub const NULLNESS_NULL: u8 = 1;
481
482pub const DICT_MAX_VALUE_BYTES: usize = 96;
485
486fn typed(tenant: TenantId, family: u8, capacity: usize) -> Vec<u8> {
487 let mut out = Vec::with_capacity(TENANT_PREFIX_LEN + 1 + capacity);
488 out.extend_from_slice(&tenant_prefix(tenant));
489 out.push(family);
490 out
491}
492
493pub fn push_str(out: &mut Vec<u8>, value: &str) {
499 let len = u16::try_from(value.len()).expect("key string field fits u16");
500 out.extend_from_slice(&len.to_be_bytes());
501 out.extend_from_slice(value.as_bytes());
502}
503
504#[must_use]
507pub fn read_str(key: &[u8], pos: usize) -> Option<(&str, usize)> {
508 let len = usize::from(u16::from_be_bytes(key.get(pos..pos + 2)?.try_into().ok()?));
509 let start = pos + 2;
510 let text = std::str::from_utf8(key.get(start..start + len)?).ok()?;
511 Some((text, start + len))
512}
513
514#[must_use]
516pub fn read_u64(key: &[u8], pos: usize) -> Option<u64> {
517 Some(u64::from_be_bytes(key.get(pos..pos + 8)?.try_into().ok()?))
518}
519
520pub fn push_ord(out: &mut Vec<u8>, value: u64) {
527 let bytes = value.to_be_bytes();
528 let skip = usize::try_from(value.leading_zeros() / 8).unwrap_or(8);
529 out.push(u8::try_from(8 - skip).unwrap_or(8));
530 out.extend_from_slice(&bytes[skip..]);
531}
532
533#[must_use]
537pub fn read_ord(key: &[u8], pos: usize) -> Option<(u64, usize)> {
538 let len = usize::from(*key.get(pos)?);
539 if len > 8 {
540 return None;
541 }
542 let digits = key.get(pos + 1..pos + 1 + len)?;
543 if digits.first() == Some(&0) {
544 return None;
545 }
546 let mut buf = [0u8; 8];
547 buf[8 - len..].copy_from_slice(digits);
548 Some((u64::from_be_bytes(buf), pos + 1 + len))
549}
550
551#[must_use]
553pub fn read_ord_tail(key: &[u8], pos: usize) -> Option<u64> {
554 let (value, end) = read_ord(key, pos)?;
555 (end == key.len()).then_some(value)
556}
557
558#[must_use]
560pub fn record_id_key(tenant: TenantId, id: &[u8; 16]) -> Vec<u8> {
561 let mut out = record_id_prefix(tenant);
562 out.extend_from_slice(id);
563 out
564}
565
566#[must_use]
568pub fn record_id_prefix(tenant: TenantId) -> Vec<u8> {
569 typed(tenant, F_RECORD_ID, 16)
570}
571
572#[must_use]
574pub fn record_hdr_key(tenant: TenantId, ordinal: u64) -> Vec<u8> {
575 let mut out = typed(tenant, F_RECORD_HDR, 9);
576 push_ord(&mut out, ordinal);
577 out
578}
579
580#[must_use]
582pub fn parse_record_hdr_key(key: &[u8]) -> Option<u64> {
583 if key.get(TENANT_PREFIX_LEN) != Some(&F_RECORD_HDR) {
584 return None;
585 }
586 read_ord_tail(key, TENANT_PREFIX_LEN + 1)
587}
588
589#[must_use]
591pub fn record_hdr_prefix(tenant: TenantId) -> Vec<u8> {
592 typed(tenant, F_RECORD_HDR, 0)
593}
594
595#[must_use]
597pub fn encode_record_hdr(id: &[u8; 16], label: &str) -> Vec<u8> {
598 let mut out = Vec::with_capacity(16 + label.len());
599 out.extend_from_slice(id);
600 out.extend_from_slice(label.as_bytes());
601 out
602}
603
604#[must_use]
606pub fn decode_record_hdr(raw: &[u8]) -> Option<([u8; 16], &str)> {
607 let id: [u8; 16] = raw.get(..16)?.try_into().ok()?;
608 Some((id, std::str::from_utf8(raw.get(16..)?).ok()?))
609}
610
611#[must_use]
613pub fn label_member_key(tenant: TenantId, label: &str, ordinal: u64) -> Vec<u8> {
614 let mut out = label_member_prefix(tenant, label);
615 push_ord(&mut out, ordinal);
616 out
617}
618
619#[must_use]
621pub fn label_member_prefix(tenant: TenantId, label: &str) -> Vec<u8> {
622 let mut out = typed(tenant, F_LABEL, 2 + label.len() + 9);
623 push_str(&mut out, label);
624 out
625}
626
627#[must_use]
629pub fn label_members_prefix(tenant: TenantId) -> Vec<u8> {
630 typed(tenant, F_LABEL, 0)
631}
632
633#[must_use]
635pub fn parse_label_member_key(key: &[u8]) -> Option<(&str, u64)> {
636 if key.get(TENANT_PREFIX_LEN) != Some(&F_LABEL) {
637 return None;
638 }
639 let (label, pos) = read_str(key, TENANT_PREFIX_LEN + 1)?;
640 Some((label, read_ord_tail(key, pos)?))
641}
642
643#[must_use]
645pub fn occ_key(tenant: TenantId, kind: OwnerKind, owner: u64, pid: u64, elem: u64) -> Vec<u8> {
646 let mut out = occ_pid_prefix(tenant, kind, owner, pid);
647 push_ord(&mut out, elem);
648 out
649}
650
651#[must_use]
653pub fn occ_owner_prefix(tenant: TenantId, kind: OwnerKind, owner: u64) -> Vec<u8> {
654 let mut out = typed(tenant, F_OCC, 1 + 9 + 9 + 9);
655 out.push(kind.as_byte());
656 push_ord(&mut out, owner);
657 out
658}
659
660#[must_use]
662pub fn occ_pid_prefix(tenant: TenantId, kind: OwnerKind, owner: u64, pid: u64) -> Vec<u8> {
663 let mut out = occ_owner_prefix(tenant, kind, owner);
664 push_ord(&mut out, pid);
665 out
666}
667
668#[must_use]
670pub fn occ_kind_prefix(tenant: TenantId, kind: OwnerKind) -> Vec<u8> {
671 let mut out = typed(tenant, F_OCC, 1);
672 out.push(kind.as_byte());
673 out
674}
675
676#[must_use]
678pub fn parse_occ_key(key: &[u8]) -> Option<(TenantId, OwnerKind, u64, u64, u64)> {
679 let tenant = parse_tenant(key)?;
680 if key.get(TENANT_PREFIX_LEN) != Some(&F_OCC) {
681 return None;
682 }
683 let kind = OwnerKind::from_byte(*key.get(TENANT_PREFIX_LEN + 1)?)?;
684 let (owner, pos) = read_ord(key, TENANT_PREFIX_LEN + 2)?;
685 let (pid, pos) = read_ord(key, pos)?;
686 let elem = read_ord_tail(key, pos)?;
687 Some((tenant, kind, owner, pid, elem))
688}
689
690#[must_use]
692pub fn presence_key(
693 tenant: TenantId,
694 kind: OwnerKind,
695 label: &str,
696 pid: u64,
697 nullness: u8,
698 owner: u64,
699) -> Vec<u8> {
700 let mut out = presence_nullness_prefix(tenant, kind, label, pid, nullness);
701 push_ord(&mut out, owner);
702 out
703}
704
705#[must_use]
707pub fn presence_nullness_prefix(
708 tenant: TenantId,
709 kind: OwnerKind,
710 label: &str,
711 pid: u64,
712 nullness: u8,
713) -> Vec<u8> {
714 let mut out = presence_pid_prefix(tenant, kind, label, pid);
715 out.push(nullness);
716 out
717}
718
719#[must_use]
721pub fn presence_pid_prefix(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
722 let mut out = typed(tenant, F_PRESENCE, 1 + 2 + label.len() + 9 + 1 + 9);
723 out.push(kind.as_byte());
724 push_str(&mut out, label);
725 push_ord(&mut out, pid);
726 out
727}
728
729#[must_use]
731pub fn parse_presence_key(key: &[u8]) -> Option<(OwnerKind, &str, u64, u8, u64)> {
732 if key.get(TENANT_PREFIX_LEN) != Some(&F_PRESENCE) {
733 return None;
734 }
735 let kind = OwnerKind::from_byte(*key.get(TENANT_PREFIX_LEN + 1)?)?;
736 let (label, pos) = read_str(key, TENANT_PREFIX_LEN + 2)?;
737 let (pid, pos) = read_ord(key, pos)?;
738 let nullness = *key.get(pos)?;
739 let owner = read_ord_tail(key, pos + 1)?;
740 Some((kind, label, pid, nullness, owner))
741}
742
743#[must_use]
745pub fn value_dict_key(
746 tenant: TenantId,
747 kind: OwnerKind,
748 label: &str,
749 pid: u64,
750 value: &[u8],
751) -> Vec<u8> {
752 let mut out = value_dict_prefix(tenant, kind, label, pid);
753 out.extend_from_slice(value);
754 out
755}
756
757#[must_use]
760pub fn value_dict_prefix(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
761 let mut out = typed(
762 tenant,
763 F_VALUE_DICT,
764 1 + 2 + label.len() + 9 + DICT_MAX_VALUE_BYTES,
765 );
766 out.push(kind.as_byte());
767 push_str(&mut out, label);
768 push_ord(&mut out, pid);
769 out
770}
771
772fn counter(tenant: TenantId, sub: u8, capacity: usize) -> Vec<u8> {
773 let mut out = typed(tenant, F_COUNTER, 1 + capacity);
774 out.push(sub);
775 out
776}
777
778#[must_use]
780pub fn counter_prefix(tenant: TenantId, sub: u8) -> Vec<u8> {
781 counter(tenant, sub, 0)
782}
783
784#[must_use]
786pub fn usage_count_key(tenant: TenantId, metric: u8) -> Vec<u8> {
787 let mut out = counter(tenant, C_USAGE, 1);
788 out.push(metric);
789 out
790}
791
792#[must_use]
794pub fn usage_bytes_key(tenant: TenantId) -> Vec<u8> {
795 typed(tenant, F_USAGE_BYTES, 0)
796}
797
798#[must_use]
800pub fn label_count_key(tenant: TenantId, label: &str) -> Vec<u8> {
801 let mut out = counter(tenant, C_LABEL, 2 + label.len());
802 push_str(&mut out, label);
803 out
804}
805
806#[must_use]
808pub fn support_total_key(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
809 let mut out = counter(tenant, C_SUPPORT_TOTAL, 9);
810 out.push(kind.as_byte());
811 out.extend_from_slice(&u64be(pid));
812 out
813}
814
815#[must_use]
817pub fn support_by_pid_key(tenant: TenantId, kind: OwnerKind, pid: u64, label: &str) -> Vec<u8> {
818 let mut out = support_by_pid_prefix(tenant, kind, pid);
819 push_str(&mut out, label);
820 out
821}
822
823#[must_use]
825pub fn support_by_pid_prefix(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
826 let mut out = counter(tenant, C_SUPPORT_BY_PID, 9 + 2 + 32);
827 out.push(kind.as_byte());
828 out.extend_from_slice(&u64be(pid));
829 out
830}
831
832#[must_use]
834pub fn support_by_label_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
835 let mut out = support_by_label_prefix(tenant, kind, label);
836 out.extend_from_slice(&u64be(pid));
837 out
838}
839
840#[must_use]
842pub fn support_by_label_prefix(tenant: TenantId, kind: OwnerKind, label: &str) -> Vec<u8> {
843 let mut out = counter(tenant, C_SUPPORT_BY_LABEL, 1 + 2 + label.len() + 8);
844 out.push(kind.as_byte());
845 push_str(&mut out, label);
846 out
847}
848
849#[must_use]
851pub fn null_count_key(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
852 let mut out = counter(tenant, C_NULL, 9);
853 out.push(kind.as_byte());
854 out.extend_from_slice(&u64be(pid));
855 out
856}
857
858#[must_use]
860pub fn degree_key(tenant: TenantId, node: u64, outgoing: bool) -> Vec<u8> {
861 let mut out = counter(tenant, C_DEGREE, 9);
862 out.extend_from_slice(&u64be(node));
863 out.push(u8::from(outgoing));
864 out
865}
866
867#[must_use]
869pub fn distinct_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
870 let mut out = counter(tenant, C_DISTINCT, 1 + 2 + label.len() + 8);
871 out.push(kind.as_byte());
872 push_str(&mut out, label);
873 out.extend_from_slice(&u64be(pid));
874 out
875}
876
877#[must_use]
879pub fn long_values_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
880 let mut out = counter(tenant, C_LONG_VALUES, 1 + 2 + label.len() + 8);
881 out.push(kind.as_byte());
882 push_str(&mut out, label);
883 out.extend_from_slice(&u64be(pid));
884 out
885}
886
887pub const LONG_DIGEST_BYTES: usize = 16;
889
890#[must_use]
893pub fn long_value_digest(entry: &[u8]) -> [u8; LONG_DIGEST_BYTES] {
894 use sha2::Digest as _;
895 let full: [u8; 32] = sha2::Sha256::digest(entry).into();
896 let mut out = [0u8; LONG_DIGEST_BYTES];
897 out.copy_from_slice(&full[..LONG_DIGEST_BYTES]);
898 out
899}
900
901#[must_use]
904pub fn long_value_prefix(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
905 let mut out = counter(
906 tenant,
907 C_LONG_VALUE,
908 1 + 2 + label.len() + 8 + LONG_DIGEST_BYTES,
909 );
910 out.push(kind.as_byte());
911 push_str(&mut out, label);
912 out.extend_from_slice(&u64be(pid));
913 out
914}
915
916#[must_use]
918pub fn long_value_key(
919 tenant: TenantId,
920 kind: OwnerKind,
921 label: &str,
922 pid: u64,
923 digest: &[u8; LONG_DIGEST_BYTES],
924) -> Vec<u8> {
925 let mut out = long_value_prefix(tenant, kind, label, pid);
926 out.extend_from_slice(digest);
927 out
928}
929
930#[must_use]
932pub fn long_distinct_key(tenant: TenantId, kind: OwnerKind, label: &str, pid: u64) -> Vec<u8> {
933 let mut out = counter(tenant, C_LONG_DISTINCT, 1 + 2 + label.len() + 8);
934 out.push(kind.as_byte());
935 push_str(&mut out, label);
936 out.extend_from_slice(&u64be(pid));
937 out
938}
939
940#[must_use]
942pub fn rel_catalog_key(tenant: TenantId, rel_type: &str, source: &str, target: &str) -> Vec<u8> {
943 let mut out = counter(
944 tenant,
945 C_REL_CATALOG,
946 6 + rel_type.len() + source.len() + target.len(),
947 );
948 push_str(&mut out, rel_type);
949 push_str(&mut out, source);
950 push_str(&mut out, target);
951 out
952}
953
954#[must_use]
957pub fn rel_shape_prop_key(
958 tenant: TenantId,
959 (rel_type, source, target): (&str, &str, &str),
960 pid: u64,
961) -> Vec<u8> {
962 let mut out = rel_shape_prop_prefix(tenant, rel_type, source, target);
963 out.extend_from_slice(&u64be(pid));
964 out
965}
966
967#[must_use]
969pub fn rel_shape_prop_prefix(
970 tenant: TenantId,
971 rel_type: &str,
972 source: &str,
973 target: &str,
974) -> Vec<u8> {
975 let mut out = counter(
976 tenant,
977 C_REL_SHAPE_PROP,
978 6 + rel_type.len() + source.len() + target.len() + 8,
979 );
980 push_str(&mut out, rel_type);
981 push_str(&mut out, source);
982 push_str(&mut out, target);
983 out
984}
985
986#[must_use]
989pub fn array_label_marker_key(tenant: TenantId, kind: OwnerKind, pid: u64, label: &str) -> Vec<u8> {
990 let mut out = array_label_marker_prefix(tenant, kind, pid);
991 push_str(&mut out, label);
992 out
993}
994
995#[must_use]
997pub fn array_label_marker_prefix(tenant: TenantId, kind: OwnerKind, pid: u64) -> Vec<u8> {
998 let mut out = typed(tenant, F_MARKER, 10 + 2 + 32);
999 out.push(M_ARRAY_LABEL);
1000 out.push(kind.as_byte());
1001 out.extend_from_slice(&u64be(pid));
1002 out
1003}
1004
1005#[must_use]
1010pub fn decode_counter(raw: Option<&[u8]>) -> Option<u64> {
1011 match raw {
1012 None => Some(0),
1013 Some(raw) => raw.try_into().ok().map(u64::from_be_bytes),
1014 }
1015}
1016
1017#[must_use]
1021pub fn index_def_key(tenant: TenantId, id: u32) -> Vec<u8> {
1022 let mut out = typed(tenant, F_INDEX_DEF, 4);
1023 out.extend_from_slice(&id.to_be_bytes());
1024 out
1025}
1026
1027#[must_use]
1029pub fn index_def_prefix(tenant: TenantId) -> Vec<u8> {
1030 typed(tenant, F_INDEX_DEF, 0)
1031}
1032
1033#[must_use]
1035pub fn index_name_key(tenant: TenantId, name: &str) -> Vec<u8> {
1036 let mut out = typed(tenant, F_INDEX_NAME, name.len());
1037 out.extend_from_slice(name.as_bytes());
1038 out
1039}
1040
1041#[must_use]
1043pub fn index_entry_prefix(tenant: TenantId, id: u32) -> Vec<u8> {
1044 let mut out = typed(tenant, F_INDEX_ENTRY, 4 + 16 + 8);
1045 out.extend_from_slice(&id.to_be_bytes());
1046 out
1047}
1048
1049#[must_use]
1051pub fn index_entry_key(tenant: TenantId, id: u32, entry: &[u8], owner: u64) -> Vec<u8> {
1052 let mut out = index_entry_prefix(tenant, id);
1053 out.extend_from_slice(entry);
1054 out.extend_from_slice(&u64be(owner));
1055 out
1056}
1057
1058#[must_use]
1061pub fn parse_index_entry_key<'a>(key: &'a [u8], prefix: &[u8]) -> Option<(&'a [u8], u64)> {
1062 let rest = key.strip_prefix(prefix)?;
1063 let split = rest.len().checked_sub(8)?;
1064 let (entry, owner) = rest.split_at(split);
1065 Some((entry, u64::from_be_bytes(owner.try_into().ok()?)))
1066}
1067
1068#[must_use]
1071pub fn index_pending_prefix(tenant: TenantId, index: u32) -> Vec<u8> {
1072 let mut out = typed(tenant, F_INDEX_PENDING, 4 + 8);
1073 out.extend_from_slice(&index.to_be_bytes());
1074 out
1075}
1076
1077#[must_use]
1079pub fn index_pending_key(tenant: TenantId, index: u32, owner: u64) -> Vec<u8> {
1080 let mut out = index_pending_prefix(tenant, index);
1081 out.extend_from_slice(&u64be(owner));
1082 out
1083}
1084
1085#[cfg(test)]
1086mod ord_tests {
1087 use super::*;
1088
1089 fn edge_values() -> Vec<u64> {
1090 let mut values = vec![0, 1, u64::MAX, u64::MAX - 1];
1091 for shift in (8..64).step_by(8) {
1092 let boundary = 1u64 << shift;
1093 values.extend([boundary - 1, boundary, boundary + 1]);
1094 }
1095 values.extend((0..2_000u64).map(|i| i.wrapping_mul(0x9e37_79b9_7f4a_7c15) >> (i % 64)));
1096 values
1097 }
1098
1099 fn encode(value: u64) -> Vec<u8> {
1100 let mut out = Vec::new();
1101 push_ord(&mut out, value);
1102 out
1103 }
1104
1105 #[test]
1106 fn ord_round_trips_with_minimal_length() {
1107 for value in edge_values() {
1108 let bytes = encode(value);
1109 assert_eq!(read_ord_tail(&bytes, 0), Some(value), "{value}");
1110 let digits = 8 - usize::try_from(value.leading_zeros() / 8).unwrap();
1111 assert_eq!(bytes.len(), 1 + digits, "{value}");
1112 }
1113 assert_eq!(encode(0), vec![0]);
1114 assert_eq!(encode(200_000), vec![3, 0x03, 0x0d, 0x40]);
1115 }
1116
1117 #[test]
1118 fn ord_byte_order_is_numeric_order() {
1119 let mut values = edge_values();
1120 values.sort_unstable();
1121 values.dedup();
1122 for pair in values.windows(2) {
1123 assert!(
1124 encode(pair[0]) < encode(pair[1]),
1125 "{} vs {}",
1126 pair[0],
1127 pair[1]
1128 );
1129 }
1130 }
1131
1132 #[test]
1133 fn non_minimal_or_oversized_encodings_are_rejected() {
1134 assert_eq!(read_ord(&[1, 0], 0), None);
1135 assert_eq!(read_ord(&[2, 0, 5], 0), None);
1136 assert_eq!(read_ord(&[9, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0), None);
1137 assert_eq!(read_ord(&[3, 1, 2], 0), None);
1138 assert_eq!(read_ord_tail(&[1, 5, 0], 0), None);
1139 }
1140
1141 #[test]
1142 fn one_owner_prefix_never_matches_another_owners_keys() {
1143 let tenant = TenantId(7);
1144 for (a, b) in [(1u64, 257u64), (0, 1), (255, 65_535), (1, 256)] {
1145 let prefix = occ_owner_prefix(tenant, OwnerKind::Record, a);
1146 let other = occ_key(tenant, OwnerKind::Record, b, 3, 0);
1147 assert!(!other.starts_with(&prefix), "{a} prefix matched owner {b}");
1148 let own = occ_key(tenant, OwnerKind::Record, a, 3, 0);
1149 assert_eq!(
1150 parse_occ_key(&own),
1151 Some((tenant, OwnerKind::Record, a, 3, 0))
1152 );
1153 }
1154 let presence = presence_key(tenant, OwnerKind::Record, "User", 42, NULLNESS_VALUE, 300);
1155 assert_eq!(
1156 parse_presence_key(&presence),
1157 Some((OwnerKind::Record, "User", 42, NULLNESS_VALUE, 300))
1158 );
1159 let member = label_member_key(tenant, "User", 70_000);
1160 assert_eq!(parse_label_member_key(&member), Some(("User", 70_000)));
1161 assert_eq!(parse_record_hdr_key(&record_hdr_key(tenant, 9)), Some(9));
1162 }
1163}