///|
/// `_delta_log` 目录路径
pub fn log_dir(root : String) -> String {
root + "/_delta_log"
}
///|
/// 版本号 → 日志文件名(20 位零填充,Delta 规范)
pub fn version_file_name(version : Int32) -> String {
let s = version.to_string()
let mut pad = ""
let mut i = 0
while i < 20 - s.length() {
pad = pad + "0"
i = i + 1
}
pad + s + ".json"
}
///|
/// 从文件名解析版本号("00000000000000000012.json" → 12)
fn version_from_name(name : String) -> Int32 {
let mut v = 0
for c in name {
let d = c.to_int() - 48
if d >= 0 && d <= 9 {
v = v * 10 + d
} else {
break
}
}
v
}
///|
/// 读取文本文件
pub fn read_text(path : String) -> String raise DeltaError {
@fs.read_file_to_string(path) catch {
@fs.IOError(msg) => raise DeltaError::IoError(msg)
}
}
///|
/// 写入文本文件
pub fn write_text(path : String, content : String) -> Unit raise DeltaError {
@fs.write_string_to_file(path, content) catch {
@fs.IOError(msg) => raise DeltaError::IoError(msg)
}
}
///|
/// 列出所有日志版本(升序、去重)
pub fn list_versions(root : String) -> Array[Int32] raise DeltaError {
let dir = log_dir(root)
if !@fs.path_exists(dir) {
raise DeltaError::NotFound("no _delta_log directory under '\{root}'")
}
let names = @fs.read_dir(dir) catch {
@fs.IOError(msg) => raise DeltaError::IoError(msg)
}
let out : Array[Int32] = []
for name in names {
if name.has_suffix(".json") && !name.has_prefix(".") {
let v = version_from_name(name)
if !out.contains(v) {
out.push(v)
}
}
}
out.sort()
out
}
///|
/// 读取某个版本的日志 actions
pub fn read_actions(
root : String,
version : Int32,
) -> Array[Action] raise DeltaError {
let path = log_dir(root) + "/" + version_file_name(version)
if !@fs.path_exists(path) {
raise DeltaError::NotFound(
"log version \{version} is missing (checkpoint-only or truncated table?)",
)
}
let text = read_text(path).replace_all(old="\r", new="")
let out : Array[Action] = []
for line in text.split("\n") {
if line.char_length() == 0 {
continue
}
out.push(parse_action(line, version))
}
out
}
///|
/// 读取 `_last_checkpoint`(不存在返回 None)
pub fn checkpoint_info(root : String) -> CheckpointInfo? raise DeltaError {
let path = log_dir(root) + "/_last_checkpoint"
if !@fs.path_exists(path) {
return None
}
let text = read_text(path)
let j = @json.parse(text) catch { _ => return None }
let version = match json_field_int(j, "version") {
Some(v) => v
None => return None
}
let size = json_field_int(j, "size")
let parts = json_field_int(j, "parts")
Some({ version, size, parts, })
}
///|
/// 是否存在 checkpoint 文件(`.checkpoint.parquet`)
pub fn has_checkpoint_file(root : String) -> Bool {
let dir = log_dir(root)
if !@fs.path_exists(dir) {
return false
}
let names = @fs.read_dir(dir) catch { _ => return false }
for name in names {
if name.has_suffix(".checkpoint.parquet") {
return true
}
}
false
}