| |
| |
| |
|
|
| import { parquetMetadataAsync, parquetSchema } from './metadata.js' |
| import { parquetReadColumn, parquetReadObjects } from './read.js' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function parquetQuery(options) { |
| if (!options.file || !(options.file.byteLength >= 0)) { |
| throw new Error('parquet expected AsyncBuffer') |
| } |
| options.metadata ??= await parquetMetadataAsync(options.file, options) |
|
|
| const { metadata, rowStart = 0, columns, orderBy, filter } = options |
| if (rowStart < 0) throw new Error('parquet rowStart must be positive') |
| const rowEnd = options.rowEnd ?? Number(metadata.num_rows) |
|
|
| |
| if (orderBy) { |
| const allColumns = parquetSchema(options.metadata).children.map(c => c.element.name) |
| if (!allColumns.includes(orderBy)) { |
| throw new Error(`parquet orderBy column not found: ${orderBy}`) |
| } |
| } |
|
|
| if (filter && !orderBy && rowEnd < metadata.num_rows) { |
| |
| |
| const filteredRows = [] |
| let groupStart = 0 |
| for (const group of metadata.row_groups) { |
| const groupEnd = groupStart + Number(group.num_rows) |
| |
| const groupData = await parquetReadObjects({ |
| ...options, rowStart: groupStart, rowEnd: groupEnd, |
| }) |
| filteredRows.push(...groupData) |
| if (filteredRows.length >= rowEnd) break |
| groupStart = groupEnd |
| } |
| return filteredRows.slice(rowStart, rowEnd) |
| } else if (filter && orderBy) { |
| |
| const readColumns = columns && !columns.includes(orderBy) |
| ? [...columns, orderBy] |
| : columns |
|
|
| const results = await parquetReadObjects({ |
| ...options, rowStart: undefined, rowEnd: undefined, columns: readColumns, |
| }) |
|
|
| |
| results.sort((a, b) => compare(a[orderBy], b[orderBy])) |
|
|
| |
| if (readColumns !== columns) { |
| for (const row of results) { |
| delete row[orderBy] |
| } |
| } |
|
|
| return results.slice(rowStart, rowEnd) |
| } else if (filter) { |
| |
| const results = await parquetReadObjects({ |
| ...options, rowStart: undefined, rowEnd: undefined, |
| }) |
| return results.slice(rowStart, rowEnd) |
| } else if (typeof orderBy === 'string') { |
| |
| const orderColumn = await parquetReadColumn({ |
| ...options, rowStart: undefined, rowEnd: undefined, columns: [orderBy], |
| }) |
|
|
| |
| const sortedIndices = Array.from(orderColumn, (_, index) => index) |
| .sort((a, b) => compare(orderColumn[a], orderColumn[b])) |
| .slice(rowStart, rowEnd) |
|
|
| const sparseData = await parquetReadRows({ ...options, rows: sortedIndices }) |
| |
| |
| const data = sortedIndices.map(index => sparseData[index]) |
| return data |
| } else { |
| return await parquetReadObjects(options) |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| async function parquetReadRows(options) { |
| const { file, rows } = options |
| options.metadata ??= await parquetMetadataAsync(file, options) |
| const { row_groups: rowGroups } = options.metadata |
| |
| const groupIncluded = Array(rowGroups.length).fill(false) |
| let groupStart = 0 |
| const groupEnds = rowGroups.map(group => groupStart += Number(group.num_rows)) |
| for (const index of rows) { |
| const groupIndex = groupEnds.findIndex(end => index < end) |
| groupIncluded[groupIndex] = true |
| } |
|
|
| |
| const rowRanges = [] |
| let rangeStart |
| groupStart = 0 |
| for (let i = 0; i < groupIncluded.length; i++) { |
| const groupEnd = groupStart + Number(rowGroups[i].num_rows) |
| if (groupIncluded[i]) { |
| if (rangeStart === undefined) { |
| rangeStart = groupStart |
| } |
| } else { |
| if (rangeStart !== undefined) { |
| rowRanges.push([rangeStart, groupEnd]) |
| rangeStart = undefined |
| } |
| } |
| groupStart = groupEnd |
| } |
| if (rangeStart !== undefined) { |
| rowRanges.push([rangeStart, groupStart]) |
| } |
|
|
| |
| |
| const sparseData = Array(Number(options.metadata.num_rows)) |
| for (const [rangeStart, rangeEnd] of rowRanges) { |
| |
| const groupData = await parquetReadObjects({ ...options, rowStart: rangeStart, rowEnd: rangeEnd }) |
| for (let i = rangeStart; i < rangeEnd; i++) { |
| |
| sparseData[i] = { __index__: i, ...groupData[i - rangeStart] } |
| } |
| } |
| return sparseData |
| } |
|
|
| |
| |
| |
| |
| |
| function compare(a, b) { |
| if (a < b) return -1 |
| if (a > b) return 1 |
| return 0 |
| } |
|
|