|
| 1 | +import { avroMetadata } from './avro/avro.metadata.js' |
| 2 | +import { avroRead } from './avro/avro.read.js' |
1 | 3 | import { fetchAvroRecords, urlResolver } from './fetch.js' |
2 | 4 |
|
3 | 5 | /** |
4 | 6 | * Returns manifest entries for a snapshot. Defaults to the current snapshot; |
5 | 7 | * pass `snapshotId` to time-travel to a prior snapshot in the metadata's |
6 | 8 | * snapshot log. |
7 | 9 | * |
8 | | - * @import {Resolver, TableMetadata, Manifest, ManifestEntry} from '../src/types.js' |
| 10 | + * @import {Resolver, TableMetadata, Manifest, ManifestEntry, Snapshot} from '../src/types.js' |
9 | 11 | * @typedef {{ url: string, entries: ManifestEntry[] }[]} ManifestList |
10 | 12 | * @param {object} options |
11 | 13 | * @param {TableMetadata} options.metadata |
@@ -36,15 +38,56 @@ export async function icebergManifests({ metadata, resolver, snapshotId }) { |
36 | 38 | const manifestListUrl = snapshot['manifest-list'] |
37 | 39 | manifests = /** @type {Manifest[]} */ (await fetchAvroRecords(manifestListUrl, resolver)) |
38 | 40 | } else if (snapshot.manifests) { |
39 | | - // Use manifest URLs directly from snapshot |
40 | | - manifests = snapshot.manifests |
| 41 | + // v1 snapshots list manifests inline instead of pointing at a manifest list |
| 42 | + manifests = await resolveInlineManifests(snapshot, resolver) |
41 | 43 | } else { |
42 | 44 | throw new Error('No manifest information found in snapshot') |
43 | 45 | } |
44 | 46 |
|
45 | 47 | return await fetchManifests(manifests, resolver) |
46 | 48 | } |
47 | 49 |
|
| 50 | +/** |
| 51 | + * Turn a v1 snapshot's inline `manifests` (a list of manifest file locations) |
| 52 | + * into manifest list records. Each manifest is read once to recover the |
| 53 | + * length, spec id, and file counts a manifest list needs, so a table upgraded |
| 54 | + * from v1 can still be read and appended to without rewriting its inherited |
| 55 | + * metadata. |
| 56 | + * |
| 57 | + * @param {Snapshot} snapshot |
| 58 | + * @param {Resolver} resolver |
| 59 | + * @returns {Promise<Manifest[]>} |
| 60 | + */ |
| 61 | +export async function resolveInlineManifests(snapshot, resolver) { |
| 62 | + const inline = snapshot.manifests ?? [] |
| 63 | + return await Promise.all(inline.map(async manifestPath => { |
| 64 | + const ab = await resolver.reader(manifestPath) |
| 65 | + const buffer = await ab.slice(0, ab.byteLength) |
| 66 | + const reader = { view: new DataView(buffer), offset: 0 } |
| 67 | + const { metadata, syncMarker } = await avroMetadata(reader) |
| 68 | + const entries = /** @type {ManifestEntry[]} */ (await avroRead({ reader, metadata, syncMarker })) |
| 69 | + const counts = [0, 0, 0] |
| 70 | + const rows = [0n, 0n, 0n] |
| 71 | + for (const entry of entries) { |
| 72 | + counts[entry.status]++ |
| 73 | + rows[entry.status] += BigInt(entry.data_file.record_count) |
| 74 | + } |
| 75 | + return { |
| 76 | + manifest_path: manifestPath, |
| 77 | + manifest_length: BigInt(ab.byteLength), |
| 78 | + partition_spec_id: Number(metadata['partition-spec-id'] ?? 0), |
| 79 | + content: 0, |
| 80 | + added_snapshot_id: BigInt(snapshot['snapshot-id']), |
| 81 | + added_files_count: counts[1], |
| 82 | + existing_files_count: counts[0], |
| 83 | + deleted_files_count: counts[2], |
| 84 | + added_rows_count: rows[1], |
| 85 | + existing_rows_count: rows[0], |
| 86 | + deleted_rows_count: rows[2], |
| 87 | + } |
| 88 | + })) |
| 89 | +} |
| 90 | + |
48 | 91 | /** |
49 | 92 | * Fetch manifest entries from a list of manifests in parallel. |
50 | 93 | * |
|
0 commit comments