Schema

Declare a schema to keep the C# model and parquet file aligned on column names, types, and options. Plank uses that declaration to generate type-safe readers and writers and reports incompatible mappings at build time.

Define a schema

Add [ParquetSchema] to a partial class:

[ParquetSchema]
public sealed partial class EventSchema
{
    public int Id { get; init; }

    public byte[]? Name { get; init; }

    public DateTimeOffset OccurredAt { get; init; }
}

Each property becomes a column. Non-nullable properties are required and nullable properties are optional.

Use plain DTOs with properties declared directly on the type. Schema classes and nested object types must not inherit from a custom base class, even an empty one. This also applies to nested objects in arrays and lists. The generator reports PLANKGEN003 instead of silently omitting inherited data. Interface implementation is allowed, and nested structs remain supported. Map inherited domain models into dedicated DTOs before writing them.

Plank generates the readers and writers for EventSchema. See Reading and Writing for usage.

Schema properties keep their declared names in generated row views and column selectors. If a generated API name conflicts with your class or one of its members, Plank adds the first available numeric suffix. For example, a property named Row is accessed as writer.GetRow().Row, and the generated row-view type becomes Row1. A property named All remains Projection.All; the all-columns selector becomes Projection.All1. This applies to both scalar and nested schemas, including generated factory methods.

Customize a column

Use [ParquetColumn] to change a column's name, logical type, physical type, or encoding:

[ParquetColumn(
    "event_name",
    LogicalType = LogicalTypeKind.String,
    Encodings = [EncodingKind.RleDictionary],
    Compression = CompressionKind.Zstd,
    CompressionLevel = 3)]
public byte[]? Name { get; init; }

Plank validates that the selected options are compatible with the property type.

Supported types

C# type Parquet type
bool Boolean
byte, ushort, int, uint Int32
long, ulong Int64
float Float
double Double
decimal FixedLenByteArray with Decimal
string ByteArray with String
byte[], ReadOnlyMemory<byte> ByteArray
Guid 16-byte FixedLenByteArray with Uuid
DateOnly Int32 with Date
TimeOnly Int64 with Time
DateTime, DateTimeOffset Int64 with Timestamp

Nullable forms use the same type and create an optional column.

CLR strings require an explicit opt-in because UTF-8 encoding and decoding allocates:

[ParquetSchema(AllowAllocatingValues = true)]
public sealed partial class SimpleSchema
{
    public string Name { get; init; } = string.Empty;

    public Guid Id { get; init; }
}

Without AllowAllocatingValues, the source generator reports an error for every string property. Use byte[] or ReadOnlyMemory<byte> when allocation-free access is required.

Decimal values

Set Precision to the total number of digits and Scale to the number of fractional digits. Precision is required and Scale defaults to zero:

[ParquetSchema]
public sealed partial class InvoiceSchema
{
    [ParquetColumn(Precision = 10, Scale = 2)]
    public decimal? Amount { get; init; }
}

Writing a value that does not fit the declared precision and scale throws an exception.

Timestamp offsets

DateTimeOffset values are read back in UTC. Store the original offset in a separate column if you need to keep it.

Runtime schemas

Use ParquetSchema when a schema needs to be created at runtime:

var schema = new ParquetSchema([
    ColumnDefinition.RequiredLeaf("id", ParquetPhysicalType.Int64),
    ColumnDefinition.OptionalLeaf(
        "name",
        ParquetPhysicalType.ByteArray,
        logicalType: new LogicalType.String())
]);