@rocicorp/zero 1.11.0-canary.13 → 1.11.0-canary.14
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/out/analyze-query/src/analyze-cli.js +2 -2
- package/out/analyze-query/src/analyze-cli.js.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts +8 -1
- package/out/ast-to-zql/src/ast-to-zql.d.ts.map +1 -1
- package/out/ast-to-zql/src/ast-to-zql.js +25 -25
- package/out/ast-to-zql/src/ast-to-zql.js.map +1 -1
- package/out/otel/src/log-options.d.ts +16 -0
- package/out/otel/src/log-options.d.ts.map +1 -1
- package/out/otel/src/log-options.js +54 -1
- package/out/otel/src/log-options.js.map +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts +1 -1
- package/out/replicache/src/get-kv-store-provider.d.ts.map +1 -1
- package/out/replicache/src/get-kv-store-provider.js +4 -4
- package/out/replicache/src/get-kv-store-provider.js.map +1 -1
- package/out/replicache/src/kv/idb-store.d.ts +10 -1
- package/out/replicache/src/kv/idb-store.d.ts.map +1 -1
- package/out/replicache/src/kv/idb-store.js +18 -2
- package/out/replicache/src/kv/idb-store.js.map +1 -1
- package/out/replicache/src/kv/mem-fallback-store.d.ts +54 -0
- package/out/replicache/src/kv/mem-fallback-store.d.ts.map +1 -0
- package/out/replicache/src/kv/mem-fallback-store.js +177 -0
- package/out/replicache/src/kv/mem-fallback-store.js.map +1 -0
- package/out/replicache/src/kv/op-sqlite/store.js +3 -3
- package/out/replicache/src/kv/op-sqlite/store.js.map +1 -1
- package/out/replicache/src/kv/sqlite-store.d.ts +9 -0
- package/out/replicache/src/kv/sqlite-store.d.ts.map +1 -1
- package/out/replicache/src/kv/sqlite-store.js +86 -6
- package/out/replicache/src/kv/sqlite-store.js.map +1 -1
- package/out/replicache/src/new-client-channel.js.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.d.ts +17 -3
- package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
- package/out/replicache/src/persist/collect-idb-databases.js +81 -11
- package/out/replicache/src/persist/collect-idb-databases.js.map +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js +1 -1
- package/out/replicache/src/persist/idb-databases-store-db-name.js.map +1 -1
- package/out/replicache/src/persist/persist.d.ts.map +1 -1
- package/out/replicache/src/persist/persist.js +6 -3
- package/out/replicache/src/persist/persist.js.map +1 -1
- package/out/replicache/src/replicache-impl.d.ts +43 -3
- package/out/replicache/src/replicache-impl.d.ts.map +1 -1
- package/out/replicache/src/replicache-impl.js +135 -17
- package/out/replicache/src/replicache-impl.js.map +1 -1
- package/out/replicache/src/storage-failure.d.ts +37 -0
- package/out/replicache/src/storage-failure.d.ts.map +1 -0
- package/out/replicache/src/storage-failure.js +31 -0
- package/out/replicache/src/storage-failure.js.map +1 -0
- package/out/shared/src/random-values.js +12 -1
- package/out/shared/src/random-values.js.map +1 -1
- package/out/zero/package.js +1 -1
- package/out/zero/package.js.map +1 -1
- package/out/zero/src/zero.js +2 -1
- package/out/zero-cache/src/config/zero-config.d.ts +26 -0
- package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
- package/out/zero-cache/src/config/zero-config.js +36 -0
- package/out/zero-cache/src/config/zero-config.js.map +1 -1
- package/out/zero-cache/src/observability/log-throttle.d.ts +44 -0
- package/out/zero-cache/src/observability/log-throttle.d.ts.map +1 -0
- package/out/zero-cache/src/observability/log-throttle.js +76 -0
- package/out/zero-cache/src/observability/log-throttle.js.map +1 -0
- package/out/zero-cache/src/scripts/permissions.d.ts +16 -0
- package/out/zero-cache/src/scripts/permissions.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.d.ts.map +1 -1
- package/out/zero-cache/src/server/runner/zero-dispatcher.js +2 -0
- package/out/zero-cache/src/server/runner/zero-dispatcher.js.map +1 -1
- package/out/zero-cache/src/server/syncer.d.ts.map +1 -1
- package/out/zero-cache/src/server/syncer.js +20 -2
- package/out/zero-cache/src/server/syncer.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js +27 -15
- package/out/zero-cache/src/services/change-source/common/backfill-manager.js.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +1 -0
- package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/common/replica-schema.js +15 -3
- package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
- package/out/zero-cache/src/services/change-source/custom/change-source.js +2 -2
- package/out/zero-cache/src/services/change-source/custom/change-source.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts +9 -0
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +204 -18
- package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js +11 -2
- package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts +68 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js +141 -0
- package/out/zero-cache/src/services/change-source/protocol/backfill-progress.js.map +1 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts +43 -0
- package/out/zero-cache/src/services/change-source/protocol/current/data.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js +10 -1
- package/out/zero-cache/src/services/change-source/protocol/current/data.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts +57 -0
- package/out/zero-cache/src/services/change-source/protocol/current/downstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts +20 -6
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js +17 -7
- package/out/zero-cache/src/services/change-source/protocol/current/upstream.js.map +1 -1
- package/out/zero-cache/src/services/change-source/protocol/current.js +2 -1
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts +66 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js +154 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state-tracker.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts +50 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.d.ts.map +1 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js +268 -0
- package/out/zero-cache/src/services/change-streamer/backfill-state.js.map +1 -0
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js +7 -6
- package/out/zero-cache/src/services/change-streamer/change-streamer-http.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +20 -4
- package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts +26 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/change-streamer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js +1 -1
- package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts +88 -0
- package/out/zero-cache/src/services/change-streamer/subscribe.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js +9 -1
- package/out/zero-cache/src/services/change-streamer/subscribe.js.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +29 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
- package/out/zero-cache/src/services/change-streamer/subscriber.js +34 -0
- package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
- package/out/zero-cache/src/services/plannerz.d.ts +159 -0
- package/out/zero-cache/src/services/plannerz.d.ts.map +1 -0
- package/out/zero-cache/src/services/plannerz.js +263 -0
- package/out/zero-cache/src/services/plannerz.js.map +1 -0
- package/out/zero-cache/src/services/replicator/change-log-cookies.js +1 -1
- package/out/zero-cache/src/services/replicator/change-log-cookies.js.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/change-processor.js +44 -12
- package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/incremental-sync.js +4 -3
- package/out/zero-cache/src/services/replicator/incremental-sync.js.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts +23 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/schema/backfilling.js +54 -2
- package/out/zero-cache/src/services/replicator/schema/backfilling.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts +11 -3
- package/out/zero-cache/src/services/replicator/write-worker-client.d.ts.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker-client.js.map +1 -1
- package/out/zero-cache/src/services/replicator/write-worker.js +5 -1
- package/out/zero-cache/src/services/replicator/write-worker.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts +81 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js +128 -0
- package/out/zero-cache/src/services/view-syncer/deferred-writes-budget.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/inspect-handler.js +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +36 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +362 -20
- package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts +7 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js +35 -0
- package/out/zero-cache/src/services/view-syncer/plan-warnings.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts +25 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js +28 -0
- package/out/zero-cache/src/services/view-syncer/query-shape.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts +77 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.d.ts.map +1 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js +206 -0
- package/out/zero-cache/src/services/view-syncer/query-stats.js.map +1 -0
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +11 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js +7 -1
- package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
- package/out/zero-cache/src/services/view-syncer/view-syncer.js +49 -3
- package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
- package/out/zero-cache/src/workers/connection.js +3 -3
- package/out/zero-cache/src/workers/connection.js.map +1 -1
- package/out/zero-cache/src/workers/syncer.js +2 -2
- package/out/zero-cache/src/workers/syncer.js.map +1 -1
- package/out/zero-client/src/client/options.d.ts +29 -0
- package/out/zero-client/src/client/options.d.ts.map +1 -1
- package/out/zero-client/src/client/options.js.map +1 -1
- package/out/zero-client/src/client/version.js +1 -1
- package/out/zero-client/src/client/zero.d.ts.map +1 -1
- package/out/zero-client/src/client/zero.js +8 -23
- package/out/zero-client/src/client/zero.js.map +1 -1
- package/out/zero-client/src/mod.d.ts +1 -0
- package/out/zero-client/src/mod.d.ts.map +1 -1
- package/out/zero-client/src/mod.js +1 -0
- package/out/zero-protocol/src/down.d.ts +2 -2
- package/out/zero-react/src/zero.js +1 -0
- package/out/zero-solid/src/zero.js +1 -0
- package/out/zql/src/builder/builder.d.ts +6 -0
- package/out/zql/src/builder/builder.d.ts.map +1 -1
- package/out/zql/src/builder/builder.js +4 -3
- package/out/zql/src/builder/builder.js.map +1 -1
- package/out/zql/src/ivm/memory-source.d.ts +13 -0
- package/out/zql/src/ivm/memory-source.d.ts.map +1 -1
- package/out/zql/src/ivm/memory-source.js +1 -1
- package/out/zql/src/ivm/memory-source.js.map +1 -1
- package/out/zql/src/planner/planner-builder.d.ts +12 -1
- package/out/zql/src/planner/planner-builder.d.ts.map +1 -1
- package/out/zql/src/planner/planner-builder.js +8 -1
- package/out/zql/src/planner/planner-builder.js.map +1 -1
- package/out/zql/src/planner/planner-connection.d.ts +43 -0
- package/out/zql/src/planner/planner-connection.d.ts.map +1 -1
- package/out/zql/src/planner/planner-connection.js +31 -4
- package/out/zql/src/planner/planner-connection.js.map +1 -1
- package/out/zql/src/planner/planner-graph.d.ts +10 -0
- package/out/zql/src/planner/planner-graph.d.ts.map +1 -1
- package/out/zql/src/planner/planner-graph.js +14 -0
- package/out/zql/src/planner/planner-graph.js.map +1 -1
- package/out/zql/src/planner/planner-warnings.d.ts +83 -0
- package/out/zql/src/planner/planner-warnings.d.ts.map +1 -0
- package/out/zql/src/planner/planner-warnings.js +78 -0
- package/out/zql/src/planner/planner-warnings.js.map +1 -0
- package/out/zql/src/query/measure-push-operator.d.ts.map +1 -1
- package/out/zql/src/query/measure-push-operator.js +30 -8
- package/out/zql/src/query/measure-push-operator.js.map +1 -1
- package/out/zqlite/src/pending-delta.d.ts +110 -0
- package/out/zqlite/src/pending-delta.d.ts.map +1 -0
- package/out/zqlite/src/pending-delta.js +278 -0
- package/out/zqlite/src/pending-delta.js.map +1 -0
- package/out/zqlite/src/query-delegate.d.ts.map +1 -1
- package/out/zqlite/src/query-delegate.js +5 -1
- package/out/zqlite/src/query-delegate.js.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-cost-model.js +52 -1
- package/out/zqlite/src/sqlite-cost-model.js.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.d.ts.map +1 -1
- package/out/zqlite/src/sqlite-stat-fanout.js +2 -26
- package/out/zqlite/src/sqlite-stat-fanout.js.map +1 -1
- package/out/zqlite/src/sqlite-stat4-sample.d.ts +37 -0
- package/out/zqlite/src/sqlite-stat4-sample.d.ts.map +1 -0
- package/out/zqlite/src/sqlite-stat4-sample.js +61 -0
- package/out/zqlite/src/sqlite-stat4-sample.js.map +1 -0
- package/out/zqlite/src/table-source.d.ts +68 -0
- package/out/zqlite/src/table-source.d.ts.map +1 -1
- package/out/zqlite/src/table-source.js +206 -7
- package/out/zqlite/src/table-source.js.map +1 -1
- package/package.json +1 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts +0 -35
- package/out/replicache/src/kv/idb-store-with-mem-fallback.d.ts.map +0 -1
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js +0 -83
- package/out/replicache/src/kv/idb-store-with-mem-fallback.js.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts +0 -156
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.d.ts.map +0 -1
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js +0 -15
- package/out/zero-cache/src/services/change-source/pg/backfill-resume.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"idb-store-with-mem-fallback.js","names":["#lc","#name","#store","#withBrainTransplant"],"sources":["../../../../../replicache/src/kv/idb-store-with-mem-fallback.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {IDBOpenError, IDBStore} from './idb-store.ts';\nimport {MemStore, dropMemStore} from './mem-store.ts';\nimport type {Read, Store, Write} from './store.ts';\n\n/**\n * This store uses an {@link IDBStore} by default. If `indexedDB.open` rejects\n * (WebKit's \"Unable to open database file on disk\" and \"Error creating Records\n * table (13) - database or disk is full\", or Firefox private browsing), the\n * {@link IDBStore} rejects `read()` and `write()` with an {@link IDBOpenError}\n * and we switch out the implementation to use a {@link MemStore} instead. Any\n * other error, such as a transaction error on a database that did open, is\n * rethrown, so a store that has data is never swapped for an empty one behind\n * the caller's back. Every caller whose call failed with the open error retries\n * on the memory store, so concurrent first calls all succeed and the switch is\n * logged once.\n *\n * The reason this is relatively complicated is that when {@link IDBStore} is\n * created, it calls `openDatabase` synchronously, but that returns a `Promise`\n * that will reject if the database cannot be opened. We don't await this\n * promise until we call `read` or `write` so we cannot do the switch until\n * then.\n */\n\nexport class IDBStoreWithMemFallback implements Store {\n readonly #lc: LogContext;\n readonly #name: string;\n #store: Store;\n constructor(lc: LogContext, name: string) {\n this.#lc = lc;\n this.#name = name;\n this.#store = new IDBStore(name);\n }\n\n read(): Promise<Read> {\n return this.#withBrainTransplant(s => s.read());\n }\n\n write(): Promise<Write> {\n return this.#withBrainTransplant(s => s.write());\n }\n\n async #withBrainTransplant<T extends Read>(\n f: (store: Store) => Promise<T>,\n ): Promise<T> {\n try {\n return await f(this.#store);\n } catch (e) {\n if (e instanceof IDBOpenError) {\n // It is possible that we end up with multiple pending read/write and\n // they all reject. Make sure we only replace the implementation once.\n if (this.#store instanceof IDBStore) {\n this.#lc.warn?.(\n 'Switching to MemStore because IndexedDB failed to open',\n e,\n );\n this.#store = new MemStore(this.#name);\n }\n return f(this.#store);\n }\n throw e;\n }\n }\n\n close(): Promise<void> {\n return this.#store.close();\n }\n\n get closed(): boolean {\n return this.#store.closed;\n }\n\n /**\n * `'idb'` until an open failure switches this store to memory, then `'mem'`.\n */\n get kind(): string | undefined {\n return this.#store.kind;\n }\n}\n\nexport function newIDBStoreWithMemFallback(\n lc: LogContext,\n name: string,\n): Store {\n return new IDBStoreWithMemFallback(lc, name);\n}\n\nexport function dropIDBStoreWithMemFallback(name: string): Promise<void> {\n return dropIDBStore(name).catch((e: unknown) => {\n if (e instanceof DOMException) {\n return dropMemStore(name);\n }\n throw e;\n });\n}\n\nfunction dropIDBStore(name: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const req = indexedDB.deleteDatabase(name);\n req.onsuccess = () => resolve();\n req.onerror = () => reject(req.error);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAwBA,IAAa,0BAAb,MAAsD;CACpD;CACA;CACA;CACA,YAAY,IAAgB,MAAc;EACxC,KAAKA,MAAM;EACX,KAAKC,QAAQ;EACb,KAAKC,SAAS,IAAI,SAAS,IAAI;CACjC;CAEA,OAAsB;EACpB,OAAO,KAAKC,sBAAqB,MAAK,EAAE,KAAK,CAAC;CAChD;CAEA,QAAwB;EACtB,OAAO,KAAKA,sBAAqB,MAAK,EAAE,MAAM,CAAC;CACjD;CAEA,MAAMA,qBACJ,GACY;EACZ,IAAI;GACF,OAAO,MAAM,EAAE,KAAKD,MAAM;EAC5B,SAAS,GAAG;GACV,IAAI,aAAa,cAAc;IAG7B,IAAI,KAAKA,kBAAkB,UAAU;KACnC,KAAKF,IAAI,OACP,0DACA,CACF;KACA,KAAKE,SAAS,IAAI,SAAS,KAAKD,KAAK;IACvC;IACA,OAAO,EAAE,KAAKC,MAAM;GACtB;GACA,MAAM;EACR;CACF;CAEA,QAAuB;EACrB,OAAO,KAAKA,OAAO,MAAM;CAC3B;CAEA,IAAI,SAAkB;EACpB,OAAO,KAAKA,OAAO;CACrB;;;;CAKA,IAAI,OAA2B;EAC7B,OAAO,KAAKA,OAAO;CACrB;AACF;AAEA,SAAgB,2BACd,IACA,MACO;CACP,OAAO,IAAI,wBAAwB,IAAI,IAAI;AAC7C;AAEA,SAAgB,4BAA4B,MAA6B;CACvE,OAAO,aAAa,IAAI,EAAE,OAAO,MAAe;EAC9C,IAAI,aAAa,cACf,OAAO,aAAa,IAAI;EAE1B,MAAM;CACR,CAAC;AACH;AAEA,SAAS,aAAa,MAA6B;CACjD,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,MAAM,UAAU,eAAe,IAAI;EACzC,IAAI,kBAAkB,QAAQ;EAC9B,IAAI,gBAAgB,OAAO,IAAI,KAAK;CACtC,CAAC;AACH"}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
import type { PublishedTableSpec } from '../../../db/specs.ts';
|
|
2
|
-
import type { PostgresDB, PostgresTransaction } from '../../../types/pg.ts';
|
|
3
|
-
/**
|
|
4
|
-
* The subset of a column spec needed to decide whether the column can
|
|
5
|
-
* participate in a resumable row key, and to render its values as SQL
|
|
6
|
-
* literals.
|
|
7
|
-
*
|
|
8
|
-
* `collationIsDeterministic` is not part of {@link PublishedTableSpec} (it is
|
|
9
|
-
* stripped when the published schema is canonicalized); it is fetched
|
|
10
|
-
* separately with {@link getKeyCollations}.
|
|
11
|
-
*/
|
|
12
|
-
export type ResumeColumnSpec = {
|
|
13
|
-
readonly typeOID: number;
|
|
14
|
-
readonly pgTypeClass?: string | null | undefined;
|
|
15
|
-
readonly elemPgTypeClass?: string | null | undefined;
|
|
16
|
-
readonly collationIsDeterministic?: boolean | null | undefined;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* A **mark** is the Postgres text form of a row key's values, in
|
|
20
|
-
* `relation.rowKey.columns` order. It is computed by the change source (which
|
|
21
|
-
* knows the column types), stored and returned opaquely by subscribers, and
|
|
22
|
-
* compared only for equality outside of Postgres.
|
|
23
|
-
*/
|
|
24
|
-
export type Mark = readonly string[];
|
|
25
|
-
/**
|
|
26
|
-
* Whether a single column's values have an exact text form whose literal is
|
|
27
|
-
* trivially safe to inline into a `COPY (query) TO STDOUT` (which accepts no
|
|
28
|
-
* bind parameters).
|
|
29
|
-
*
|
|
30
|
-
* Types whose text form is lossy (`numeric`, the timestamp family, floats) and
|
|
31
|
-
* columns with a non-deterministic collation are excluded. Excluding the latter
|
|
32
|
-
* is conservatism rather than necessity: a unique index under a
|
|
33
|
-
* non-deterministic collation enforces uniqueness by the same equality that
|
|
34
|
-
* orders it.
|
|
35
|
-
*/
|
|
36
|
-
export declare function isResumableColumn(spec: ResumeColumnSpec): boolean;
|
|
37
|
-
/**
|
|
38
|
-
* Whether every column of the row key is resumable. When this is false the
|
|
39
|
-
* change source never attaches a `lastKey` to its `backfill` messages, so no
|
|
40
|
-
* subscriber ever holds a mark for the table and every run starts from the
|
|
41
|
-
* beginning.
|
|
42
|
-
*/
|
|
43
|
-
export declare function isResumableKey(specs: readonly ResumeColumnSpec[]): boolean;
|
|
44
|
-
/**
|
|
45
|
-
* Renders one mark value as a SQL literal.
|
|
46
|
-
*
|
|
47
|
-
* The literal must be a *constant* expression so that Postgres can use it as
|
|
48
|
-
* an index condition. (This rules out, e.g.,
|
|
49
|
-
* `convert_from(decode(...), 'UTF8')`, which is `STABLE` and therefore
|
|
50
|
-
* degrades a resume seek into a filtered full index scan.)
|
|
51
|
-
*
|
|
52
|
-
* @throws if `text` is not a valid value for the column's type, which
|
|
53
|
-
* indicates a corrupt or forged mark.
|
|
54
|
-
*/
|
|
55
|
-
export declare function keyLiteral(spec: ResumeColumnSpec, text: string): string;
|
|
56
|
-
export declare class InvalidMarkError extends Error {
|
|
57
|
-
readonly name = "InvalidMarkError";
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* The `ORDER BY` clause for an ordered backfill: the row key columns in
|
|
61
|
-
* `relation.rowKey.columns` order, each in its own (native) collation.
|
|
62
|
-
*
|
|
63
|
-
* Ordering in the column's native collation — rather than `COLLATE "C"` —
|
|
64
|
-
* keeps the key's btree usable for both the ordering and the resume seek. Any
|
|
65
|
-
* total, snapshot-stable order suffices, because keys are only ever compared
|
|
66
|
-
* by Postgres.
|
|
67
|
-
*/
|
|
68
|
-
export declare function orderByRowKey(rowKeyColumns: readonly string[]): string;
|
|
69
|
-
/**
|
|
70
|
-
* A boolean SQL expression selecting rows whose key sorts strictly after
|
|
71
|
-
* `mark`, using a row-constructor comparison so that it is index-optimizable
|
|
72
|
-
* on the key's btree.
|
|
73
|
-
*/
|
|
74
|
-
export declare function resumeWhere(rowKeyColumns: readonly string[], specs: readonly ResumeColumnSpec[], mark: Mark): string;
|
|
75
|
-
/**
|
|
76
|
-
* The row filter of the table's publications, as a parenthesized boolean
|
|
77
|
-
* expression, or `null` if the table is published without a filter.
|
|
78
|
-
*/
|
|
79
|
-
export declare function publicationRowFilter(table: PublishedTableSpec): string | null;
|
|
80
|
-
/**
|
|
81
|
-
* Returns whether any row of `table` has a key in `(from, to]`, under the
|
|
82
|
-
* table's publication row filter. `from === null` means "from the beginning",
|
|
83
|
-
* in which case the answer is whether any row exists up to and including `to`.
|
|
84
|
-
*
|
|
85
|
-
* This is the only key comparison the system performs outside of the backfill
|
|
86
|
-
* COPY itself, and it is what the {@link BackfillManager} asks in order to
|
|
87
|
-
* decide whether a declaring subscriber needs rows that the running run has
|
|
88
|
-
* already passed.
|
|
89
|
-
*/
|
|
90
|
-
export declare function rowsExist(sql: PostgresDB | PostgresTransaction, table: PublishedTableSpec, rowKeyColumns: readonly string[], specs: readonly ResumeColumnSpec[], from: Mark | null, to: Mark): Promise<boolean>;
|
|
91
|
-
/**
|
|
92
|
-
* Converts a decoded row key value to its mark (Postgres text) form.
|
|
93
|
-
*
|
|
94
|
-
* Accepts the shapes produced by both the binary COPY decoders
|
|
95
|
-
* (`pg-copy-binary.ts`) and the text COPY type parsers.
|
|
96
|
-
*/
|
|
97
|
-
export declare function textKey(spec: ResumeColumnSpec, value: unknown): string;
|
|
98
|
-
/**
|
|
99
|
-
* Computes the mark of the last row of a batch of `rowValues`, whose leading
|
|
100
|
-
* values are the row key values (see the `backfill` message).
|
|
101
|
-
*/
|
|
102
|
-
export declare function markOfLastRow(specs: readonly ResumeColumnSpec[], rowValues: readonly (readonly unknown[])[]): string[];
|
|
103
|
-
/**
|
|
104
|
-
* Reads the collation determinism of the specified columns.
|
|
105
|
-
*
|
|
106
|
-
* `PublishedTableSpec` does not carry this (the published schema strips it
|
|
107
|
-
* during canonicalization), so it is read directly. `null` means the column
|
|
108
|
-
* has no collation, e.g. an integer or uuid column.
|
|
109
|
-
*/
|
|
110
|
-
export declare function getKeyCollations(sql: PostgresDB | PostgresTransaction, relationOID: number, columns: readonly string[]): Promise<Map<string, boolean | null>>;
|
|
111
|
-
/**
|
|
112
|
-
* The minimum `pg_stats.correlation` of a row key's leading column for its
|
|
113
|
-
* table's backfill to be ordered (and therefore resumable).
|
|
114
|
-
*
|
|
115
|
-
* Ordering a backfill COPY by the row key means walking the key's index in
|
|
116
|
-
* order and fetching each row from the heap. When the heap's physical order
|
|
117
|
-
* matches the key order the fetches are sequential and ordering is nearly
|
|
118
|
-
* free; when it does not, each of N rows is a scattered visit into the same
|
|
119
|
-
* pages, and throughput collapses.
|
|
120
|
-
*
|
|
121
|
-
* Measured on 1M rows (~155MB, warm cache, PG 17) with
|
|
122
|
-
* `backfill-resume.bench.pg.ts`, ordered COPY as a fraction of unordered:
|
|
123
|
-
*
|
|
124
|
-
* | correlation | ordered / unordered |
|
|
125
|
-
* | ----------- | ------------------- |
|
|
126
|
-
* | 1.0 | 82% |
|
|
127
|
-
* | 0.99995 | 51% |
|
|
128
|
-
* | 0.995 | 32% |
|
|
129
|
-
* | 0.70 | 22% |
|
|
130
|
-
* | 0.0 (uuid) | 18% |
|
|
131
|
-
* | 0.0 (text) | 17% |
|
|
132
|
-
*
|
|
133
|
-
* Correlation saturates near 1, so the threshold has to be tight to hold
|
|
134
|
-
* ordering within ~2x of an unordered COPY. In practice this admits
|
|
135
|
-
* append-mostly tables with a monotonic key, and `CLUSTER`ed tables; a table
|
|
136
|
-
* with a random key (uuid, nanoid) is not ordered, and so is not resumable,
|
|
137
|
-
* and keeps exactly today's behavior.
|
|
138
|
-
*/
|
|
139
|
-
export declare const MIN_KEY_CORRELATION = 0.9999;
|
|
140
|
-
/**
|
|
141
|
-
* Reads `pg_stats.correlation` for the leading column of the row key: the
|
|
142
|
-
* statistical correlation between the column's value order and the physical
|
|
143
|
-
* row order.
|
|
144
|
-
*
|
|
145
|
-
* Returns `null` if the table has never been analyzed, or if the column has
|
|
146
|
-
* no correlation statistic. Both are treated as "do not order" by
|
|
147
|
-
* {@link isCheaplyOrderable}, which keeps today's behavior for a table whose
|
|
148
|
-
* cost is unknown.
|
|
149
|
-
*/
|
|
150
|
-
export declare function getKeyCorrelation(sql: PostgresDB | PostgresTransaction, relationOID: number, leadingKeyColumn: string): Promise<number | null>;
|
|
151
|
-
/**
|
|
152
|
-
* Whether ordering a backfill of the table by its row key is cheap enough to
|
|
153
|
-
* be worth doing, based on {@link getKeyCorrelation}.
|
|
154
|
-
*/
|
|
155
|
-
export declare function isCheaplyOrderable(correlation: number | null, minCorrelation?: number): boolean;
|
|
156
|
-
//# sourceMappingURL=backfill-resume.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"backfill-resume.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/change-source/pg/backfill-resume.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
//#region ../zero-cache/src/services/change-source/pg/backfill-resume.ts
|
|
2
|
-
/**
|
|
3
|
-
* The row filter of the table's publications, as a parenthesized boolean
|
|
4
|
-
* expression, or `null` if the table is published without a filter.
|
|
5
|
-
*/
|
|
6
|
-
function publicationRowFilter(table) {
|
|
7
|
-
const rowFilters = Object.values(table.publications).map(({ rowFilter }) => rowFilter);
|
|
8
|
-
if (rowFilters.some((filter) => filter === null)) return null;
|
|
9
|
-
const filters = rowFilters.filter((filter) => !!filter);
|
|
10
|
-
return filters.length === 0 ? null : `(${filters.join(" OR ")})`;
|
|
11
|
-
}
|
|
12
|
-
//#endregion
|
|
13
|
-
export { publicationRowFilter };
|
|
14
|
-
|
|
15
|
-
//# sourceMappingURL=backfill-resume.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"backfill-resume.js","names":[],"sources":["../../../../../../../zero-cache/src/services/change-source/pg/backfill-resume.ts"],"sourcesContent":["import {must} from '../../../../../shared/src/must.ts';\nimport * as PostgresTypeClass from '../../../db/postgres-type-class-enum.ts';\nimport type {PublishedTableSpec} from '../../../db/specs.ts';\nimport {\n BOOL,\n INT2,\n INT4,\n INT8,\n TEXT,\n UUID,\n VARCHAR,\n} from '../../../types/pg-types.ts';\nimport type {PostgresDB, PostgresTransaction} from '../../../types/pg.ts';\nimport {id} from '../../../types/sql.ts';\n\n/**\n * The subset of a column spec needed to decide whether the column can\n * participate in a resumable row key, and to render its values as SQL\n * literals.\n *\n * `collationIsDeterministic` is not part of {@link PublishedTableSpec} (it is\n * stripped when the published schema is canonicalized); it is fetched\n * separately with {@link getKeyCollations}.\n */\nexport type ResumeColumnSpec = {\n readonly typeOID: number;\n readonly pgTypeClass?: string | null | undefined;\n readonly elemPgTypeClass?: string | null | undefined;\n readonly collationIsDeterministic?: boolean | null | undefined;\n};\n\n/**\n * A **mark** is the Postgres text form of a row key's values, in\n * `relation.rowKey.columns` order. It is computed by the change source (which\n * knows the column types), stored and returned opaquely by subscribers, and\n * compared only for equality outside of Postgres.\n */\nexport type Mark = readonly string[];\n\nconst INT_TYPES: ReadonlySet<number> = new Set([INT2, INT4, INT8]);\nconst TEXT_TYPES: ReadonlySet<number> = new Set([TEXT, VARCHAR]);\n\nconst INT_RE = /^-?[0-9]+$/;\nconst UUID_RE =\n /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;\n\n/**\n * Whether a single column's values have an exact text form whose literal is\n * trivially safe to inline into a `COPY (query) TO STDOUT` (which accepts no\n * bind parameters).\n *\n * Types whose text form is lossy (`numeric`, the timestamp family, floats) and\n * columns with a non-deterministic collation are excluded. Excluding the latter\n * is conservatism rather than necessity: a unique index under a\n * non-deterministic collation enforces uniqueness by the same equality that\n * orders it.\n */\nexport function isResumableColumn(spec: ResumeColumnSpec): boolean {\n if (spec.elemPgTypeClass !== null && spec.elemPgTypeClass !== undefined) {\n return false; // array types\n }\n if (spec.pgTypeClass === PostgresTypeClass.Enum) {\n return false; // enum sort order is by attnum, not by text\n }\n const {typeOID} = spec;\n if (TEXT_TYPES.has(typeOID)) {\n // A non-deterministic collation is excluded; no collation at all\n // (`null`/`undefined`) is not expected for a text column, but is treated\n // conservatively as unknown.\n return spec.collationIsDeterministic === true;\n }\n return INT_TYPES.has(typeOID) || typeOID === UUID || typeOID === BOOL;\n}\n\n/**\n * Whether every column of the row key is resumable. When this is false the\n * change source never attaches a `lastKey` to its `backfill` messages, so no\n * subscriber ever holds a mark for the table and every run starts from the\n * beginning.\n */\nexport function isResumableKey(specs: readonly ResumeColumnSpec[]): boolean {\n return specs.length > 0 && specs.every(isResumableColumn);\n}\n\n/**\n * Escapes a string as a Postgres `E'...'` literal, which has the same meaning\n * whether or not `standard_conforming_strings` is on.\n *\n * A plain `'...'` literal would suffice under the default (and, since PG 9.1,\n * only supported) setting, but the `E` form makes the escaping explicit and\n * independent of server configuration.\n */\nfunction escapeString(value: string): string {\n return `E'${value.replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")}'`;\n}\n\n/**\n * Renders one mark value as a SQL literal.\n *\n * The literal must be a *constant* expression so that Postgres can use it as\n * an index condition. (This rules out, e.g.,\n * `convert_from(decode(...), 'UTF8')`, which is `STABLE` and therefore\n * degrades a resume seek into a filtered full index scan.)\n *\n * @throws if `text` is not a valid value for the column's type, which\n * indicates a corrupt or forged mark.\n */\nexport function keyLiteral(spec: ResumeColumnSpec, text: string): string {\n const {typeOID} = spec;\n if (INT_TYPES.has(typeOID)) {\n if (!INT_RE.test(text)) {\n throw new InvalidMarkError(`${text} is not an integer`);\n }\n return text;\n }\n if (typeOID === UUID) {\n if (!UUID_RE.test(text)) {\n throw new InvalidMarkError(`${text} is not a uuid`);\n }\n return `'${text.toLowerCase()}'::uuid`;\n }\n if (typeOID === BOOL) {\n if (text !== 'true' && text !== 'false') {\n throw new InvalidMarkError(`${text} is not a boolean`);\n }\n return text;\n }\n if (TEXT_TYPES.has(typeOID)) {\n return escapeString(text);\n }\n throw new InvalidMarkError(`type OID ${typeOID} is not resumable`);\n}\n\nexport class InvalidMarkError extends Error {\n readonly name = 'InvalidMarkError';\n}\n\n/**\n * The `ORDER BY` clause for an ordered backfill: the row key columns in\n * `relation.rowKey.columns` order, each in its own (native) collation.\n *\n * Ordering in the column's native collation — rather than `COLLATE \"C\"` —\n * keeps the key's btree usable for both the ordering and the resume seek. Any\n * total, snapshot-stable order suffices, because keys are only ever compared\n * by Postgres.\n */\nexport function orderByRowKey(rowKeyColumns: readonly string[]): string {\n return rowKeyColumns.map(id).join(',');\n}\n\n/**\n * A boolean SQL expression selecting rows whose key sorts strictly after\n * `mark`, using a row-constructor comparison so that it is index-optimizable\n * on the key's btree.\n */\nexport function resumeWhere(\n rowKeyColumns: readonly string[],\n specs: readonly ResumeColumnSpec[],\n mark: Mark,\n): string {\n return rowComparison(rowKeyColumns, specs, mark, '>');\n}\n\nfunction rowComparison(\n rowKeyColumns: readonly string[],\n specs: readonly ResumeColumnSpec[],\n mark: Mark,\n op: '>' | '<=',\n): string {\n if (rowKeyColumns.length !== mark.length) {\n throw new InvalidMarkError(\n `mark has ${mark.length} values for a ${rowKeyColumns.length} column key`,\n );\n }\n const cols = rowKeyColumns.map(id).join(',');\n const vals = mark.map((text, i) => keyLiteral(specs[i], text)).join(',');\n return `(${cols}) ${op} (${vals})`;\n}\n\n/**\n * The row filter of the table's publications, as a parenthesized boolean\n * expression, or `null` if the table is published without a filter.\n */\nexport function publicationRowFilter(table: PublishedTableSpec): string | null {\n const rowFilters = Object.values(table.publications).map(\n ({rowFilter}) => rowFilter,\n );\n // PostgreSQL publishes the union of all publications. An unfiltered\n // publication therefore makes every row eligible, regardless of filters on\n // any other publication containing the table.\n if (rowFilters.some(filter => filter === null)) {\n return null;\n }\n const filters = rowFilters.filter((filter): filter is string => !!filter);\n return filters.length === 0 ? null : `(${filters.join(' OR ')})`;\n}\n\n/**\n * Returns whether any row of `table` has a key in `(from, to]`, under the\n * table's publication row filter. `from === null` means \"from the beginning\",\n * in which case the answer is whether any row exists up to and including `to`.\n *\n * This is the only key comparison the system performs outside of the backfill\n * COPY itself, and it is what the {@link BackfillManager} asks in order to\n * decide whether a declaring subscriber needs rows that the running run has\n * already passed.\n */\nexport async function rowsExist(\n sql: PostgresDB | PostgresTransaction,\n table: PublishedTableSpec,\n rowKeyColumns: readonly string[],\n specs: readonly ResumeColumnSpec[],\n from: Mark | null,\n to: Mark,\n): Promise<boolean> {\n const conditions = [\n publicationRowFilter(table),\n from === null ? null : rowComparison(rowKeyColumns, specs, from, '>'),\n rowComparison(rowKeyColumns, specs, to, '<='),\n ].filter(c => c !== null);\n const rows = await sql.unsafe(\n /*sql*/ `SELECT 1 FROM ${id(table.schema)}.${id(table.name)} ` +\n `WHERE ${conditions.join(' AND ')} LIMIT 1`,\n );\n return rows.length > 0;\n}\n\n/**\n * Converts a decoded row key value to its mark (Postgres text) form.\n *\n * Accepts the shapes produced by both the binary COPY decoders\n * (`pg-copy-binary.ts`) and the text COPY type parsers.\n */\nexport function textKey(spec: ResumeColumnSpec, value: unknown): string {\n const {typeOID} = spec;\n if (value === null || value === undefined) {\n // Row key columns are `NOT NULL` (see `db/lite-tables.ts`), so this\n // indicates a bug rather than data.\n throw new InvalidMarkError(`null value in a row key`);\n }\n if (typeOID === BOOL) {\n switch (typeof value) {\n case 'boolean':\n return value ? 'true' : 'false';\n case 'number':\n case 'bigint':\n return value ? 'true' : 'false';\n case 'string':\n return value === 't' || value === 'true' ? 'true' : 'false';\n }\n }\n const text = typeof value === 'string' ? value : String(value);\n if (typeOID === UUID) {\n return text.toLowerCase();\n }\n return text;\n}\n\n/**\n * Computes the mark of the last row of a batch of `rowValues`, whose leading\n * values are the row key values (see the `backfill` message).\n */\nexport function markOfLastRow(\n specs: readonly ResumeColumnSpec[],\n rowValues: readonly (readonly unknown[])[],\n): string[] {\n const last = must(rowValues.at(-1), `no rows`);\n return specs.map((spec, i) => textKey(spec, last[i]));\n}\n\n/**\n * Reads the collation determinism of the specified columns.\n *\n * `PublishedTableSpec` does not carry this (the published schema strips it\n * during canonicalization), so it is read directly. `null` means the column\n * has no collation, e.g. an integer or uuid column.\n */\nexport async function getKeyCollations(\n sql: PostgresDB | PostgresTransaction,\n relationOID: number,\n columns: readonly string[],\n): Promise<Map<string, boolean | null>> {\n const rows = await sql<{col: string; deterministic: boolean | null}[]>`\n SELECT attname AS \"col\", coll.collisdeterministic AS \"deterministic\"\n FROM pg_attribute\n LEFT JOIN pg_collation coll ON coll.oid = attcollation\n WHERE attrelid = ${relationOID} AND attname IN ${sql(columns as string[])}`;\n return new Map(rows.map(({col, deterministic}) => [col, deterministic]));\n}\n\n/**\n * The minimum `pg_stats.correlation` of a row key's leading column for its\n * table's backfill to be ordered (and therefore resumable).\n *\n * Ordering a backfill COPY by the row key means walking the key's index in\n * order and fetching each row from the heap. When the heap's physical order\n * matches the key order the fetches are sequential and ordering is nearly\n * free; when it does not, each of N rows is a scattered visit into the same\n * pages, and throughput collapses.\n *\n * Measured on 1M rows (~155MB, warm cache, PG 17) with\n * `backfill-resume.bench.pg.ts`, ordered COPY as a fraction of unordered:\n *\n * | correlation | ordered / unordered |\n * | ----------- | ------------------- |\n * | 1.0 | 82% |\n * | 0.99995 | 51% |\n * | 0.995 | 32% |\n * | 0.70 | 22% |\n * | 0.0 (uuid) | 18% |\n * | 0.0 (text) | 17% |\n *\n * Correlation saturates near 1, so the threshold has to be tight to hold\n * ordering within ~2x of an unordered COPY. In practice this admits\n * append-mostly tables with a monotonic key, and `CLUSTER`ed tables; a table\n * with a random key (uuid, nanoid) is not ordered, and so is not resumable,\n * and keeps exactly today's behavior.\n */\nexport const MIN_KEY_CORRELATION = 0.9999;\n\n/**\n * Reads `pg_stats.correlation` for the leading column of the row key: the\n * statistical correlation between the column's value order and the physical\n * row order.\n *\n * Returns `null` if the table has never been analyzed, or if the column has\n * no correlation statistic. Both are treated as \"do not order\" by\n * {@link isCheaplyOrderable}, which keeps today's behavior for a table whose\n * cost is unknown.\n */\nexport async function getKeyCorrelation(\n sql: PostgresDB | PostgresTransaction,\n relationOID: number,\n leadingKeyColumn: string,\n): Promise<number | null> {\n const rows = await sql<{correlation: number | null}[]>`\n SELECT s.correlation\n FROM pg_stats s, pg_class c, pg_namespace n\n WHERE c.oid = ${relationOID}\n AND n.oid = c.relnamespace\n AND s.schemaname = n.nspname\n AND s.tablename = c.relname\n AND s.attname = ${leadingKeyColumn}`;\n return rows[0]?.correlation ?? null;\n}\n\n/**\n * Whether ordering a backfill of the table by its row key is cheap enough to\n * be worth doing, based on {@link getKeyCorrelation}.\n */\nexport function isCheaplyOrderable(\n correlation: number | null,\n minCorrelation: number = MIN_KEY_CORRELATION,\n): boolean {\n return correlation !== null && Math.abs(correlation) >= minCorrelation;\n}\n"],"mappings":";;;;;AAuLA,SAAgB,qBAAqB,OAA0C;CAC7E,MAAM,aAAa,OAAO,OAAO,MAAM,YAAY,EAAE,KAClD,EAAC,gBAAe,SACnB;CAIA,IAAI,WAAW,MAAK,WAAU,WAAW,IAAI,GAC3C,OAAO;CAET,MAAM,UAAU,WAAW,QAAQ,WAA6B,CAAC,CAAC,MAAM;CACxE,OAAO,QAAQ,WAAW,IAAI,OAAO,IAAI,QAAQ,KAAK,MAAM,EAAE;AAChE"}
|