@gscdump/engine 0.32.12 → 0.33.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/engine.mjs +54 -25
- package/dist/_chunks/entities.mjs +5 -2
- package/dist/_chunks/manifest-store-utils.mjs +56 -0
- package/dist/_chunks/pg-adapter.d.mts +30 -4
- package/dist/_chunks/resolver.mjs +201 -37
- package/dist/_chunks/schema.d.mts +0 -120
- package/dist/_chunks/schema.mjs +4 -8
- package/dist/_chunks/schema2.mjs +8 -6
- package/dist/_chunks/sink.d.mts +18 -12
- package/dist/_chunks/types.d.mts +3 -0
- package/dist/adapters/filesystem.mjs +11 -65
- package/dist/adapters/hyparquet.mjs +13 -10
- package/dist/adapters/r2-manifest.mjs +51 -50
- package/dist/entities.d.mts +2 -2
- package/dist/iceberg/index.d.mts +2 -2
- package/dist/iceberg/index.mjs +9 -9
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/ingest.d.mts +1 -6
- package/dist/ingest.mjs +0 -7
- package/dist/resolver/index.d.mts +91 -36
- package/dist/resolver/index.mjs +2 -2
- package/dist/rollups.d.mts +4 -4
- package/dist/rollups.mjs +28 -19
- package/package.json +3 -3
package/dist/_chunks/schema.mjs
CHANGED
|
@@ -14,7 +14,6 @@ const pages = pgTable("pages", {
|
|
|
14
14
|
});
|
|
15
15
|
const queries = pgTable("queries", {
|
|
16
16
|
query: varchar("query").notNull(),
|
|
17
|
-
query_canonical: varchar("query_canonical"),
|
|
18
17
|
date: dateCol(),
|
|
19
18
|
...metricCols()
|
|
20
19
|
});
|
|
@@ -26,7 +25,6 @@ const countries = pgTable("countries", {
|
|
|
26
25
|
const page_queries = pgTable("page_queries", {
|
|
27
26
|
url: varchar("url").notNull(),
|
|
28
27
|
query: varchar("query").notNull(),
|
|
29
|
-
query_canonical: varchar("query_canonical"),
|
|
30
28
|
date: dateCol(),
|
|
31
29
|
...metricCols()
|
|
32
30
|
});
|
|
@@ -60,7 +58,6 @@ const search_appearance_pages = pgTable("search_appearance_pages", {
|
|
|
60
58
|
const search_appearance_queries = pgTable("search_appearance_queries", {
|
|
61
59
|
searchAppearance: varchar("searchAppearance").notNull(),
|
|
62
60
|
query: varchar("query").notNull(),
|
|
63
|
-
query_canonical: varchar("query_canonical"),
|
|
64
61
|
date: dateCol(),
|
|
65
62
|
...metricCols()
|
|
66
63
|
});
|
|
@@ -68,7 +65,6 @@ const search_appearance_page_queries = pgTable("search_appearance_page_queries",
|
|
|
68
65
|
searchAppearance: varchar("searchAppearance").notNull(),
|
|
69
66
|
url: varchar("url").notNull(),
|
|
70
67
|
query: varchar("query").notNull(),
|
|
71
|
-
query_canonical: varchar("query_canonical"),
|
|
72
68
|
date: dateCol(),
|
|
73
69
|
...metricCols()
|
|
74
70
|
});
|
|
@@ -99,7 +95,7 @@ const TABLE_METADATA = {
|
|
|
99
95
|
queries: {
|
|
100
96
|
sortKey: ["date", "query"],
|
|
101
97
|
clusterKey: ["query", "date"],
|
|
102
|
-
version:
|
|
98
|
+
version: 3
|
|
103
99
|
},
|
|
104
100
|
countries: {
|
|
105
101
|
sortKey: ["date", "country"],
|
|
@@ -117,7 +113,7 @@ const TABLE_METADATA = {
|
|
|
117
113
|
"query",
|
|
118
114
|
"date"
|
|
119
115
|
],
|
|
120
|
-
version:
|
|
116
|
+
version: 3
|
|
121
117
|
},
|
|
122
118
|
dates: {
|
|
123
119
|
sortKey: ["date"],
|
|
@@ -153,7 +149,7 @@ const TABLE_METADATA = {
|
|
|
153
149
|
"query",
|
|
154
150
|
"date"
|
|
155
151
|
],
|
|
156
|
-
version:
|
|
152
|
+
version: 2
|
|
157
153
|
},
|
|
158
154
|
search_appearance_page_queries: {
|
|
159
155
|
sortKey: [
|
|
@@ -168,7 +164,7 @@ const TABLE_METADATA = {
|
|
|
168
164
|
"query",
|
|
169
165
|
"date"
|
|
170
166
|
],
|
|
171
|
-
version:
|
|
167
|
+
version: 2
|
|
172
168
|
},
|
|
173
169
|
hourly_pages: {
|
|
174
170
|
sortKey: [
|
package/dist/_chunks/schema2.mjs
CHANGED
|
@@ -10,6 +10,7 @@ const ICEBERG_TABLES = [
|
|
|
10
10
|
"search_appearance_queries",
|
|
11
11
|
"search_appearance_page_queries"
|
|
12
12
|
];
|
|
13
|
+
const DEFAULT_PARTITION_KEY_ENCODING = "int";
|
|
13
14
|
const SEARCH_TYPE_INT = {
|
|
14
15
|
web: 1,
|
|
15
16
|
image: 2,
|
|
@@ -30,7 +31,7 @@ const ICEBERG_PARTITION_COLUMNS = [{
|
|
|
30
31
|
required: true,
|
|
31
32
|
fieldId: 2
|
|
32
33
|
}];
|
|
33
|
-
function icebergPartitionColumns(encoding = "
|
|
34
|
+
function icebergPartitionColumns(encoding = "int") {
|
|
34
35
|
if (encoding === "string") return ICEBERG_PARTITION_COLUMNS;
|
|
35
36
|
return [{
|
|
36
37
|
name: "site_id",
|
|
@@ -71,7 +72,7 @@ function mapColumnType(t) {
|
|
|
71
72
|
case "DATE": return "DATE";
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
|
-
function icebergTableSpec(table, encoding = "
|
|
75
|
+
function icebergTableSpec(table, encoding = "int") {
|
|
75
76
|
const base = SCHEMAS[table];
|
|
76
77
|
const dataColumns = base.columns.map((col, i) => ({
|
|
77
78
|
name: col.name,
|
|
@@ -90,10 +91,11 @@ function icebergTableSpec(table, encoding = "string") {
|
|
|
90
91
|
]
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
|
-
const
|
|
94
|
+
const ICEBERG_SCHEMAS_STRING = Object.fromEntries(ICEBERG_TABLES.map((t) => [t, icebergTableSpec(t, "string")]));
|
|
94
95
|
const ICEBERG_SCHEMAS_INT = Object.fromEntries(ICEBERG_TABLES.map((t) => [t, icebergTableSpec(t, "int")]));
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
const ICEBERG_SCHEMAS = ICEBERG_SCHEMAS_INT;
|
|
97
|
+
function icebergSchemasFor(encoding = "int") {
|
|
98
|
+
return encoding === "int" ? ICEBERG_SCHEMAS_INT : ICEBERG_SCHEMAS_STRING;
|
|
97
99
|
}
|
|
98
100
|
const ICEBERG_TABLE_SET = new Set(ICEBERG_TABLES);
|
|
99
101
|
function isIcebergTable(table) {
|
|
@@ -103,4 +105,4 @@ function assertIcebergTable(table) {
|
|
|
103
105
|
if (!isIcebergTable(table)) throw new Error(`Unknown Iceberg table '${table}'. Expected one of: ${ICEBERG_TABLES.join(", ")}`);
|
|
104
106
|
return table;
|
|
105
107
|
}
|
|
106
|
-
export { ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_TABLES, INT_SEARCH_TYPE, SEARCH_TYPE_INT, assertIcebergTable, icebergPartitionColumns, icebergSchemasFor, icebergTableSpec, isIcebergTable };
|
|
108
|
+
export { DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_SCHEMAS_STRING, ICEBERG_TABLES, INT_SEARCH_TYPE, SEARCH_TYPE_INT, assertIcebergTable, icebergPartitionColumns, icebergSchemasFor, icebergTableSpec, isIcebergTable };
|
package/dist/_chunks/sink.d.mts
CHANGED
|
@@ -53,7 +53,7 @@ interface IcebergColumn {
|
|
|
53
53
|
/**
|
|
54
54
|
* Partition-key encoding for the two identity columns (`site_id`, `search_type`).
|
|
55
55
|
*
|
|
56
|
-
* - `'string'` (
|
|
56
|
+
* - `'string'` (legacy): both columns are STRING. Correct, but R2 SQL's
|
|
57
57
|
* string min/max statistics are truncated in predicate pushdown, so a bare
|
|
58
58
|
* `WHERE site_id='<uuid>'` UNDERCOUNTS — callers must CONCAT(col,'') to stay
|
|
59
59
|
* correct, which defeats partition pruning.
|
|
@@ -66,10 +66,12 @@ interface IcebergColumn {
|
|
|
66
66
|
* {@link SEARCH_TYPE_INT} for `search_type` (engine-owned, fixed enum).
|
|
67
67
|
*
|
|
68
68
|
* New per-team catalogs are provisioned `'int'`; existing catalogs stay
|
|
69
|
-
* `'string'`.
|
|
70
|
-
*
|
|
69
|
+
* `'string'`. Callers that read or write legacy catalogs must pass
|
|
70
|
+
* `encoding: 'string'` explicitly.
|
|
71
71
|
*/
|
|
72
72
|
type PartitionKeyEncoding = 'string' | 'int';
|
|
73
|
+
/** Default for new Iceberg/R2 Data Catalog tables. */
|
|
74
|
+
declare const DEFAULT_PARTITION_KEY_ENCODING: PartitionKeyEncoding;
|
|
73
75
|
/**
|
|
74
76
|
* Stable `search_type` enum → int map for `'int'`-encoded catalogs. Engine-owned
|
|
75
77
|
* and FROZEN: never renumber or reuse an id (it's the on-disk partition value).
|
|
@@ -142,11 +144,13 @@ declare const ICEBERG_PARTITION_SPEC: readonly IcebergPartitionField[];
|
|
|
142
144
|
* source of truth — do not hand-list columns elsewhere.
|
|
143
145
|
*/
|
|
144
146
|
declare function icebergTableSpec(table: IcebergTableName, encoding?: PartitionKeyEncoding): IcebergTableSpec;
|
|
145
|
-
/** All Iceberg table specs
|
|
146
|
-
declare const
|
|
147
|
+
/** All Iceberg table specs in legacy `'string'` encoding, keyed by table name. */
|
|
148
|
+
declare const ICEBERG_SCHEMAS_STRING: Record<IcebergTableName, IcebergTableSpec>;
|
|
147
149
|
/** All Iceberg table specs in `'int'` encoding (INT site_id + INT search_type). */
|
|
148
150
|
declare const ICEBERG_SCHEMAS_INT: Record<IcebergTableName, IcebergTableSpec>;
|
|
149
|
-
/**
|
|
151
|
+
/** All Iceberg table specs for the default new-catalog encoding. */
|
|
152
|
+
declare const ICEBERG_SCHEMAS: Record<IcebergTableName, IcebergTableSpec>;
|
|
153
|
+
/** Table specs for the given encoding (`'int'` default). */
|
|
150
154
|
declare function icebergSchemasFor(encoding?: PartitionKeyEncoding): Record<IcebergTableName, IcebergTableSpec>;
|
|
151
155
|
/** True when `table` is one of the canonical {@link ICEBERG_TABLES}. */
|
|
152
156
|
declare function isIcebergTable(table: string): table is IcebergTableName;
|
|
@@ -385,7 +389,8 @@ interface ListIcebergDataFilesOptions {
|
|
|
385
389
|
/**
|
|
386
390
|
* Partition-key encoding of the catalog. `'int'` changes how manifest-summary
|
|
387
391
|
* bounds are decoded (int bytes vs UTF-8) and how the per-file partition value
|
|
388
|
-
* is compared. Defaults to `'string'
|
|
392
|
+
* is compared. Defaults to `'int'` for new catalogs; pass `'string'` for
|
|
393
|
+
* legacy catalogs.
|
|
389
394
|
*/
|
|
390
395
|
encoding?: PartitionKeyEncoding;
|
|
391
396
|
/**
|
|
@@ -542,10 +547,11 @@ interface IcebergAppendSinkOptions extends SinkOptions {
|
|
|
542
547
|
*/
|
|
543
548
|
commitRetry?: CommitRetryOptions;
|
|
544
549
|
/**
|
|
545
|
-
* Partition-key encoding (default `'
|
|
546
|
-
* and `search_type` as INT — the caller MUST pass the numeric
|
|
547
|
-
* numeric string is fine; it's `Number()`-coerced) in
|
|
548
|
-
*
|
|
550
|
+
* Partition-key encoding (default `'int'` for new catalogs). `'int'` writes
|
|
551
|
+
* BOTH `site_id` and `search_type` as INT — the caller MUST pass the numeric
|
|
552
|
+
* `site_id` (a numeric string is fine; it's `Number()`-coerced) in
|
|
553
|
+
* `slice.ctx.siteId`. Pass `'string'` explicitly for legacy catalogs. A small
|
|
554
|
+
* INT is ample (≪ 2.1B sites), so no LONG/BigInt is involved. See
|
|
549
555
|
* {@link import('./iceberg/schema').PartitionKeyEncoding}.
|
|
550
556
|
*/
|
|
551
557
|
encoding?: PartitionKeyEncoding;
|
|
@@ -559,4 +565,4 @@ interface LocalIcebergSinkOptions extends SinkOptions {
|
|
|
559
565
|
/** S3-compatible warehouse location (POC: MinIO). */
|
|
560
566
|
warehouse: string;
|
|
561
567
|
}
|
|
562
|
-
export { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_TABLES, INT_SEARCH_TYPE, IcebergAppendSinkOptions, IcebergCatalogConfig, IcebergColumn, IcebergColumnType, IcebergConnection, IcebergListedDataFile, IcebergPartitionField, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPartitionTransform, IcebergPrimitiveType, IcebergS3Config, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableName, IcebergTableOpResult, IcebergTableSpec, ListIcebergDataFilesOptions, LocalIcebergSinkOptions, PartitionKeyEncoding, SEARCH_TYPE_INT, Sink, SinkCapabilities, SinkCloseResult, SinkOptions, SinkSlice, SinkWriteResult, SliceOverwriteWriter, assertIcebergTable, connectIcebergCatalog, createIcebergTables, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, icebergPartitionColumns, icebergPartitionSpecFor, icebergSchemaFor, icebergSchemasFor, icebergSortOrderFor, icebergTableSpec, isCommitRateLimited, isIcebergTable, listIcebergDataFiles, listIcebergTables };
|
|
568
|
+
export { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_SCHEMAS_STRING, ICEBERG_TABLES, INT_SEARCH_TYPE, IcebergAppendSinkOptions, IcebergCatalogConfig, IcebergColumn, IcebergColumnType, IcebergConnection, IcebergListedDataFile, IcebergPartitionField, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPartitionTransform, IcebergPrimitiveType, IcebergS3Config, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableName, IcebergTableOpResult, IcebergTableSpec, ListIcebergDataFilesOptions, LocalIcebergSinkOptions, PartitionKeyEncoding, SEARCH_TYPE_INT, Sink, SinkCapabilities, SinkCloseResult, SinkOptions, SinkSlice, SinkWriteResult, SliceOverwriteWriter, assertIcebergTable, connectIcebergCatalog, createIcebergTables, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, icebergPartitionColumns, icebergPartitionSpecFor, icebergSchemaFor, icebergSchemasFor, icebergSortOrderFor, icebergTableSpec, isCommitRateLimited, isIcebergTable, listIcebergDataFiles, listIcebergTables };
|
package/dist/_chunks/types.d.mts
CHANGED
|
@@ -10,6 +10,9 @@ interface ResolverAdapter<TableKey extends string = string> {
|
|
|
10
10
|
dimColumn: (dim: Dimension, tableKey: TableKey) => string;
|
|
11
11
|
isMetricDimension: (dim: string) => dim is Metric;
|
|
12
12
|
tableRef: (tableKey: TableKey) => SQL;
|
|
13
|
+
fromSql: (tableKey: TableKey, options?: {
|
|
14
|
+
queryCanonical?: boolean;
|
|
15
|
+
}) => SQL;
|
|
13
16
|
dateColRef: (tableKey: TableKey) => SQL;
|
|
14
17
|
urlToPathExpr: (col: string) => string;
|
|
15
18
|
siteIdColRef?: (tableKey: TableKey) => SQL;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { manifestEntryKey, matchesManifestEntryFilter, matchesSyncStateFilter, matchesWatermarkFilter, mergeSyncState, syncStateKey, watermarkKey } from "../_chunks/manifest-store-utils.mjs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { Buffer } from "node:buffer";
|
|
4
4
|
import { randomBytes } from "node:crypto";
|
|
@@ -29,13 +29,13 @@ function createFilesystemDataSource(opts) {
|
|
|
29
29
|
}));
|
|
30
30
|
},
|
|
31
31
|
async list(prefix) {
|
|
32
|
-
const full =
|
|
32
|
+
const full = pathFor(prefix);
|
|
33
33
|
const out = [];
|
|
34
34
|
await walk(full, out);
|
|
35
35
|
return out.map((p) => p.slice(root.length + 1));
|
|
36
36
|
},
|
|
37
37
|
async *streamList(prefix) {
|
|
38
|
-
const full =
|
|
38
|
+
const full = pathFor(prefix);
|
|
39
39
|
for await (const p of walkStream(full)) yield p.slice(root.length + 1);
|
|
40
40
|
},
|
|
41
41
|
async head(key) {
|
|
@@ -71,57 +71,6 @@ async function walk(dir, out) {
|
|
|
71
71
|
else out.push(p);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
function watermarkKey(w) {
|
|
75
|
-
return `${w.userId}|${w.siteId ?? ""}|${w.table}`;
|
|
76
|
-
}
|
|
77
|
-
function matchesWatermarkFilter(w, filter) {
|
|
78
|
-
if (w.userId !== filter.userId) return false;
|
|
79
|
-
if (filter.siteId !== void 0 && w.siteId !== filter.siteId) return false;
|
|
80
|
-
if (filter.table !== void 0 && w.table !== filter.table) return false;
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
function syncStateKey(s) {
|
|
84
|
-
return `${s.userId}|${s.siteId ?? ""}|${s.table}|${s.date}|${inferSearchType(s)}`;
|
|
85
|
-
}
|
|
86
|
-
function matchesSyncStateFilter(s, filter) {
|
|
87
|
-
if (s.userId !== filter.userId) return false;
|
|
88
|
-
if (filter.siteId !== void 0 && s.siteId !== filter.siteId) return false;
|
|
89
|
-
if (filter.table !== void 0 && s.table !== filter.table) return false;
|
|
90
|
-
if (filter.state !== void 0 && s.state !== filter.state) return false;
|
|
91
|
-
if (filter.searchType !== void 0 && inferSearchType(s) !== filter.searchType) return false;
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
function mergeSyncState(existing, scope, state, detail) {
|
|
95
|
-
const at = detail?.at ?? Date.now();
|
|
96
|
-
const attemptsBump = state === "inflight" ? 1 : 0;
|
|
97
|
-
if (!existing) return {
|
|
98
|
-
userId: scope.userId,
|
|
99
|
-
siteId: scope.siteId,
|
|
100
|
-
table: scope.table,
|
|
101
|
-
date: scope.date,
|
|
102
|
-
state,
|
|
103
|
-
updatedAt: at,
|
|
104
|
-
attempts: attemptsBump,
|
|
105
|
-
error: detail?.error,
|
|
106
|
-
...scope.searchType !== void 0 ? { searchType: scope.searchType } : {}
|
|
107
|
-
};
|
|
108
|
-
return {
|
|
109
|
-
...existing,
|
|
110
|
-
state,
|
|
111
|
-
updatedAt: at,
|
|
112
|
-
attempts: existing.attempts + attemptsBump,
|
|
113
|
-
error: state === "done" ? void 0 : detail?.error ?? existing.error
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
function matchesFilter(entry, filter) {
|
|
117
|
-
if (entry.userId !== filter.userId) return false;
|
|
118
|
-
if (filter.siteId !== void 0 && entry.siteId !== filter.siteId) return false;
|
|
119
|
-
if (filter.table !== void 0 && entry.table !== filter.table) return false;
|
|
120
|
-
if (filter.partitions && !filter.partitions.includes(entry.partition)) return false;
|
|
121
|
-
if (filter.tier !== void 0 && inferLegacyTier(entry) !== filter.tier) return false;
|
|
122
|
-
if (filter.searchType !== void 0 && inferSearchType(entry) !== filter.searchType) return false;
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
74
|
function lockFileFor(locksDir, scope) {
|
|
126
75
|
return join(locksDir, `${`${scope.userId}|${scope.siteId ?? ""}|${scope.table}|${scope.partition}`.replace(/[^\w.-]/g, "_")}.lock`);
|
|
127
76
|
}
|
|
@@ -166,30 +115,27 @@ function createFilesystemManifestStore(opts) {
|
|
|
166
115
|
while (queue.length > 0) await queue.shift()().catch(() => {});
|
|
167
116
|
running = false;
|
|
168
117
|
}
|
|
169
|
-
function entryKey(e) {
|
|
170
|
-
return e.objectKey;
|
|
171
|
-
}
|
|
172
118
|
async function registerVersionsImpl(newEntries, superseding) {
|
|
173
119
|
const data = await load();
|
|
174
120
|
const supersededAt = newEntries[0]?.createdAt ?? Date.now();
|
|
175
|
-
const byKey = new Map(data.entries.map((e) => [
|
|
121
|
+
const byKey = new Map(data.entries.map((e) => [manifestEntryKey(e), e]));
|
|
176
122
|
if (superseding) for (const s of superseding) {
|
|
177
|
-
const existing = byKey.get(
|
|
178
|
-
if (existing && existing.retiredAt === void 0) byKey.set(
|
|
123
|
+
const existing = byKey.get(manifestEntryKey(s));
|
|
124
|
+
if (existing && existing.retiredAt === void 0) byKey.set(manifestEntryKey(s), {
|
|
179
125
|
...existing,
|
|
180
126
|
retiredAt: supersededAt
|
|
181
127
|
});
|
|
182
128
|
}
|
|
183
|
-
for (const e of newEntries) byKey.set(
|
|
129
|
+
for (const e of newEntries) byKey.set(manifestEntryKey(e), e);
|
|
184
130
|
data.entries = Array.from(byKey.values());
|
|
185
131
|
await save(data);
|
|
186
132
|
}
|
|
187
133
|
return {
|
|
188
134
|
async listLive(filter) {
|
|
189
|
-
return (await load()).entries.filter((e) => e.retiredAt === void 0 &&
|
|
135
|
+
return (await load()).entries.filter((e) => e.retiredAt === void 0 && matchesManifestEntryFilter(e, filter));
|
|
190
136
|
},
|
|
191
137
|
async listAll(filter) {
|
|
192
|
-
return (await load()).entries.filter((e) =>
|
|
138
|
+
return (await load()).entries.filter((e) => matchesManifestEntryFilter(e, filter));
|
|
193
139
|
},
|
|
194
140
|
async registerVersion(entry, superseding) {
|
|
195
141
|
return enqueue(() => registerVersionsImpl([entry], superseding));
|
|
@@ -203,8 +149,8 @@ function createFilesystemManifestStore(opts) {
|
|
|
203
149
|
async delete(toDelete) {
|
|
204
150
|
return enqueue(async () => {
|
|
205
151
|
const data = await load();
|
|
206
|
-
const toDeleteKeys = new Set(toDelete.map(
|
|
207
|
-
data.entries = data.entries.filter((e) => !toDeleteKeys.has(
|
|
152
|
+
const toDeleteKeys = new Set(toDelete.map(manifestEntryKey));
|
|
153
|
+
data.entries = data.entries.filter((e) => !toDeleteKeys.has(manifestEntryKey(e)));
|
|
208
154
|
await save(data);
|
|
209
155
|
});
|
|
210
156
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SCHEMAS, TABLE_METADATA
|
|
1
|
+
import { SCHEMAS, TABLE_METADATA } from "../_chunks/schema.mjs";
|
|
2
2
|
import { parquetReadObjects } from "../_chunks/libs/hyparquet.mjs";
|
|
3
3
|
import { ByteWriter, parquetWriteRows } from "../_chunks/libs/hyparquet-writer.mjs";
|
|
4
4
|
const ROW_GROUP_SIZE = 25e3;
|
|
@@ -119,10 +119,16 @@ function sortRowsByClusterKey(table, rows) {
|
|
|
119
119
|
});
|
|
120
120
|
return copy;
|
|
121
121
|
}
|
|
122
|
+
function naturalKeyFor(table, row) {
|
|
123
|
+
return TABLE_METADATA[table].sortKey.map((col) => `${row[col] ?? ""}`).join("\0");
|
|
124
|
+
}
|
|
122
125
|
function encodeOrderedRows(rows, columns, rowGroupSize) {
|
|
123
126
|
const schema = buildWriteSchema(columns);
|
|
124
|
-
const
|
|
125
|
-
|
|
127
|
+
const codecs = columns.map((col) => ({
|
|
128
|
+
name: col.name,
|
|
129
|
+
isDate: col.type === "DATE",
|
|
130
|
+
type: basicTypeFor(col.type)
|
|
131
|
+
}));
|
|
126
132
|
const columnSpecs = columns.map((col) => ({
|
|
127
133
|
name: col.name,
|
|
128
134
|
nullable: col.nullable,
|
|
@@ -131,10 +137,7 @@ function encodeOrderedRows(rows, columns, rowGroupSize) {
|
|
|
131
137
|
function* coercedRows() {
|
|
132
138
|
for (const r of rows) {
|
|
133
139
|
const out = {};
|
|
134
|
-
for (
|
|
135
|
-
const name = columns[c].name;
|
|
136
|
-
out[name] = isDate[c] ? toEpochDays(r[name]) : coerceValue(r[name], types[c]);
|
|
137
|
-
}
|
|
140
|
+
for (const codec of codecs) out[codec.name] = codec.isDate ? toEpochDays(r[codec.name]) : coerceValue(r[codec.name], codec.type);
|
|
138
141
|
yield out;
|
|
139
142
|
}
|
|
140
143
|
}
|
|
@@ -222,12 +225,12 @@ function createHyparquetCodec(options = {}) {
|
|
|
222
225
|
rowCount: 0
|
|
223
226
|
};
|
|
224
227
|
}
|
|
225
|
-
const
|
|
228
|
+
const byNaturalKey = /* @__PURE__ */ new Map();
|
|
226
229
|
for (const key of inputKeys) {
|
|
227
230
|
const rows = await decodeParquetToRows(await dataSource.read(key));
|
|
228
|
-
for (let i = 0; i < rows.length; i++)
|
|
231
|
+
for (let i = 0; i < rows.length; i++) byNaturalKey.set(naturalKeyFor(ctx.table, rows[i]), rows[i]);
|
|
229
232
|
}
|
|
230
|
-
const rows =
|
|
233
|
+
const rows = [...byNaturalKey.values()];
|
|
231
234
|
const bytes = encodeRowsToParquet(ctx.table, rows);
|
|
232
235
|
await dataSource.write(outputKey, bytes);
|
|
233
236
|
return {
|
|
@@ -1,13 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { inferSearchType } from "../_chunks/layout.mjs";
|
|
2
2
|
import { engineErrorToException, engineErrors } from "../errors.mjs";
|
|
3
|
+
import { matchesManifestEntryFilter, matchesSyncStateFilter, matchesWatermarkFilter } from "../_chunks/manifest-store-utils.mjs";
|
|
3
4
|
import { err, ok, unwrapResult } from "gscdump/result";
|
|
4
5
|
const SHARD_RE = /^u_[^/]+\/manifest\/(?<siteId>[^/]+)\/(?<table>[^/]+)\/HEAD$/;
|
|
5
6
|
const CAS_BACKOFF_BASE_MS = 5;
|
|
6
7
|
const CAS_BACKOFF_CAP_MS = 250;
|
|
8
|
+
const SHARD_IO_CONCURRENCY = 8;
|
|
7
9
|
async function casBackoff(attempt) {
|
|
8
10
|
const ceil = Math.min(CAS_BACKOFF_CAP_MS, CAS_BACKOFF_BASE_MS * 2 ** attempt);
|
|
9
11
|
await new Promise((resolve) => setTimeout(resolve, Math.random() * ceil));
|
|
10
12
|
}
|
|
13
|
+
async function mapWithConcurrency(items, concurrency, fn) {
|
|
14
|
+
if (items.length === 0) return [];
|
|
15
|
+
const workerCount = Math.max(1, Math.min(items.length, Math.floor(concurrency)));
|
|
16
|
+
const results = Array.from({ length: items.length }, () => void 0);
|
|
17
|
+
let nextIndex = 0;
|
|
18
|
+
async function worker() {
|
|
19
|
+
while (true) {
|
|
20
|
+
const index = nextIndex++;
|
|
21
|
+
if (index >= items.length) return;
|
|
22
|
+
results[index] = await fn(items[index], index);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
26
|
+
return results;
|
|
27
|
+
}
|
|
11
28
|
function defaultSnapshotId() {
|
|
12
29
|
return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
13
30
|
}
|
|
@@ -36,26 +53,6 @@ function shardScopesFromEntries(entries) {
|
|
|
36
53
|
}
|
|
37
54
|
return out;
|
|
38
55
|
}
|
|
39
|
-
function matchesEntryFilter(entry, filter) {
|
|
40
|
-
if (filter.siteId !== void 0 && entry.siteId !== filter.siteId) return false;
|
|
41
|
-
if (filter.table !== void 0 && entry.table !== filter.table) return false;
|
|
42
|
-
if (filter.partitions && !filter.partitions.includes(entry.partition)) return false;
|
|
43
|
-
if (filter.tier !== void 0 && inferLegacyTier(entry) !== filter.tier) return false;
|
|
44
|
-
if (filter.searchType !== void 0 && inferSearchType(entry) !== filter.searchType) return false;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
function matchesWatermarkFilter(w, filter) {
|
|
48
|
-
if (filter.siteId !== void 0 && w.siteId !== filter.siteId) return false;
|
|
49
|
-
if (filter.table !== void 0 && w.table !== filter.table) return false;
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
function matchesSyncStateFilter(s, filter) {
|
|
53
|
-
if (filter.siteId !== void 0 && s.siteId !== filter.siteId) return false;
|
|
54
|
-
if (filter.table !== void 0 && s.table !== filter.table) return false;
|
|
55
|
-
if (filter.state !== void 0 && s.state !== filter.state) return false;
|
|
56
|
-
if (filter.searchType !== void 0 && inferSearchType(s) !== filter.searchType) return false;
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
56
|
function createR2ManifestStore(opts) {
|
|
60
57
|
const { bucket, userId } = opts;
|
|
61
58
|
const newSnapshotId = opts.newSnapshotId ?? defaultSnapshotId;
|
|
@@ -154,17 +151,20 @@ function createR2ManifestStore(opts) {
|
|
|
154
151
|
}];
|
|
155
152
|
return (await listShards()).filter((s) => (filter.siteId === void 0 || s.siteId === filter.siteId) && (filter.table === void 0 || s.table === filter.table));
|
|
156
153
|
}
|
|
154
|
+
function assertScopedUser(got, op) {
|
|
155
|
+
if (got !== userId) throw new Error(`${op}: R2 manifest store is scoped to userId=${userId}, got ${got}`);
|
|
156
|
+
}
|
|
157
157
|
async function readEntriesAcrossShards(filter, includeRetired) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
for (const { siteId, table } of shards) {
|
|
158
|
+
assertScopedUser(filter.userId, includeRetired ? "listAll" : "listLive");
|
|
159
|
+
return (await mapWithConcurrency(await shardsForFilter(filter), SHARD_IO_CONCURRENCY, async ({ siteId, table }) => {
|
|
161
160
|
const { snapshot } = await readShard(siteId, table);
|
|
161
|
+
const entries = [];
|
|
162
162
|
for (const entry of snapshot.entries) {
|
|
163
163
|
if (!includeRetired && entry.retiredAt !== void 0) continue;
|
|
164
|
-
if (
|
|
164
|
+
if (matchesManifestEntryFilter(entry, filter, { ignoreUserId: true })) entries.push(entry);
|
|
165
165
|
}
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
return entries;
|
|
167
|
+
})).flat();
|
|
168
168
|
}
|
|
169
169
|
function groupBySiteTable(entries) {
|
|
170
170
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -180,6 +180,7 @@ function createR2ManifestStore(opts) {
|
|
|
180
180
|
const supersededAt = newEntries[0]?.createdAt ?? now();
|
|
181
181
|
const byShard = /* @__PURE__ */ new Map();
|
|
182
182
|
function bucket(entry, kind) {
|
|
183
|
+
assertScopedUser(entry.userId, "registerVersions");
|
|
183
184
|
if (entry.siteId === void 0) throw new Error("R2 manifest store requires entries to carry siteId");
|
|
184
185
|
const key = `${entry.siteId}\0${entry.table}`;
|
|
185
186
|
let bag = byShard.get(key);
|
|
@@ -195,7 +196,7 @@ function createR2ManifestStore(opts) {
|
|
|
195
196
|
}
|
|
196
197
|
for (const e of newEntries) bucket(e, "new");
|
|
197
198
|
if (superseding) for (const e of superseding) bucket(e, "super");
|
|
198
|
-
|
|
199
|
+
await mapWithConcurrency([...byShard], SHARD_IO_CONCURRENCY, async ([shardKey, { newEntries: news, superseding: supers }]) => {
|
|
199
200
|
const [siteId, table] = shardKey.split("\0");
|
|
200
201
|
await mutateShard(siteId, table, (snap) => {
|
|
201
202
|
const byObjectKey = new Map(snap.entries.map((e) => [e.objectKey, e]));
|
|
@@ -209,7 +210,7 @@ function createR2ManifestStore(opts) {
|
|
|
209
210
|
for (const n of news) byObjectKey.set(n.objectKey, n);
|
|
210
211
|
snap.entries = Array.from(byObjectKey.values());
|
|
211
212
|
});
|
|
212
|
-
}
|
|
213
|
+
});
|
|
213
214
|
}
|
|
214
215
|
return {
|
|
215
216
|
async listLive(filter) {
|
|
@@ -226,34 +227,33 @@ function createR2ManifestStore(opts) {
|
|
|
226
227
|
return registerVersionsImpl(entries, superseding);
|
|
227
228
|
},
|
|
228
229
|
async listRetired(olderThan) {
|
|
229
|
-
|
|
230
|
-
const out = [];
|
|
231
|
-
for (const { siteId, table } of shards) {
|
|
230
|
+
return (await mapWithConcurrency(await listShards(), SHARD_IO_CONCURRENCY, async ({ siteId, table }) => {
|
|
232
231
|
const { snapshot } = await readShard(siteId, table);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
232
|
+
const retired = [];
|
|
233
|
+
for (const e of snapshot.entries) if (e.retiredAt !== void 0 && e.retiredAt <= olderThan) retired.push(e);
|
|
234
|
+
return retired;
|
|
235
|
+
})).flat();
|
|
236
236
|
},
|
|
237
237
|
async delete(toDelete) {
|
|
238
|
-
|
|
239
|
-
for (const [shardKey, entries] of grouped) {
|
|
238
|
+
await mapWithConcurrency([...groupBySiteTable(toDelete)], SHARD_IO_CONCURRENCY, async ([shardKey, entries]) => {
|
|
240
239
|
const [siteId, table] = shardKey.split("\0");
|
|
241
240
|
await mutateShard(siteId, table, (snap) => {
|
|
242
241
|
const drop = new Set(entries.map((e) => e.objectKey));
|
|
243
242
|
snap.entries = snap.entries.filter((e) => !drop.has(e.objectKey));
|
|
244
243
|
});
|
|
245
|
-
}
|
|
244
|
+
});
|
|
246
245
|
},
|
|
247
246
|
async getWatermarks(filter) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
for (const { siteId, table } of shards) {
|
|
247
|
+
assertScopedUser(filter.userId, "getWatermarks");
|
|
248
|
+
return (await mapWithConcurrency(await shardsForFilter(filter), SHARD_IO_CONCURRENCY, async ({ siteId, table }) => {
|
|
251
249
|
const { snapshot } = await readShard(siteId, table);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
const watermarks = [];
|
|
251
|
+
for (const w of snapshot.watermarks) if (matchesWatermarkFilter(w, filter, { ignoreUserId: true })) watermarks.push(w);
|
|
252
|
+
return watermarks;
|
|
253
|
+
})).flat();
|
|
255
254
|
},
|
|
256
255
|
async bumpWatermark(scope, date, at) {
|
|
256
|
+
assertScopedUser(scope.userId, "bumpWatermark");
|
|
257
257
|
if (scope.siteId === void 0) throw new Error("R2 manifest store requires watermarks to carry siteId");
|
|
258
258
|
const ts = at ?? now();
|
|
259
259
|
await mutateShard(scope.siteId, scope.table, (snap) => {
|
|
@@ -282,15 +282,16 @@ function createR2ManifestStore(opts) {
|
|
|
282
282
|
});
|
|
283
283
|
},
|
|
284
284
|
async getSyncStates(filter) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
for (const { siteId, table } of shards) {
|
|
285
|
+
assertScopedUser(filter.userId, "getSyncStates");
|
|
286
|
+
return (await mapWithConcurrency(await shardsForFilter(filter), SHARD_IO_CONCURRENCY, async ({ siteId, table }) => {
|
|
288
287
|
const { snapshot } = await readShard(siteId, table);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
288
|
+
const states = [];
|
|
289
|
+
for (const s of snapshot.syncStates) if (matchesSyncStateFilter(s, filter, { ignoreUserId: true })) states.push(s);
|
|
290
|
+
return states;
|
|
291
|
+
})).flat();
|
|
292
292
|
},
|
|
293
293
|
async setSyncState(scope, state, detail) {
|
|
294
|
+
assertScopedUser(scope.userId, "setSyncState");
|
|
294
295
|
if (scope.siteId === void 0) throw new Error("R2 manifest store requires sync states to carry siteId");
|
|
295
296
|
const at = detail?.at ?? now();
|
|
296
297
|
const scopeSearchType = inferSearchType(scope);
|
package/dist/entities.d.mts
CHANGED
|
@@ -34,8 +34,8 @@ interface QueryDimDeps {
|
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* Pure: distinct raw queries → dimension records. De-dupes, drops empties, and
|
|
37
|
-
* folds an empty/whitespace canonical back to the raw query so the
|
|
38
|
-
* total
|
|
37
|
+
* folds an empty/whitespace canonical back to the raw query so the dimension
|
|
38
|
+
* is total for read-time joins.
|
|
39
39
|
*/
|
|
40
40
|
declare function buildQueryDimRecords(queries: Iterable<string>, deps: QueryDimDeps): QueryDimRecord[];
|
|
41
41
|
interface QueryDimStore {
|
package/dist/iceberg/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_TABLES, INT_SEARCH_TYPE, IcebergAppendSinkOptions, IcebergCatalogConfig, IcebergColumn, IcebergColumnType, IcebergConnection, IcebergListedDataFile, IcebergPartitionField, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPartitionTransform, IcebergPrimitiveType, IcebergS3Config, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableName, IcebergTableOpResult, IcebergTableSpec, ListIcebergDataFilesOptions, PartitionKeyEncoding, SEARCH_TYPE_INT, Sink, assertIcebergTable, connectIcebergCatalog, createIcebergTables, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, icebergPartitionColumns, icebergPartitionSpecFor, icebergSchemaFor, icebergSchemasFor, icebergSortOrderFor, icebergTableSpec, isCommitRateLimited, isIcebergTable, listIcebergDataFiles, listIcebergTables } from "../_chunks/sink.mjs";
|
|
1
|
+
import { CatalogCache, CommitRetryOptions, ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_SCHEMAS_STRING, ICEBERG_TABLES, INT_SEARCH_TYPE, IcebergAppendSinkOptions, IcebergCatalogConfig, IcebergColumn, IcebergColumnType, IcebergConnection, IcebergListedDataFile, IcebergPartitionField, IcebergPartitionSpec, IcebergPartitionSpecField, IcebergPartitionTransform, IcebergPrimitiveType, IcebergS3Config, IcebergSchema, IcebergSchemaField, IcebergSortOrder, IcebergSortOrderField, IcebergTableName, IcebergTableOpResult, IcebergTableSpec, ListIcebergDataFilesOptions, PartitionKeyEncoding, SEARCH_TYPE_INT, Sink, assertIcebergTable, connectIcebergCatalog, createIcebergTables, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, icebergPartitionColumns, icebergPartitionSpecFor, icebergSchemaFor, icebergSchemasFor, icebergSortOrderFor, icebergTableSpec, isCommitRateLimited, isIcebergTable, listIcebergDataFiles, listIcebergTables } from "../_chunks/sink.mjs";
|
|
2
2
|
import { icebergCreateTable, icebergManifests, restCatalogLoadTable } from "../_chunks/libs/icebird.mjs";
|
|
3
3
|
type IcebergAppendSink = Sink;
|
|
4
4
|
/**
|
|
@@ -10,4 +10,4 @@ type IcebergAppendSink = Sink;
|
|
|
10
10
|
* with no rows never touches the network.
|
|
11
11
|
*/
|
|
12
12
|
declare function createIcebergAppendSink(options: IcebergAppendSinkOptions): IcebergAppendSink;
|
|
13
|
-
export { type CatalogCache, type CommitRetryOptions, type ConnectIcebergOptions, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_TABLES, INT_SEARCH_TYPE, type IcebergAppendSink, type IcebergAppendSinkOptions, type IcebergCatalogConfig, type IcebergColumn, type IcebergColumnType, type IcebergConnection, type IcebergListedDataFile, type IcebergPartitionField, type IcebergPartitionSpec, type IcebergPartitionSpecField, type IcebergPartitionTransform, type IcebergPrimitiveType, type IcebergS3Config, type IcebergSchema, type IcebergSchemaField, type IcebergSortOrder, type IcebergSortOrderField, type IcebergTableName, type IcebergTableOpResult, type IcebergTableSpec, type ListIcebergDataFilesOptions, type PartitionKeyEncoding, SEARCH_TYPE_INT, assertIcebergTable, connectIcebergCatalog, createIcebergAppendSink, createIcebergTables, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, icebergCreateTable, icebergManifests, icebergPartitionColumns, icebergPartitionSpecFor, icebergSchemaFor, icebergSchemasFor, icebergSortOrderFor, icebergTableSpec, isCommitRateLimited, isIcebergTable, listIcebergDataFiles, listIcebergTables, restCatalogLoadTable };
|
|
13
|
+
export { type CatalogCache, type CommitRetryOptions, type ConnectIcebergOptions, DEFAULT_PARTITION_KEY_ENCODING, ICEBERG_FIELD_ID_BASE, ICEBERG_PARTITION_COLUMNS, ICEBERG_PARTITION_SPEC, ICEBERG_SCHEMAS, ICEBERG_SCHEMAS_INT, ICEBERG_SCHEMAS_STRING, ICEBERG_TABLES, INT_SEARCH_TYPE, type IcebergAppendSink, type IcebergAppendSinkOptions, type IcebergCatalogConfig, type IcebergColumn, type IcebergColumnType, type IcebergConnection, type IcebergListedDataFile, type IcebergPartitionField, type IcebergPartitionSpec, type IcebergPartitionSpecField, type IcebergPartitionTransform, type IcebergPrimitiveType, type IcebergS3Config, type IcebergSchema, type IcebergSchemaField, type IcebergSortOrder, type IcebergSortOrderField, type IcebergTableName, type IcebergTableOpResult, type IcebergTableSpec, type ListIcebergDataFilesOptions, type PartitionKeyEncoding, SEARCH_TYPE_INT, assertIcebergTable, connectIcebergCatalog, createIcebergAppendSink, createIcebergTables, dropIcebergTables, ensureIcebergNamespace, icebergAppendRetrying, icebergCreateTable, icebergManifests, icebergPartitionColumns, icebergPartitionSpecFor, icebergSchemaFor, icebergSchemasFor, icebergSortOrderFor, icebergTableSpec, isCommitRateLimited, isIcebergTable, listIcebergDataFiles, listIcebergTables, restCatalogLoadTable };
|