use std::error::Error;
use std::marker::PhantomData;
use std::path::Path;
use genawaiter::rc::Gen;
use lmdb::{Cursor, Transaction as LmdbTransaction};
use crate::types::{Location, Node, Region, Relation, Way};
pub const CELL_INDEX_LEVEL: u64 = 16;
pub struct Database {
env: lmdb::Environment,
locations: lmdb::Database,
nodes: lmdb::Database,
ways: lmdb::Database,
relations: lmdb::Database,
cell_node: lmdb::Database,
node_way: lmdb::Database,
node_relation: lmdb::Database,
way_relation: lmdb::Database,
relation_relation: lmdb::Database,
}
impl Database {
pub fn open(path: impl AsRef<Path>) -> Result<Self, Box<dyn Error>> {
let env = lmdb::Environment::new()
.set_flags(
lmdb::EnvironmentFlags::NO_SUB_DIR
| lmdb::EnvironmentFlags::NO_READAHEAD
| lmdb::EnvironmentFlags::NO_SYNC,
)
.set_max_dbs(10)
.set_map_size(50 * 1024 * 1024 * 1024) .open(path.as_ref())?;
let locations = env.open_db(Some("locations"))?;
let nodes = env.open_db(Some("nodes"))?;
let ways = env.open_db(Some("ways"))?;
let relations = env.open_db(Some("relations"))?;
let cell_node = env.open_db(Some("cell_node"))?;
let node_way = env.open_db(Some("node_way"))?;
let node_relation = env.open_db(Some("node_relation"))?;
let way_relation = env.open_db(Some("way_relation"))?;
let relation_relation = env.open_db(Some("relation_relation"))?;
Ok(Self {
env,
locations,
nodes,
ways,
relations,
cell_node,
node_way,
node_relation,
way_relation,
relation_relation,
})
}
}
pub struct Transaction<'db> {
db: &'db Database,
txn: lmdb::RoTransaction<'db>, }
impl<'db> Transaction<'db> {
pub fn begin(db: &'db Database) -> Result<Self, Box<dyn Error>> {
let txn = db.env.begin_ro_txn()?;
Ok(Self { db, txn })
}
pub fn locations(&self) -> Result<Locations, Box<dyn Error>> {
Ok(Locations::new(&self.txn, self.db.locations))
}
pub fn nodes(&self) -> Result<Nodes, Box<dyn Error>> {
Ok(Nodes::new(&self.txn, self.db.nodes))
}
pub fn ways(&self) -> Result<Ways, Box<dyn Error>> {
Ok(Ways::new(&self.txn, self.db.ways))
}
pub fn relations(&self) -> Result<Relations, Box<dyn Error>> {
Ok(Relations::new(&self.txn, self.db.relations))
}
pub fn cell_nodes(&self) -> Result<SpatialIndexTable, Box<dyn Error>> {
Ok(SpatialIndexTable::new(&self.txn, self.db.cell_node))
}
pub fn node_ways(&self) -> Result<JoinTable, Box<dyn Error>> {
Ok(JoinTable::new(&self.txn, self.db.node_way))
}
pub fn node_relations(&self) -> Result<JoinTable, Box<dyn Error>> {
Ok(JoinTable::new(&self.txn, self.db.node_relation))
}
pub fn way_relations(&self) -> Result<JoinTable, Box<dyn Error>> {
Ok(JoinTable::new(&self.txn, self.db.way_relation))
}
pub fn relation_relations(&self) -> Result<JoinTable, Box<dyn Error>> {
Ok(JoinTable::new(&self.txn, self.db.relation_relation))
}
}
pub struct ElementTable<'txn, E: TryFrom<&'txn [u8]> + 'txn> {
txn: &'txn lmdb::RoTransaction<'txn>,
table: lmdb::Database,
phantom: PhantomData<E>,
}
impl<'txn, E: TryFrom<&'txn [u8]>> ElementTable<'txn, E> {
fn new(txn: &'txn lmdb::RoTransaction<'txn>, table: lmdb::Database) -> Self {
Self {
txn,
table,
phantom: PhantomData,
}
}
pub fn get(&self, id: u64) -> Option<E> {
match self.txn.get(self.table, &id.to_le_bytes()) {
Ok(raw_val) => Some(E::try_from(raw_val).ok().unwrap()),
Err(lmdb::Error::NotFound) => None,
Err(e) => unreachable!("Unexpected LMDB error: {:?}", e),
}
}
pub fn iter(&self) -> impl Iterator<Item = (u64, E)> + 'txn {
let cursor = self.txn.open_ro_cursor(self.table).unwrap();
Gen::new(|co| async move {
let mut cursor = cursor;
for (raw_key, raw_val) in cursor.iter_start() {
let id = u64::from_le_bytes(raw_key.try_into().expect("key with incorrect length"));
let elem = E::try_from(raw_val).ok().unwrap();
co.yield_((id, elem)).await;
}
})
.into_iter()
}
}
pub type Locations<'txn> = ElementTable<'txn, Location<'txn>>;
pub type Nodes<'txn> = ElementTable<'txn, Node<'txn>>;
pub type Ways<'txn> = ElementTable<'txn, Way<'txn>>;
pub type Relations<'txn> = ElementTable<'txn, Relation<'txn>>;
pub struct SpatialIndexTable<'txn> {
txn: &'txn lmdb::RoTransaction<'txn>,
table: lmdb::Database,
}
impl<'txn> SpatialIndexTable<'txn> {
fn new(txn: &'txn lmdb::RoTransaction<'txn>, table: lmdb::Database) -> Self {
Self { txn, table }
}
pub fn find_in_region(&self, region: &'txn Region) -> impl Iterator<Item = u64> + 'txn {
let cursor = self.txn.open_ro_cursor(self.table).unwrap();
Gen::new(|co| async move {
let mut cursor = cursor;
for cell_id in region.cells.0.clone() {
let start = cell_id.child_begin_at_level(CELL_INDEX_LEVEL);
let end = cell_id.child_end_at_level(CELL_INDEX_LEVEL);
for (_, node_id) in cursor
.iter_dup_from(&start.0.to_le_bytes())
.flatten()
.map(|(raw_key, raw_val)| {
let cell_id = u64::from_le_bytes(
raw_key.try_into().expect("key with incorrect length"),
);
let node_id = u64::from_le_bytes(
raw_val.try_into().expect("val with incorrect length"),
);
(cell_id, node_id)
})
.take_while(|&(key, _)| end.0 > key)
{
co.yield_(node_id).await;
}
}
})
.into_iter()
}
}
pub struct JoinTable<'txn> {
txn: &'txn lmdb::RoTransaction<'txn>,
table: lmdb::Database,
}
impl<'txn> JoinTable<'txn> {
fn new(txn: &'txn lmdb::RoTransaction<'txn>, table: lmdb::Database) -> Self {
Self { txn, table }
}
pub fn get(&self, id: u64) -> impl Iterator<Item = u64> + 'txn {
let cursor = self.txn.open_ro_cursor(self.table).unwrap();
Gen::new(|co| async move {
let mut cursor = cursor;
match cursor.iter_dup_of(&id.to_le_bytes()) {
Ok(iter) => {
for (_, raw_val) in iter {
let val = u64::from_le_bytes(
raw_val.try_into().expect("key with incorrect length"),
);
co.yield_(val).await;
}
}
Err(lmdb::Error::NotFound) => (),
Err(e) => unreachable!("Unexpected LMDB error: {:?}", e),
}
})
.into_iter()
}
}