1use std::collections::BTreeMap;
4use std::fs;
5use std::io;
6use std::path::{Path, PathBuf};
7use std::time::{SystemTime, UNIX_EPOCH};
8
9use crate::api::{CatalogSnapshot, CollectionStats, RedDBOptions, SchemaManifest, StorageMode};
10use crate::index::IndexKind;
11use crate::serde_json::{Map, Value as JsonValue};
12
13pub const DEFAULT_GRID_BLOCK_SIZE: usize = 512 * 1024;
14pub const DEFAULT_PAGE_SIZE: usize = 4096;
15pub use reddb_file::layout::PHYSICAL_METADATA_BINARY_EXTENSION;
16pub use reddb_file::{
17 fold_dwb_into_wal_enabled, meta_json_sidecar_enabled, seqn_journal_enabled,
18 seqn_journal_retention, set_fold_dwb_into_wal_enabled, set_meta_json_sidecar_enabled,
19 set_seqn_journal_enabled, set_seqn_journal_retention, BlockReference, ExportDescriptor,
20 ManifestEvent, ManifestEventKind, ManifestPointers, PhysicalAnalyticsJob,
21 PhysicalGraphProjection, PhysicalTreeDefinition, SnapshotDescriptor, SuperblockHeader,
22 DEFAULT_METADATA_JOURNAL_RETENTION, DEFAULT_SUPERBLOCK_COPIES,
23 OPT_IN_METADATA_JOURNAL_RETENTION, PHYSICAL_METADATA_PROTOCOL_VERSION,
24};
25pub const DEFAULT_MANIFEST_EVENT_HISTORY: usize = 256;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum PhysicalMetadataSource {
29 Binary,
30 BinaryJournal,
31 Json,
32}
33
34impl PhysicalMetadataSource {
35 pub fn as_str(self) -> &'static str {
36 match self {
37 Self::Binary => "binary",
38 Self::BinaryJournal => "binary_journal",
39 Self::Json => "json",
40 }
41 }
42}
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum CompactionPolicy {
45 Incremental,
46 Manual,
47}
48
49#[derive(Debug, Clone)]
50pub struct WalPolicy {
51 pub auto_checkpoint_pages: u32,
52 pub fsync_on_commit: bool,
53 pub ring_buffer_bytes: u64,
54}
55
56impl Default for WalPolicy {
57 fn default() -> Self {
58 Self {
59 auto_checkpoint_pages: 1000,
60 fsync_on_commit: true,
61 ring_buffer_bytes: 64 * 1024 * 1024,
62 }
63 }
64}
65
66#[derive(Debug, Clone)]
67pub struct GridLayout {
68 pub block_size: usize,
69 pub page_size: usize,
70 pub superblock_copies: u8,
71}
72
73impl Default for GridLayout {
74 fn default() -> Self {
75 Self {
76 block_size: DEFAULT_GRID_BLOCK_SIZE,
77 page_size: DEFAULT_PAGE_SIZE,
78 superblock_copies: DEFAULT_SUPERBLOCK_COPIES,
79 }
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct PhysicalLayout {
85 pub mode: StorageMode,
86 pub grid: GridLayout,
87 pub wal: WalPolicy,
88 pub compaction: CompactionPolicy,
89}
90
91impl PhysicalLayout {
92 pub fn from_options(options: &RedDBOptions) -> Self {
93 Self {
94 mode: options.mode,
95 grid: GridLayout::default(),
96 wal: WalPolicy {
97 auto_checkpoint_pages: options.auto_checkpoint_pages,
98 ..WalPolicy::default()
99 },
100 compaction: CompactionPolicy::Incremental,
101 }
102 }
103
104 pub fn is_persistent(&self) -> bool {
105 self.mode == StorageMode::Persistent
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum ContractOrigin {
111 Explicit,
112 Implicit,
113 Migrated,
114}
115
116impl ContractOrigin {
117 pub fn as_str(self) -> &'static str {
118 match self {
119 Self::Explicit => "explicit",
120 Self::Implicit => "implicit",
121 Self::Migrated => "migrated",
122 }
123 }
124}
125
126#[derive(Debug)]
128pub(crate) struct CollectionContractDecodeError(pub String);
129
130impl std::fmt::Display for CollectionContractDecodeError {
131 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 write!(formatter, "invalid collection contract: {}", self.0)
133 }
134}
135
136impl std::error::Error for CollectionContractDecodeError {}
137
138pub(crate) fn is_collection_contract_decode_error(error: &std::io::Error) -> bool {
139 error
140 .get_ref()
141 .is_some_and(|source| source.is::<CollectionContractDecodeError>())
142}
143
144#[derive(Debug, Clone)]
145pub struct DeclaredColumnContract {
146 pub name: String,
147 pub data_type: String,
148 pub sql_type: Option<reddb_types::SqlTypeName>,
149 pub not_null: bool,
150 pub default: Option<String>,
151 pub generated: Option<reddb_rql::schema_expression::SchemaExpression>,
153 pub check: Option<reddb_rql::schema_expression::SchemaExpression>,
155 pub compress: Option<u8>,
156 pub unique: bool,
157 pub primary_key: bool,
158 pub enum_variants: Vec<String>,
159 pub array_element: Option<String>,
160 pub decimal_precision: Option<u8>,
161}
162
163#[derive(Debug, Clone)]
164pub struct CollectionContract {
165 pub name: String,
166 pub declared_model: crate::catalog::CollectionModel,
167 pub schema_mode: crate::catalog::SchemaMode,
168 pub origin: ContractOrigin,
169 pub version: u32,
170 pub created_at_unix_ms: u128,
171 pub updated_at_unix_ms: u128,
172 pub default_ttl_ms: Option<u64>,
173 pub vector_dimension: Option<usize>,
174 pub vector_metric: Option<crate::storage::engine::distance::DistanceMetric>,
175 pub context_index_fields: Vec<String>,
176 pub declared_columns: Vec<DeclaredColumnContract>,
177 pub table_def: Option<reddb_types::TableDef>,
178 pub timestamps_enabled: bool,
184 pub context_index_enabled: bool,
196 pub metrics_raw_retention_ms: Option<u64>,
199 pub metrics_rollup_policies: Vec<String>,
201 pub metrics_tenant_identity: Option<String>,
204 pub metrics_namespace: Option<String>,
207 pub append_only: bool,
215 pub subscriptions: Vec<crate::catalog::SubscriptionDescriptor>,
218 pub analytics_config: Vec<crate::catalog::AnalyticsViewDescriptor>,
222 pub session_key: Option<String>,
227 pub session_gap_ms: Option<u64>,
233 pub retention_duration_ms: Option<u64>,
239 pub analytical_storage: Option<crate::catalog::AnalyticalStorageConfig>,
245 pub ai_policy: Option<crate::catalog::AiPolicy>,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
266pub enum ArtifactState {
267 Declared,
269 Building,
271 Ready,
273 Disabled,
275 Stale,
277 Failed,
279 RequiresRebuild,
281}
282
283impl ArtifactState {
284 pub fn from_build_state(s: &str, enabled: bool) -> Self {
286 if !enabled {
287 return Self::Disabled;
288 }
289 match s {
290 "ready" => Self::Ready,
291 "building" | "catalog-derived" | "metadata-only" | "artifact-published"
292 | "registry-loaded" => Self::Building,
293 "stale" => Self::Stale,
294 "failed" => Self::Failed,
295 "requires_rebuild" | "requires-rebuild" => Self::RequiresRebuild,
296 _ => Self::Declared,
297 }
298 }
299
300 pub fn as_str(&self) -> &'static str {
302 match self {
303 Self::Declared => "declared",
304 Self::Building => "building",
305 Self::Ready => "ready",
306 Self::Disabled => "disabled",
307 Self::Stale => "stale",
308 Self::Failed => "failed",
309 Self::RequiresRebuild => "requires_rebuild",
310 }
311 }
312
313 pub fn is_queryable(&self) -> bool {
315 matches!(self, Self::Ready)
316 }
317
318 pub fn can_rebuild(&self) -> bool {
320 matches!(
321 self,
322 Self::Declared | Self::Stale | Self::Failed | Self::RequiresRebuild
323 )
324 }
325
326 pub fn needs_attention(&self) -> bool {
328 matches!(self, Self::Failed | Self::RequiresRebuild | Self::Stale)
329 }
330}
331
332impl std::fmt::Display for ArtifactState {
333 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
334 f.write_str(self.as_str())
335 }
336}
337
338#[derive(Debug, Clone)]
339pub struct PhysicalIndexState {
340 pub name: String,
341 pub kind: IndexKind,
342 pub collection: Option<String>,
343 pub enabled: bool,
344 pub entries: usize,
345 pub estimated_memory_bytes: u64,
346 pub last_refresh_ms: Option<u128>,
347 pub backend: String,
348 pub artifact_kind: Option<String>,
349 pub artifact_root_page: Option<u32>,
350 pub artifact_checksum: Option<u64>,
351 pub build_state: String,
352}
353
354impl PhysicalIndexState {
355 pub fn artifact_state(&self) -> ArtifactState {
357 ArtifactState::from_build_state(&self.build_state, self.enabled)
358 }
359}
360
361#[derive(Debug, Clone)]
367pub struct PhysicalHypertableChunk {
368 pub start_ns: u64,
369 pub end_ns_exclusive: u64,
370 pub row_count: u64,
371 pub min_ts_ns: u64,
372 pub max_ts_ns: u64,
373 pub sealed: bool,
374 pub ttl_override_ns: Option<u64>,
375 pub columnar_page: Option<crate::storage::engine::PageLocation>,
380 pub columnar_derived: bool,
383}
384
385#[derive(Debug, Clone)]
391pub struct PhysicalHypertable {
392 pub name: String,
393 pub time_column: String,
394 pub chunk_interval_ns: u64,
395 pub default_ttl_ns: Option<u64>,
396 pub chunks: Vec<PhysicalHypertableChunk>,
397}
398
399#[derive(Debug, Clone)]
400pub struct PhysicalMetadataFile {
401 pub protocol_version: String,
402 pub generated_at_unix_ms: u128,
403 pub last_loaded_from: Option<String>,
404 pub last_healed_at_unix_ms: Option<u128>,
405 pub manifest: SchemaManifest,
406 pub catalog: CatalogSnapshot,
407 pub manifest_events: Vec<ManifestEvent>,
408 pub indexes: Vec<PhysicalIndexState>,
409 pub graph_projections: Vec<PhysicalGraphProjection>,
410 pub analytics_jobs: Vec<PhysicalAnalyticsJob>,
411 pub tree_definitions: Vec<PhysicalTreeDefinition>,
412 pub collection_ttl_defaults_ms: BTreeMap<String, u64>,
413 pub collection_contracts: Vec<CollectionContract>,
414 pub hypertables: Vec<PhysicalHypertable>,
418 pub exports: Vec<ExportDescriptor>,
419 pub superblock: SuperblockHeader,
420 pub snapshots: Vec<SnapshotDescriptor>,
421}
422
423mod helpers;
424mod json_codec;
425mod metadata_file;
426pub mod shm;
427
428pub(crate) use self::json_codec::{
429 deserialize_collection_contracts, serialize_collection_contracts,
430};
431
432pub use self::shm::{
433 provision_shm, read_shm_header, set_shm_provisioning_enabled, shm_path_for,
434 shm_provisioning_enabled, ShmHandle, ShmHeader, ShmProvisionState, SHM_FILE_SIZE,
435 SHM_HEADER_SIZE, SHM_MAGIC, SHM_VERSION,
436};
437
438use self::helpers::*;
439use self::json_codec::*;