use log::*;
use sanakirja_core::btree::*;
use sanakirja_core::*;
use std::collections::HashSet;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
pub fn debug<
P: AsRef<Path>,
T: LoadPage,
K: Storable + ?Sized + std::fmt::Debug,
V: Storable + ?Sized + std::fmt::Debug,
PP: BTreePage<K, V>,
>(
t: &T,
db: &[&Db_<K, V, PP>],
p: P,
recurse: bool,
) where
T::Error: std::fmt::Debug,
{
let f = File::create(p.as_ref()).unwrap();
let mut buf = BufWriter::new(f);
writeln!(&mut buf, "digraph{{").unwrap();
let mut h = HashSet::new();
for db in db {
print_page::<T, K, V, PP>(t, &mut h, &mut buf, &t.load_page(db.db).unwrap(), recurse);
}
writeln!(&mut buf, "}}").unwrap();
}
fn print_page<
T: LoadPage,
K: Storable + ?Sized + std::fmt::Debug,
V: Storable + ?Sized + std::fmt::Debug,
P: BTreePage<K, V>,
>(
txn: &T,
pages: &mut HashSet<u64>,
buf: &mut BufWriter<File>,
p: &CowPage,
print_children: bool,
) where
T::Error: std::fmt::Debug,
{
if !pages.contains(&p.offset) {
pages.insert(p.offset);
writeln!(
buf,
"subgraph cluster{} {{\nlabel=\"Page {}, rc \
{} {}\";\ncolor=black;",
p.offset,
p.offset,
txn.rc(p.offset).unwrap(),
unsafe { u64::from_le(*(p.data as *const u64).add(1)) & 0xfff }
)
.unwrap();
let mut h = Vec::new();
let mut edges = Vec::new();
print_cursor::<T, K, V, P>(txn, buf, &mut edges, &mut h, p);
writeln!(buf, "}}").unwrap();
for p in edges.iter() {
writeln!(buf, "{}", p).unwrap()
}
if print_children {
for p in h.iter() {
print_page::<T, K, V, P>(txn, pages, buf, &p, print_children)
}
}
}
}
fn print_cursor<
T: LoadPage,
K: Storable + ?Sized + std::fmt::Debug,
V: Storable + ?Sized + std::fmt::Debug,
P: BTreePage<K, V>,
>(
txn: &T,
buf: &mut dyn Write,
edges: &mut Vec<String>,
pages: &mut Vec<CowPage>,
p: &CowPage,
) where
T::Error: std::fmt::Debug,
{
let mut cursor = P::cursor_first(p);
let mut i = 0;
let l = P::left_child(p.as_page(), &cursor);
if l > 0 {
pages.push(txn.load_page(l).unwrap());
edges.push(format!(
"n_{}_{}->n_{}_0[color=\"ForestGreen\"];",
p.offset, i, l
))
}
debug!("CURSOR: {:?} {:?}", p.offset, cursor);
while let Some((key, val, r)) = P::next(txn, p.as_page(), &mut cursor) {
if i > 0 {
writeln!(
buf,
"n_{}_{}->n_{}_{}[color=\"blue\"];",
p.offset,
i - 1,
p.offset,
i
)
.unwrap();
}
writeln!(
buf,
"n_{}_{}[label=\"{}: {:?} -> {:?}\"];",
p.offset, i, i, key, val
)
.unwrap();
if r > 0 {
pages.push(txn.load_page(r).unwrap());
edges.push(format!("n_{}_{}->n_{}_0[color=\"red\"];", p.offset, i, r))
};
i += 1
}
}
type B = btree::page::Page<u64, ()>;
pub fn check_free_mut(
txn: &crate::MutTxn<&crate::Env, ()>,
refs: &std::collections::BTreeMap<u64, usize>,
) {
let db_free = if txn.free > 0 {
let db_free = Db::from_page(txn.free);
let mut curs: Cursor<_, _, B> = btree::cursor::Cursor::new(txn, &db_free).unwrap();
while let Some((k, _)) = curs.next(txn).unwrap() {
assert!(refs.get(k).is_none())
}
Some(db_free)
} else {
None
};
debug!("{:?}", db_free);
for (r, _) in refs.iter() {
assert!(*r < txn.length)
}
let env = txn.env;
let len = txn.length;
for i in env.roots.len() as u64..(len >> 12) {
let page = i << 12;
if refs.contains_key(&page) {
continue;
} else if let Some(ref f) = db_free {
if let Some((x, _)) = get(txn, f, &page, None).unwrap() {
if *x == page {
continue;
}
}
} else if txn.free_owned_pages.iter().any(|x| *x == page) {
continue;
} else if txn.free_pages.iter().any(|x| *x == page) {
continue;
}
panic!("page not found: 0x{:x} (total length 0x{:x})", page, len);
}
}
pub fn check_free<B: std::borrow::Borrow<crate::Env>>(
txn: &crate::Txn<B>,
refs: &std::collections::BTreeMap<u64, usize>,
) {
let env = txn.env.borrow();
let (db_free, length): (Option<Db<u64, ()>>, _) = unsafe {
let hdr = &*(env.mmaps.lock()[0].ptr.add(txn.root * PAGE_SIZE)
as *const crate::environment::GlobalHeader);
(
if hdr.free_db != 0 {
Some(Db::from_page(u64::from_le(hdr.free_db)))
} else {
None
},
u64::from_le(hdr.length),
)
};
debug!("db_free: {:?}", db_free);
for (r, _) in refs.iter() {
debug!("r = 0x{:x}, length = 0x{:x}", r, length);
assert!(*r < length)
}
for i in env.roots.len() as u64..(length >> 12) {
let page = i << 12;
if refs.contains_key(&page) {
continue;
} else if let Some(ref f) = db_free {
if let Some((x, _)) = get(txn, f, &page, None).unwrap() {
if *x == page {
continue;
}
}
}
panic!("page not found: 0x{:x} (total length 0x{:x})", page, length);
}
}
pub fn add_refs<T: LoadPage, K: Storable + ?Sized, V: Storable + ?Sized, P: BTreePage<K, V>>(
txn: &T,
db: &Db_<K, V, P>,
pages: &mut std::collections::BTreeMap<u64, usize>,
) -> Result<(), T::Error> {
use std::collections::btree_map::Entry;
let mut stack = vec![db.db];
while let Some(p) = stack.pop() {
match pages.entry(p) {
Entry::Vacant(e) => {
debug!("add_refs: 0x{:x}", p);
e.insert(1);
let p = txn.load_page(p)?;
let mut c = P::cursor_first(&p);
let l = P::left_child(p.as_page(), &c);
if l > 0 {
stack.push(l);
while let Some((_, _, r)) = P::next(txn, p.as_page(), &mut c) {
stack.push(r);
}
}
}
Entry::Occupied(mut e) => {
e.insert(e.get() + 1);
}
}
}
Ok(())
}
pub fn add_free_refs<B: std::borrow::Borrow<crate::Env>>(
txn: &crate::Txn<B>,
pages: &mut std::collections::BTreeMap<u64, usize>,
) -> Result<(), crate::Error> {
let env = txn.env.borrow();
unsafe {
let p = &*(env.mmaps.lock()[0].ptr.add(txn.root * PAGE_SIZE)
as *const crate::environment::GlobalHeader);
if p.free_db != 0 {
debug!("add_free_refs: free = 0x{:x}", p.free_db);
let free_db: Db<u64, ()> = btree::Db::from_page(p.free_db);
add_refs(txn, &free_db, pages)?;
}
if p.rc_db != 0 {
debug!("add_free_refs: rc = 0x{:x}", p.rc_db);
let rc_db: Db<u64, ()> = btree::Db::from_page(p.rc_db);
add_refs(txn, &rc_db, pages)?;
}
};
Ok(())
}
pub fn add_free_refs_mut<B: std::borrow::Borrow<crate::Env>, T>(
txn: &crate::MutTxn<B, T>,
pages: &mut std::collections::BTreeMap<u64, usize>,
) -> Result<(), crate::Error> {
if txn.free != 0 {
debug!("add_free_refs: free = 0x{:x}", txn.free);
let free_db: Db<u64, ()> = btree::Db::from_page(txn.free);
add_refs(txn, &free_db, pages)?;
}
if let Some(ref rc) = txn.rc {
debug!("add_free_refs: rc = 0x{:x}", rc.db);
add_refs(txn, &rc, pages)?;
}
Ok(())
}