use std::collections::HashMap;
use std::sync::Arc;
use arrow_array::builder::{Date32Builder, Float64Builder, StringBuilder};
use arrow_array::ArrayRef;
use arrow_array::RecordBatch;
use arrow_schema::{DataType, Field, Schema};
use tokio::sync::mpsc;
use xbbg_log::trace;
use super::value_utils::{top_level_response_error, ResponseMetadata};
use xbbg_core::{BlpError, Message};
pub struct HistDataStreamState {
field_names: Vec<String>,
stream: mpsc::Sender<Result<RecordBatch, BlpError>>,
schema: Option<Arc<Schema>>,
}
impl HistDataStreamState {
pub fn new(fields: Vec<String>, stream: mpsc::Sender<Result<RecordBatch, BlpError>>) -> Self {
Self::with_types(fields, None, stream)
}
pub fn with_types(
fields: Vec<String>,
_field_types: Option<HashMap<String, String>>,
stream: mpsc::Sender<Result<RecordBatch, BlpError>>,
) -> Self {
Self {
field_names: fields,
stream,
schema: None,
}
}
pub fn on_partial(&mut self, msg: &Message) {
if let Some(batch) = self.process_message(msg) {
let _ = self.stream.try_send(Ok(batch));
}
}
pub fn finish(mut self, msg: &Message) {
if let Some(error) = top_level_response_error(msg, "//blp/refdata", "HistoricalDataRequest")
{
let _ = self.stream.try_send(Err(error));
return;
}
if let Some(batch) = self.process_message(msg) {
let _ = self.stream.try_send(Ok(batch));
}
}
pub fn fail(self, error: BlpError) {
let _ = self.stream.try_send(Err(error));
}
fn process_message(&mut self, msg: &Message) -> Option<RecordBatch> {
let root = msg.elements();
let security_data = root.get_by_str("securityData")?;
let ticker = security_data
.get_by_str("security")
.and_then(|e| e.get_str(0))
.unwrap_or("");
let mut response_meta = ResponseMetadata::default();
let has_eids = if let Some(eids) = security_data.get_by_str("eidData") {
response_meta.record_eid_data(ticker, &eids);
true
} else {
false
};
if security_data.get_by_str("securityError").is_some() && !has_eids {
trace!(ticker = ticker, "Security has error, skipping");
return None;
}
let field_data = security_data.get_by_str("fieldData");
let n = field_data.as_ref().map_or(0, |data| data.len());
if n == 0 && !has_eids {
return None;
}
let mut ticker_builder = StringBuilder::with_capacity(n, ticker.len().saturating_mul(n));
let mut date_builder = Date32Builder::with_capacity(n);
let mut field_builders: Vec<Float64Builder> = self
.field_names
.iter()
.map(|_| Float64Builder::with_capacity(n))
.collect();
for i in 0..n {
let field_data = field_data
.as_ref()
.expect("nonzero length requires fieldData");
let Some(row) = field_data.get_element(i) else {
continue;
};
ticker_builder.append_value(ticker);
if let Some(date_elem) = row.get_by_str("date") {
if let Some(days) = date_elem.get_date32(0) {
date_builder.append_value(days);
} else {
date_builder.append_null();
}
} else {
date_builder.append_null();
}
for (j, field_name) in self.field_names.iter().enumerate() {
if let Some(field_elem) = row.get_by_str(field_name) {
if let Some(value) = field_elem.get_value(0) {
match value.as_f64() {
Some(f) => field_builders[j].append_value(f),
None => field_builders[j].append_null(),
}
} else {
field_builders[j].append_null();
}
} else {
field_builders[j].append_null();
}
}
}
let schema = self.schema.get_or_insert_with(|| {
let mut fields = vec![
Field::new("ticker", DataType::Utf8, false),
Field::new("date", DataType::Date32, true),
];
for name in &self.field_names {
fields.push(Field::new(name.as_str(), DataType::Float64, true));
}
Arc::new(Schema::new(fields))
});
let ticker_array = ticker_builder.finish();
let date_array = date_builder.finish();
let field_arrays: Vec<ArrayRef> = field_builders
.iter_mut()
.map(|b| Arc::new(b.finish()) as ArrayRef)
.collect();
let mut columns: Vec<ArrayRef> = Vec::with_capacity(2 + field_arrays.len());
columns.push(Arc::new(ticker_array));
columns.push(Arc::new(date_array));
columns.extend(field_arrays);
let batch = RecordBatch::try_new(schema.clone(), columns).ok()?;
Some(response_meta.attach(batch))
}
}