(
prefix: string,
processor: (lines: string[]) => Promise<number>
)
| 295 | |
| 296 | // Iterate BigQuery EXPORT DATA shards (prefix-000000000000.csv, -000000000001.csv, ...) |
| 297 | async function processGcsFile( |
| 298 | prefix: string, |
| 299 | processor: (lines: string[]) => Promise<number> |
| 300 | ): Promise<{ rows: number; time_ms: number }> { |
| 301 | const start = Date.now(); |
| 302 | let totalRows = 0; |
| 303 | let shard = 0; |
| 304 | |
| 305 | while (true) { |
| 306 | const shardId = String(shard).padStart(12, '0'); |
| 307 | const url = `${GCS_BASE}/${prefix}-${shardId}.csv`; |
| 308 | logger.info(`Fetching ${url}`); |
| 309 | |
| 310 | const response = await fetch(url); |
| 311 | if (response.status === 404) break; |
| 312 | if (!response.ok) { |
| 313 | throw new Error(`GCS fetch failed for ${url}: ${response.status} ${response.statusText}`); |
| 314 | } |
| 315 | |
| 316 | totalRows += await processGcsShard(response, url, prefix, shard, processor); |
| 317 | progress({ step: prefix, shard, rows: totalRows, status: 'shard_done' }); |
| 318 | logger.info(` ${prefix}: shard ${shard} done (${totalRows.toLocaleString()} rows so far)`); |
| 319 | shard++; |
| 320 | } |
| 321 | |
| 322 | if (shard === 0) { |
| 323 | throw new Error(`No shards found for ${prefix} at ${GCS_BASE}`); |
| 324 | } |
| 325 | |
| 326 | return { rows: totalRows, time_ms: Date.now() - start }; |
| 327 | } |
| 328 | |
| 329 | // 1. Ad infra classifications (bulk insert) |
| 330 | results.ad_infra = await processGcsFile('ad-infra', async (lines) => { |
no test coverage detected