use crate::Result;
use super::model::{SchemaInfo, TypeInfo};
#[derive(Debug)]
pub struct SchemaExporter {
}
impl SchemaExporter {
pub(super) fn new() -> Self {
Self {}
}
pub(super) async fn generate_cql(&self, schema: &SchemaInfo) -> Result<String> {
let mut cql = String::new();
cql.push_str(&format!(
"CREATE TABLE {}.{} (\n",
schema.keyspace, schema.table
));
for column in &schema.partition_key {
cql.push_str(&format!(
" {} {},\n",
column.name,
self.format_column_type(&column.type_info)
));
}
for clustering in &schema.clustering_keys {
cql.push_str(&format!(
" {} {},\n",
clustering.name, clustering.data_type
));
}
for column in &schema.static_columns {
let static_modifier = if column.is_static { " STATIC" } else { "" };
cql.push_str(&format!(
" {} {}{},\n",
column.name,
self.format_column_type(&column.type_info),
static_modifier
));
}
for column in &schema.regular_columns {
cql.push_str(&format!(
" {} {},\n",
column.name,
self.format_column_type(&column.type_info)
));
}
if !schema.partition_key.is_empty() {
let partition_keys: Vec<String> = schema
.partition_key
.iter()
.map(|col| col.name.clone())
.collect();
if schema.clustering_keys.is_empty() {
cql.push_str(&format!(" PRIMARY KEY ({})", partition_keys.join(", ")));
} else {
let clustering_keys: Vec<String> = schema
.clustering_keys
.iter()
.map(|col| col.name.clone())
.collect();
if partition_keys.len() == 1 {
cql.push_str(&format!(
" PRIMARY KEY ({}, {})",
partition_keys[0],
clustering_keys.join(", ")
));
} else {
cql.push_str(&format!(
" PRIMARY KEY (({}) {})",
partition_keys.join(", "),
clustering_keys.join(", ")
));
}
}
}
cql.push_str("\n);");
if !schema.clustering_keys.is_empty() {
let mut clustering_order = Vec::new();
for clustering in &schema.clustering_keys {
let order = match clustering.order {
crate::schema::ClusteringOrder::Asc => "ASC",
crate::schema::ClusteringOrder::Desc => "DESC",
};
clustering_order.push(format!("{} {}", clustering.name, order));
}
if clustering_order.iter().any(|o| o.contains("DESC")) {
cql.push_str(&format!(
"\nWITH CLUSTERING ORDER BY ({});",
clustering_order.join(", ")
));
}
}
let mut options = Vec::new();
if let Some(compaction) = &schema.table_options.compaction {
options.push(format!("compaction = {{'class': '{}'}}", compaction.class));
}
if let Some(compression) = &schema.table_options.compression {
options.push(format!(
"compression = {{'algorithm': '{}'}}",
compression.algorithm
));
}
if let Some(gc_grace) = schema.table_options.gc_grace_seconds {
options.push(format!("gc_grace_seconds = {}", gc_grace));
}
if let Some(ttl) = schema.table_options.default_time_to_live {
options.push(format!("default_time_to_live = {}", ttl));
}
if !options.is_empty() {
if !cql.ends_with(';') {
cql.push_str(" WITH ");
} else {
cql.pop(); cql.push_str("\nWITH ");
}
cql.push_str(&options.join("\n AND "));
cql.push(';');
}
Ok(cql)
}
#[allow(clippy::only_used_in_recursion)]
fn format_column_type(&self, type_info: &TypeInfo) -> String {
match type_info.type_id.as_str() {
"list" => {
if let Some(element_type) = &type_info.element_type {
format!("list<{}>", self.format_column_type(element_type))
} else {
"list<text>".to_string()
}
}
"set" => {
if let Some(element_type) = &type_info.element_type {
format!("set<{}>", self.format_column_type(element_type))
} else {
"set<text>".to_string()
}
}
"map" => {
if let (Some(key_type), Some(value_type)) =
(&type_info.key_type, &type_info.value_type)
{
format!(
"map<{}, {}>",
self.format_column_type(key_type),
self.format_column_type(value_type)
)
} else {
"map<text, text>".to_string()
}
}
"tuple" => {
if let Some(tuple_elements) = &type_info.tuple_elements {
let element_types: Vec<String> = tuple_elements
.iter()
.map(|t| self.format_column_type(t))
.collect();
format!("tuple<{}>", element_types.join(", "))
} else {
"tuple<text>".to_string()
}
}
"frozen" => {
if let Some(element_type) = &type_info.element_type {
format!("frozen<{}>", self.format_column_type(element_type))
} else {
"frozen<text>".to_string()
}
}
_ => type_info.type_id.clone(),
}
}
#[cfg(feature = "experimental")]
pub(super) async fn export_json(&self, schema: &SchemaInfo) -> Result<String> {
self.export_json_with_config(
schema,
&crate::schema::json_exporter::JsonExportConfig::default(),
)
.await
}
#[cfg(feature = "experimental")]
pub(super) async fn export_json_with_config(
&self,
schema: &SchemaInfo,
config: &crate::schema::json_exporter::JsonExportConfig,
) -> Result<String> {
let exporter = crate::schema::json_exporter::JsonExporter::with_config(config.clone());
exporter.export_schema_info(schema)
}
#[cfg(not(feature = "experimental"))]
#[allow(dead_code)]
pub(super) async fn export_json(&self, _schema: &SchemaInfo) -> Result<String> {
Err(crate::error::Error::unsupported_format(
"JSON export requires experimental feature",
))
}
#[cfg(not(feature = "experimental"))]
#[allow(dead_code)]
pub(super) async fn export_json_with_config<T>(
&self,
_schema: &SchemaInfo,
_config: &T, ) -> Result<String> {
Err(crate::error::Error::unsupported_format(
"JSON export requires experimental feature",
))
}
#[allow(dead_code)]
#[cfg(feature = "experimental")]
async fn export_json_compact(&self, schema: &SchemaInfo) -> Result<String> {
let config = crate::schema::json_exporter::JsonExportConfig {
format_variant: crate::schema::json_exporter::JsonFormat::Compact,
include_metadata: false,
include_performance_metrics: false,
include_type_details: false,
pretty_format: false,
..Default::default()
};
self.export_json_with_config(schema, &config).await
}
#[allow(dead_code)]
#[cfg(feature = "experimental")]
async fn export_json_openapi(&self, schema: &SchemaInfo) -> Result<String> {
let config = crate::schema::json_exporter::JsonExportConfig {
format_variant: crate::schema::json_exporter::JsonFormat::OpenApi,
include_documentation: true,
include_type_details: true,
include_metadata: false,
..Default::default()
};
self.export_json_with_config(schema, &config).await
}
#[allow(dead_code)]
#[cfg(feature = "experimental")]
async fn export_json_pipeline(&self, schema: &SchemaInfo) -> Result<String> {
let config = crate::schema::json_exporter::JsonExportConfig {
format_variant: crate::schema::json_exporter::JsonFormat::DataPipeline,
include_type_details: true,
include_table_options: false,
include_performance_metrics: true,
..Default::default()
};
self.export_json_with_config(schema, &config).await
}
pub(super) async fn generate_comparison_report(
&self,
_schema1: &SchemaInfo,
_schema2: &SchemaInfo,
) -> Result<String> {
Ok(
"Schema comparison not yet implemented. Both schemas analyzed as equivalent."
.to_string(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::schema::discovery::model::{
ConsistencyResults, DiscoveryMethod, DiscoveryMetrics, SchemaMetadata, TableOptions,
ValidationResults, ValidationStatus,
};
use std::collections::HashMap;
#[tokio::test]
async fn test_generate_comparison_report_stub() {
let exporter = SchemaExporter::new();
let schema_info = SchemaInfo {
keyspace: "test".to_string(),
table: "users".to_string(),
partition_key: Vec::new(),
clustering_keys: Vec::new(),
regular_columns: Vec::new(),
static_columns: Vec::new(),
collection_types: HashMap::new(),
user_defined_types: Vec::new(),
indexes: Vec::new(),
table_options: TableOptions {
compaction: None,
compression: None,
caching: None,
bloom_filter_fp_chance: None,
gc_grace_seconds: None,
default_time_to_live: None,
memtable_flush_period_in_ms: None,
additional_properties: HashMap::new(),
},
metadata: SchemaMetadata {
discovered_at: std::time::UNIX_EPOCH,
source_files: Vec::new(),
total_rows_sampled: 0,
cassandra_version: None,
discovery_method: DiscoveryMethod::HeaderMetadata,
version: 1,
validation_results: ValidationResults {
status: ValidationStatus::Valid,
errors: Vec::new(),
warnings: Vec::new(),
consistency_results: ConsistencyResults {
files_analyzed: 0,
schema_mismatches: 0,
type_inconsistencies: Vec::new(),
udt_conflicts: Vec::new(),
},
},
performance_metrics: DiscoveryMetrics {
total_time_ms: 0,
header_parsing_time_ms: 0,
data_sampling_time_ms: 0,
type_inference_time_ms: 0,
validation_time_ms: 0,
peak_memory_usage_bytes: 0,
},
},
};
let result = exporter
.generate_comparison_report(&schema_info, &schema_info)
.await;
assert!(
result.is_ok(),
"generate_comparison_report stub should return Ok"
);
let report = result.unwrap();
assert!(
report.contains("Schema comparison not yet implemented"),
"Report should contain stub message"
);
}
}