pub mod bloom;
pub mod bti;
pub mod bulletproof_reader;
pub mod chunk_decompressor;
pub mod chunk_reader;
pub mod chunked_data_reader;
pub mod compression;
pub mod compression_info;
pub mod directory;
pub mod directory_integration_tests;
pub mod format_detector;
pub mod header_spec;
pub mod index;
pub mod index_reader;
pub mod key_digest;
pub mod performance_benchmarks;
pub mod reader;
pub mod summary_reader;
pub mod version_gate;
pub use reader::SSTableReader;
pub mod schema_aware_reader;
pub use schema_aware_reader::SchemaAwareReader;
pub mod row_cell_state_machine;
pub mod statistics_reader;
#[cfg(feature = "tombstones")]
pub mod tombstone_merger;
pub mod validation;
#[cfg(feature = "write-support")]
pub mod writer;
#[cfg(test)]
mod issue_653_version_gates_plumbing_test;
#[cfg(test)]
mod key_digest_integration_test;
#[cfg(test)]
mod key_digest_test;
#[cfg(all(test, feature = "experimental"))]
mod oa_format_compliance_test;
#[cfg(all(test, feature = "state_machine"))]
mod row_cell_state_machine_test;
#[cfg(test)]
mod s3_verification_test;
#[cfg(test)]
mod s4_verification_test;
#[cfg(test)]
mod schema_aware_reader_test;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use tokio::sync::RwLock;
#[cfg(feature = "tombstones")]
use self::tombstone_merger::{EntryMetadata, GenerationValue, TombstoneMerger};
use crate::platform::Platform;
use crate::{types::TableId, Config, Result, RowKey, Value};
pub(crate) const MAX_SSTABLE_SCAN_DEPTH: usize = 3;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct SSTableId(pub String);
impl Default for SSTableId {
fn default() -> Self {
Self::new()
}
}
impl SSTableId {
pub fn new() -> Self {
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros();
Self(format!("sstable-{}-big-Data.db", timestamp))
}
pub fn from_filename(filename: &str) -> Self {
Self(filename.to_string())
}
pub fn filename(&self) -> &str {
&self.0
}
}
pub(crate) fn extract_table_name(sstable_path: &Path) -> Option<String> {
let dir_name = sstable_path.parent()?.file_name()?.to_str()?;
if let Some(uuid_start) = dir_name.rfind('-') {
let potential_uuid = &dir_name[uuid_start + 1..];
if potential_uuid.len() == 32 && potential_uuid.chars().all(|c| c.is_ascii_hexdigit()) {
return Some(dir_name[..uuid_start].to_string());
}
}
Some(dir_name.to_string())
}
pub fn extract_keyspace_and_table_name(sstable_path: &Path) -> Option<(String, String)> {
let table_name = extract_table_name(sstable_path)?;
let keyspace = sstable_path
.parent() .and_then(|p| p.parent()) .and_then(|p| p.file_name())
.and_then(|n| n.to_str())
.map(|s| s.to_string())?;
Some((keyspace, table_name))
}
#[inline]
fn is_apple_double_sidecar(filename: &str) -> bool {
filename.starts_with("._")
}
#[derive(Debug)]
pub struct SSTableManager {
base_path: PathBuf,
readers: Arc<RwLock<HashMap<SSTableId, Arc<reader::SSTableReader>>>>,
table_readers: Arc<RwLock<HashMap<String, Vec<Arc<reader::SSTableReader>>>>>,
platform: Arc<Platform>,
config: Config,
#[cfg(feature = "state_machine")]
schema_registry: Arc<RwLock<Option<Arc<RwLock<crate::schema::SchemaRegistry>>>>>,
}
impl SSTableManager {
pub async fn new(
path: &Path,
config: &Config,
platform: Arc<Platform>,
#[cfg(feature = "state_machine")] schema_registry: Option<
Arc<RwLock<crate::schema::SchemaRegistry>>,
>,
) -> Result<Self> {
let base_path = path.to_path_buf();
let readers = Arc::new(RwLock::new(HashMap::new()));
let table_readers = Arc::new(RwLock::new(HashMap::new()));
let manager = Self {
base_path,
readers,
table_readers,
platform,
config: config.clone(),
#[cfg(feature = "state_machine")]
schema_registry: Arc::new(RwLock::new(schema_registry)),
};
manager.load_existing_sstables().await?;
Ok(manager)
}
pub async fn new_from_discovered_paths(
storage_path: &Path,
table_dirs: Vec<PathBuf>,
config: &Config,
platform: Arc<Platform>,
#[cfg(feature = "state_machine")] schema_registry: Option<
Arc<RwLock<crate::schema::SchemaRegistry>>,
>,
) -> Result<Self> {
let base_path = storage_path.to_path_buf();
let readers = Arc::new(RwLock::new(HashMap::new()));
let table_readers = Arc::new(RwLock::new(HashMap::new()));
let manager = Self {
base_path,
readers,
table_readers,
platform: platform.clone(),
config: config.clone(),
#[cfg(feature = "state_machine")]
schema_registry: Arc::new(RwLock::new(schema_registry)),
};
manager.load_from_table_directories(table_dirs).await?;
Ok(manager)
}
async fn load_from_table_directories(&self, table_dirs: Vec<PathBuf>) -> Result<()> {
let mut readers = self.readers.write().await;
let mut table_readers = self.table_readers.write().await;
log::debug!(
"SSTableManager::load_from_table_directories: processing {} directories",
table_dirs.len()
);
for table_dir in table_dirs {
if !self.platform.fs().exists(&table_dir).await? {
log::warn!("Table directory does not exist: {:?}", table_dir);
continue;
}
log::debug!("SSTableManager scanning directory: {:?}", table_dir);
let mut dir_entries = match self.platform.fs().read_dir(&table_dir).await {
Ok(entries) => entries,
Err(e) => {
log::warn!("Cannot read table directory {:?}: {}", table_dir, e);
continue;
}
};
let mut files_found = 0;
while let Some(entry) = dir_entries.next_entry().await? {
let path = entry.path();
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
if filename.ends_with("-Data.db") && !is_apple_double_sidecar(filename) {
files_found += 1;
log::debug!("SSTableManager found SSTable file: {:?}", path);
let sstable_id = SSTableId::from_filename(filename);
match reader::SSTableReader::open(
&path,
&self.config,
self.platform.clone(),
)
.await
{
#[cfg_attr(not(feature = "state_machine"), allow(unused_mut))]
Ok(mut reader) => {
log::debug!(
"SSTableManager successfully loaded SSTable: {}",
sstable_id.0
);
#[cfg(feature = "state_machine")]
{
let schema_reg_guard = self.schema_registry.read().await;
if let Some(ref registry_rwlock) = *schema_reg_guard {
log::debug!(
"SSTableManager setting schema registry on reader: {}",
sstable_id.0
);
reader.set_schema_registry(Arc::clone(registry_rwlock));
let schema_registry = registry_rwlock.read().await;
let udt_registry_lock = schema_registry.get_udt_registry();
let udt_registry = udt_registry_lock.read().await.clone();
reader.set_udt_registry(udt_registry);
}
}
let reader_arc = Arc::new(reader);
readers.insert(sstable_id, reader_arc.clone());
if let Some((keyspace, table_name)) =
extract_keyspace_and_table_name(&path)
{
let qualified_key = format!("{}.{}", keyspace, table_name);
log::debug!(
"SSTableManager mapping table '{}' to SSTable '{}'",
qualified_key,
path.display()
);
table_readers
.entry(qualified_key)
.or_insert_with(Vec::new)
.push(reader_arc);
} else if let Some(table_name) = extract_table_name(&path) {
log::debug!(
"SSTableManager mapping table '{}' (no keyspace) to SSTable '{}'",
table_name,
path.display()
);
table_readers
.entry(table_name)
.or_insert_with(Vec::new)
.push(reader_arc);
} else {
log::warn!(
"SSTableManager could not extract table name from path: {}",
path.display()
);
}
}
Err(e) => {
log::warn!("Could not load SSTable file {:?}: {}", path, e);
}
}
}
}
}
log::debug!(
"SSTableManager directory scan complete: found {} Data.db files in {:?}",
files_found,
table_dir
);
}
log::debug!("SSTableManager total SSTables loaded: {}", readers.len());
log::debug!(
"SSTableManager tables discovered: {:?}",
table_readers.keys().collect::<Vec<_>>()
);
Ok(())
}
async fn load_existing_sstables(&self) -> Result<()> {
if !self.platform.fs().exists(&self.base_path).await? {
return Ok(()); }
let data_files: Vec<PathBuf> =
Self::find_data_files(&self.platform, &self.base_path, MAX_SSTABLE_SCAN_DEPTH).await?;
if data_files.is_empty() {
return Ok(());
}
let mut readers = self.readers.write().await;
let mut table_readers = self.table_readers.write().await;
let base_dir_name = self
.base_path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("")
.to_string();
for path in data_files {
let filename = match path.file_name().and_then(|n| n.to_str()) {
Some(f) => f.to_string(),
None => continue,
};
let sstable_id = SSTableId::from_filename(&filename);
match reader::SSTableReader::open(&path, &self.config, self.platform.clone()).await {
#[cfg_attr(not(feature = "state_machine"), allow(unused_mut))]
Ok(mut reader) => {
#[cfg(feature = "state_machine")]
{
let schema_reg_guard = self.schema_registry.read().await;
if let Some(ref registry_rwlock) = *schema_reg_guard {
reader.set_schema_registry(Arc::clone(registry_rwlock));
let schema_registry = registry_rwlock.read().await;
let udt_registry_lock = schema_registry.get_udt_registry();
let udt_registry = udt_registry_lock.read().await.clone();
reader.set_udt_registry(udt_registry);
}
}
let reader_arc = Arc::new(reader);
readers.insert(sstable_id, reader_arc.clone());
let table_key: Option<String> = if let Some((keyspace, table_name)) =
extract_keyspace_and_table_name(&path)
{
if table_name.as_str() != base_dir_name {
Some(format!("{}.{}", keyspace, table_name))
} else {
None
}
} else {
None
}
.or_else(|| {
extract_table_name(&path).filter(|name| name.as_str() != base_dir_name)
})
.or_else(|| {
let header_table = reader_arc.header().table_name.clone();
if header_table != "test_table" && !header_table.is_empty() {
Some(header_table)
} else {
None
}
});
if let Some(key) = table_key {
log::debug!(
"SSTableManager mapping table '{}' to SSTable '{}'",
key,
path.display()
);
table_readers
.entry(key)
.or_insert_with(Vec::new)
.push(reader_arc);
} else {
log::warn!(
"SSTableManager could not determine table name for: {}",
path.display()
);
}
}
Err(_) => {
log::warn!("Could not load SSTable file: {:?}", path);
}
}
}
Ok(())
}
fn find_data_files<'a>(
platform: &'a Platform,
dir: &'a Path,
max_depth: usize,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Vec<PathBuf>>> + Send + 'a>>
{
let dir = dir.to_path_buf();
Box::pin(async move {
let mut results = Vec::new();
let mut dir_entries = match platform.fs().read_dir(&dir).await {
Ok(entries) => entries,
Err(_) => return Ok(results),
};
while let Some(entry) = dir_entries.next_entry().await? {
let path = entry.path();
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
if filename.ends_with("-Data.db") && !is_apple_double_sidecar(filename) {
results.push(path);
} else if max_depth > 0 {
if entry
.file_type()
.await
.map(|ft| ft.is_dir())
.unwrap_or(false)
{
let sub_results =
Self::find_data_files(platform, &path, max_depth - 1).await?;
results.extend(sub_results);
}
}
}
}
Ok(results)
})
}
#[cfg(feature = "experimental")]
pub async fn create_from_memtable(
&self,
_data: Vec<(TableId, RowKey, Value)>,
) -> Result<SSTableId> {
Err(crate::error::Error::unsupported_format(
"SSTable writing removed in Issue #176 - writer.rs deleted",
))
}
#[cfg(not(feature = "experimental"))]
pub async fn create_from_memtable(
&self,
_data: Vec<(TableId, RowKey, Value)>,
) -> Result<SSTableId> {
Err(crate::error::Error::unsupported_format(
"SSTable writing requires experimental feature",
))
}
#[cfg(feature = "tombstones")]
pub async fn get(&self, table_id: &TableId, key: &RowKey) -> Result<Option<Value>> {
let readers = self.readers.read().await;
let mut all_values = Vec::new();
for (_sstable_id, reader) in readers.iter() {
if let Some(value) = reader.get(table_id, key).await? {
let generation = reader.generation;
let write_time = reader.extract_write_time_from_entry(key, &value);
let gen_value = GenerationValue {
value,
metadata: EntryMetadata {
write_time,
generation,
ttl: None, },
};
all_values.push(gen_value);
}
}
let merger = TombstoneMerger::new();
merger.merge_generations(all_values)
}
#[cfg(not(feature = "tombstones"))]
pub async fn get(&self, table_id: &TableId, key: &RowKey) -> Result<Option<Value>> {
let table_readers = self.table_readers.read().await;
let table_name = table_id.name();
let reader_list = if let Some(list) = table_readers.get(table_name) {
list
} else {
let unqualified_name = if let Some(dot_pos) = table_name.rfind('.') {
&table_name[dot_pos + 1..]
} else {
table_name
};
match table_readers.get(unqualified_name) {
Some(list) => list,
None => return Ok(None),
}
};
for reader in reader_list {
if let Some(value) = reader.get(table_id, key).await? {
return Ok(Some(value));
}
}
Ok(None)
}
#[cfg(feature = "tombstones")]
pub async fn scan(
&self,
table_id: &TableId,
start_key: Option<&RowKey>,
end_key: Option<&RowKey>,
limit: Option<usize>,
schema: Option<&crate::schema::TableSchema>,
) -> Result<Vec<(RowKey, Value)>> {
let readers = self.readers.read().await;
let mut key_values = std::collections::HashMap::new();
for reader in readers.values() {
let results = reader
.scan(table_id, start_key, end_key, None, schema)
.await?;
for (row_key, value) in results {
let generation = reader.generation;
let write_time = reader.extract_write_time_from_entry(&row_key, &value);
let gen_value = GenerationValue {
value,
metadata: EntryMetadata {
write_time,
generation,
ttl: None,
},
};
key_values
.entry(row_key)
.or_insert_with(Vec::new)
.push(gen_value);
}
}
let merger = TombstoneMerger::new();
let mut final_results = Vec::new();
for (row_key, values) in key_values {
if let Some(merged_value) = merger.merge_generations(values)? {
final_results.push((row_key, merged_value));
}
}
final_results.sort_by(|a, b| a.0.cmp(&b.0));
if let Some(limit) = limit {
final_results.truncate(limit);
}
Ok(final_results)
}
#[cfg(not(feature = "tombstones"))]
pub async fn scan(
&self,
table_id: &TableId,
start_key: Option<&RowKey>,
end_key: Option<&RowKey>,
limit: Option<usize>,
schema: Option<&crate::schema::TableSchema>,
) -> Result<Vec<(RowKey, Value)>> {
let table_readers = self.table_readers.read().await;
log::debug!("SSTableManager::scan - Scanning table_id='{}'", table_id);
let table_name = table_id.name();
let readers = if table_readers.contains_key(table_name) {
log::debug!(
"SSTableManager::scan - Found readers via qualified name '{}'",
table_name
);
table_readers.get(table_name)
} else {
let unqualified_name = if let Some(dot_pos) = table_name.rfind('.') {
&table_name[dot_pos + 1..]
} else {
table_name
};
log::debug!(
"SSTableManager::scan - Falling back to unqualified name '{}'",
unqualified_name
);
table_readers.get(unqualified_name)
};
if let Some(reader_list) = readers {
log::debug!(
"SSTableManager::scan - Found {} readers for table '{}'",
reader_list.len(),
table_id
);
let mut all_results = Vec::new();
for reader in reader_list {
log::debug!(
"SSTableManager::scan - Calling scan on reader for file: {:?}",
reader.file_path
);
let results = reader
.scan(table_id, start_key, end_key, None, schema)
.await?;
log::debug!(
"SSTableManager::scan - Reader returned {} results",
results.len()
);
all_results.extend(results);
}
log::debug!(
"SSTableManager::scan - Total results from all readers: {}",
all_results.len()
);
all_results.sort_by(|a, b| a.0.cmp(&b.0));
if let Some(limit) = limit {
all_results.truncate(limit);
}
log::debug!(
"SSTableManager::scan - Returning {} final results",
all_results.len()
);
Ok(all_results)
} else {
log::debug!(
"SSTableManager::scan - No readers found for table '{}'",
table_id
);
log::debug!(
"SSTableManager::scan - Available tables: {:?}",
table_readers.keys().collect::<Vec<_>>()
);
Ok(Vec::new())
}
}
pub async fn list_sstables(&self) -> Vec<SSTableId> {
let readers = self.readers.read().await;
readers.keys().cloned().collect()
}
pub async fn remove_sstable(&self, sstable_id: &SSTableId) -> Result<()> {
{
let mut readers = self.readers.write().await;
readers.remove(sstable_id);
}
let file_path = self.base_path.join(sstable_id.filename());
if self.platform.fs().exists(&file_path).await? {
self.platform.fs().remove_file(&file_path).await?;
}
Ok(())
}
pub async fn stats(&self) -> Result<SSTableStats> {
let readers = self.readers.read().await;
let mut total_size = 0u64;
let mut total_entries = 0u64;
let mut total_tables = 0u64;
let sstable_count = readers.len();
for reader in readers.values() {
let reader_stats = reader.stats().await?;
total_size += reader_stats.file_size;
total_entries += reader_stats.entry_count;
total_tables += reader_stats.table_count;
}
Ok(SSTableStats {
sstable_count,
total_size,
total_entries,
total_tables,
average_size: if sstable_count > 0 {
total_size / sstable_count as u64
} else {
0
},
})
}
#[cfg(feature = "state_machine")]
pub async fn set_schema_registry(
&self,
registry: Arc<RwLock<crate::schema::SchemaRegistry>>,
) -> Result<()> {
{
let mut schema_reg = self.schema_registry.write().await;
*schema_reg = Some(registry.clone());
}
Ok(())
}
#[cfg(feature = "experimental")]
pub async fn merge_sstables(
&self,
_source_ids: Vec<SSTableId>,
_target_id: SSTableId,
) -> Result<()> {
Err(crate::error::Error::unsupported_format(
"SSTable merging removed in Issue #176 - writer.rs deleted",
))
}
#[cfg(not(feature = "experimental"))]
pub async fn merge_sstables(
&self,
_source_ids: Vec<SSTableId>,
_target_id: SSTableId,
) -> Result<()> {
Err(crate::error::Error::unsupported_format(
"SSTable merging requires experimental feature",
))
}
}
#[derive(Debug, Clone)]
pub struct SSTableStats {
pub sstable_count: usize,
pub total_size: u64,
pub total_entries: u64,
pub total_tables: u64,
pub average_size: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::platform::Platform;
use tempfile::TempDir;
#[tokio::test]
async fn test_sstable_manager_creation() {
let temp_dir = TempDir::new().unwrap();
let config = Config::default();
let platform = Arc::new(Platform::new(&config).await.unwrap());
let manager = SSTableManager::new(
temp_dir.path(),
&config,
platform,
#[cfg(feature = "state_machine")]
None,
)
.await
.unwrap();
let stats = manager.stats().await.unwrap();
assert_eq!(stats.sstable_count, 0);
assert_eq!(stats.total_size, 0);
}
#[tokio::test]
async fn test_sstable_manager_from_discovered_paths_empty() {
let temp_dir = TempDir::new().unwrap();
let config = Config::default();
let platform = Arc::new(Platform::new(&config).await.unwrap());
let discovered_paths = Vec::new();
let manager = SSTableManager::new_from_discovered_paths(
temp_dir.path(),
discovered_paths,
&config,
platform,
#[cfg(feature = "state_machine")]
None,
)
.await
.unwrap();
let stats = manager.stats().await.unwrap();
assert_eq!(stats.sstable_count, 0);
assert_eq!(stats.total_size, 0);
}
#[tokio::test]
async fn test_sstable_manager_from_discovered_paths_with_directories() {
use std::fs;
let temp_dir = TempDir::new().unwrap();
let config = Config::default();
let platform = Arc::new(Platform::new(&config).await.unwrap());
let keyspace_dir = temp_dir.path().join("test_ks");
fs::create_dir(&keyspace_dir).unwrap();
let table1_dir = keyspace_dir.join("users-abc123");
fs::create_dir(&table1_dir).unwrap();
fs::write(table1_dir.join("na-1-big-Data.db"), b"mock_data").unwrap();
let table2_dir = keyspace_dir.join("posts-def456");
fs::create_dir(&table2_dir).unwrap();
fs::write(table2_dir.join("na-2-big-Data.db"), b"mock_data").unwrap();
fs::write(table2_dir.join("na-3-big-Data.db"), b"mock_data").unwrap();
let table_dirs = vec![table1_dir.clone(), table2_dir.clone()];
let manager = SSTableManager::new_from_discovered_paths(
temp_dir.path(),
table_dirs,
&config,
platform,
#[cfg(feature = "state_machine")]
None,
)
.await
.unwrap();
let stats = manager.stats().await.unwrap();
let _ = stats.sstable_count; }
#[tokio::test]
#[ignore = "M3+ feature; gated for M1"]
async fn test_sstable_id_generation() {
let id1 = SSTableId::new();
let id2 = SSTableId::new();
assert_ne!(id1.filename(), id2.filename());
assert!(id1.filename().starts_with("sstable_"));
assert!(id1.filename().ends_with(".sst"));
}
#[tokio::test]
async fn test_find_data_files_excludes_apple_double_sidecar() {
use std::fs;
let temp_dir = tempfile::TempDir::new().unwrap();
let config = Config::default();
let platform = Arc::new(Platform::new(&config).await.unwrap());
let real_file = temp_dir.path().join("nb-1-big-Data.db");
let sidecar = temp_dir.path().join("._nb-1-big-Data.db");
fs::write(&real_file, b"\x00").unwrap();
fs::write(&sidecar, b"\x00\x00").unwrap();
let results = SSTableManager::find_data_files(&platform, temp_dir.path(), 0)
.await
.unwrap();
assert_eq!(
results.len(),
1,
"expected exactly 1 result but got {}: {:?}",
results.len(),
results
);
assert_eq!(results[0], real_file);
assert!(
!results.contains(&sidecar),
"AppleDouble sidecar must not appear in results"
);
}
#[test]
fn test_is_apple_double_sidecar() {
assert!(is_apple_double_sidecar("._nb-1-big-Data.db"));
assert!(is_apple_double_sidecar("._anything"));
assert!(is_apple_double_sidecar("._"));
assert!(!is_apple_double_sidecar("nb-1-big-Data.db"));
assert!(!is_apple_double_sidecar("na-2-big-Data.db"));
assert!(!is_apple_double_sidecar(""));
}
#[test]
fn test_extract_table_name() {
use std::path::PathBuf;
let path =
PathBuf::from("test-data/datasets/sstables/test_basic/simple_table-6aa08200a25111f0a3fef1a551383fb9/nb-1-big-Data.db");
assert_eq!(extract_table_name(&path), Some("simple_table".to_string()));
let path = PathBuf::from(
"test-data/datasets/sstables/test_basic/my-test-table-6aa08200a25111f0a3fef1a551383fb9/nb-1-big-Data.db",
);
assert_eq!(extract_table_name(&path), Some("my-test-table".to_string()));
let path = PathBuf::from(
"test-data/datasets/sstables/test_basic/multi_partition_table-6ac52100a25111f0a3fef1a551383fb9/nb-1-big-Data.db",
);
assert_eq!(
extract_table_name(&path),
Some("multi_partition_table".to_string())
);
let path = PathBuf::from(
"test-data/datasets/sstables/test_basic/compression_test_table-6ad6ad30a25111f0a3fef1a551383fb9/nb-1-big-Data.db",
);
assert_eq!(
extract_table_name(&path),
Some("compression_test_table".to_string())
);
let path =
PathBuf::from("test-data/datasets/sstables/test_basic/simple_table/nb-1-big-Data.db");
assert_eq!(extract_table_name(&path), Some("simple_table".to_string()));
let path = PathBuf::from("nb-1-big-Data.db");
assert_eq!(extract_table_name(&path), None);
}
}