use std::fs::File;
use std::sync::Arc;
use std::io::{self, Read, Seek, SeekFrom};
use std::convert::TryInto;
use arrow::ipc::reader::FileReader;
use arrow::array::RecordBatch;
use arrow::error::ArrowError;
use arrow::array::{
Array,
UInt8Array, UInt16Array, UInt32Array, UInt64Array,
Int64Array
};
use serde_json::Value;
use zstd::stream::decode_all;
use arrow::datatypes::DataType;
use num_bigint::BigInt;
use crate::utils::{hex_str_to_bigint, string_at_dictionary_column};
extern crate hex;
extern crate rlp;
use rlp::Rlp;
extern crate base64;
use serde::{Serialize, Deserialize};
#[derive(Clone, Debug, PartialEq)]
pub enum ArrowBatchCompression {
Uncompressed = 0,
Zstd = 1,
}
#[derive(Debug, Clone)]
pub struct ArrowBatchGlobalHeader {
pub version_constant: String,
}
#[derive(Clone, Debug)]
pub struct ArrowBatchHeader {
pub header_constant: String,
pub batch_byte_size: usize,
pub compression: ArrowBatchCompression,
pub start_ordinal: u64,
pub last_ordinal: u64
}
#[derive(Clone, Debug)]
pub struct ArrowBatchMetadata {
pub header: ArrowBatchHeader,
pub start: usize,
pub end: usize
}
#[derive(Clone, Debug)]
pub struct ArrowBatchFileMetadata {
pub size: usize,
pub header: ArrowBatchGlobalHeader,
pub batches: Vec<ArrowBatchMetadata>,
}
pub const ARROW_BATCH_VERSION_CONSTANT: &'static str = "ARROW-BATCH1";
pub const GLOBAL_HEADER_SIZE: usize = ARROW_BATCH_VERSION_CONSTANT.len();
pub const ARROW_BATCH_HEADER_CONSTANT: &'static str = "ARROW-BATCH-TABLE";
pub const BATCH_HEADER_SIZE: usize = ARROW_BATCH_HEADER_CONSTANT.len() + 8 + 1 + 8 + 8;
pub fn new_global_header() -> Vec<u8> {
ARROW_BATCH_VERSION_CONSTANT.as_bytes().to_vec()
}
pub fn new_batch_header(byte_size: u64, compression: ArrowBatchCompression) -> Vec<u8> {
let mut buffer = Vec::new();
buffer.extend_from_slice(ARROW_BATCH_HEADER_CONSTANT.as_bytes());
buffer.extend_from_slice(&byte_size.to_le_bytes());
buffer.push(compression as u8);
buffer
}
pub fn read_global_header(buffer: &[u8]) -> ArrowBatchGlobalHeader {
let version_constant = std::str::from_utf8(&buffer[0..GLOBAL_HEADER_SIZE]).unwrap_or_default().to_string();
ArrowBatchGlobalHeader { version_constant }
}
pub fn read_batch_header(buffer: &[u8]) -> ArrowBatchHeader {
let header_constant = std::str::from_utf8(&buffer[0..ARROW_BATCH_HEADER_CONSTANT.len()]).unwrap_or_default().to_string();
let size_start = ARROW_BATCH_HEADER_CONSTANT.len();
let batch_byte_size = usize::from_le_bytes(buffer[size_start..size_start+8].try_into().unwrap());
let compression = match buffer[size_start + 8] {
0 => ArrowBatchCompression::Uncompressed,
1 => ArrowBatchCompression::Zstd,
_ => panic!("Invalid compression type"),
};
let ord_info_start = size_start + 8 + 1;
let start_ordinal = u64::from_le_bytes(buffer[ord_info_start..ord_info_start+8].try_into().unwrap());
let last_ordinal = u64::from_le_bytes(buffer[ord_info_start+8..ord_info_start+16].try_into().unwrap());
ArrowBatchHeader { header_constant, batch_byte_size, compression, start_ordinal, last_ordinal }
}
pub fn read_metadata(file_path: &str) -> io::Result<ArrowBatchFileMetadata> {
let mut file = File::open(file_path)?;
let mut buffer = vec![0; GLOBAL_HEADER_SIZE];
file.read_exact(&mut buffer)?;
let global_header = read_global_header(&buffer);
let mut batches = Vec::new();
let mut offset = GLOBAL_HEADER_SIZE;
while offset < file.metadata()?.len() as usize {
let mut buffer = vec![0; BATCH_HEADER_SIZE];
file.seek(SeekFrom::Start(offset as u64))?;
file.read_exact(&mut buffer)?;
let header = read_batch_header(&buffer);
let start = offset + BATCH_HEADER_SIZE;
let end = start + header.batch_byte_size - 1;
batches.push(ArrowBatchMetadata{header, start, end});
offset = end + 1;
}
Ok(ArrowBatchFileMetadata {
size: offset,
header: global_header,
batches,
})
}
pub fn read_batch(file_path: &str, metadata: &ArrowBatchFileMetadata, batch_index: usize) -> Result<RecordBatch, ArrowError> {
let batch_meta = &metadata.batches[batch_index];
let mut file = File::open(file_path)?;
let mut buffer = vec![0u8; (batch_meta.end - batch_meta.start + 1) as usize];
file.seek(io::SeekFrom::Start(batch_meta.start as u64))?;
file.read_exact(&mut buffer)?;
let decompressed_data = match batch_meta.header.compression {
ArrowBatchCompression::Uncompressed => buffer,
ArrowBatchCompression::Zstd => decode_all(&buffer[..])?,
};
let cursor = io::Cursor::new(decompressed_data);
let mut reader = FileReader::try_new(cursor, None).expect("Failed to read Arrow file");
reader.next().unwrap()
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
pub struct ArrowTableMapping {
pub name: String,
#[serde(rename = "type")]
pub data_type: String,
pub optional: Option<bool>,
pub length: Option<usize>,
pub array: Option<bool>,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum ArrowBatchTypes {
U8(u8),
U16(u16),
U32(u32),
U64(u64),
UVar(BigInt),
I64(i64),
Bytes(Vec<u8>),
Str(String),
Checksum160(String),
Checksum256(String),
Struct(Value),
U16Array(Vec<u16>),
U32Array(Vec<u32>),
U64Array(Vec<u64>),
BytesArray(Vec<Vec<u8>>),
StrArray(Vec<String>),
StructArray(Vec<Value>)
}
fn decode_row_value(schema_type: &DataType, column: &Arc<dyn Array>, field: &ArrowTableMapping, row_index: usize) -> ArrowBatchTypes {
let decoded: ArrowBatchTypes;
match field.data_type.as_str() {
"u8" => {
assert!(schema_type == &DataType::UInt8);
let array = column.as_any().downcast_ref::<UInt8Array>().unwrap();
decoded = ArrowBatchTypes::U8(array.value(row_index));
},
"u16" => {
assert!(schema_type == &DataType::UInt16);
let array = column.as_any().downcast_ref::<UInt16Array>().unwrap();
decoded = ArrowBatchTypes::U16(array.value(row_index));
},
"u32" => {
assert!(schema_type == &DataType::UInt32, "Expected UInt32, found {:?} for column {}", schema_type, field.name);
let array = column.as_any().downcast_ref::<UInt32Array>().unwrap();
decoded = ArrowBatchTypes::U32(array.value(row_index));
},
"u64" => {
assert!(schema_type == &DataType::UInt64);
let array = column.as_any().downcast_ref::<UInt64Array>().unwrap();
decoded = ArrowBatchTypes::U64(array.value(row_index));
},
"uintvar" => {
let base64_val = string_at_dictionary_column(column, schema_type, row_index);
let bytes_val = base64::decode(base64_val).unwrap();
let hex_val = hex::encode(bytes_val);
decoded = ArrowBatchTypes::UVar(hex_str_to_bigint(&hex_val));
},
"i64" => {
assert!(schema_type == &DataType::Int64);
let array = column.as_any().downcast_ref::<Int64Array>().unwrap();
decoded = ArrowBatchTypes::I64(array.value(row_index));
},
"bytes" => {
let base64_val = string_at_dictionary_column(column, schema_type, row_index);
let val = base64::decode(base64_val).unwrap();
decoded = ArrowBatchTypes::Bytes(val);
},
"string" => {
let val = string_at_dictionary_column(column, schema_type, row_index);
decoded = ArrowBatchTypes::Str(val.to_string());
},
"checksum160" => {
let base64_val = string_at_dictionary_column(column, schema_type, row_index);
let bytes_val = base64::decode(base64_val).unwrap();
assert!(bytes_val.len() == 20);
let hex_val = hex::encode(bytes_val);
decoded = ArrowBatchTypes::Checksum160(hex_val);
},
"checksum256" => {
let base64_val = string_at_dictionary_column(column, schema_type, row_index);
let bytes_val = base64::decode(base64_val).unwrap();
let hex_val = hex::encode(bytes_val);
decoded = ArrowBatchTypes::Checksum256(hex_val);
},
"struct" => {
let base64_val = string_at_dictionary_column(column, schema_type, row_index);
let struct_val: Value = serde_json::from_str(base64_val).unwrap();
decoded = ArrowBatchTypes::Struct(struct_val);
}
_ => {
panic!("Unsupported field {}", field.data_type);
}
}
decoded
}
fn decode_rlp_array<T, F>(bytes_array: Vec<u8>, decoder: F) -> Vec<T>
where
F: Fn(&Rlp, usize) -> Result<T, rlp::DecoderError>,
{
let rlp = Rlp::new(&bytes_array);
assert!(rlp.is_list());
(0..rlp.item_count().unwrap())
.map(|i| decoder(&rlp, i).unwrap())
.collect()
}
pub fn read_row(record_batch: &RecordBatch, mapping: &Vec<ArrowTableMapping>, row_index: usize) -> Result<Vec<ArrowBatchTypes>, ArrowError> {
let schema = record_batch.schema();
let mut row = Vec::new();
for field in mapping.iter() {
let column = record_batch.column_by_name(field.name.as_str()).unwrap();
let schema_field = schema.field_with_name(field.name.as_str()).unwrap();
let schema_type = schema_field.data_type();
if let Some(true) = field.array {
let base64_array = string_at_dictionary_column(column, schema_type, row_index);
let bytes_array = base64::decode(base64_array).unwrap();
let decoded_array = match field.data_type.as_str() {
"u8" => ArrowBatchTypes::Bytes(
decode_rlp_array(bytes_array, |rlp, i| rlp.val_at(i))),
"u16" => ArrowBatchTypes::U16Array(
decode_rlp_array(bytes_array, |rlp, i| rlp.val_at(i))),
"u32" => ArrowBatchTypes::U32Array(
decode_rlp_array(bytes_array, |rlp, i| rlp.val_at(i))),
"u64" => ArrowBatchTypes::U64Array(
decode_rlp_array(bytes_array, |rlp, i| rlp.val_at(i))),
"bytes" => ArrowBatchTypes::BytesArray(
decode_rlp_array(bytes_array, |rlp, i| {
let b64_bytes = rlp.val_at::<Vec<u8>>(i).unwrap_or_default();
let bytes = base64::decode(String::from_utf8(b64_bytes).unwrap()).unwrap();
Ok(bytes)
})),
"string" => ArrowBatchTypes::StrArray(
decode_rlp_array(bytes_array, |rlp, i| {
let bytes = rlp.val_at::<Vec<u8>>(i).unwrap_or_default();
Ok(String::from_utf8(bytes).unwrap_or_default())
})),
"struct" => ArrowBatchTypes::StructArray(
decode_rlp_array(bytes_array, |rlp, i| {
let b64_bytes = rlp.val_at::<Vec<u8>>(i).unwrap_or_default();
let struct_val: Value = serde_json::from_str(
String::from_utf8(b64_bytes).unwrap().as_str()).unwrap();
Ok(struct_val)
})),
_ => return Err(ArrowError::ParseError("Unsupported field type".to_string())),
};
row.push(decoded_array);
} else {
row.push(decode_row_value(schema_type, column, field, row_index));
}
}
Ok(row)
}