use core::fmt::Write;
use alloc::{format, string::String};
use indoc::writedoc;
use crate::{
stream::{FmtWriter, StreamError, Writes},
types::{Coda, Text, Type, Unspecified},
};
pub fn generate_types(coda: &Coda, stream: &mut impl Writes) -> Result<(), StreamError> {
let mut writer = FmtWriter::from(stream);
for (ordinal, typing) in [Unspecified::DATA_TYPE]
.iter()
.chain(coda.iter())
.enumerate()
{
let data_type_name = typing.name.trim();
let mut field_string = String::new();
for field in typing.iter() {
let field_name = &field.name;
let duckdb_type = duckdb_type(&field.typing);
field_string.push_str(&format!(" \"{field_name}\" {duckdb_type},\n"));
}
if field_string.is_empty() {
let _ = writedoc!(
writer,
r#"
-- Data {ordinal}.
-- Data with no specified fields is stored as a blob of Coda-encoded data.
CREATE TYPE {data_type_name} AS BLOB;"#
);
let _ = writedoc!(writer, "\n");
} else {
field_string.pop(); field_string.pop();
let _ = writedoc!(
writer,
r#"
-- Data {ordinal}.
CREATE TYPE {data_type_name} AS STRUCT (
"#
);
let _ = writedoc!(writer, "{field_string}\n);\n");
}
}
Ok(())
}
fn duckdb_type(typing: &Type) -> Text {
match typing {
Type::Unspecified => Text::Static("BLOB"),
Type::U8 => Text::Static("UTINYINT"),
Type::U16 => Text::Static("USMALLINT"),
Type::U32 => Text::Static("UINTEGER"),
Type::U64 => Text::Static("UBIGINT"),
Type::I8 => Text::Static("TINYINT"),
Type::I16 => Text::Static("SMALLINT"),
Type::I32 => Text::Static("INTEGER"),
Type::I64 => Text::Static("BIGINT"),
Type::F32 => Text::Static("FLOAT"),
Type::F64 => Text::Static("DOUBLE"),
Type::Bool => Text::Static("BOOLEAN"),
Type::Text => Text::Static("VARCHAR"),
Type::Data(typing) => typing.name.clone(),
Type::List(typing) => {
let inner = duckdb_type(typing);
format!("{}[]", inner).into()
}
Type::Map(typing) => {
let key_type = duckdb_type(&typing.0);
let value_type = duckdb_type(&typing.1);
format!("MAP({}, {})", key_type, value_type).into()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parse::{parse, tests::TEST_CODA_MARKDOWN};
#[test]
fn smoke() {
let coda = parse(TEST_CODA_MARKDOWN).unwrap();
let mut sql = Vec::new();
generate_types(&coda, &mut sql).unwrap();
let sql = String::from_utf8_lossy(&sql);
assert_eq!(
r#"
-- Data 0.
-- Data with no specified fields is stored as a blob of Coda-encoded data.
CREATE TYPE Unspecified AS BLOB;
-- Data 1.
CREATE TYPE MyNestedDataType AS STRUCT (
"floaty_field" FLOAT,
"listy_field" VARCHAR[]
);
-- Data 2.
CREATE TYPE MyDataType AS STRUCT (
"integral_field" INTEGER,
"textual_field" VARCHAR,
"nested_field" MyNestedDataType,
"optional_field" UBIGINT,
"3d_field" INTEGER[][][],
"map_field" MAP(VARCHAR, INTEGER),
"unspecified_field" BLOB
);"#
.trim(),
sql.trim()
);
}
}