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;
use crate::L64;
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,
&unsafe { t.load_page(db.db.into()) }.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 {
if let Ok(page) = unsafe { txn.load_page(l) } {
pages.push(page);
}
edges.push(format!(
"n_{}_{}->n_{}_0[color=\"ForestGreen\"];",
p.offset, i, l
))
}
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(unsafe { txn.load_page(r).unwrap() });
edges.push(format!("n_{}_{}->n_{}_0[color=\"red\"];", p.offset, i, r))
};
i += 1
}
}
pub trait Check: core::fmt::Debug {
fn add_refs<T: LoadPage>(
&self,
_txn: &T,
_refs: &mut std::collections::BTreeMap<u64, usize>,
) -> Result<(), T::Error>
where
T::Error: std::fmt::Debug,
{
Ok(())
}
}
impl Check for u64 {}
impl Check for () {}
impl<
K: Check + Ord + UnsizedStorable + ?Sized,
V: Check + Ord + UnsizedStorable + ?Sized,
P: BTreePage<K, V> + std::fmt::Debug,
> Check for Db_<K, V, P>
{
fn add_refs<T: LoadPage>(
&self,
txn: &T,
pages: &mut std::collections::BTreeMap<u64, usize>,
) -> Result<(), T::Error>
where
T::Error: std::fmt::Debug,
{
use std::collections::btree_map::Entry;
let mut stack = vec![self.db];
while let Some(p) = stack.pop() {
match pages.entry(p.into()) {
Entry::Vacant(e) => {
debug!("add_refs: 0x{:x}", p);
e.insert(1);
let p = unsafe { txn.load_page(p.into())? };
let mut c = P::cursor_first(&p);
let l: u64 = P::left_child(p.as_page(), &c);
if l > 0 {
stack.push(core::num::NonZeroU64::new(l).unwrap());
}
let mut kv = None;
while let Some((k, v, r)) = P::next(txn, p.as_page(), &mut c) {
debug!("{:?} {:?} {:?}", k, v, kv);
if let Some((k_, v_)) = kv {
debug!("{:?} {:?} {:?}", k_ > k, k_ == k, v_ > v);
if k_ > k || (k_ == k && v_ > v) {
debug(txn, &[self], "debug_ord", true);
panic!("{:?} {:?} {:?} {:?} {:?}", kv, k_, v_, k_ > k, v_ > v);
}
}
k.add_refs(txn, pages)?;
v.add_refs(txn, pages)?;
kv = Some((k, v));
if r > 0 {
stack.push(core::num::NonZeroU64::new(r).unwrap())
}
}
}
Entry::Occupied(mut e) => {
e.insert(e.get() + 1);
}
}
}
Ok(())
}
}
type B = page::Page<L64, ()>;
pub fn check_free_mut(
txn: &crate::MutTxn<&crate::Env, ()>,
refs: &std::collections::BTreeMap<u64, usize>,
) {
let db_free: Option<Db<L64, ()>> = if txn.free > 0 {
let db_free = unsafe { Db::from_page(txn.free) };
let mut curs: Cursor<_, _, B> = Cursor::new(txn, &db_free).unwrap();
while let Some((k, _)) = curs.next(txn).unwrap() {
assert!(refs.get(&k.as_u64()).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, &L64(page.to_le()), None).unwrap() {
if x.as_u64() == 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<L64, ()>>, _) = 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, &L64(page.to_le()), None).unwrap() {
if x.as_u64() == page {
continue;
}
}
}
panic!("page not found: 0x{:x} (total length 0x{:x})", page, length);
}
if let Some(ref db_free) = db_free {
let mut free = HashSet::new();
for p in iter(txn, db_free, None).unwrap() {
assert!(free.insert(*p.unwrap().0))
}
}
}
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<L64, ()> = Db::from_page(u64::from_le(p.free_db));
free_db.add_refs(txn, pages)?;
}
if p.rc_db != 0 {
debug!("add_free_refs: rc = 0x{:x}", p.rc_db);
let rc_db: Db<L64, ()> = Db::from_page(u64::from_le(p.rc_db));
rc_db.add_refs(txn, 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<L64, ()> = unsafe { Db::from_page(txn.free) };
free_db.add_refs(txn, pages)?;
}
if let Some(ref rc) = txn.rc {
debug!("add_free_refs: rc = 0x{:x}", rc.db);
rc.add_refs(txn, pages)?;
}
Ok(())
}
pub fn check_refs<B: std::borrow::Borrow<crate::Env>, T>(
txn: &crate::MutTxn<B, T>,
refs: &std::collections::BTreeMap<u64, usize>,
) {
for (p, r) in refs.iter() {
if *r >= 2 {
assert_eq!(txn.rc(*p).unwrap(), *r as u64);
} else {
assert_eq!(txn.rc(*p).unwrap(), 0);
}
}
}