Skip to main content
This feature is available starting in StarTree release 0.15.0. It must be enabled on demand — contact your StarTree representative to have it activated for your environment.
When you onboard an External Table, StarTree infers a Pinot schema from the source metadata. The mapping depends on the source:
  • Raw S3 Parquet (S3 Data Lake, catalogType=s3) — inferred from the Parquet file’s physical and logical types.
  • Iceberg (AWS Glue / S3 Tables, catalogType=iceberg-rest) — inferred from the Iceberg table schema.
Numeric columns (INT, LONG, FLOAT, DOUBLE, BIG_DECIMAL) are inferred as metrics; everything else (BOOLEAN, STRING, JSON, BYTES, time columns) as dimensions. You can adjust this in the schema before creating the table.

Raw S3 Parquet → Pinot

For the S3 Data Lake source. Types come from the Parquet file metadata.

Primitive Types


Logical Types

Integer Subtypes

String and Binary Types

Temporal Types

Decimal Types

All Parquet DECIMAL variants map to Pinot BIG_DECIMAL, preserving scale and precision.

Complex Types

To use JSON_MATCH or JSON_EXTRACT_SCALAR on complex columns, you must enable a JSON index on those columns. Without it, queries will fail with a Cannot apply JSON_MATCH on column without json index error. Configure the index in the table’s index settings.
Multi-value columns are capped at 1,000 entries per row — the segment metadata is written with a fixed upper bound, and rows whose source array is longer are silently truncated at query time. If your lists can exceed 1,000 elements, keep the column as JSON instead.

Iceberg → Pinot

For the AWS Glue and AWS S3 Tables sources. Types come from the Iceberg table schema.
Timestamp granularity is inferred from the Iceberg type (current releases):
  • timestamp_ns / timestamptz_nsEPOCH|NANOSECONDS
  • timestamp(p) with an explicit precision → milliseconds for p ≤ 3, microseconds for p ≤ 6, nanoseconds above that
  • plain timestamp / timestamptzEPOCH|MICROSECONDS, matching Iceberg’s on-disk microsecond storage
Older 0.16.0 builds inferred plain timestamp as EPOCH|MILLISECONDS, which mislabeled microsecond data (values ~1000× off). Tables onboarded on those builds keep their inferred format — set the time column’s granularity explicitly if it doesn’t match the data. See Time column.

Time column

A Pinot table can designate one column as its primary time column — a DateTimeFieldSpec that records the event time of each row. See the Apache Pinot DateTimeFieldSpec reference for the general schema requirement and format syntax.

How the time column is used in an External Table

  • Segment pruning — a query with a time filter skips segments whose time range can’t match, so fewer remote files are read.
  • Retention — if a retention period is configured on the table, segments older than it are dropped.
The time column is optional: without one, the table is still created and fully queryable, but loses time-based segment pruning and retention.

How the time column is detected

During onboarding, after the schema is inferred, StarTree selects a time column automatically — choosing, in order:
  1. The first non-nullable millisecond-granularity datetime column.
  2. Otherwise, the first millisecond-granularity datetime column (nullable allowed).
  3. Otherwise, the first datetime column of any unit.
Only columns that map to a Pinot DateTimeFieldSpec are candidates — TIMESTAMP, DATE, and (raw Parquet) INT96. A plain integer column holding an epoch value is not auto-detected; set it as the time column manually if needed. If the source has no timestamp/date column, no time column is set; you can assign one in the schema before creating the table.

Granularity

For raw Parquet, the inferred DateTimeFieldSpec preserves the source unit: For Iceberg, the granularity follows the type: timestamp_ns → nanoseconds, timestamp(p) → from the precision, and plain timestamp / timestamptzmicroseconds (older 0.16.0 builds inferred milliseconds — see the note in the Iceberg → Pinot section above).
Values are stored in the source unit — there is no implicit downscaling — so account for the unit in time-range queries. If the inferred granularity doesn’t match what the underlying Parquet actually stores (for example, a table onboarded on an older build), set the time column’s granularity explicitly.

Known Limitations

Unsigned Integer Types

Parquet UINT32 and UINT64 (unsigned 32-bit and 64-bit integers) are stored in Pinot as a signed LONG. This means:
  • Values that fit within the signed range are accurate.
  • Values above 2,147,483,647 (for UINT32) are stored as negative numbers or wrap around, silently corrupting approximately half the data.
If your dataset contains unsigned integers larger than INT32_MAX, treat the Pinot LONG values with caution or apply a transformation at ingestion time.

Null Handling in Complex Types

MAP columns that contain null values in keys or values may not propagate null bitmaps correctly to Pinot segments. This can result in nulls being replaced by type defaults (e.g., 0 for integers, empty string for strings). Verify null behavior after ingestion. Current onboarding flows set nullHandlingEnabled: true on the generated table config automatically, and when the source schema has nullable fields, the inferred Pinot schema marks them nullable and enables column-based null handling (enableColumnBasedNullHandling). Tables created on older builds may have null handling off — see Advanced Guidance.

INT96 (Deprecated)

INT96 has no column statistics in Parquet, which affects min/max pruning during segment generation. It should not be used as a timeColumnName.