1use std::collections::{BTreeMap, BTreeSet};
10use std::fmt;
11use std::io;
12use std::path::{Path, PathBuf};
13use std::sync::Arc;
14use std::time::{SystemTime, UNIX_EPOCH};
15
16use crate::auth::AuthConfig;
17use crate::replication::ReplicationConfig;
18
19pub const DEFAULT_SNAPSHOT_RETENTION: usize = 16;
20pub const DEFAULT_EXPORT_RETENTION: usize = 16;
21
22pub const REDDB_PROTOCOL_VERSION: &str = "reddb-v2";
23pub const REDDB_FORMAT_VERSION: u32 = 2;
24pub const DEFAULT_GROUP_COMMIT_WINDOW_MS: u64 = 0;
37pub const DEFAULT_GROUP_COMMIT_MAX_STATEMENTS: usize = 128;
38pub const DEFAULT_GROUP_COMMIT_MAX_WAL_BYTES: u64 = 1024 * 1024;
39
40pub type RedDBResult<T> = Result<T, RedDBError>;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
43pub enum StorageMode {
44 #[default]
46 Persistent,
47}
48
49impl StorageMode {
50 pub const fn is_persistent(self) -> bool {
51 matches!(self, Self::Persistent)
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
56pub enum DurabilityMode {
57 #[default]
58 Strict,
59 WalDurableGrouped,
60 Async,
68}
69
70impl DurabilityMode {
71 pub const fn as_str(self) -> &'static str {
72 match self {
73 Self::Strict => "strict",
74 Self::WalDurableGrouped => "wal_durable_grouped",
75 Self::Async => "async",
76 }
77 }
78
79 pub fn from_str(value: &str) -> Option<Self> {
80 let normalized = value.trim().to_ascii_lowercase();
81 match normalized.as_str() {
82 "strict" => Some(Self::Strict),
84 "sync"
90 | "wal_durable_grouped"
91 | "wal-durable-grouped"
92 | "grouped"
93 | "wal_grouped"
94 | "wal-grouped" => Some(Self::WalDurableGrouped),
95 "async" | "fire_and_forget" | "fire-and-forget" => Some(Self::Async),
99 _ => None,
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub struct GroupCommitOptions {
106 pub window_ms: u64,
107 pub max_statements: usize,
108 pub max_wal_bytes: u64,
109}
110
111impl Default for GroupCommitOptions {
112 fn default() -> Self {
113 Self {
114 window_ms: DEFAULT_GROUP_COMMIT_WINDOW_MS,
115 max_statements: DEFAULT_GROUP_COMMIT_MAX_STATEMENTS,
116 max_wal_bytes: DEFAULT_GROUP_COMMIT_MAX_WAL_BYTES,
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
122pub enum Capability {
123 Table,
125 Graph,
127 Vector,
129 FullText,
131 Security,
133 Encryption,
135}
136
137impl Capability {
138 pub const fn as_str(self) -> &'static str {
139 match self {
140 Self::Table => "table",
141 Self::Graph => "graph",
142 Self::Vector => "vector",
143 Self::FullText => "fulltext",
144 Self::Security => "security",
145 Self::Encryption => "encryption",
146 }
147 }
148}
149
150#[derive(Debug, Clone, Default)]
151pub struct CapabilitySet {
152 items: BTreeSet<Capability>,
153}
154
155impl CapabilitySet {
156 pub fn new() -> Self {
157 Self::default()
158 }
159
160 pub fn with(mut self, capability: Capability) -> Self {
161 self.items.insert(capability);
162 self
163 }
164
165 pub fn with_all(mut self, capabilities: &[Capability]) -> Self {
166 capabilities.iter().copied().for_each(|capability| {
167 self.items.insert(capability);
168 });
169 self
170 }
171
172 pub fn has(&self, capability: Capability) -> bool {
173 self.items.contains(&capability)
174 }
175
176 pub fn as_slice(&self) -> Vec<Capability> {
177 self.items.iter().copied().collect()
178 }
179}
180
181pub struct RedDBOptions {
182 pub mode: StorageMode,
183 pub data_path: Option<PathBuf>,
184 pub read_only: bool,
185 pub create_if_missing: bool,
186 pub verify_checksums: bool,
187 pub durability_mode: DurabilityMode,
188 pub group_commit: GroupCommitOptions,
189 pub auto_checkpoint_pages: u32,
190 pub cache_pages: usize,
191 pub snapshot_retention: usize,
192 pub export_retention: usize,
193 pub feature_gates: CapabilitySet,
194 pub force_create: bool,
195 pub metadata: BTreeMap<String, String>,
196 pub remote_backend: Option<Arc<dyn crate::storage::backend::RemoteBackend>>,
198 pub remote_backend_atomic: Option<Arc<dyn crate::storage::backend::AtomicRemoteBackend>>,
205 pub remote_key: Option<String>,
207 pub replication: ReplicationConfig,
209 pub auth: AuthConfig,
211 pub control_events: crate::runtime::control_events::ControlEventConfig,
214 pub query_audit: crate::runtime::query_audit::QueryAuditConfig,
218 pub auto_index_id: bool,
224 pub layout: crate::storage::layout::StorageLayout,
230 pub layout_overrides: crate::storage::layout::LayoutOverrides,
232 pub storage_profile: crate::storage::profile::StorageProfileSelection,
235 pub layout_explicit: bool,
241}
242
243impl fmt::Debug for RedDBOptions {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 let backend_name = self.remote_backend.as_ref().map(|b| b.name().to_string());
246 f.debug_struct("RedDBOptions")
247 .field("mode", &self.mode)
248 .field("data_path", &self.data_path)
249 .field("read_only", &self.read_only)
250 .field("create_if_missing", &self.create_if_missing)
251 .field("verify_checksums", &self.verify_checksums)
252 .field("durability_mode", &self.durability_mode)
253 .field("group_commit", &self.group_commit)
254 .field("auto_checkpoint_pages", &self.auto_checkpoint_pages)
255 .field("cache_pages", &self.cache_pages)
256 .field("snapshot_retention", &self.snapshot_retention)
257 .field("export_retention", &self.export_retention)
258 .field("feature_gates", &self.feature_gates)
259 .field("force_create", &self.force_create)
260 .field("metadata", &self.metadata)
261 .field("remote_backend", &backend_name)
262 .field("remote_key", &self.remote_key)
263 .field("replication", &self.replication)
264 .field("auth", &self.auth)
265 .field("control_events", &self.control_events)
266 .field("query_audit", &self.query_audit)
267 .field("layout", &self.layout)
268 .field("layout_overrides", &self.layout_overrides)
269 .field("storage_profile", &self.storage_profile)
270 .finish()
271 }
272}
273
274impl Clone for RedDBOptions {
275 fn clone(&self) -> Self {
276 Self {
277 mode: self.mode,
278 data_path: self.data_path.clone(),
279 read_only: self.read_only,
280 create_if_missing: self.create_if_missing,
281 verify_checksums: self.verify_checksums,
282 durability_mode: self.durability_mode,
283 group_commit: self.group_commit,
284 auto_checkpoint_pages: self.auto_checkpoint_pages,
285 cache_pages: self.cache_pages,
286 snapshot_retention: self.snapshot_retention,
287 export_retention: self.export_retention,
288 feature_gates: self.feature_gates.clone(),
289 force_create: self.force_create,
290 metadata: self.metadata.clone(),
291 remote_backend: self.remote_backend.clone(),
292 remote_backend_atomic: self.remote_backend_atomic.clone(),
293 remote_key: self.remote_key.clone(),
294 replication: self.replication.clone(),
295 auth: self.auth.clone(),
296 control_events: self.control_events,
297 query_audit: self.query_audit.clone(),
298 auto_index_id: self.auto_index_id,
299 layout: self.layout,
300 layout_overrides: self.layout_overrides.clone(),
301 storage_profile: self.storage_profile,
302 layout_explicit: self.layout_explicit,
303 }
304 }
305}
306
307impl Default for RedDBOptions {
308 fn default() -> Self {
309 Self {
310 mode: StorageMode::Persistent,
311 data_path: None,
312 read_only: false,
313 create_if_missing: true,
314 verify_checksums: true,
315 durability_mode: DurabilityMode::WalDurableGrouped,
321 group_commit: GroupCommitOptions::default(),
322 auto_checkpoint_pages: 1000,
323 cache_pages: 10_000,
324 snapshot_retention: DEFAULT_SNAPSHOT_RETENTION,
325 export_retention: DEFAULT_EXPORT_RETENTION,
326 feature_gates: CapabilitySet::new()
327 .with(Capability::Table)
328 .with(Capability::Graph)
329 .with(Capability::Vector),
330 force_create: true,
331 metadata: BTreeMap::new(),
332 remote_backend: None,
333 remote_backend_atomic: None,
334 remote_key: None,
335 replication: ReplicationConfig::standalone(),
336 auth: AuthConfig::default(),
337 control_events: crate::runtime::control_events::ControlEventConfig::default(),
338 query_audit: crate::runtime::query_audit::QueryAuditConfig::default(),
339 auto_index_id: true,
340 layout: crate::storage::layout::StorageLayout::default(),
341 layout_overrides: crate::storage::layout::LayoutOverrides::default(),
342 storage_profile: crate::storage::profile::StorageProfileSelection::embedded_single_file(
343 ),
344 layout_explicit: false,
345 }
346 }
347}
348
349impl RedDBOptions {
350 pub fn persistent<P: Into<PathBuf>>(path: P) -> Self {
351 Self {
352 mode: StorageMode::Persistent,
353 data_path: Some(path.into()),
354 ..Default::default()
355 }
356 }
357
358 pub fn in_memory() -> Self {
365 static NEXT_EPHEMERAL_ID: std::sync::atomic::AtomicU64 =
366 std::sync::atomic::AtomicU64::new(0);
367
368 let now_nanos = std::time::SystemTime::now()
369 .duration_since(std::time::UNIX_EPOCH)
370 .map(|duration| duration.as_nanos())
371 .unwrap_or(0);
372 let unique = NEXT_EPHEMERAL_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
373 let path = std::env::temp_dir().join(format!(
374 "reddb-ephemeral-{}-{}-{}.rdb",
375 std::process::id(),
376 now_nanos,
377 unique
378 ));
379 let _ = std::fs::remove_file(&path);
380 Self {
381 mode: StorageMode::Persistent,
382 data_path: Some(path),
383 auto_checkpoint_pages: 0,
384 cache_pages: 2_000,
385 snapshot_retention: DEFAULT_SNAPSHOT_RETENTION,
386 export_retention: DEFAULT_EXPORT_RETENTION,
387 read_only: false,
388 force_create: true,
389 ..Default::default()
390 }
391 }
392
393 pub fn with_mode(mut self, mode: StorageMode) -> Self {
394 self.mode = mode;
395 self
396 }
397
398 pub fn with_data_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
399 self.data_path = Some(path.into());
400 self
401 }
402
403 pub fn with_read_only(mut self, read_only: bool) -> Self {
404 self.read_only = read_only;
405 self
406 }
407
408 pub fn with_auto_checkpoint(mut self, pages: u32) -> Self {
409 self.auto_checkpoint_pages = pages;
410 self
411 }
412
413 pub fn with_durability_mode(mut self, mode: DurabilityMode) -> Self {
414 self.durability_mode = mode;
415 self
416 }
417
418 pub fn with_group_commit_window_ms(mut self, window_ms: u64) -> Self {
419 self.group_commit.window_ms = window_ms;
422 self
423 }
424
425 pub fn with_group_commit_max_statements(mut self, max_statements: usize) -> Self {
426 self.group_commit.max_statements = max_statements.max(1);
427 self
428 }
429
430 pub fn with_group_commit_max_wal_bytes(mut self, max_wal_bytes: u64) -> Self {
431 self.group_commit.max_wal_bytes = max_wal_bytes.max(1);
432 self
433 }
434
435 pub fn with_cache_pages(mut self, pages: usize) -> Self {
436 self.cache_pages = pages.max(2);
437 self
438 }
439
440 pub fn with_snapshot_retention(mut self, limit: usize) -> Self {
441 self.snapshot_retention = limit.max(1);
442 self
443 }
444
445 pub fn with_export_retention(mut self, limit: usize) -> Self {
446 self.export_retention = limit.max(1);
447 self
448 }
449
450 pub fn with_metadata<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
451 self.metadata.insert(key.into(), value.into());
452 self
453 }
454
455 pub fn with_auto_index_id(mut self, enabled: bool) -> Self {
459 self.auto_index_id = enabled;
460 self
461 }
462
463 pub fn with_capability(mut self, capability: Capability) -> Self {
464 self.feature_gates = self.feature_gates.with(capability);
465 self
466 }
467
468 pub fn with_remote_backend(
474 mut self,
475 backend: Arc<dyn crate::storage::backend::RemoteBackend>,
476 key: impl Into<String>,
477 ) -> Self {
478 self.remote_backend = Some(backend);
479 self.remote_key = Some(key.into());
480 self
481 }
482
483 pub fn with_atomic_remote_backend(
489 mut self,
490 backend: Arc<dyn crate::storage::backend::AtomicRemoteBackend>,
491 ) -> Self {
492 self.remote_backend_atomic = Some(backend);
493 self
494 }
495
496 pub fn with_replication(mut self, config: ReplicationConfig) -> Self {
497 self.replication = config;
498 self
499 }
500
501 pub fn with_auth(mut self, config: AuthConfig) -> Self {
502 self.auth = config;
503 self
504 }
505
506 pub fn resolved_path(&self, fallback: impl AsRef<Path>) -> PathBuf {
507 self.data_path
508 .clone()
509 .unwrap_or_else(|| fallback.as_ref().to_path_buf())
510 }
511
512 pub fn remote_namespace_prefix(&self) -> String {
513 let Some(remote_key) = &self.remote_key else {
514 return String::new();
515 };
516 let normalized = remote_key.trim_matches('/');
517 if normalized.is_empty() {
518 return String::new();
519 }
520 match normalized.rsplit_once('/') {
521 Some((parent, _)) if !parent.is_empty() => format!("{parent}/"),
522 _ => String::new(),
523 }
524 }
525
526 pub fn default_backup_head_key(&self) -> String {
527 if let Some(value) = self.metadata.get("red.config.backup.head_key") {
528 return value.clone();
529 }
530 reddb_file::backup_head_key(&self.remote_namespace_prefix())
531 }
532
533 pub fn default_snapshot_prefix(&self) -> String {
534 if let Some(value) = self.metadata.get("red.config.backup.snapshot_prefix") {
535 return value.clone();
536 }
537 reddb_file::backup_snapshot_prefix(&self.remote_namespace_prefix())
538 }
539
540 pub fn default_wal_archive_prefix(&self) -> String {
541 if let Some(value) = self.metadata.get("red.config.wal.archive.prefix") {
542 return value.clone();
543 }
544 reddb_file::backup_wal_prefix(&self.remote_namespace_prefix())
545 }
546
547 pub fn has_capability(&self, capability: Capability) -> bool {
548 self.feature_gates.has(capability)
549 }
550
551 pub fn with_layout(mut self, layout: crate::storage::layout::StorageLayout) -> Self {
554 self.layout = layout;
555 self.layout_explicit = true;
556 self
557 }
558
559 pub fn with_layout_overrides(
562 mut self,
563 overrides: crate::storage::layout::LayoutOverrides,
564 ) -> Self {
565 self.layout_overrides = overrides;
566 self
567 }
568
569 pub fn with_storage_profile(
570 mut self,
571 selection: crate::storage::profile::StorageProfileSelection,
572 ) -> Result<Self, String> {
573 self.storage_profile = selection.validate()?;
574 Ok(self)
575 }
576
577 pub fn resolve_tiered_layout(
581 &self,
582 ) -> Option<(PathBuf, crate::storage::layout::TieredLayoutPaths)> {
583 let data_path = self.data_path.clone()?;
584 let paths = crate::storage::layout::TieredLayoutPaths::new(
585 &data_path,
586 self.layout,
587 self.layout_overrides.clone(),
588 );
589 Some((data_path, paths))
590 }
591
592 pub fn apply_tier_defaults(&self) {
613 use crate::storage::layout::StorageLayout;
614
615 if !self.layout_explicit {
620 if let Some((_, paths)) = self.resolve_tiered_layout() {
621 tier_wiring::stash_layout_paths(paths);
622 }
623 return;
624 }
625
626 let layout = self.layout;
627 crate::physical::set_meta_json_sidecar_enabled(matches!(layout, StorageLayout::Max));
629
630 crate::physical::set_seqn_journal_enabled(matches!(layout, StorageLayout::Max));
634 crate::physical::set_seqn_journal_retention(match layout {
635 StorageLayout::Max => crate::physical::DEFAULT_METADATA_JOURNAL_RETENTION,
636 _ => crate::physical::OPT_IN_METADATA_JOURNAL_RETENTION,
637 });
638
639 crate::physical::set_shm_provisioning_enabled(matches!(
641 layout,
642 StorageLayout::Standard | StorageLayout::Performance | StorageLayout::Max
643 ));
644
645 crate::physical::set_fold_pager_meta_enabled(matches!(layout, StorageLayout::Max));
648 crate::physical::set_fold_dwb_into_wal_enabled(matches!(layout, StorageLayout::Max));
649
650 if let Some((_, paths)) = self.resolve_tiered_layout() {
652 tier_wiring::stash_layout_paths(paths);
653 }
654 }
655}
656
657pub mod tier_wiring {
661 use std::sync::Mutex;
662
663 use crate::storage::layout::{LogDestination, TieredLayoutPaths};
664
665 static CURRENT_LAYOUT_PATHS: Mutex<Option<TieredLayoutPaths>> = Mutex::new(None);
666
667 pub fn stash_layout_paths(paths: TieredLayoutPaths) {
668 if let Ok(mut slot) = CURRENT_LAYOUT_PATHS.lock() {
669 *slot = Some(paths);
670 }
671 }
672
673 pub fn current_layout_paths() -> Option<TieredLayoutPaths> {
674 CURRENT_LAYOUT_PATHS
675 .lock()
676 .ok()
677 .and_then(|slot| slot.clone())
678 }
679
680 pub fn current_log_destinations() -> (LogDestination, LogDestination) {
684 match current_layout_paths() {
685 Some(p) => (p.audit_log_destination, p.slow_log_destination),
686 None => (LogDestination::Stderr, LogDestination::Stderr),
687 }
688 }
689}
690
691#[derive(Debug, Clone, Default)]
692pub struct CollectionStats {
693 pub entities: usize,
694 pub cross_refs: usize,
695 pub segments: usize,
696}
697
698#[derive(Debug, Clone)]
699pub struct CatalogSnapshot {
700 pub name: String,
701 pub total_entities: usize,
702 pub total_collections: usize,
703 pub stats_by_collection: BTreeMap<String, CollectionStats>,
704 pub updated_at: SystemTime,
705}
706
707impl Default for CatalogSnapshot {
708 fn default() -> Self {
709 Self {
710 name: String::new(),
711 total_entities: 0,
712 total_collections: 0,
713 stats_by_collection: BTreeMap::new(),
714 updated_at: UNIX_EPOCH,
715 }
716 }
717}
718
719#[derive(Debug, Clone)]
720pub struct SchemaManifest {
721 pub format_version: u32,
722 pub created_at_unix_ms: u128,
723 pub updated_at_unix_ms: u128,
724 pub options: RedDBOptions,
725 pub collection_count: usize,
726}
727
728impl SchemaManifest {
729 pub fn now(options: RedDBOptions, collection_count: usize) -> Self {
730 let now = SystemTime::now()
731 .duration_since(UNIX_EPOCH)
732 .unwrap_or_default()
733 .as_millis();
734 Self {
735 format_version: REDDB_FORMAT_VERSION,
736 created_at_unix_ms: now,
737 updated_at_unix_ms: now,
738 options,
739 collection_count,
740 }
741 }
742}
743
744#[derive(Debug)]
745pub enum RedDBError {
746 InvalidConfig(String),
747 SchemaVersionMismatch {
748 expected: u32,
749 found: u32,
750 },
751 FeatureNotEnabled(String),
752 NotFound(String),
753 ReadOnly(String),
754 InvalidOperation(String),
755 Engine(String),
756 Catalog(String),
757 Query(String),
758 Validation {
759 message: String,
760 validation: crate::json::Value,
761 },
762 Io(io::Error),
763 VersionUnavailable,
764 QuotaExceeded(String),
770 MaterializationLimitExceeded {
779 executor: &'static str,
780 limit: usize,
781 current: usize,
782 },
783 Internal(String),
784}
785
786impl fmt::Display for RedDBError {
787 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
788 match self {
789 Self::InvalidConfig(msg) => write!(f, "invalid config: {msg}"),
790 Self::SchemaVersionMismatch { expected, found } => {
791 write!(
792 f,
793 "schema version mismatch: expected {expected}, found {found}"
794 )
795 }
796 Self::FeatureNotEnabled(msg) => write!(f, "feature disabled: {msg}"),
797 Self::NotFound(msg) => write!(f, "not found: {msg}"),
798 Self::ReadOnly(msg) => write!(f, "read-only violation: {msg}"),
799 Self::InvalidOperation(msg) => write!(f, "INVALID_OPERATION: {msg}"),
800 Self::Engine(msg) => write!(f, "engine error: {msg}"),
801 Self::Catalog(msg) => write!(f, "catalog error: {msg}"),
802 Self::Query(msg) => write!(f, "query error: {msg}"),
803 Self::Validation { message, .. } => write!(f, "validation error: {message}"),
804 Self::Io(err) => write!(f, "io error: {err}"),
805 Self::VersionUnavailable => write!(f, "version information unavailable"),
806 Self::QuotaExceeded(msg) => write!(f, "quota exceeded: {msg}"),
807 Self::MaterializationLimitExceeded {
808 executor,
809 limit,
810 current,
811 } => write!(
812 f,
813 "materialization limit exceeded: executor={executor} current={current} limit={limit}"
814 ),
815 Self::Internal(msg) => write!(f, "internal error: {msg}"),
816 }
817 }
818}
819
820impl std::error::Error for RedDBError {}
821
822impl From<io::Error> for RedDBError {
823 fn from(err: io::Error) -> Self {
824 Self::Io(err)
825 }
826}
827
828impl From<crate::storage::engine::DatabaseError> for RedDBError {
829 fn from(err: crate::storage::engine::DatabaseError) -> Self {
830 Self::Engine(err.to_string())
831 }
832}
833
834impl From<crate::storage::wal::TxError> for RedDBError {
835 fn from(err: crate::storage::wal::TxError) -> Self {
836 Self::Engine(err.to_string())
837 }
838}
839
840impl From<crate::storage::StoreError> for RedDBError {
841 fn from(err: crate::storage::StoreError) -> Self {
842 Self::Catalog(err.to_string())
843 }
844}
845
846impl From<crate::storage::unified::devx::DevXError> for RedDBError {
847 fn from(err: crate::storage::unified::devx::DevXError) -> Self {
848 match err {
849 crate::storage::unified::devx::DevXError::Validation(msg) => Self::InvalidConfig(msg),
850 crate::storage::unified::devx::DevXError::Storage(msg) => Self::Engine(msg),
851 crate::storage::unified::devx::DevXError::NotFound(msg) => Self::NotFound(msg),
852 }
853 }
854}
855
856pub trait CatalogService {
857 fn list_collections(&self) -> Vec<String>;
858 fn collection_stats(&self, collection: &str) -> Option<CollectionStats>;
859 fn catalog_snapshot(&self) -> CatalogSnapshot;
860}
861
862pub trait QueryPlanner {
863 fn plan_cost(&self, query: &str) -> Option<f64>;
864}
865
866pub trait DataOps {
867 fn execute_query(&self, query: &str) -> RedDBResult<()>;
868}
869
870pub mod prelude {
871 pub use super::{
872 Capability, CapabilitySet, CatalogService, CatalogSnapshot, CollectionStats, DataOps,
873 QueryPlanner, RedDBError, RedDBOptions, RedDBResult, SchemaManifest, StorageMode,
874 REDDB_FORMAT_VERSION, REDDB_PROTOCOL_VERSION,
875 };
876}