| import { getMaxDefinitionLevel, isListLike, isMapLike } from './schema.js' |
| import { decodeVariantColumn } from './variant.js' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export function assembleLists(output, definitionLevels, repetitionLevels, values, schemaPath) { |
| const maxDefinitionLevel = getMaxDefinitionLevel(schemaPath) |
| |
| if (!definitionLevels?.length && !repetitionLevels.length) { |
| if (!maxDefinitionLevel || !values.length) return values |
| definitionLevels = new Array(values.length).fill(maxDefinitionLevel) |
| } |
| const n = definitionLevels?.length || repetitionLevels.length |
| const repetitionPath = schemaPath.map(({ element }) => element.repetition_type) |
| let valueIndex = 0 |
|
|
| |
| const containerStack = [output] |
| let currentContainer = output |
| let currentDepth = 0 |
| let currentDefLevel = 0 |
| let currentRepLevel = 0 |
|
|
| if (repetitionLevels[0]) { |
| |
| while (currentDepth < repetitionPath.length - 2 && currentRepLevel < repetitionLevels[0]) { |
| currentDepth++ |
| if (repetitionPath[currentDepth] !== 'REQUIRED') { |
| |
| currentContainer = currentContainer.at(-1) |
| containerStack.push(currentContainer) |
| currentDefLevel++ |
| } |
| if (repetitionPath[currentDepth] === 'REPEATED') currentRepLevel++ |
| } |
| } |
|
|
| for (let i = 0; i < n; i++) { |
| |
| const def = definitionLevels?.length ? definitionLevels[i] : maxDefinitionLevel |
| const rep = repetitionLevels[i] |
|
|
| |
| while (currentDepth && (rep < currentRepLevel || repetitionPath[currentDepth] !== 'REPEATED')) { |
| if (repetitionPath[currentDepth] !== 'REQUIRED') { |
| containerStack.pop() |
| currentDefLevel-- |
| } |
| if (repetitionPath[currentDepth] === 'REPEATED') currentRepLevel-- |
| currentDepth-- |
| } |
| |
| currentContainer = containerStack.at(-1) |
|
|
| |
| while ( |
| (currentDepth < repetitionPath.length - 2 || repetitionPath[currentDepth + 1] === 'REPEATED') && |
| (currentDefLevel < def || repetitionPath[currentDepth + 1] === 'REQUIRED') |
| ) { |
| currentDepth++ |
| if (repetitionPath[currentDepth] !== 'REQUIRED') { |
| |
| const newList = [] |
| currentContainer.push(newList) |
| currentContainer = newList |
| containerStack.push(newList) |
| currentDefLevel++ |
| } |
| if (repetitionPath[currentDepth] === 'REPEATED') currentRepLevel++ |
| } |
|
|
| |
| if (def === maxDefinitionLevel) { |
| |
| currentContainer.push(values[valueIndex++]) |
| } else if (currentDepth === repetitionPath.length - 2) { |
| currentContainer.push(null) |
| } else { |
| currentContainer.push([]) |
| } |
| } |
|
|
| |
| if (!output.length) { |
| |
| for (let i = 0; i < maxDefinitionLevel; i++) { |
| |
| const newList = [] |
| currentContainer.push(newList) |
| currentContainer = newList |
| } |
| } |
|
|
| return output |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| export function assembleNested(subcolumnData, schema, parsers, depth = 0) { |
| const path = schema.path.join('.') |
| const optional = schema.element.repetition_type === 'OPTIONAL' |
| const nextDepth = optional ? depth + 1 : depth |
|
|
| if (isListLike(schema)) { |
| let sublist = schema.children[0] |
| let subDepth = nextDepth |
| if (sublist.children.length === 1) { |
| sublist = sublist.children[0] |
| subDepth++ |
| } |
| assembleNested(subcolumnData, sublist, parsers, subDepth) |
|
|
| const subcolumn = sublist.path.join('.') |
| const values = subcolumnData.get(subcolumn) |
| if (!values) throw new Error('parquet list column missing values') |
| if (optional) flattenAtDepth(values, depth) |
| subcolumnData.set(path, values) |
| subcolumnData.delete(subcolumn) |
| return |
| } |
|
|
| if (isMapLike(schema)) { |
| const mapName = schema.children[0].element.name |
|
|
| |
| assembleNested(subcolumnData, schema.children[0].children[0], parsers, nextDepth + 1) |
| assembleNested(subcolumnData, schema.children[0].children[1], parsers, nextDepth + 1) |
|
|
| const keys = subcolumnData.get(`${path}.${mapName}.key`) |
| const values = subcolumnData.get(`${path}.${mapName}.value`) |
|
|
| if (!keys) throw new Error('parquet map column missing keys') |
| if (!values) throw new Error('parquet map column missing values') |
| if (keys.length !== values.length) { |
| throw new Error('parquet map column key/value length mismatch') |
| } |
|
|
| const out = assembleMaps(keys, values, nextDepth) |
| if (optional) flattenAtDepth(out, depth) |
|
|
| subcolumnData.delete(`${path}.${mapName}.key`) |
| subcolumnData.delete(`${path}.${mapName}.value`) |
| subcolumnData.set(path, out) |
| return |
| } |
|
|
| |
| if (schema.children.length) { |
| |
| const invertDepth = schema.element.repetition_type === 'REQUIRED' ? depth : depth + 1 |
| |
| const struct = {} |
| for (const child of schema.children) { |
| assembleNested(subcolumnData, child, parsers, invertDepth) |
| const childData = subcolumnData.get(child.path.join('.')) |
| if (!childData) throw new Error('parquet struct missing child data') |
| struct[child.element.name] = childData |
| } |
| |
| for (const child of schema.children) { |
| subcolumnData.delete(child.path.join('.')) |
| } |
|
|
| |
| let inverted = invertStruct(struct, invertDepth) |
| if (schema.element.logical_type?.type === 'VARIANT') { |
| inverted = decodeVariantColumn(inverted, parsers) |
| } |
| if (optional) flattenAtDepth(inverted, depth) |
| subcolumnData.set(path, inverted) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| function flattenAtDepth(arr, depth) { |
| for (let i = 0; i < arr.length; i++) { |
| if (depth) { |
| flattenAtDepth(arr[i], depth - 1) |
| } else { |
| arr[i] = arr[i][0] |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function assembleMaps(keys, values, depth) { |
| const out = [] |
| for (let i = 0; i < keys.length; i++) { |
| if (depth) { |
| out.push(assembleMaps(keys[i], values[i], depth - 1)) |
| } else { |
| if (keys[i]) { |
| |
| const obj = {} |
| for (let j = 0; j < keys[i].length; j++) { |
| const value = values[i][j] |
| obj[keys[i][j]] = value === undefined ? null : value |
| } |
| out.push(obj) |
| } else { |
| out.push(undefined) |
| } |
| } |
| } |
| return out |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function invertStruct(struct, depth) { |
| const keys = Object.keys(struct) |
| const length = struct[keys[0]]?.length |
| const out = [] |
| for (let i = 0; i < length; i++) { |
| |
| const obj = {} |
| for (const key of keys) { |
| if (struct[key].length !== length) throw new Error('parquet struct parsing error') |
| obj[key] = struct[key][i] |
| } |
| if (depth) { |
| out.push(invertStruct(obj, depth - 1)) |
| } else { |
| out.push(obj) |
| } |
| } |
| return out |
| } |
|
|