// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright The Lance Authors
 
syntax = "proto3";

package lance.file.v2;

import "google/protobuf/any.proto";
import "google/protobuf/empty.proto";

// # Lance v2.X File Format
//
// The Lance file format is a barebones format for serializing columnar data
// into a file.
//
// * Each Lance file contains between 0 and 4Gi columns
// * Each column contains between 0 and 4Gi pages
// * Each page contains between 0 and 2^64 items
// * Different pages within a column can have different items counts
// * Columns may have up to 2^64 items
// * Different columns within a file can have different item counts
//
// The Lance file format does not have any notion of a type system or schemas.
// From the perspective of the file format all data is arbitrary buffers of
// bytes with an extensible metadata block to describe the data.  It is up to
// the user to interpret these bytes meaningfully.
//
// Data buffers are written to the file first.  These data buffers can be
// referenced from three different places in the file:
//
// * Page encodings can reference data buffers.  This is the most common way
//   that actual data is stored.
// * Column encodings can reference data buffers.  For example, a column encoding
//   may reference data buffer(s) containing statistics or dictionaries.
// * Finally, the global buffer offset table can reference data buffers.  This
//   is useful for storing data that is shared across multiple columns.
//   This is also useful for global file metadata (e.g. a schema that describes
//   the file)
//
// ## File Layout
//
// Note: the number of buffers (BN) is independent of the number of columns (CN)
//       and pages.
//
//       Buffers often need to be aligned.  64-byte alignment is common when
//       working with SIMD operations.  4096-byte alignment is common when
//       working with direct I/O.  In order to ensure these buffers are aligned
//       writers may need to insert padding before the buffers.
//       
//       If direct I/O is required then most (but not all) fields described
//       below must be sector aligned.  We have marked these fields with an
//       asterisk for clarity.  Readers should assume there will be optional
//       padding inserted before these fields.
//
//       All footer fields are unsigned integers written with  little endian
//       byte order.
//
// ├──────────────────────────────────┤
// | Data Pages                       |
// |   Data Buffer 0*                 |
// |   ...                            |
// |   Data Buffer BN*                |
// ├──────────────────────────────────┤
// | Column Metadatas                 |
// | |A| Column 0 Metadata*           |
// |     Column 1 Metadata*           |
// |     ...                          |
// |     Column CN Metadata*          |
// ├──────────────────────────────────┤
// | Column Metadata Offset Table     |
// | |B| Column 0 Metadata Position*  |
// |     Column 0 Metadata Size       |
// |     ...                          |
// |     Column CN Metadata Position  |
// |     Column CN Metadata Size      |
// ├──────────────────────────────────┤
// | Global Buffers Offset Table      |
// | |C| Global Buffer 0 Position*    |
// |     Global Buffer 0 Size         |
// |     ...                          |
// |     Global Buffer GN Position    |
// |     Global Buffer GN Size        |
// ├──────────────────────────────────┤
// | Footer                           |
// | A u64: Offset to column meta 0   |
// | B u64: Offset to CMO table       |
// | C u64: Offset to GBO table       |
// |   u32: Number of global bufs     |
// |   u32: Number of columns         |
// |   u16: Major version             |
// |   u16: Minor version             |
// |   "LANC"                         |
// ├──────────────────────────────────┤
//
// File Layout-End
//
// ## Data Pages
//
// A lot of flexibility is provided in how data is stored.  A page's buffers do
// not strictly need to be contiguous on the disk.  However, it is recommended
// that buffers within a page be grouped together for best performance.
//
// Data pages should be large.  The only time a page should be written to disk
// is when the writer needs to flush the page to disk because it has accumulated
// too much data.  Pages are not read in sequential order and if pages are too
// small then the seek overhead (or request overhead) will be problematic.  We
// generally advise that pages be at least 8MB or larger.
//
// ## Encodings
//
// Specific encodings are not part of this minimal format.  They are provided
// by extensions. Readers and writers should be designed so that encodings can
// be easily added and removed. Ideally, they should allow for this without
// requiring recompilation through some kind of plugin system.

// The deferred encoding is used to place the encoding itself in a different
// part of the file.  This is most commonly used to allow encodings to be shared
// across different columns.  For example, when writing a file with thousands of
// columns, where many pages have the exact same encoding, it can be useful
// to cut down on the size of the metadata by using a deferred encoding.
message DeferredEncoding {
   // Location of the buffer containing the encoding.
   //
   // * If sharing encodings across columns then this will be in a global buffer
   // * If sharing encodings across pages within a column this could be in a
   //   column metadata buffer.
   // * This could also be a page buffer if the encoding is not shared, needs
   //   to be written before the file ends, and the encoding is too large to load
   //   unless we first determine the page needs to be read.  This combination
   //   seems unusual.
   uint64 buffer_location = 1;
   uint64 buffer_length = 2;
}

// The encoding is placed directly in the metadata section
message DirectEncoding {
    // The bytes that make up the encoding embedded directly in the metadata
    //
    // This is the most common approach.
    bytes encoding = 1;
}

// An encoding stores the information needed to decode a column or page
//
// For example, it could describe if the page is using bit packing, and how many bits
// there are in each individual value.
//
// At the column level it can be used to wrap columns with dictionaries or statistics.
message Encoding {
    oneof location {
        // The encoding is stored elsewhere and not part of this protobuf message
        DeferredEncoding indirect = 1;
        // The encoding is stored within this protobuf message
        DirectEncoding direct = 2;
        // There is no encoding information
        google.protobuf.Empty none = 3;
    }
}

// ## Metadata

// Each column has a metadata block that is placed at the end of the file.
// These may be read individually to allow for column projection.
message ColumnMetadata {

  // This describes a page of column data.
  message Page {
    // The file offsets for each of the page buffers
    //
    // The number of buffers is variable and depends on the encoding.  There
    // may be zero buffers (e.g. constant encoded data) in which case this
    // could be empty.
    repeated uint64 buffer_offsets = 1;
    // The size (in bytes) of each of the page buffers
    //
    // This field will have the same length as `buffer_offsets` and
    // may be empty.
    repeated uint64 buffer_sizes = 2;
    // Logical length (e.g. # rows) of the page
    uint64 length = 3;
    // The encoding used to encode the page
    Encoding encoding = 4;
    // The priority of the page
    //
    // For tabular data this will be the top-level row number of the first row
    // in the page (and top-level rows should not split across pages).
    uint64 priority = 5;
  }
  // Encoding information about the column itself.  This typically describes
  // how to interpret the column metadata buffers.  For example, it could
  // describe how statistics or dictionaries are stored in the column metadata.
  Encoding encoding = 1;
  // The pages in the column
  repeated Page pages = 2;   
  // The file offsets of each of the column metadata buffers
  //
  // There may be zero buffers.
  repeated uint64 buffer_offsets = 3;
  // The size (in bytes) of each of the column metadata buffers
  //
  // This field will have the same length as `buffer_offsets` and
  // may be empty.
  repeated uint64 buffer_sizes = 4;
} // Metadata-End

// ## Where is the rest?
//
// This file format is extremely minimal.  It is a building block for
// creating more useful readers and writers and not terribly useful by itself.
// Other protobuf files will describe how this can be extended.
