mod meta;
mod polygon;
mod query;
use rusqlite::{functions::FunctionFlags, Connection};
use std::sync::{Mutex, MutexGuard, OnceLock};
use crate::geometry::haversine_km;
use crate::types::{DataInfo, Location, LookupResult, PrefixResult, Village};
const DB_BYTES: &[u8] = include_bytes!(env!("LOCATION_DB_PATH"));
pub(super) const VILLAGE_COLS: &str = "kode, nama, kecamatan, kota, provinsi, lat, lon";
pub(super) const VILLAGE_COLS_L: &str =
"l.kode, l.nama, l.kecamatan, l.kota, l.provinsi, l.lat, l.lon";
#[derive(Debug)]
#[non_exhaustive]
pub struct Error {
inner: rusqlite::Error,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.inner)
}
}
impl From<rusqlite::Error> for Error {
fn from(e: rusqlite::Error) -> Self {
Error { inner: e }
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Error {
fn serialize<S: serde::Serializer>(&self, s: S) -> std::result::Result<S::Ok, S::Error> {
s.serialize_str(&self.to_string())
}
}
pub type Result<T> = std::result::Result<T, Error>;
pub struct Database {
conn: Mutex<Connection>,
poly_conn: Option<Mutex<Connection>>,
}
impl Database {
fn lock_conn(&self) -> MutexGuard<'_, Connection> {
self.conn.lock().unwrap_or_else(|e| e.into_inner())
}
fn lock_poly(&self) -> MutexGuard<'_, Connection> {
self.poly_conn
.as_ref()
.expect("lock_poly called without polygon DB")
.lock()
.unwrap_or_else(|e| e.into_inner())
}
pub fn open() -> Result<Self> {
let mut conn = Connection::open_in_memory()?;
conn.execute_batch("PRAGMA journal_mode = OFF")?;
conn.deserialize_bytes("main", DB_BYTES)?;
conn.create_scalar_function(
"haversine_km",
4,
FunctionFlags::SQLITE_UTF8 | FunctionFlags::SQLITE_DETERMINISTIC,
move |ctx| {
Ok(haversine_km(
ctx.get::<f64>(0)?,
ctx.get::<f64>(1)?,
ctx.get::<f64>(2)?,
ctx.get::<f64>(3)?,
))
},
)?;
Ok(Database {
conn: Mutex::new(conn),
poly_conn: None,
})
}
pub fn open_with_polygons(poly_path: &str) -> Result<Self> {
let mut db = Self::open()?;
let poly_conn = Connection::open(poly_path)?;
db.poly_conn = Some(Mutex::new(poly_conn));
Ok(db)
}
pub fn has_polygons(&self) -> bool {
self.poly_conn.is_some()
}
pub fn find_nearest(&self, lat: f64, lon: f64, limit: usize) -> Result<Vec<Village>> {
query::nearest(&self.lock_conn(), lat, lon, limit)
}
pub fn find_by_name(&self, query: &str, limit: usize) -> Result<Vec<Village>> {
query::search(&self.lock_conn(), query, limit)
}
pub fn find_by_name_unique(&self, query: &str) -> Result<LookupResult> {
query::search_unique(&self.lock_conn(), query)
}
pub fn find_by_code(&self, code: &str) -> Result<Option<Village>> {
query::by_code(&self.lock_conn(), code)
}
pub fn find_by_code_prefix(
&self,
prefix: &str,
limit: usize,
offset: usize,
) -> Result<PrefixResult> {
query::by_code_prefix(&self.lock_conn(), prefix, limit, offset)
}
pub fn locate(&self, lat: f64, lon: f64) -> Result<Option<Location>> {
if self.poly_conn.is_some() {
let candidates = {
let poly = self.lock_poly();
polygon::query_polygon_candidates(&poly, lat, lon)?
};
if let Some(loc) =
polygon::locate_contained(&candidates, &self.lock_conn(), lat, lon, query::by_id)?
{
return Ok(Some(loc));
}
return query::locate_nearest(&self.lock_conn(), lat, lon);
}
query::locate_nearest(&self.lock_conn(), lat, lon)
}
pub fn data_info(&self) -> DataInfo {
meta::data_info_from_conn(&self.lock_conn())
}
pub fn village_count(&self) -> Result<u32> {
let count: i64 =
self.lock_conn()
.query_row("SELECT COUNT(*) FROM locations", [], |row| row.get(0))?;
Ok(u32::try_from(count).expect("village count exceeds u32 range"))
}
}
#[cfg(feature = "raw-sqlite")]
impl Database {
pub fn conn_guard(&self) -> MutexGuard<'_, Connection> {
self.lock_conn()
}
}
static CACHED_DATA_INFO: OnceLock<DataInfo> = OnceLock::new();
pub(crate) fn cached_data_info() -> &'static DataInfo {
CACHED_DATA_INFO.get_or_init(|| {
Database::open()
.map(|db| db.data_info())
.unwrap_or_default()
})
}