use std::fs::File;
use std::io::Write;
use crate::layout::{LAYOUT_VERSION_V1, SuperblockV1};
use crate::utils::wire;
use super::dataset::RawArrayWrite;
use super::execution::FileExecutionSettingsV1;
use super::index::{self, ChunkIndexEntryV1};
use super::tile;
use super::{CHUNK_PAYLOAD_CODEC_V1, CatalogError, ChunkGridPlan, MAX_NDIM, usize_from_u64};
pub(crate) fn wire_dataset_id(dataset_id: usize) -> Result<u64, CatalogError> {
u64::try_from(dataset_id).map_err(|_| CatalogError::TooLargeForPlatform {
field: "dataset_id",
value: u64::MAX,
})
}
pub(crate) fn u64_from_usize(field: &'static str, v: usize) -> Result<u64, CatalogError> {
u64::try_from(v).map_err(|_| CatalogError::TooLargeForPlatform {
field,
value: u64::MAX,
})
}
pub(crate) fn sum_chunk_counts<'a>(
shapes: impl IntoIterator<Item = (&'a [u64], &'a [u64])>,
) -> Result<u64, CatalogError> {
let mut total = 0u64;
for (shape, chunk_shape) in shapes {
let counts = tile::chunk_grid_counts(shape, chunk_shape);
total = total
.checked_add(tile::total_chunk_count(&counts)?)
.ok_or(CatalogError::InvalidWriteSpec("chunk index size overflow"))?;
}
Ok(total)
}
pub(crate) fn chunk_index_layout(
dataset_blob_len: u64,
n_chunks_total: u64,
) -> Result<(u64, u64, u64), CatalogError> {
let index_base = wire::align8_u64(40u64 + dataset_blob_len);
let index_header_len = index::CHUNK_INDEX_HEADER_V1.header_len as u64;
let entries_len_u64 = n_chunks_total
.checked_mul(ChunkIndexEntryV1::WIRE_LEN as u64)
.ok_or(CatalogError::InvalidWriteSpec("chunk index size overflow"))?;
let chunk_index_length = index_header_len
.checked_add(entries_len_u64)
.ok_or(CatalogError::InvalidWriteSpec("chunk index size overflow"))?;
let payload_start = index_base
.checked_add(chunk_index_length)
.ok_or(CatalogError::InvalidWriteSpec("payload start overflow"))?;
Ok((index_base, chunk_index_length, payload_start))
}
pub(crate) fn layout_superblock(
dataset_count: usize,
index_base: u64,
chunk_index_length: u64,
) -> Result<SuperblockV1, CatalogError> {
Ok(SuperblockV1 {
layout_version: LAYOUT_VERSION_V1,
dataset_count: u32::try_from(dataset_count).map_err(|_| {
CatalogError::TooLargeForPlatform {
field: "dataset_count",
value: dataset_count as u64,
}
})?,
flags: 0,
chunk_index_offset: index_base,
chunk_index_length,
})
}
pub(crate) fn build_chunk_index_bytes(
entries: &[ChunkIndexEntryV1],
n_chunks_total: u64,
chunk_index_length: u64,
file_execution: FileExecutionSettingsV1,
) -> Result<Vec<u8>, CatalogError> {
let mut index_bytes = Vec::with_capacity(usize_from_u64(
"chunk_index_byte_length",
chunk_index_length,
)?);
index::write_chunk_index_header(&mut index_bytes, n_chunks_total, file_execution);
for e in entries {
index_bytes.extend_from_slice(&e.to_bytes());
}
debug_assert_eq!(index_bytes.len() as u64, chunk_index_length);
Ok(index_bytes)
}
pub(crate) fn write_file_preamble(
f: &mut File,
sb: &SuperblockV1,
blob: &[u8],
index_base: u64,
index_bytes: &[u8],
) -> Result<(), CatalogError> {
f.write_all(&sb.to_bytes())?;
f.write_all(&(blob.len() as u64).to_le_bytes())?;
f.write_all(blob)?;
let after_blob = 40usize + blob.len();
let pad = usize_from_u64("chunk_index_base", index_base)?.saturating_sub(after_blob);
if pad > 0 {
f.write_all(&vec![0u8; pad])?;
}
f.write_all(index_bytes)?;
Ok(())
}
pub(crate) struct EncodedChunkPush {
pub dataset_id: u64,
pub chunk_index: [u64; MAX_NDIM],
pub raw_byte_len: u64,
pub chunk_codec: u32,
pub stored: Vec<u8>,
}
impl EncodedChunkPush {
pub(crate) fn push(
self,
entries: &mut Vec<ChunkIndexEntryV1>,
payloads: &mut Vec<Vec<u8>>,
cursor: u64,
) -> Result<u64, CatalogError> {
let stored_len = u64_from_usize("chunk_stored_len", self.stored.len())?;
entries.push(ChunkIndexEntryV1 {
dataset_id: self.dataset_id,
chunk_index: self.chunk_index,
payload_offset: cursor,
raw_byte_len: self.raw_byte_len,
stored_byte_len: stored_len,
codec: self.chunk_codec,
});
let next = cursor
.checked_add(stored_len)
.ok_or(CatalogError::InvalidWriteSpec("payload cursor overflow"))?;
payloads.push(self.stored);
Ok(next)
}
}
pub(crate) fn push_raw_tiles_from_tensor(
spec: &RawArrayWrite<'_>,
grid: &ChunkGridPlan,
dataset_id: u64,
mut cursor: u64,
entries: &mut Vec<ChunkIndexEntryV1>,
payloads: &mut Vec<Vec<u8>>,
) -> Result<u64, CatalogError> {
for k in 0..grid.n_chunks {
let coord = tile::chunk_coord_from_linear(k, &grid.counts, grid.ndim);
let tile_bytes = tile::extract_tile_row_major(
spec.data,
spec.shape,
spec.chunk_shape,
&coord[..grid.ndim],
grid.ndim,
grid.elem_size,
)?;
let raw_len = u64_from_usize("chunk_payload_len", tile_bytes.len())?;
let stored_vec =
CHUNK_PAYLOAD_CODEC_V1.encode_tile_payload(spec.chunk_codec, tile_bytes)?;
cursor = EncodedChunkPush {
dataset_id,
chunk_index: ChunkIndexEntryV1::padded_chunk_index(&coord[..grid.ndim], grid.ndim),
raw_byte_len: raw_len,
chunk_codec: spec.chunk_codec,
stored: stored_vec,
}
.push(entries, payloads, cursor)?;
}
Ok(cursor)
}