use std::{
cmp::max,
fs::{File, OpenOptions},
io::BufRead,
marker::PhantomData,
path::Path,
};
use prost::{
Message,
bytes::{Buf, BytesMut},
};
use typedb_protocol::migration::Item as MigrationItemProto;
use crate::{Error, Result, error::MigrationError};
#[derive(Debug)]
pub(crate) enum DatabaseExportAnswer {
Schema(String),
Items(Vec<MigrationItemProto>),
Done,
}
pub struct ProtoMessageIterator<M: Message + Default, R: BufRead> {
reader: R,
buffer: BytesMut,
_phantom_data: PhantomData<M>,
}
impl<M: Message + Default, R: BufRead> ProtoMessageIterator<M, R> {
const BUF_CAPACITY: usize = 8 * 1024;
const MAX_LENGTH_DELIMITER_LEN: usize = 10;
pub fn new(reader: R) -> Self {
Self { reader, buffer: BytesMut::with_capacity(Self::BUF_CAPACITY), _phantom_data: PhantomData }
}
fn read_more(&mut self, bytes_to_read: usize) -> std::io::Result<usize> {
if self.buffer.capacity() - self.buffer.len() < bytes_to_read {
self.buffer.reserve(max(bytes_to_read, Self::BUF_CAPACITY));
}
let mut addition = vec![0u8; max(bytes_to_read, 1)];
let bytes_read = self.reader.read(&mut addition)?;
self.buffer.extend_from_slice(&addition[..bytes_read]);
Ok(bytes_read)
}
fn decode_next_len(&mut self) -> Result<Option<(usize /*len*/, usize /*consumed*/)>> {
loop {
let mut cursor: &[u8] = &self.buffer;
match prost::decode_length_delimiter(&mut cursor) {
Ok(len) => {
let consumed = self.buffer.len() - cursor.len();
return Ok(Some((len, consumed)));
}
Err(_) => {
if self.buffer.len() >= Self::MAX_LENGTH_DELIMITER_LEN {
return Err(Error::Migration(MigrationError::CannotDecodeImportedConceptLength));
}
match self.read_more(Self::MAX_LENGTH_DELIMITER_LEN - self.buffer.len()) {
Ok(0) => {
return if self.buffer.is_empty() {
Ok(None)
} else {
Err(Error::Migration(MigrationError::CannotDecodeImportedConceptLength))
};
}
Err(_) => return Err(Error::Migration(MigrationError::CannotDecodeImportedConceptLength)),
Ok(_) => continue,
}
}
}
}
}
}
impl<M: Message + Default, R: BufRead> Iterator for ProtoMessageIterator<M, R> {
type Item = Result<M>;
fn next(&mut self) -> Option<Self::Item> {
let (message_len, consumed) = match self.decode_next_len() {
Ok(Some(res)) => res,
Ok(None) => return None,
Err(err) => return Some(Err(err)),
};
let required = consumed + message_len;
while self.buffer.len() < required {
let to_read = required - self.buffer.len();
match self.read_more(max(to_read, Self::BUF_CAPACITY)) {
Ok(0) | Err(_) => return Some(Err(Error::Migration(MigrationError::CannotDecodeImportedConcept))),
Ok(_) => {}
}
}
self.buffer.advance(consumed);
let message_bytes = self.buffer.split_to(message_len).freeze();
Some(M::decode(message_bytes).map_err(|_| Error::Migration(MigrationError::CannotDecodeImportedConcept)))
}
}
pub(crate) fn try_create_export_file(path: impl AsRef<Path>) -> Result<File> {
try_open_export_file(path, true)
}
pub(crate) fn try_open_existing_export_file(path: impl AsRef<Path>) -> Result<File> {
try_open_export_file(path, false)
}
fn try_open_export_file(path: impl AsRef<Path>, is_new: bool) -> Result<File> {
OpenOptions::new().write(true).create_new(is_new).open(path.as_ref()).map_err(|source| {
Error::Migration(MigrationError::CannotCreateExportFile {
path: path.as_ref().to_str().unwrap_or("").to_string(),
reason: source.to_string(),
})
})
}
pub(crate) fn try_open_import_file(path: impl AsRef<Path>) -> Result<File> {
File::open(path.as_ref()).map_err(|source| {
Error::Migration(MigrationError::CannotOpenImportFile {
path: path.as_ref().to_str().unwrap_or("").to_string(),
reason: source.to_string(),
})
})
}