Система типов
Hive Type Tree
ORC хранит схему данных как дерево типов (type tree) — направленное дерево, где каждый узел описывает один тип. Корень дерева — всегда STRUCT, представляющий строку таблицы. Дочерние узлы — колонки.
Каждый тип в дереве получает column ID — целое число, присвоенное при обходе дерева в pre-order (корень → первый потомок → его потомки → второй потомок → …). Column ID определяет порядок потоков и статистик в файле.
ID 0: STRUCT (корень — строка таблицы)
Корень type tree — всегда STRUCT. Column ID = 0. Представляет всю строку таблицы. Потомки = колонки таблицы. Footer.types[0] = STRUCT.ID 1: INT (id)
Первая колонка. Column ID = 1 (pre-order после корня). Примитивный тип — нет потомков. Stream: DATA (RLEv2).ID 2: STRING (name)
Column ID = 2. STRING — примитивный тип. Streams: DATA (UTF-8 или dictionary indices), LENGTH, [DICTIONARY_DATA].ID 3: MAP (tags)
Column ID = 3. MAP — комплексный тип с двумя потомками (key и value). Сам MAP порождает LENGTH stream (число пар в каждой строке).ID 4: STRING (key)
Ключ map. Column ID = 4. STRING — примитивный тип. Pre-order: после MAP (3) идёт его первый потомок.ID 5: INT (value)
Значение map. Column ID = 5. INT — примитивный тип. Pre-order: после первого потомка (4) идёт второй.ID 6: LIST (scores)
Column ID = 6. LIST — комплексный тип с одним потомком (element). Сам LIST порождает LENGTH stream (число элементов в каждой строке).ID 7: DOUBLE (element)
Элемент списка. Column ID = 7. DOUBLE — примитивный тип. Pre-order: потомок LIST идёт сразу после LIST.Pre-order обход критичен для понимания stream layout. Column ID определяет порядок в ColumnStatistics (Footer), Row Index streams и Stripe Footer stream directory. Если у MAP column ID = 3, то его key = 4, value = 5 — всегда. Ридер полагается на эту нумерацию для навигации.
19 примитивных типов
ORC определяет 19 примитивных типов (Type.Kind enum в спецификации). Каждый тип определяет, какие streams порождает колонка и какую кодировку использует:
Целочисленные
С плавающей точкой и decimal
Строки, даты, бинарные
4 комплексных типа
Комплексные типы ORC — контейнеры, содержащие вложенные типы. Каждый комплексный тип порождает свои потоки и потомки в type tree:
UNION — уникальный тип ORC, отсутствующий в Parquet. Он реализует tagged union (сумма типов): каждая строка содержит значение одного из N вариантов. Hive использует это для UNIONTYPE. На практике UNION встречается редко — большинство движков предпочитают nullable STRUCT.
Stream layout по типам
Полная карта потоков для каждого типа — ключ к пониманию, как ORC-ридер декодирует данные:
Column Statistics по типам
Statistics — это type-aware: для каждого типа ORC хранит разные метрики. Ридер использует конкретную специализацию для predicate pushdown:
| Тип | Статистики | Использование в SARG |
|---|---|---|
| BOOLEAN | trueCount | Оптимизация WHERE flag = true |
| BYTE / SHORT / INT / LONG | min, max, sum | Range predicates, равенство |
| FLOAT / DOUBLE | min, max, sum | Range predicates (осторожно с NaN) |
| STRING / CHAR / VARCHAR | min, max, sum (total length) | Lexicographic range, equality |
| DATE | min, max (days from epoch) | Date range predicates |
| TIMESTAMP | min, max (ms from epoch) | Timestamp range predicates |
| DECIMAL | min, max, sum | Decimal range predicates |
| BINARY | — (только numberOfValues, hasNull) | Только IS NULL |
| Все типы | numberOfValues, hasNull, bytesOnDisk | NULL checks, projection pushdown |
Статистики для FLOAT/DOUBLE не учитывают NaN в min/max. Если колонка содержит NaN, min/max будут вычислены по non-NaN значениям. Это корректно для IEEE 754, но может сбить с толку при отладке: row group с NaN не будет отсечён по range predicate.
Type Tree в Protobuf
Footer хранит type tree как массив Type messages. Каждый Type содержит:
message Type {
kind: Kind // BOOLEAN, BYTE, SHORT, INT, ... STRUCT, LIST, MAP, UNION
subtypes: [uint32] // column IDs потомков (для комплексных типов)
fieldNames: [string] // имена полей (только для STRUCT)
maximumLength: uint32 // для CHAR/VARCHAR
precision: uint32 // для DECIMAL
scale: uint32 // для DECIMAL
}
Таблица
CREATE TABLE users (
id INT,
name STRING,
scores ARRAY<DOUBLE>
)
Footer.types[]
ORC vs Parquet: система типов
| Характеристика | ORC | Parquet |
|---|---|---|
| Модель схемы | Type tree (pre-order) | Message tree (Dremel/Thrift) |
| Количество примитивных типов | 19 | 7 (+ logical types) |
| DECIMAL | Нативный тип | Logical type поверх INT32/INT64/FIXED_LEN |
| DATE | Нативный тип | Logical type поверх INT32 |
| TIMESTAMP | Нативный (секунды + наносекунды) | Logical type (INT96 или INT64) |
| UNION (tagged) | Да | Нет |
| Вложенные структуры | STRUCT / LIST / MAP | group / repeated / map (Dremel) |
| Rep/Def levels | — через PRESENT + LENGTH | (Dremel model) |
| Нумерация колонок | Pre-order column IDs | Schema path / column indices |
ORC использует нативные типы для DATE, DECIMAL и TIMESTAMP — каждый с собственной кодировкой и статистиками. Parquet реализует те же логические типы как аннотации поверх примитивных типов (INT32, INT64, FIXED_LEN_BYTE_ARRAY). Подход ORC проще для ридера — тип однозначно определяет stream layout. Подход Parquet гибче — новые логические типы добавляются без изменения формата.
Ключевые выводы
- Type tree хранит схему ORC как дерево с pre-order column IDs — нумерация определяет порядок потоков и статистик
- 19 примитивных типов: от BOOLEAN и BYTE до TIMESTAMP_INSTANT и DECIMAL — каждый с уникальным stream layout
- 4 комплексных типа: STRUCT (именованные поля), LIST (массив), MAP (словарь), UNION (tagged union — уникально для ORC)
- Stream layout определяется типом: INT → один DATA stream, STRING → до 4 streams, TIMESTAMP → DATA + SECONDARY
- Column statistics — type-specific: IntegerStatistics для числовых, StringStatistics для строк, BooleanStatistics для boolean
- UNION — tagged union, отсутствует в Parquet. Каждая строка содержит значение ровно одного из N вариантов