/**
* @typedef {import('../src/types.js').FileMetaData} FileMetaData
*/
import { getColumnRange } from '../src/column.js'
/**
* @param {FileMetaData} metadata
* @returns {HTMLDivElement}
*/
export function fileMetadata(metadata) {
let html = '
Metadata
'
html += `${JSON.stringify(metadata, null, 2)}`
const div = document.createElement('div')
div.innerHTML = html
div.classList.add('layout', 'collapsed') // start collapsed
div.children[0].addEventListener('click', () => {
div.classList.toggle('collapsed')
})
return div
}
/**
* Render parquet file layout.
*
* @param {FileMetaData} metadata
* @param {import('../src/types.js').AsyncBuffer} asyncBuffer
* @returns {HTMLDivElement}
*/
export function fileLayout(metadata, asyncBuffer) {
let html = 'File layout
'
html += cell('PAR1', 0n, 4n) // magic number
// data pages by row group and column
/** @type {[string, bigint, bigint][]} */
const indexPages = []
for (const rowGroupIndex in metadata.row_groups) {
const rowGroup = metadata.row_groups[rowGroupIndex]
html += group(`RowGroup ${rowGroupIndex} (${rowGroup.total_byte_size.toLocaleString()} bytes)`)
for (const column of rowGroup.columns) {
const columnName = column.meta_data?.path_in_schema.join('.')
html += group(`Column ${columnName}`)
if (column.meta_data) {
const end = getColumnRange(column.meta_data)[1]
/* eslint-disable no-extra-parens */
const pages = (/** @type {[string, bigint][]} */
([
['Dictionary', column.meta_data.dictionary_page_offset],
['Data', column.meta_data.data_page_offset],
['Index', column.meta_data.index_page_offset],
['End', end],
]))
.filter(([, offset]) => offset !== undefined)
.sort((a, b) => Number(a[1]) - Number(b[1]))
for (let i = 0; i < pages.length - 1; i++) {
const [name, start] = pages[i]
const end = pages[i + 1][1]
html += cell(name, start, end)
}
}
if (column.column_index_offset) {
indexPages.push([`ColumnIndex RowGroup${rowGroupIndex} ${columnName}`, column.column_index_offset, BigInt(column.column_index_length || 0)])
}
if (column.offset_index_offset) {
indexPages.push([`OffsetIndex RowGroup${rowGroupIndex} ${columnName}`, column.offset_index_offset, BigInt(column.offset_index_length || 0)])
}
html += ''
}
html += ''
}
// column and offset indexes
for (const [name, start, length] of indexPages.sort((a, b) => Number(a[1]) - Number(b[1]))) {
html += cell(name, start, start + length)
}
// metadata footer
const metadataStart = BigInt(asyncBuffer.byteLength - metadata.metadata_length - 4)
const metadataEnd = BigInt(asyncBuffer.byteLength - 4)
html += cell('Metadata', metadataStart, metadataEnd)
html += cell('PAR1', metadataEnd, BigInt(asyncBuffer.byteLength)) // magic number
const div = document.createElement('div')
div.innerHTML = html
div.classList.add('layout', 'collapsed') // start collapsed
div.children[0].addEventListener('click', () => {
div.classList.toggle('collapsed')
})
return div
}
/**
* @param {string} name
* @returns {string}
*/
function group(name) {
return `${name}`
}
/**
* @param {string} name
* @param {bigint} start
* @param {bigint} end
* @returns {string}
*/
function cell(name, start, end) {
const bytes = end - start
return `
- start ${start.toLocaleString()}
- bytes ${bytes.toLocaleString()}
- end ${end.toLocaleString()}
`
}