use rusqlite::{params, Connection, Result as SqlResult};
use serde_json::{self, Value};
use serde::{Serialize, Deserialize};
use std::fs;
use uuid::Uuid;
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Point {
pub id: Option<Uuid>,
pub x: f64,
pub y: f64,
pub z: f64,
pub object_type: String,
pub custom_data: Value,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Region {
pub id: Uuid,
pub center: [f64; 3],
pub size: f64,
}
#[derive(Debug)]
pub struct Database {
conn: Connection,
}
impl Point {
pub fn new(id: Option<Uuid>, x: f64, y: f64, z: f64, object_type: String, custom_data: Value) -> Self {
Point { id, x, y, z, object_type, custom_data }
}
}
impl Database {
pub fn new(db_path: &str) -> SqlResult<Self> {
let conn = Connection::open(db_path)?;
Ok(Database { conn })
}
pub fn create_table(&self) -> SqlResult<()> {
self.conn.execute(
"CREATE TABLE IF NOT EXISTS points (
id TEXT PRIMARY KEY,
x REAL NOT NULL,
y REAL NOT NULL,
z REAL NOT NULL,
dataFile TEXT NOT NULL,
region_id TEXT,
object_type TEXT NOT NULL
)",
[],
)?;
self.conn.execute(
"CREATE TABLE IF NOT EXISTS regions (
id TEXT PRIMARY KEY,
center_x REAL NOT NULL,
center_y REAL NOT NULL,
center_z REAL NOT NULL,
size REAL NOT NULL
)",
[],
)?;
Ok(())
}
pub fn add_point(&self, point: &Point, region_id: Uuid) -> SqlResult<()> {
let id = point.id.unwrap_or_else(Uuid::new_v4).to_string();
let custom_data_str = serde_json::to_string(&point.custom_data)
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
let folder_name: String = id.chars().take(2).collect();
let file_path: String = format!("./data/{}/{}", folder_name, id);
fs::create_dir_all(format!("./data/{}", folder_name))
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
fs::write(&file_path, &custom_data_str)
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
self.conn.execute(
"INSERT OR REPLACE INTO points (id, x, y, z, dataFile, region_id, object_type) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
params![id, point.x, point.y, point.z, &file_path, region_id.to_string(), &point.object_type],
)?;
Ok(())
}
pub fn get_points_within_radius(&self, x1: f64, y1: f64, z1: f64, radius: f64) -> SqlResult<Vec<Point>> {
let radius_sq = radius * radius;
let mut stmt = self.conn.prepare(
"SELECT id, x, y, z, dataFile, object_type FROM points
WHERE ((x - ?1) * (x - ?1) + (y - ?2) * (y - ?2) + (z - ?3) * (z - ?3)) <= ?4",
)?;
let points_iter = stmt.query_map(params![x1, y1, z1, radius_sq], |row| {
let id: String = row.get(0)?;
let x: f64 = row.get(1)?;
let y: f64 = row.get(2)?;
let z: f64 = row.get(3)?;
let data_file: String = row.get(4)?;
let object_type: String = row.get(5)?;
let custom_data_str = fs::read_to_string(&data_file)
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
let custom_data: Value = serde_json::from_str(&custom_data_str)
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
Ok(Point {
id: Some(Uuid::parse_str(&id).unwrap()),
x,
y,
z,
object_type,
custom_data,
})
})?;
let mut points = Vec::new();
for point in points_iter {
points.push(point?);
}
Ok(points)
}
pub fn create_region(&self, region_id: Uuid, center: [f64; 3], size: f64) -> SqlResult<()> {
self.conn.execute(
"INSERT OR REPLACE INTO regions (id, center_x, center_y, center_z, size) VALUES (?1, ?2, ?3, ?4, ?5)",
params![region_id.to_string(), center[0], center[1], center[2], size],
)?;
Ok(())
}
pub fn remove_point(&self, point_id: Uuid) -> SqlResult<()> {
self.conn.execute(
"DELETE FROM points WHERE id = ?1",
params![point_id.to_string()],
)?;
Ok(())
}
pub fn update_point_position(&self, point_id: Uuid, x: f64, y: f64, z: f64) -> SqlResult<()> {
self.conn.execute(
"UPDATE points SET x = ?1, y = ?2, z = ?3 WHERE id = ?4",
params![x, y, z, point_id.to_string()],
)?;
Ok(())
}
pub fn get_all_regions(&self) -> SqlResult<Vec<Region>> {
let mut stmt = self.conn.prepare(
"SELECT id, center_x, center_y, center_z, size FROM regions",
)?;
let regions_iter = stmt.query_map([], |row| {
let id: String = row.get(0)?;
let center_x: f64 = row.get(1)?;
let center_y: f64 = row.get(2)?;
let center_z: f64 = row.get(3)?;
let size: f64 = row.get(4)?;
Ok(Region {
id: Uuid::parse_str(&id).unwrap(),
center: [center_x, center_y, center_z],
size,
})
})?;
let mut regions = Vec::new();
for region in regions_iter {
let region = region?;
println!("Retrieved region: ID: {}, Center: {:?}, Size: {}", region.id, region.center, region.size);
regions.push(region);
}
println!("Total regions retrieved from database: {}", regions.len());
Ok(regions)
}
pub fn get_points_in_region(&self, region_id: Uuid) -> SqlResult<Vec<Point>> {
let mut stmt = self.conn.prepare(
"SELECT id, x, y, z, dataFile, object_type FROM points WHERE region_id = ?1",
)?;
let points_iter = stmt.query_map(params![region_id.to_string()], |row| {
let id: String = row.get(0)?;
let x: f64 = row.get(1)?;
let y: f64 = row.get(2)?;
let z: f64 = row.get(3)?;
let data_file: String = row.get(4)?;
let object_type: String = row.get(5)?;
let custom_data_str = fs::read_to_string(&data_file)
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
let custom_data: Value = serde_json::from_str(&custom_data_str)
.map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
Ok(Point {
id: Some(Uuid::parse_str(&id).unwrap()),
x,
y,
z,
object_type,
custom_data,
})
})?;
let mut points = Vec::new();
for point in points_iter {
points.push(point?);
}
println!("Retrieved {} points for region {}", points.len(), region_id);
Ok(points)
}
pub fn clear_all_points(&self) -> SqlResult<()> {
self.conn.execute("DELETE FROM points", [])?;
Ok(())
}
}