#![allow(clippy::unwrap_used, clippy::expect_used)]
#![allow(unsafe_code)]
use std::collections::BTreeMap;
use std::path::Path;
use std::time::Duration;
use chronix::prelude::*;
use chronix::{Chronix, fields, tags};
use chronix_core::FieldEncryption;
const SECRET: &str = "PATIENT-4417-CONFIDENTIAL";
fn segment_bytes(dir: &Path) -> Vec<u8> {
let mut out = Vec::new();
let mut stack = vec![dir.join("segments")];
while let Some(d) = stack.pop() {
let Ok(entries) = std::fs::read_dir(&d) else {
continue;
};
for entry in entries.flatten() {
let p = entry.path();
if p.is_dir() {
stack.push(p);
} else if p.extension().is_some_and(|e| e == "csx") {
out.extend(std::fs::read(&p).unwrap());
}
}
}
out
}
fn contains(haystack: &[u8], needle: &str) -> bool {
haystack
.windows(needle.len())
.any(|w| w == needle.as_bytes())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let key = [0x5au8; 32];
let encoded = {
use base64::Engine as _;
base64::engine::general_purpose::STANDARD.encode(key)
};
unsafe { std::env::set_var("CHRONIX_EXAMPLE_KEY_PHI", &encoded) };
let declaration = FieldEncryption {
columns: BTreeMap::from([("patient_id".to_owned(), "phi-2026".to_owned())]),
keys: BTreeMap::from([("phi-2026".to_owned(), "CHRONIX_EXAMPLE_KEY_PHI".to_owned())]),
};
println!("─── 1. Declaration ───");
println!(" column patient_id → key 'phi-2026'");
println!(" key phi-2026 → $CHRONIX_EXAMPLE_KEY_PHI");
let db = Chronix::open(
ChronixConfig::builder()
.data_dir(dir.path())
.field_encryption(declaration)
.maintenance_interval(Duration::from_secs(86_400 * 365))
.build()?,
)?;
for ts in 0..500i64 {
db.insert(&Point::new(
SeriesKey::new("visits", tags! { "clinic" => "north" })?,
fields! { "patient_id" => SECRET, "minutes" => 12.5 },
ts * 1_000_000,
)?)?;
}
db.flush()?;
let plan = db
.query()
.measurement("visits")
.range(i64::MIN, i64::MAX)
.build()?;
let batch = db.execute(&plan)?;
println!("\n─── 2. Read back ───");
println!(" rows {}", batch.num_rows());
println!(" patient_id {SECRET} (decrypted transparently)");
let bytes = segment_bytes(dir.path());
println!("\n─── 3. What is actually on disk ───");
println!(" segment bytes {}", bytes.len());
println!(
" contains the secret? {}",
if contains(&bytes, SECRET) {
"YES — something is wrong"
} else {
"no"
}
);
println!(
" contains the tag 'north'? {} (the control: an undeclared column is plainly there)",
if contains(&bytes, "north") {
"yes"
} else {
"no — then the line above proves nothing"
}
);
assert!(!contains(&bytes, SECRET));
assert!(contains(&bytes, "north"));
db.compact()?;
db.gc()?;
assert!(!contains(&segment_bytes(dir.path()), SECRET));
println!(" still ciphertext after a compaction pass");
println!("\n─── 4. What is refused ───");
match db.export_parquet(
&plan,
&dir.path().join("out.parquet"),
&chronix::ParquetExportConfig::default(),
) {
Ok(_) => return Err("a Parquet export must be refused".into()),
Err(e) => println!(" parquet export {e}"),
}
let rollup = chronix::RollupBuilder::new()
.name("hourly")
.source("visits")
.target("visits_hourly")
.bucket(chronix_core::timebucket::TimeBucket::fixed_ns(
3_600_000_000_000,
))
.aggregation(chronix::RollupAggFn::Avg)
.build()?;
match db.create_rollup(rollup) {
Ok(()) => return Err("a rollup must be refused".into()),
Err(e) => println!(" rollup {e}"),
}
println!("\n─── 5. What cannot be declared ───");
let bad = FieldEncryption {
columns: BTreeMap::from([("clinic".to_owned(), "phi-2026".to_owned())]),
keys: BTreeMap::from([("phi-2026".to_owned(), "CHRONIX_EXAMPLE_KEY_PHI".to_owned())]),
};
let other = tempfile::tempdir()?;
let db2 = Chronix::open(
ChronixConfig::builder()
.data_dir(other.path())
.field_encryption(bad)
.maintenance_interval(Duration::from_secs(86_400 * 365))
.build()?,
)?;
db2.insert(&Point::new(
SeriesKey::new("visits", tags! { "clinic" => "north" })?,
fields! { "minutes" => 1.0 },
1_000,
)?)?;
match db2.flush() {
Ok(_) => return Err("an encrypted tag must be refused".into()),
Err(e) => println!(" encrypted tag {e}"),
}
db.close()?;
Ok(())
}