7 个版本 (破坏性更新)

0.10.0 2024年6月24日
0.9.0 2024年5月17日
0.8.0 2024年4月2日
0.7.0 2024年3月7日
0.4.0 2023年10月4日

#1762 in 解析器实现

Download history 1/week @ 2024-04-09 135/week @ 2024-05-14 20/week @ 2024-05-21 92/week @ 2024-06-18 48/week @ 2024-06-25

每月477次下载

自定义许可证

36KB
322

为 Rust 编写 Parquet 代码

Rust build status Coverage status

本项目提供生成 Rust 代码的工具,用于使用 Parquet 文件,该代码使用了 Arrow 的 Rust 实现。它包括一个代码生成crate(parquetry-gen)和生成代码所需的小型运行时库(parquetry)。

请注意,此软件不是“开源”的,但源代码可供个人、非营利组织和工人拥有企业使用和修改(有关详细信息,请参阅下面的许可证部分)。

目录

示例

给定如下架构

message user {
    required int64 id (integer(64, false));
    required int64 ts (timestamp(millis, true));
    optional int32 status;

    optional group user_info {
        required byte_array screen_name (string);

        optional group user_name_info {
            required byte_array name (string);

            optional group user_profile_info {
                required int64 created_at (timestamp(millis, true));
                required byte_array location (string);
                required byte_array description (string);
                optional byte_array url (string);

                required int32 followers_count;
                required int32 friends_count;
                required int32 favourites_count;
                required int32 statuses_count;

                optional group withheld_in_countries (list) {
                    repeated group list {
                        required byte_array element (string);
                    }
                }
            }
        }
    }
}

代码生成器将生成以下 Rust 结构

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct User {
    pub id: u64,
    #[serde(with = "chrono::serde::ts_milliseconds")]
    pub ts: chrono::DateTime<chrono::Utc>,
    pub status: Option<i32>,
    pub user_info: Option<UserInfo>,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct UserInfo {
    pub screen_name: String,
    pub user_name_info: Option<UserNameInfo>,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct UserNameInfo {
    pub name: String,
    pub user_profile_info: Option<UserProfileInfo>,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct UserProfileInfo {
    #[serde(with = "chrono::serde::ts_milliseconds")]
    pub created_at: chrono::DateTime<chrono::Utc>,
    pub location: String,
    pub description: String,
    pub url: Option<String>,
    pub followers_count: i32,
    pub friends_count: i32,
    pub favourites_count: i32,
    pub statuses_count: i32,
    pub withheld_in_countries: Option<Vec<String>>,
}

它还将为 User 生成一个 parquetry::Schema trait 实例,其中包括读取和写入 Parquet 文件值的代码。

依赖项

所有使用都要求使用 parquetryparquetchronolazy_static crate 作为运行时依赖项。

如果配置中启用了 serde_support 标志(默认情况下是启用的),您还需要对 serde 有依赖,并启用 derive 功能。

如果配置中启用了 tests 标志(默认情况也是如此),您需要将 bincode(最新版本的 2.0.0-rc.x 并启用 serde 功能)、tempdirquickcheck 添加到您的 dev-dependencies

用法

example 目录提供了一个相当简化的示例,生成的代码也存储在那里。在大多数情况下,您可能只需要像以下这样的 build.rs

use std::{fs::File, io::Write};

fn main() -> Result<(), parquetry_gen::error::Error> {
    for schema in parquetry_gen::ParsedFileSchema::open_dir(
        "src/schemas/",
        Default::default(),
        Some(".parquet.txt"),
    )? {
        println!("cargo:rerun-if-changed={}", schema.absolute_path_str()?);
        let mut output = File::create(format!("src/{}.rs", schema.name))?;
        write!(output, "{}", schema.code()?)?;
    }

    Ok(())
}

默认情况下,生成的代码使用 prettyplease 格式化,并注释表明不应使用 Rustfmt 格式化,但如果您想自己使用 Rustfmt,可以在配置中将 format 设置为 false。

测试

默认配置将生成使用 QuickCheck 生成任意值并确认它们正确序列化和反序列化的测试代码。

当前的测试代码生成不支持某些类型。具体来说,不支持浮点数字符串、固定长度的字节数组和时间戳。如果您的模式包含这些类型中的任何一种,生成的测试代码将无法编译,您需要在配置中禁用 tests 标志(您也可以提出问题)。

生成的测试代码不会为浮点类型生成 NaN 值。如果您想确认您的系统正确处理这些值,您必须手动进行。

默认情况下,为您的类型生成的任意值可能非常大。如果您的测试速度太慢,您可以将 QUICKCHECK_GENERATOR_SIZE 环境变量设置为较小的值(例如 1020)。

状态和范围

这些工具支持具有大多数物理和逻辑类型的模式,以及列表、可选字段和结构的任意嵌套。

我可能在某个时候添加的缺失功能

  • 8 位和 16 位逻辑整数字符类型(很简单,我只是还没有用到它们)
  • DATETIMEINTERVALUUID(与之前相同)
  • DECIMAL(至少对于 INT32INT64 表示)
  • ENUM(在这个上下文中可能不是很有用,因为模式没有列出变体?)
  • 映射(需要更多工作,但可能值得拥有)

可能永远不会支持的功能

此项目与 parquet_derive 在几个方面有所不同

  • 这两个项目都生成读取和写入代码,但这个项目从模式生成Rust结构体,而不是相反。
  • 该项目不使用parquet::record::RecordWriter(它似乎并不那么有用,而且我希望有更多的灵活性)。
  • 该项目支持嵌套结构。

一般来说,这两个项目有不同的用途,如果你只想将一些Rust值存储在Parquet中,我建议选择parquet_derive

警告

名称冲突

目前还没有对与Rust关键字、标准库中的名称等冲突的字段名称进行特殊处理。组名称也应在该模式内保持唯一。代码生成器还会生成非公共结构体,其名称理论上可能与其他用户生成的代码冲突。

在这些大多数情况下,问题应该立即显而易见,因为生成的代码通常根本无法编译。检查这些冲突并提供更好的错误,或者允许用户更多地控制命名以避免这些问题并不困难,但这对我来说还不是优先事项。

构造函数

生成的代码包括每个结构的fn new构造函数,这些构造函数将截断任何DateTime<Utc>的精度到列表示支持的子秒数字。这些构造函数还将检查任何字符串参数是否包含空字节,并在发现空字节时返回错误。

如果你不希望有任何这些行为,你可以手动构造结构体,因为所有字段始终是公开的。

Serde实例

默认情况下,生成的代码将包括用于序列化的派生Serde实例。这些实例将使用模式指定的时单位(毫秒或微秒)来处理类型为DateTime<Utc>Option<DateTime<Utc>>的字段,但类型为Vec<DateTime<Utc>>的字段将使用默认的Serde序列化编码来处理DateTime<Utc>

这并非没有原因,只是因为chrono::serde仅提供了例如ts_millisecondsts_milliseconds_option函数,并且运行时库可以轻松提供自己的ts_milliseconds_vec,这些函数将用于这些情况。

性能

我没有尝试优化生成的代码,可能直到性能成为我的用例问题之前都不会。

许可证

此软件根据Anti-Capitalist Software License(v. 1.4)发布。

依赖项

~48MB
~1M SLoC