9个重大版本更新

0.10.0 2024年6月24日
0.8.0 2024年4月2日
0.7.0 2024年3月7日
0.6.0 2023年11月14日

#78 in 过程宏

自定义许可协议

82KB
2K SLoC

Rust的Parquet代码生成

Rust build status Coverage status

本项目提供使用Rust实现Parquet文件的生成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关键字、标准库中的名称等冲突的字段名称进行特殊处理。组名称也应在该模式内唯一。代码生成器还会生成可能理论上与用户生成的代码冲突的非公开struct。

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

构造函数

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

如果您不希望这两种行为中的任何一种,可以手动构造struct,因为所有字段始终是公开的。

Serde实例

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

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

性能

我没有尝试优化生成的代码,除非性能成为我的用例的问题。

许可协议

本软件根据反资本主义软件许可协议(版本1.4)发布。

依赖项

~17–24MB
~505K SLoC