///|
/// 嵌套写入(shredded 输入,读取侧 read_bytes_shredded 的镜像)。
/// 调用方按 schema 树把记录拆成叶级槽位流(值 + def/rep levels),
/// 并提供与前序遍历一致的 WriteNode 树;本文件负责编码:
/// 数据页 = [rep levels][def levels][PLAIN 密集值](v1 布局,均未压缩),
/// def==max_def 的槽与 values 中的密集值一一对应。
///|
pub let converted_type_list = 3
///|
pub let converted_type_map = 1
///|
pub(all) struct WriteLeaf {
/// 根到叶的路径(与前序遍历顺序一致)
path : Array[String]
column_type : ColumnType
max_definition_level : Int32
max_repetition_level : Int32
/// 密集非空值(def==max_def 的槽按顺序取用)
values : Array[Value]
def_levels : Array[Int32]
rep_levels : Array[Int32]
}
///|
pub(all) struct WriteNode {
name : String
repetition : Repetition
/// None = group(有 children);Some = 叶
column_type : ColumnType?
/// converted_type 注解:UTF8(0) / LIST(3) / MAP(4) / DATE(6) /
/// TIMESTAMP_MICROS(10);None = 按叶物理类型推导或省略
converted_type : Int32?
children : Array[WriteNode]
}
///|
fn encode_data_page_shredded(leaf : WriteLeaf) -> Bytes raise ParquetError {
let count = leaf.def_levels.length()
if leaf.rep_levels.length() != count || leaf.max_definition_level >= 0 {
if leaf.rep_levels.length() != count {
invalid_data("rep level count does not match def level count")
}
}
let rep_bytes = if leaf.max_repetition_level > 0 {
encode_rle_levels(leaf.rep_levels, bit_width(leaf.max_repetition_level), true)
} else {
Default::default()
}
let def_bytes = if leaf.max_definition_level > 0 {
encode_rle_levels(leaf.def_levels, bit_width(leaf.max_definition_level), true)
} else {
Default::default()
}
let value_bytes = encode_plain_non_null_values(leaf.values, leaf.column_type)
let payload = rep_bytes + def_bytes + value_bytes
let writer = CompactWriter::new()
writer.begin_struct()
writer.write_i32_field(1, page_type_data_page)
writer.write_i32_field(2, payload.length())
writer.write_i32_field(3, payload.length())
writer.write_field_header(5, thrift_wire_struct)
writer.begin_struct()
writer.write_i32_field(1, count)
writer.write_i32_field(2, encoding_plain)
writer.write_i32_field(3, encoding_rle)
writer.write_i32_field(4, encoding_rle)
writer.end_struct()
writer.end_struct()
writer.write_bytes() + payload
}
///|
fn writer_type_code_or_invalid(column_type : ColumnType) -> Int32 raise ParquetError {
match column_type {
Int32 | Date => parquet_type_int32
Int64 | TimestampMicros64 => parquet_type_int64
String | Binary => parquet_type_byte_array
Boolean => parquet_type_boolean
Float => parquet_type_float
Double => parquet_type_double
_ => {
unsupported("This column type is not supported by the writer yet")
0
}
}
}
///|
fn count_schema_nodes(nodes : Array[WriteNode]) -> Int {
let mut n = 0
for node in nodes {
n = n + 1
n = n + count_schema_nodes(node.children)
}
n
}
///|
/// 前序遍历写出 SchemaElement 列表(先本节点,再递归子节点)
fn write_schema_elements(
footer : CompactWriter,
nodes : Array[WriteNode],
) -> Unit raise ParquetError {
for node in nodes {
let element = CompactWriter::new()
element.begin_struct()
match node.column_type {
Some(column_type) => {
element.write_i32_field(1, writer_type_code_or_invalid(column_type))
element.write_i32_field(
3,
match node.repetition {
Required => repetition_required
Optional => repetition_optional
Repeated => repetition_repeated
},
)
element.write_string_field(4, node.name)
match node.converted_type {
Some(ct) => element.write_i32_field(6, ct)
None =>
match column_type {
String => element.write_i32_field(6, converted_type_utf8)
Date => element.write_i32_field(6, converted_type_date)
TimestampMicros64 =>
element.write_i32_field(6, converted_type_timestamp_micros)
_ => ()
}
}
}
None => {
element.write_i32_field(
3,
match node.repetition {
Required => repetition_required
Optional => repetition_optional
Repeated => repetition_repeated
},
)
element.write_string_field(4, node.name)
match node.converted_type {
Some(ct) => element.write_i32_field(6, ct)
None => ()
}
element.write_i32_field(5, node.children.length())
}
}
element.end_struct()
footer.sink.push_bytes(element.write_bytes()[:])
write_schema_elements(footer, node.children)
}
}
///|
fn collect_write_leaves(
nodes : Array[WriteNode],
out : Array[WriteLeaf],
) -> Unit raise ParquetError {
for node in nodes {
match node.column_type {
Some(_) =>
unsupported("write_bytes_shredded requires leaves at the top array")
None => collect_write_leaves(node.children, out)
}
}
}
///|
/// 嵌套写入:nodes 为根的子节点(前序遍历顺序决定列块顺序),
/// leaves 与该前序遍历中的叶顺序一一对应。
pub fn write_bytes_shredded(
nodes : Array[WriteNode],
leaves : Array[WriteLeaf],
num_rows : Int32,
) -> Bytes raise ParquetError {
if nodes.is_empty() {
invalid_data("Cannot write parquet file without columns")
}
let ordered : Array[WriteLeaf] = []
// 由 schema 树推导前序叶序列,与调用方提供的 leaves 逐位核对
fn walk(node : WriteNode, path : Array[String]) -> Unit raise ParquetError {
let next_path = path + [node.name]
match node.column_type {
Some(column_type) => {
if ordered.length() >= leaves.length() {
invalid_data("Fewer leaf streams than schema leaves")
}
let leaf = leaves[ordered.length()]
let _ = column_type
if leaf.path != next_path {
invalid_data(
"Leaf stream order does not match schema tree (expected '\{path_text_shredded(next_path)}')",
)
}
if leaf.def_levels.length() == 0 && num_rows > 0 {
invalid_data("Leaf stream is empty for a non-empty row count")
}
ordered.push(leaf)
}
None =>
for child in node.children {
walk(child, next_path)
}
}
}
for node in nodes {
walk(node, [])
}
if ordered.length() != leaves.length() {
invalid_data("More leaf streams than schema leaves")
}
let body = ByteSink::new()
let mut offset = 4
let column_chunks : Array[ColumnChunkMeta] = []
for leaf in ordered {
let page = encode_data_page_shredded(leaf)
let page_offset = offset
body.push_bytes(page[:])
offset += page.length()
column_chunks.push({
file_offset: 0L,
meta_data: {
type_code: writer_type_code_or_invalid(leaf.column_type),
encodings: [encoding_plain, encoding_rle],
path_in_schema: leaf.path,
codec: codec_uncompressed,
num_values: leaf.def_levels.length().to_int64(),
total_uncompressed_size: page.length().to_int64(),
total_compressed_size: page.length().to_int64(),
data_page_offset: page_offset.to_int64(),
dictionary_page_offset: None,
},
})
}
let row_group = {
columns: column_chunks,
total_byte_size: body.length().to_int64(),
num_rows: num_rows.to_int64(),
file_offset: Some(4L),
total_compressed_size: Some(body.length().to_int64()),
}
let footer = CompactWriter::new()
footer.begin_struct()
footer.write_i32_field(1, 1)
footer.write_field_header(2, thrift_wire_list)
footer.write_list_header(count_schema_nodes(nodes) + 1, thrift_wire_struct)
let root = CompactWriter::new()
root.begin_struct()
root.write_string_field(4, "schema")
root.write_i32_field(5, nodes.length())
root.end_struct()
footer.sink.push_bytes(root.write_bytes()[:])
write_schema_elements(footer, nodes)
footer.write_i64_field(3, num_rows.to_int64())
footer.write_field_header(4, thrift_wire_list)
footer.write_list_header(1, thrift_wire_struct)
let row_group_writer = CompactWriter::new()
row_group_writer.begin_struct()
row_group_writer.write_field_header(1, thrift_wire_list)
row_group_writer.write_list_header(
row_group.columns.length(),
thrift_wire_struct,
)
for chunk in row_group.columns {
let chunk_writer = CompactWriter::new()
chunk_writer.begin_struct()
chunk_writer.write_i64_field(2, chunk.file_offset)
chunk_writer.write_field_header(3, thrift_wire_struct)
chunk_writer.begin_struct()
chunk_writer.write_i32_field(1, chunk.meta_data.type_code)
write_list_of_i32(chunk_writer, 2, chunk.meta_data.encodings)
write_list_of_string(chunk_writer, 3, chunk.meta_data.path_in_schema)
chunk_writer.write_i32_field(4, chunk.meta_data.codec)
chunk_writer.write_i64_field(5, chunk.meta_data.num_values)
chunk_writer.write_i64_field(6, chunk.meta_data.total_uncompressed_size)
chunk_writer.write_i64_field(7, chunk.meta_data.total_compressed_size)
chunk_writer.write_i64_field(9, chunk.meta_data.data_page_offset)
chunk_writer.end_struct()
chunk_writer.end_struct()
row_group_writer.sink.push_bytes(chunk_writer.write_bytes()[:])
}
row_group_writer.write_i64_field(2, row_group.total_byte_size)
row_group_writer.write_i64_field(3, row_group.num_rows)
row_group_writer.write_i64_field(5, row_group.file_offset.unwrap_or(0L))
row_group_writer.write_i64_field(
6,
row_group.total_compressed_size.unwrap_or(0L),
)
row_group_writer.end_struct()
footer.sink.push_bytes(row_group_writer.write_bytes()[:])
footer.write_string_field(6, "mizchi/parquet")
footer.end_struct()
let footer_bytes = footer.write_bytes()
let file_bytes = ByteSink::new()
file_bytes.push_byte(parquet_magic_0)
file_bytes.push_byte(parquet_magic_1)
file_bytes.push_byte(parquet_magic_2)
file_bytes.push_byte(parquet_magic_3)
file_bytes.push_bytes(body.to_bytes()[:])
file_bytes.push_bytes(footer_bytes[:])
file_bytes.push_u32_le(footer_bytes.length())
file_bytes.push_byte(parquet_magic_0)
file_bytes.push_byte(parquet_magic_1)
file_bytes.push_byte(parquet_magic_2)
file_bytes.push_byte(parquet_magic_3)
file_bytes.to_bytes()
}
///|
fn path_text_shredded(path : Array[String]) -> String {
let sb = StringBuilder()
let mut i = 0
while i < path.length() {
if i > 0 {
sb.write_char('.')
}
sb.write_string(path[i])
i = i + 1
}
sb.to_string()
}