use base64::{engine::general_purpose, Engine};
use odbc_api::{ColumnDescription, Cursor, CursorRow, DataType, Nullable};
use serde_json::Value as JsonValue;
use crate::column_info::ColumnBaseInfo;
use crate::decode::decode_auto;
use super::f64_to_json_safe;
type OdbcCellParser = for<'a> fn(row: &mut CursorRow<'a>, col_index: u16) -> anyhow::Result<JsonValue>;
pub struct OdbcRowParse {
pub methods: Vec<OdbcCellParser>,
pub columns: Vec<ColumnBaseInfo>,
}
fn determine_parsing_methods(
column_names: &[String],
column_descriptions: &[ColumnDescription],
) -> OdbcRowParse {
let mut methods = Vec::with_capacity(column_descriptions.len());
let mut columns = Vec::with_capacity(column_descriptions.len());
for (i, desc) in column_descriptions.iter().enumerate() {
let type_str = data_type_to_str(&desc.data_type);
let method: OdbcCellParser = match type_str {
"INTEGER" => parse_integer,
"FLOAT" => parse_float,
"DECIMAL" => parse_decimal,
"BIT" => parse_bit,
"BINARY" => parse_binary,
_ => parse_text,
};
methods.push(method);
columns.push(ColumnBaseInfo {
name: column_names[i].clone(),
r#type: type_str.to_string(),
index: i as u64,
});
}
OdbcRowParse { methods, columns }
}
fn normalize_column_names(raw_names: Vec<String>) -> Vec<String> {
let mut result = Vec::with_capacity(raw_names.len());
let mut seen = std::collections::HashMap::<String, usize>::new();
for (i, name) in raw_names.into_iter().enumerate() {
let base = if name.trim().is_empty() {
format!("col_{}", i + 1)
} else {
name
};
let count = seen.entry(base.clone()).or_insert(0);
*count += 1;
let final_name = if *count == 1 {
base
} else {
format!("{}_{}", base, count)
};
result.push(final_name);
}
result
}
pub fn to_json(
cursor: &mut impl Cursor,
) -> anyhow::Result<(Vec<JsonValue>, Vec<ColumnBaseInfo>)> {
let cols = cursor.num_result_cols()?;
if cols == 0 {
return Ok((vec![], vec![]));
}
let raw_names: Vec<String> = cursor
.column_names()?
.collect::<Result<Vec<_>, _>>()?;
let column_names = normalize_column_names(raw_names);
let column_descriptions: Vec<ColumnDescription> = (1..=cols)
.map(|col_index| {
let mut description = ColumnDescription::default();
cursor.describe_col(col_index as u16, &mut description)?;
Ok(description)
})
.collect::<Result<_, odbc_api::Error>>()?;
let parse = determine_parsing_methods(&column_names, &column_descriptions);
let methods = &parse.methods;
let columns = parse.columns;
let mut data: Vec<JsonValue> = Vec::new();
while let Some(mut row) = cursor.next_row()? {
let mut map = serde_json::Map::with_capacity(cols as usize);
for (i, method) in methods.iter().enumerate() {
let col_index = i as u16 + 1; let value = method(&mut row, col_index)?;
map.insert(columns[i].name.clone(), value);
}
data.push(JsonValue::Object(map));
}
Ok((data, columns))
}
fn data_type_to_str(dt: &DataType) -> &'static str {
match dt {
DataType::Integer | DataType::SmallInt | DataType::TinyInt | DataType::BigInt => "INTEGER",
DataType::Float { .. } | DataType::Double | DataType::Real => "FLOAT",
DataType::Decimal { .. } | DataType::Numeric { .. } => "DECIMAL",
DataType::Bit => "BIT",
DataType::Char { .. }
| DataType::Varchar { .. }
| DataType::LongVarchar { .. }
| DataType::WChar { .. }
| DataType::WVarchar { .. }
| DataType::WLongVarchar { .. } => "TEXT",
DataType::Date => "DATE",
DataType::Time { .. } => "TIME",
DataType::Timestamp { .. } => "TIMESTAMP",
DataType::Binary { .. } | DataType::Varbinary { .. } | DataType::LongVarbinary { .. } => {
"BINARY"
}
_ => "TEXT",
}
}
fn parse_integer(row: &mut CursorRow<'_>, col_index: u16) -> anyhow::Result<JsonValue> {
let mut buf: Vec<u8> = Vec::new();
let not_null = row.get_text(col_index, &mut buf)?;
if !not_null || buf.is_empty() {
return Ok(JsonValue::Null);
}
let s = decode_auto(&buf);
if s.is_empty() {
Ok(JsonValue::Null)
} else if let Ok(n) = s.parse::<i64>() {
Ok(JsonValue::Number(n.into()))
} else {
Ok(JsonValue::String(s))
}
}
fn parse_float(row: &mut CursorRow<'_>, col_index: u16) -> anyhow::Result<JsonValue> {
let mut target = Nullable::<f64>::null();
row.get_data(col_index, &mut target)?;
match target.into_opt() {
Some(f) => Ok(f64_to_json_safe(f)),
None => {
let mut buf: Vec<u8> = Vec::new();
let not_null = row.get_text(col_index, &mut buf)?;
if !not_null || buf.is_empty() {
Ok(JsonValue::Null)
} else {
let s = decode_auto(&buf);
Ok(s.parse::<f64>()
.map(f64_to_json_safe)
.unwrap_or(JsonValue::Null))
}
}
}
}
fn parse_decimal(row: &mut CursorRow<'_>, col_index: u16) -> anyhow::Result<JsonValue> {
let mut buf: Vec<u8> = Vec::new();
let not_null = row.get_text(col_index, &mut buf)?;
if !not_null || buf.is_empty() {
return Ok(JsonValue::Null);
}
let s = decode_auto(&buf);
if s.is_empty() {
Ok(JsonValue::Null)
} else {
Ok(JsonValue::String(s))
}
}
fn parse_bit(row: &mut CursorRow<'_>, col_index: u16) -> anyhow::Result<JsonValue> {
let mut buf: Vec<u8> = Vec::new();
let not_null = row.get_text(col_index, &mut buf)?;
if !not_null || buf.is_empty() {
return Ok(JsonValue::Null);
}
let s = decode_auto(&buf);
Ok(match s.as_str() {
"1" | "-1" => JsonValue::Bool(true),
"0" => JsonValue::Bool(false),
other if !other.is_empty() => JsonValue::String(other.to_owned()),
_ => JsonValue::Null,
})
}
fn parse_binary(row: &mut CursorRow<'_>, col_index: u16) -> anyhow::Result<JsonValue> {
let mut buf: Vec<u8> = Vec::new();
let not_null = row.get_binary(col_index, &mut buf)?;
if !not_null || buf.is_empty() {
Ok(JsonValue::Null)
} else {
Ok(JsonValue::String(general_purpose::STANDARD.encode(&buf)))
}
}
fn parse_text(row: &mut CursorRow<'_>, col_index: u16) -> anyhow::Result<JsonValue> {
let mut buf: Vec<u8> = Vec::new();
let not_null = row.get_text(col_index, &mut buf)?;
if !not_null || buf.is_empty() {
return Ok(JsonValue::Null);
}
let s = decode_auto(&buf);
if s.is_empty() {
Ok(JsonValue::Null)
} else {
Ok(JsonValue::String(s))
}
}