@rocicorp/zero 0.26.1-canary.8 → 0.26.1

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.
Files changed (75) hide show
  1. package/out/analyze-query/src/bin-analyze.js +3 -0
  2. package/out/analyze-query/src/bin-analyze.js.map +1 -1
  3. package/out/analyze-query/src/run-ast.d.ts.map +1 -1
  4. package/out/analyze-query/src/run-ast.js +11 -2
  5. package/out/analyze-query/src/run-ast.js.map +1 -1
  6. package/out/zero/package.json.js +1 -1
  7. package/out/zero-cache/src/config/zero-config.d.ts +4 -0
  8. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  9. package/out/zero-cache/src/config/zero-config.js +17 -0
  10. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  11. package/out/zero-cache/src/db/lite-tables.d.ts +2 -1
  12. package/out/zero-cache/src/db/lite-tables.d.ts.map +1 -1
  13. package/out/zero-cache/src/db/lite-tables.js +7 -3
  14. package/out/zero-cache/src/db/lite-tables.js.map +1 -1
  15. package/out/zero-cache/src/db/specs.d.ts +8 -2
  16. package/out/zero-cache/src/db/specs.d.ts.map +1 -1
  17. package/out/zero-cache/src/db/specs.js.map +1 -1
  18. package/out/zero-cache/src/db/transaction-pool.d.ts.map +1 -1
  19. package/out/zero-cache/src/db/transaction-pool.js +17 -11
  20. package/out/zero-cache/src/db/transaction-pool.js.map +1 -1
  21. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  22. package/out/zero-cache/src/server/change-streamer.js +3 -1
  23. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  24. package/out/zero-cache/src/services/analyze.js +1 -0
  25. package/out/zero-cache/src/services/analyze.js.map +1 -1
  26. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts +2 -0
  27. package/out/zero-cache/src/services/change-source/common/replica-schema.d.ts.map +1 -1
  28. package/out/zero-cache/src/services/change-source/common/replica-schema.js +56 -3
  29. package/out/zero-cache/src/services/change-source/common/replica-schema.js.map +1 -1
  30. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  31. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +15 -11
  32. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  33. package/out/zero-cache/src/services/change-source/pg/change-source.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/change-source/pg/change-source.js +61 -0
  35. package/out/zero-cache/src/services/change-source/pg/change-source.js.map +1 -1
  36. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts +100 -0
  37. package/out/zero-cache/src/services/change-streamer/broadcast.d.ts.map +1 -0
  38. package/out/zero-cache/src/services/change-streamer/broadcast.js +171 -0
  39. package/out/zero-cache/src/services/change-streamer/broadcast.js.map +1 -0
  40. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +1 -1
  41. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +14 -7
  43. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts +17 -1
  45. package/out/zero-cache/src/services/change-streamer/forwarder.d.ts.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/forwarder.js +52 -4
  47. package/out/zero-cache/src/services/change-streamer/forwarder.js.map +1 -1
  48. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts +18 -0
  49. package/out/zero-cache/src/services/change-streamer/subscriber.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/change-streamer/subscriber.js +68 -12
  51. package/out/zero-cache/src/services/change-streamer/subscriber.js.map +1 -1
  52. package/out/zero-cache/src/services/replicator/change-processor.d.ts.map +1 -1
  53. package/out/zero-cache/src/services/replicator/change-processor.js +10 -13
  54. package/out/zero-cache/src/services/replicator/change-processor.js.map +1 -1
  55. package/out/zero-cache/src/services/replicator/schema/table-metadata.d.ts +28 -7
  56. package/out/zero-cache/src/services/replicator/schema/table-metadata.d.ts.map +1 -1
  57. package/out/zero-cache/src/services/replicator/schema/table-metadata.js +55 -24
  58. package/out/zero-cache/src/services/replicator/schema/table-metadata.js.map +1 -1
  59. package/out/zero-cache/src/services/run-ast.d.ts.map +1 -1
  60. package/out/zero-cache/src/services/run-ast.js +4 -2
  61. package/out/zero-cache/src/services/run-ast.js.map +1 -1
  62. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts +3 -2
  63. package/out/zero-cache/src/services/view-syncer/pipeline-driver.d.ts.map +1 -1
  64. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js +27 -12
  65. package/out/zero-cache/src/services/view-syncer/pipeline-driver.js.map +1 -1
  66. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts +3 -3
  67. package/out/zero-cache/src/services/view-syncer/snapshotter.d.ts.map +1 -1
  68. package/out/zero-cache/src/services/view-syncer/snapshotter.js +4 -0
  69. package/out/zero-cache/src/services/view-syncer/snapshotter.js.map +1 -1
  70. package/out/zero-cache/src/types/subscription.d.ts +3 -1
  71. package/out/zero-cache/src/types/subscription.d.ts.map +1 -1
  72. package/out/zero-cache/src/types/subscription.js +21 -9
  73. package/out/zero-cache/src/types/subscription.js.map +1 -1
  74. package/out/zero-client/src/client/version.js +1 -1
  75. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"forwarder.js","sources":["../../../../../../zero-cache/src/services/change-streamer/forwarder.ts"],"sourcesContent":["import {joinIterables, wrapIterable} from '../../../../shared/src/iterables.ts';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport type {Subscriber} from './subscriber.ts';\n\nexport class Forwarder {\n readonly #active = new Set<Subscriber>();\n readonly #queued = new Set<Subscriber>();\n #inTransaction = false;\n\n /**\n * `add()` is called in lock step with `Storer.catchup()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n */\n add(sub: Subscriber) {\n if (this.#inTransaction) {\n this.#queued.add(sub);\n } else {\n this.#active.add(sub);\n }\n }\n\n remove(sub: Subscriber) {\n this.#active.delete(sub);\n this.#queued.delete(sub);\n sub.close();\n }\n\n /**\n * `forward()` is called in lockstep with `Storer.store()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n */\n async forward(entry: WatermarkedChange) {\n const [type] = entry[1];\n const results = Array.from(this.#active.values(), sub => sub.send(entry));\n switch (type) {\n case 'begin':\n // While in a Transaction, all added subscribers are \"queued\" so that no\n // messages are forwarded to them. This state corresponds to being queued\n // for catchup in the Storer, which will retrieve historic changes\n // and call catchup() once the current transaction is committed.\n this.#inTransaction = true;\n break;\n case 'commit':\n case 'rollback':\n // Upon commit or rollback, all queued subscribers are transferred to\n // the active set. This means that they can receive messages starting\n // from the next transaction.\n //\n // Note that if catchup is still in progress (in the Storer), these messages\n // will be buffered in the backlog until catchup completes.\n this.#inTransaction = false;\n for (const sub of this.#queued.values()) {\n this.#active.add(sub);\n }\n this.#queued.clear();\n break;\n }\n await Promise.all(results);\n }\n\n getAcks(): Set<string> {\n return new Set(\n joinIterables(\n wrapIterable(this.#active).map(s => s.acked),\n wrapIterable(this.#queued).map(s => s.acked),\n ),\n );\n }\n}\n"],"names":[],"mappings":";AAIO,MAAM,UAAU;AAAA,EACZ,8BAAc,IAAA;AAAA,EACd,8BAAc,IAAA;AAAA,EACvB,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOjB,IAAI,KAAiB;AACnB,QAAI,KAAK,gBAAgB;AACvB,WAAK,QAAQ,IAAI,GAAG;AAAA,IACtB,OAAO;AACL,WAAK,QAAQ,IAAI,GAAG;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,OAAO,KAAiB;AACtB,SAAK,QAAQ,OAAO,GAAG;AACvB,SAAK,QAAQ,OAAO,GAAG;AACvB,QAAI,MAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,QAAQ,OAA0B;AACtC,UAAM,CAAC,IAAI,IAAI,MAAM,CAAC;AACtB,UAAM,UAAU,MAAM,KAAK,KAAK,QAAQ,OAAA,GAAU,CAAA,QAAO,IAAI,KAAK,KAAK,CAAC;AACxE,YAAQ,MAAA;AAAA,MACN,KAAK;AAKH,aAAK,iBAAiB;AACtB;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AAOH,aAAK,iBAAiB;AACtB,mBAAW,OAAO,KAAK,QAAQ,OAAA,GAAU;AACvC,eAAK,QAAQ,IAAI,GAAG;AAAA,QACtB;AACA,aAAK,QAAQ,MAAA;AACb;AAAA,IAAA;AAEJ,UAAM,QAAQ,IAAI,OAAO;AAAA,EAC3B;AAAA,EAEA,UAAuB;AACrB,WAAO,IAAI;AAAA,MACT;AAAA,QACE,aAAa,KAAK,OAAO,EAAE,IAAI,CAAA,MAAK,EAAE,KAAK;AAAA,QAC3C,aAAa,KAAK,OAAO,EAAE,IAAI,CAAA,MAAK,EAAE,KAAK;AAAA,MAAA;AAAA,IAC7C;AAAA,EAEJ;AACF;"}
1
+ {"version":3,"file":"forwarder.js","sources":["../../../../../../zero-cache/src/services/change-streamer/forwarder.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {joinIterables, wrapIterable} from '../../../../shared/src/iterables.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current.ts';\nimport {Broadcast} from './broadcast.ts';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport type {Subscriber} from './subscriber.ts';\n\nexport type ProgressMonitorOptions = {\n flowControlConsensusPaddingSeconds: number;\n};\n\nexport class Forwarder {\n readonly #lc: LogContext;\n readonly #progressMonitorOptions: ProgressMonitorOptions;\n readonly #active = new Set<Subscriber>();\n readonly #queued = new Set<Subscriber>();\n #inTransaction = false;\n\n #currentBroadcast: Broadcast | undefined;\n #progressMonitor: NodeJS.Timeout | undefined;\n\n constructor(\n lc: LogContext,\n opts: ProgressMonitorOptions = {flowControlConsensusPaddingSeconds: 1},\n ) {\n this.#lc = lc.withContext('component', 'progress-monitor');\n this.#progressMonitorOptions = opts;\n }\n\n startProgressMonitor() {\n clearInterval(this.#progressMonitor);\n this.#progressMonitor = setInterval(this.#trackProgress, 1000);\n }\n\n readonly #trackProgress = () => {\n const now = performance.now();\n for (const sub of this.#active) {\n sub.sampleProcessRate(now);\n }\n\n const {flowControlConsensusPaddingSeconds} = this.#progressMonitorOptions;\n // A negative number disables early flow control release.\n if (flowControlConsensusPaddingSeconds >= 0) {\n this.#currentBroadcast?.checkProgress(\n this.#lc,\n flowControlConsensusPaddingSeconds * 1000,\n now,\n );\n }\n };\n\n stopProgressMonitor() {\n clearInterval(this.#progressMonitor);\n }\n\n /**\n * `add()` is called in lock step with `Storer.catchup()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n */\n add(sub: Subscriber) {\n if (this.#inTransaction) {\n this.#queued.add(sub);\n } else {\n this.#active.add(sub);\n }\n }\n\n remove(sub: Subscriber) {\n this.#active.delete(sub);\n this.#queued.delete(sub);\n sub.close();\n }\n\n /**\n * `forward()` is called in lockstep with `Storer.store()` so that the\n * two components have an equivalent interpretation of whether a Transaction is\n * currently being streamed.\n *\n * This version of forward is fire-and-forget, with no flow control. The\n * change-streamer should call and await {@link forwardWithFlowControl()}\n * occasionally to avoid memory blowup.\n */\n forward(entry: WatermarkedChange) {\n Broadcast.withoutTracking(this.#active.values(), entry);\n this.#updateActiveSubscribers(entry[1]);\n }\n\n /**\n * The flow-control-aware equivalent of {@link forward()}, returning a\n * Promise that resolves when replication should continue.\n */\n async forwardWithFlowControl(entry: WatermarkedChange) {\n const broadcast = new Broadcast(this.#active.values(), entry);\n this.#updateActiveSubscribers(entry[1]);\n\n // set for progress tracking\n this.#currentBroadcast = broadcast;\n\n await broadcast.done;\n\n // Technically #currentBroadcast may have changed, so only\n // unset if it if is still the same.\n if (this.#currentBroadcast === broadcast) {\n this.#currentBroadcast = undefined;\n }\n }\n\n #updateActiveSubscribers([type]: ChangeStreamData) {\n switch (type) {\n case 'begin':\n // While in a Transaction, all added subscribers are \"queued\" so that no\n // messages are forwarded to them. This state corresponds to being queued\n // for catchup in the Storer, which will retrieve historic changes\n // and call catchup() once the current transaction is committed.\n this.#inTransaction = true;\n break;\n case 'commit':\n case 'rollback':\n // Upon commit or rollback, all queued subscribers are transferred to\n // the active set. This means that they can receive messages starting\n // from the next transaction.\n //\n // Note that if catchup is still in progress (in the Storer), these messages\n // will be buffered in the backlog until catchup completes.\n this.#inTransaction = false;\n for (const sub of this.#queued.values()) {\n this.#active.add(sub);\n }\n this.#queued.clear();\n break;\n }\n }\n\n getAcks(): Set<string> {\n return new Set(\n joinIterables(\n wrapIterable(this.#active).map(s => s.acked),\n wrapIterable(this.#queued).map(s => s.acked),\n ),\n );\n }\n}\n"],"names":[],"mappings":";;AAWO,MAAM,UAAU;AAAA,EACZ;AAAA,EACA;AAAA,EACA,8BAAc,IAAA;AAAA,EACd,8BAAc,IAAA;AAAA,EACvB,iBAAiB;AAAA,EAEjB;AAAA,EACA;AAAA,EAEA,YACE,IACA,OAA+B,EAAC,oCAAoC,KACpE;AACA,SAAK,MAAM,GAAG,YAAY,aAAa,kBAAkB;AACzD,SAAK,0BAA0B;AAAA,EACjC;AAAA,EAEA,uBAAuB;AACrB,kBAAc,KAAK,gBAAgB;AACnC,SAAK,mBAAmB,YAAY,KAAK,gBAAgB,GAAI;AAAA,EAC/D;AAAA,EAES,iBAAiB,MAAM;AAC9B,UAAM,MAAM,YAAY,IAAA;AACxB,eAAW,OAAO,KAAK,SAAS;AAC9B,UAAI,kBAAkB,GAAG;AAAA,IAC3B;AAEA,UAAM,EAAC,uCAAsC,KAAK;AAElD,QAAI,sCAAsC,GAAG;AAC3C,WAAK,mBAAmB;AAAA,QACtB,KAAK;AAAA,QACL,qCAAqC;AAAA,QACrC;AAAA,MAAA;AAAA,IAEJ;AAAA,EACF;AAAA,EAEA,sBAAsB;AACpB,kBAAc,KAAK,gBAAgB;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAAiB;AACnB,QAAI,KAAK,gBAAgB;AACvB,WAAK,QAAQ,IAAI,GAAG;AAAA,IACtB,OAAO;AACL,WAAK,QAAQ,IAAI,GAAG;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,OAAO,KAAiB;AACtB,SAAK,QAAQ,OAAO,GAAG;AACvB,SAAK,QAAQ,OAAO,GAAG;AACvB,QAAI,MAAA;AAAA,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAQ,OAA0B;AAChC,cAAU,gBAAgB,KAAK,QAAQ,OAAA,GAAU,KAAK;AACtD,SAAK,yBAAyB,MAAM,CAAC,CAAC;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,uBAAuB,OAA0B;AACrD,UAAM,YAAY,IAAI,UAAU,KAAK,QAAQ,OAAA,GAAU,KAAK;AAC5D,SAAK,yBAAyB,MAAM,CAAC,CAAC;AAGtC,SAAK,oBAAoB;AAEzB,UAAM,UAAU;AAIhB,QAAI,KAAK,sBAAsB,WAAW;AACxC,WAAK,oBAAoB;AAAA,IAC3B;AAAA,EACF;AAAA,EAEA,yBAAyB,CAAC,IAAI,GAAqB;AACjD,YAAQ,MAAA;AAAA,MACN,KAAK;AAKH,aAAK,iBAAiB;AACtB;AAAA,MACF,KAAK;AAAA,MACL,KAAK;AAOH,aAAK,iBAAiB;AACtB,mBAAW,OAAO,KAAK,QAAQ,OAAA,GAAU;AACvC,eAAK,QAAQ,IAAI,GAAG;AAAA,QACtB;AACA,aAAK,QAAQ,MAAA;AACb;AAAA,IAAA;AAAA,EAEN;AAAA,EAEA,UAAuB;AACrB,WAAO,IAAI;AAAA,MACT;AAAA,QACE,aAAa,KAAK,OAAO,EAAE,IAAI,CAAA,MAAK,EAAE,KAAK;AAAA,QAC3C,aAAa,KAAK,OAAO,EAAE,IAAI,CAAA,MAAK,EAAE,KAAK;AAAA,MAAA;AAAA,IAC7C;AAAA,EAEJ;AACF;"}
@@ -26,6 +26,24 @@ export declare class Subscriber {
26
26
  * entries that were received during the catchup.
27
27
  */
28
28
  setCaughtUp(): void;
29
+ /**
30
+ * The number of downstream messages that have yet to be acked.
31
+ */
32
+ get numPending(): number;
33
+ /**
34
+ * The total number of downstream messages that the subscriber has
35
+ * processed (i.e. acked).
36
+ */
37
+ get numProcessed(): number;
38
+ /**
39
+ * Records a new history entry for the number of messages processed,
40
+ * keeping the number of samples bounded to `maxSamples`.
41
+ */
42
+ sampleProcessRate(now: number, maxSamples?: number): this;
43
+ getStats(): {
44
+ processRate: number;
45
+ pending: number;
46
+ };
29
47
  supportsMessage(change: ChangeStreamData[1]): boolean;
30
48
  fail(err?: unknown): void;
31
49
  close(error?: ErrorType, message?: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"subscriber.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/subscriber.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,gCAAgC,CAAC;AAEzD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAElD,KAAK,SAAS,GAAG,IAAI,CAAC,OAAO,SAAS,CAAC,CAAC;AAExC;;;;;;GAMG;AACH,qBAAa,UAAU;;IAErB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAOlB,eAAe,EAAE,MAAM,EACvB,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,YAAY,CAAC,UAAU,CAAC;IAUtC,IAAI,SAAS,WAEZ;IAED,IAAI,KAAK,WAER;IAEK,IAAI,CAAC,MAAM,EAAE,iBAAiB;IAoBpC,kEAAkE;IAC5D,OAAO,CAAC,MAAM,EAAE,iBAAiB;IAKvC;;;OAGG;IACH,WAAW;IAoCX,eAAe,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAS3C,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO;IAIlB,KAAK,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM;CAS1C"}
1
+ {"version":3,"file":"subscriber.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/change-streamer/subscriber.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,gCAAgC,CAAC;AAGzD,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,6BAA6B,CAAC;AAC9D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,sCAAsC,CAAC;AAC3E,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAC,KAAK,UAAU,EAAC,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,SAAS,MAAM,sBAAsB,CAAC;AAElD,KAAK,SAAS,GAAG,IAAI,CAAC,OAAO,SAAS,CAAC,CAAC;AAExC;;;;;;GAMG;AACH,qBAAa,UAAU;;IAErB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;gBAOlB,eAAe,EAAE,MAAM,EACvB,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,YAAY,CAAC,UAAU,CAAC;IAUtC,IAAI,SAAS,WAEZ;IAED,IAAI,KAAK,WAER;IAEK,IAAI,CAAC,MAAM,EAAE,iBAAiB;IAoBpC,kEAAkE;IAC5D,OAAO,CAAC,MAAM,EAAE,iBAAiB;IAKvC;;;OAGG;IACH,WAAW;IA0DX;;OAEG;IACH,IAAI,UAAU,WAEb;IAED;;;OAGG;IACH,IAAI,YAAY,WAEf;IAED;;;OAGG;IACH,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,IAAI;IAQrD,QAAQ,IAAI;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAC;IAalD,eAAe,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAS3C,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO;IAIlB,KAAK,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,MAAM;CAS1C"}
@@ -1,4 +1,5 @@
1
1
  import { assert } from "../../../../shared/src/asserts.js";
2
+ import { must } from "../../../../shared/src/must.js";
2
3
  import { max } from "../../types/lexi-version.js";
3
4
  import "./change-streamer.js";
4
5
  import { Unknown } from "./error-type-enum.js";
@@ -29,21 +30,21 @@ class Subscriber {
29
30
  if (this.#backlog) {
30
31
  this.#backlog.push(change);
31
32
  } else {
32
- await this.#send(change);
33
+ await this.#sendChange(change);
33
34
  }
34
35
  }
35
36
  }
36
37
  #initialStatusSent = false;
37
38
  #ensureInitialStatusSent() {
38
39
  if (this.#protocolVersion >= 2 && !this.#initialStatusSent) {
39
- this.#downstream.push(["status", { tag: "status" }]);
40
+ void this.#sendDownstream(["status", { tag: "status" }]);
40
41
  this.#initialStatusSent = true;
41
42
  }
42
43
  }
43
44
  /** catchup() is called on ChangeEntries loaded from the store. */
44
45
  async catchup(change) {
45
46
  this.#ensureInitialStatusSent();
46
- await this.#send(change);
47
+ await this.#sendChange(change);
47
48
  }
48
49
  /**
49
50
  * Marks the Subscribe as "caught up" and flushes any backlog of
@@ -56,11 +57,11 @@ class Subscriber {
56
57
  "setCaughtUp() called but subscriber is not in catchup mode"
57
58
  );
58
59
  for (const change of this.#backlog) {
59
- void this.#send(change);
60
+ void this.#sendChange(change);
60
61
  }
61
62
  this.#backlog = null;
62
63
  }
63
- async #send(change) {
64
+ async #sendChange(change) {
64
65
  const [watermark, downstream] = change;
65
66
  if (watermark <= this.watermark) {
66
67
  return;
@@ -68,16 +69,71 @@ class Subscriber {
68
69
  if (!this.supportsMessage(downstream[1])) {
69
70
  return;
70
71
  }
71
- const pending = this.#downstream.push(downstream);
72
72
  if (downstream[0] === "commit") {
73
73
  this.#watermark = watermark;
74
- void pending.result.then((val) => {
75
- if (val === "consumed") {
76
- this.#acked = max(this.#acked, watermark);
77
- }
78
- });
79
74
  }
80
- await pending.result;
75
+ const result = await this.#sendDownstream(downstream);
76
+ if (downstream[0] === "commit" && result === "consumed") {
77
+ this.#acked = max(this.#acked, watermark);
78
+ }
79
+ }
80
+ async #sendDownstream(downstream) {
81
+ this.#pending++;
82
+ const { result } = this.#downstream.push(downstream);
83
+ try {
84
+ return await result;
85
+ } finally {
86
+ this.#pending--;
87
+ this.#processed++;
88
+ }
89
+ }
90
+ // `pending` and `processed` stats are tracked by periodically sampling
91
+ // the running totals (by the progress tracker in the Forwarder).
92
+ // This information was originally collected for use in flow control
93
+ // decisions. The final flow control algorithm ended up being simpler
94
+ // than expected and does not actually use this information. However, the
95
+ // stats are still tracked and logged during flow control decisions for
96
+ // debugging, forensics, and potential improvements to the algorithm.
97
+ #pending = 0;
98
+ #processed = 0;
99
+ #samples = [
100
+ { processed: 0, timestamp: performance.now() }
101
+ ];
102
+ /**
103
+ * The number of downstream messages that have yet to be acked.
104
+ */
105
+ get numPending() {
106
+ return this.#pending;
107
+ }
108
+ /**
109
+ * The total number of downstream messages that the subscriber has
110
+ * processed (i.e. acked).
111
+ */
112
+ get numProcessed() {
113
+ return this.#processed;
114
+ }
115
+ /**
116
+ * Records a new history entry for the number of messages processed,
117
+ * keeping the number of samples bounded to `maxSamples`.
118
+ */
119
+ sampleProcessRate(now, maxSamples = 10) {
120
+ while (this.#samples.length >= maxSamples) {
121
+ this.#samples.shift();
122
+ }
123
+ this.#samples.push({ processed: this.#processed, timestamp: now });
124
+ return this;
125
+ }
126
+ getStats() {
127
+ const pending = this.#pending;
128
+ if (this.#samples.length < 2) {
129
+ return { processRate: 0, pending };
130
+ }
131
+ const from = this.#samples[0];
132
+ const to = must(this.#samples.at(-1));
133
+ const processed = to.processed - from.processed;
134
+ const seconds = (to.timestamp - from.timestamp) / 1e3;
135
+ const processRate = seconds === 0 ? 0 : processed / seconds;
136
+ return { processRate, pending };
81
137
  }
82
138
  supportsMessage(change) {
83
139
  switch (change.tag) {
@@ -1 +1 @@
1
- {"version":3,"file":"subscriber.js","sources":["../../../../../../zero-cache/src/services/change-streamer/subscriber.ts"],"sourcesContent":["import {assert} from '../../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../../shared/src/enum.ts';\nimport {max} from '../../types/lexi-version.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current.ts';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport {type Downstream} from './change-streamer.ts';\nimport * as ErrorType from './error-type-enum.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\n/**\n * Encapsulates a subscriber to changes. All subscribers start in a\n * \"catchup\" phase in which changes are buffered in a backlog while the\n * storer is queried to send any changes that were committed since the\n * subscriber's watermark. Once the catchup is complete, calls to\n * {@link send()} result in immediately sending the change.\n */\nexport class Subscriber {\n readonly #protocolVersion: number;\n readonly id: string;\n readonly #downstream: Subscription<Downstream>;\n #watermark: string;\n #acked: string;\n #backlog: WatermarkedChange[] | null;\n\n constructor(\n protocolVersion: number,\n id: string,\n watermark: string,\n downstream: Subscription<Downstream>,\n ) {\n this.#protocolVersion = protocolVersion;\n this.id = id;\n this.#downstream = downstream;\n this.#watermark = watermark;\n this.#acked = watermark;\n this.#backlog = [];\n }\n\n get watermark() {\n return this.#watermark;\n }\n\n get acked() {\n return this.#acked;\n }\n\n async send(change: WatermarkedChange) {\n const [watermark] = change;\n if (watermark > this.#watermark) {\n if (this.#backlog) {\n this.#backlog.push(change);\n } else {\n await this.#send(change);\n }\n }\n }\n\n #initialStatusSent = false;\n\n #ensureInitialStatusSent() {\n if (this.#protocolVersion >= 2 && !this.#initialStatusSent) {\n this.#downstream.push(['status', {tag: 'status'}]);\n this.#initialStatusSent = true;\n }\n }\n\n /** catchup() is called on ChangeEntries loaded from the store. */\n async catchup(change: WatermarkedChange) {\n this.#ensureInitialStatusSent();\n await this.#send(change);\n }\n\n /**\n * Marks the Subscribe as \"caught up\" and flushes any backlog of\n * entries that were received during the catchup.\n */\n setCaughtUp() {\n this.#ensureInitialStatusSent();\n assert(\n this.#backlog,\n 'setCaughtUp() called but subscriber is not in catchup mode',\n );\n // Note that this method must be asynchronous in order for send() to\n // interpret the #backlog variable correctly. This is the only place\n // where I/O flow control is not heeded. However, it will be awaited\n // by the next caller to send().\n for (const change of this.#backlog) {\n void this.#send(change);\n }\n this.#backlog = null;\n }\n\n async #send(change: WatermarkedChange) {\n const [watermark, downstream] = change;\n if (watermark <= this.watermark) {\n return;\n }\n if (!this.supportsMessage(downstream[1])) {\n return;\n }\n const pending = this.#downstream.push(downstream);\n if (downstream[0] === 'commit') {\n this.#watermark = watermark;\n void pending.result.then(val => {\n if (val === 'consumed') {\n this.#acked = max(this.#acked, watermark);\n }\n });\n }\n await pending.result;\n }\n\n supportsMessage(change: ChangeStreamData[1]) {\n switch (change.tag) {\n case 'update-table-metadata':\n // update-table-row-key is only understood by subscribers >= protocol v5\n return this.#protocolVersion >= 5;\n }\n return true;\n }\n\n fail(err?: unknown) {\n this.close(ErrorType.Unknown, String(err));\n }\n\n close(error?: ErrorType, message?: string) {\n if (error) {\n const {result} = this.#downstream.push(['error', {type: error, message}]);\n // Wait for the ACK of the error message before closing the connection.\n void result.then(() => this.#downstream.cancel());\n } else {\n this.#downstream.cancel();\n }\n }\n}\n"],"names":["ErrorType.Unknown"],"mappings":";;;;AAkBO,MAAM,WAAW;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YACE,iBACA,IACA,WACA,YACA;AACA,SAAK,mBAAmB;AACxB,SAAK,KAAK;AACV,SAAK,cAAc;AACnB,SAAK,aAAa;AAClB,SAAK,SAAS;AACd,SAAK,WAAW,CAAA;AAAA,EAClB;AAAA,EAEA,IAAI,YAAY;AACd,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,QAAQ;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,KAAK,QAA2B;AACpC,UAAM,CAAC,SAAS,IAAI;AACpB,QAAI,YAAY,KAAK,YAAY;AAC/B,UAAI,KAAK,UAAU;AACjB,aAAK,SAAS,KAAK,MAAM;AAAA,MAC3B,OAAO;AACL,cAAM,KAAK,MAAM,MAAM;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,qBAAqB;AAAA,EAErB,2BAA2B;AACzB,QAAI,KAAK,oBAAoB,KAAK,CAAC,KAAK,oBAAoB;AAC1D,WAAK,YAAY,KAAK,CAAC,UAAU,EAAC,KAAK,SAAA,CAAS,CAAC;AACjD,WAAK,qBAAqB;AAAA,IAC5B;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAQ,QAA2B;AACvC,SAAK,yBAAA;AACL,UAAM,KAAK,MAAM,MAAM;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAc;AACZ,SAAK,yBAAA;AACL;AAAA,MACE,KAAK;AAAA,MACL;AAAA,IAAA;AAMF,eAAW,UAAU,KAAK,UAAU;AAClC,WAAK,KAAK,MAAM,MAAM;AAAA,IACxB;AACA,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,MAAM,MAAM,QAA2B;AACrC,UAAM,CAAC,WAAW,UAAU,IAAI;AAChC,QAAI,aAAa,KAAK,WAAW;AAC/B;AAAA,IACF;AACA,QAAI,CAAC,KAAK,gBAAgB,WAAW,CAAC,CAAC,GAAG;AACxC;AAAA,IACF;AACA,UAAM,UAAU,KAAK,YAAY,KAAK,UAAU;AAChD,QAAI,WAAW,CAAC,MAAM,UAAU;AAC9B,WAAK,aAAa;AAClB,WAAK,QAAQ,OAAO,KAAK,CAAA,QAAO;AAC9B,YAAI,QAAQ,YAAY;AACtB,eAAK,SAAS,IAAI,KAAK,QAAQ,SAAS;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,QAAQ;AAAA,EAChB;AAAA,EAEA,gBAAgB,QAA6B;AAC3C,YAAQ,OAAO,KAAA;AAAA,MACb,KAAK;AAEH,eAAO,KAAK,oBAAoB;AAAA,IAAA;AAEpC,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,KAAe;AAClB,SAAK,MAAMA,SAAmB,OAAO,GAAG,CAAC;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAmB,SAAkB;AACzC,QAAI,OAAO;AACT,YAAM,EAAC,OAAA,IAAU,KAAK,YAAY,KAAK,CAAC,SAAS,EAAC,MAAM,OAAO,QAAA,CAAQ,CAAC;AAExE,WAAK,OAAO,KAAK,MAAM,KAAK,YAAY,QAAQ;AAAA,IAClD,OAAO;AACL,WAAK,YAAY,OAAA;AAAA,IACnB;AAAA,EACF;AACF;"}
1
+ {"version":3,"file":"subscriber.js","sources":["../../../../../../zero-cache/src/services/change-streamer/subscriber.ts"],"sourcesContent":["import {assert} from '../../../../shared/src/asserts.ts';\nimport type {Enum} from '../../../../shared/src/enum.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport {max} from '../../types/lexi-version.ts';\nimport type {Subscription} from '../../types/subscription.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current.ts';\nimport type {WatermarkedChange} from './change-streamer-service.ts';\nimport {type Downstream} from './change-streamer.ts';\nimport * as ErrorType from './error-type-enum.ts';\n\ntype ErrorType = Enum<typeof ErrorType>;\n\n/**\n * Encapsulates a subscriber to changes. All subscribers start in a\n * \"catchup\" phase in which changes are buffered in a backlog while the\n * storer is queried to send any changes that were committed since the\n * subscriber's watermark. Once the catchup is complete, calls to\n * {@link send()} result in immediately sending the change.\n */\nexport class Subscriber {\n readonly #protocolVersion: number;\n readonly id: string;\n readonly #downstream: Subscription<Downstream>;\n #watermark: string;\n #acked: string;\n #backlog: WatermarkedChange[] | null;\n\n constructor(\n protocolVersion: number,\n id: string,\n watermark: string,\n downstream: Subscription<Downstream>,\n ) {\n this.#protocolVersion = protocolVersion;\n this.id = id;\n this.#downstream = downstream;\n this.#watermark = watermark;\n this.#acked = watermark;\n this.#backlog = [];\n }\n\n get watermark() {\n return this.#watermark;\n }\n\n get acked() {\n return this.#acked;\n }\n\n async send(change: WatermarkedChange) {\n const [watermark] = change;\n if (watermark > this.#watermark) {\n if (this.#backlog) {\n this.#backlog.push(change);\n } else {\n await this.#sendChange(change);\n }\n }\n }\n\n #initialStatusSent = false;\n\n #ensureInitialStatusSent() {\n if (this.#protocolVersion >= 2 && !this.#initialStatusSent) {\n void this.#sendDownstream(['status', {tag: 'status'}]);\n this.#initialStatusSent = true;\n }\n }\n\n /** catchup() is called on ChangeEntries loaded from the store. */\n async catchup(change: WatermarkedChange) {\n this.#ensureInitialStatusSent();\n await this.#sendChange(change);\n }\n\n /**\n * Marks the Subscribe as \"caught up\" and flushes any backlog of\n * entries that were received during the catchup.\n */\n setCaughtUp() {\n this.#ensureInitialStatusSent();\n assert(\n this.#backlog,\n 'setCaughtUp() called but subscriber is not in catchup mode',\n );\n // Note that this method must be asynchronous in order for send() to\n // interpret the #backlog variable correctly. This is the only place\n // where I/O flow control is not heeded. However, it will be awaited\n // by the next caller to send().\n for (const change of this.#backlog) {\n void this.#sendChange(change);\n }\n this.#backlog = null;\n }\n\n async #sendChange(change: WatermarkedChange) {\n const [watermark, downstream] = change;\n if (watermark <= this.watermark) {\n return;\n }\n if (!this.supportsMessage(downstream[1])) {\n return;\n }\n if (downstream[0] === 'commit') {\n this.#watermark = watermark;\n }\n const result = await this.#sendDownstream(downstream);\n if (downstream[0] === 'commit' && result === 'consumed') {\n this.#acked = max(this.#acked, watermark);\n }\n }\n\n async #sendDownstream(downstream: Downstream) {\n this.#pending++;\n const {result} = this.#downstream.push(downstream);\n try {\n return await result;\n } finally {\n this.#pending--;\n this.#processed++;\n }\n }\n\n // `pending` and `processed` stats are tracked by periodically sampling\n // the running totals (by the progress tracker in the Forwarder).\n // This information was originally collected for use in flow control\n // decisions. The final flow control algorithm ended up being simpler\n // than expected and does not actually use this information. However, the\n // stats are still tracked and logged during flow control decisions for\n // debugging, forensics, and potential improvements to the algorithm.\n\n #pending = 0;\n #processed = 0;\n #samples: {processed: number; timestamp: number}[] = [\n {processed: 0, timestamp: performance.now()},\n ];\n\n /**\n * The number of downstream messages that have yet to be acked.\n */\n get numPending() {\n return this.#pending;\n }\n\n /**\n * The total number of downstream messages that the subscriber has\n * processed (i.e. acked).\n */\n get numProcessed() {\n return this.#processed;\n }\n\n /**\n * Records a new history entry for the number of messages processed,\n * keeping the number of samples bounded to `maxSamples`.\n */\n sampleProcessRate(now: number, maxSamples = 10): this {\n while (this.#samples.length >= maxSamples) {\n this.#samples.shift();\n }\n this.#samples.push({processed: this.#processed, timestamp: now});\n return this;\n }\n\n getStats(): {processRate: number; pending: number} {\n const pending = this.#pending;\n if (this.#samples.length < 2) {\n return {processRate: 0, pending};\n }\n const from = this.#samples[0];\n const to = must(this.#samples.at(-1));\n const processed = to.processed - from.processed;\n const seconds = (to.timestamp - from.timestamp) / 1000;\n const processRate = seconds === 0 ? 0 : processed / seconds;\n return {processRate, pending};\n }\n\n supportsMessage(change: ChangeStreamData[1]) {\n switch (change.tag) {\n case 'update-table-metadata':\n // update-table-row-key is only understood by subscribers >= protocol v5\n return this.#protocolVersion >= 5;\n }\n return true;\n }\n\n fail(err?: unknown) {\n this.close(ErrorType.Unknown, String(err));\n }\n\n close(error?: ErrorType, message?: string) {\n if (error) {\n const {result} = this.#downstream.push(['error', {type: error, message}]);\n // Wait for the ACK of the error message before closing the connection.\n void result.then(() => this.#downstream.cancel());\n } else {\n this.#downstream.cancel();\n }\n }\n}\n"],"names":["ErrorType.Unknown"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"change-processor.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAMjD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAkB1E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAgC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AASpD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;;gBAiBxB,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI;IAuBrD,KAAK,CAAC,EAAE,EAAE,UAAU;IAIpB,8CAA8C;IAC9C,cAAc,CACZ,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,gBAAgB,GAC3B,YAAY,GAAG,IAAI;CAyJvB"}
1
+ {"version":3,"file":"change-processor.d.ts","sourceRoot":"","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAC,MAAM,kBAAkB,CAAC;AAMjD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uCAAuC,CAAC;AAiB1E,OAAO,KAAK,EAAC,eAAe,EAAC,MAAM,wBAAwB,CAAC;AAiC5D,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,iDAAiD,CAAC;AACtF,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AASpD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,cAAc,GAAG,SAAS,CAAC;IAC9C,aAAa,EAAE,OAAO,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;;gBAiBxB,EAAE,EAAE,eAAe,EACnB,IAAI,EAAE,mBAAmB,EACzB,WAAW,EAAE,CAAC,EAAE,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI;IAuBrD,KAAK,CAAC,EAAE,EAAE,UAAU;IAIpB,8CAA8C;IAC9C,cAAc,CACZ,EAAE,EAAE,UAAU,EACd,UAAU,EAAE,gBAAgB,GAC3B,YAAY,GAAG,IAAI;CAyJvB"}
@@ -336,7 +336,7 @@ class TransactionProcessor {
336
336
  }
337
337
  processCreateTable(create) {
338
338
  if (create.metadata) {
339
- this.#tableMetadata.set(create.spec, create.metadata);
339
+ this.#tableMetadata.setUpstreamMetadata(create.spec, create.metadata);
340
340
  }
341
341
  const table = mapPostgresToLite(create.spec);
342
342
  this.#db.db.exec(createLiteTableStatement(table));
@@ -356,7 +356,7 @@ class TransactionProcessor {
356
356
  this.#lc.info?.(create.tag, table.name);
357
357
  }
358
358
  processTableMetadata(msg) {
359
- this.#tableMetadata.set(msg.table, msg.new);
359
+ this.#tableMetadata.setUpstreamMetadata(msg.table, msg.new);
360
360
  }
361
361
  processRenameTable(rename) {
362
362
  this.#tableMetadata.rename(rename.old, rename.new);
@@ -364,13 +364,13 @@ class TransactionProcessor {
364
364
  const newName = liteTableName(rename.new);
365
365
  this.#db.db.exec(`ALTER TABLE ${id(oldName)} RENAME TO ${id(newName)}`);
366
366
  this.#columnMetadata.renameTable(oldName, newName);
367
- this.#bumpVersions(newName);
367
+ this.#bumpVersions(rename.new);
368
368
  this.#logResetOp(oldName);
369
369
  this.#lc.info?.(rename.tag, oldName, newName);
370
370
  }
371
371
  processAddColumn(msg) {
372
372
  if (msg.tableMetadata) {
373
- this.#tableMetadata.set(msg.table, msg.tableMetadata);
373
+ this.#tableMetadata.setUpstreamMetadata(msg.table, msg.tableMetadata);
374
374
  }
375
375
  const table = liteTableName(msg.table);
376
376
  const { name } = msg.column;
@@ -382,7 +382,7 @@ class TransactionProcessor {
382
382
  if (msg.backfill) {
383
383
  this.#reloadTableSpecs();
384
384
  } else {
385
- this.#bumpVersions(table);
385
+ this.#bumpVersions(msg.table);
386
386
  }
387
387
  this.#lc.info?.(msg.tag, table, msg.column);
388
388
  }
@@ -426,7 +426,7 @@ class TransactionProcessor {
426
426
  msg.new.name,
427
427
  msg.new.spec
428
428
  );
429
- this.#bumpVersions(table);
429
+ this.#bumpVersions(msg.table);
430
430
  this.#lc.info?.(msg.tag, table, msg.new);
431
431
  }
432
432
  processDropColumn(msg) {
@@ -434,7 +434,7 @@ class TransactionProcessor {
434
434
  const { column } = msg;
435
435
  this.#db.db.exec(`ALTER TABLE ${id(table)} DROP ${id(column)}`);
436
436
  this.#columnMetadata.deleteColumn(table, column);
437
- this.#bumpVersions(table);
437
+ this.#bumpVersions(msg.table);
438
438
  this.#lc.info?.(msg.tag, table, column);
439
439
  }
440
440
  processDropTable(drop) {
@@ -462,11 +462,8 @@ class TransactionProcessor {
462
462
  this.#lc.info?.(drop.tag, name);
463
463
  }
464
464
  #bumpVersions(table) {
465
- this.#db.run(
466
- `UPDATE ${id(table)} SET ${id(ZERO_VERSION_COLUMN_NAME)} = ?`,
467
- this.#version
468
- );
469
- this.#logResetOp(table);
465
+ this.#tableMetadata.setMinRowVersion(table, this.#version);
466
+ this.#logResetOp(liteTableName(table));
470
467
  }
471
468
  /**
472
469
  * @param backfilledColumns `backfilling` columns for which values were set
@@ -559,7 +556,7 @@ class TransactionProcessor {
559
556
  for (const col of cols) {
560
557
  columnMetadata.clearBackfilling(tableName, col);
561
558
  }
562
- this.#bumpVersions(tableName);
559
+ this.#bumpVersions(relation);
563
560
  if (status) {
564
561
  this.#completedBackfill = { table: tableName, columns: cols, ...status };
565
562
  }
@@ -1 +1 @@
1
- {"version":3,"file":"change-processor.js","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {DownloadStatus} from '../../../../zero-events/src/status.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n liteColumnDef,\n} from '../../db/create.ts';\nimport {\n computeZqlSpecs,\n listIndexes,\n listTables,\n type LiteTableSpecWithReplicationStatus,\n} from '../../db/lite-tables.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteColumn,\n mapPostgresToLiteIndex,\n} from '../../db/pg-to-lite.ts';\nimport type {LiteTableSpec} from '../../db/specs.ts';\nimport type {StatementRunner} from '../../db/statements.ts';\nimport type {LexiVersion} from '../../types/lexi-version.ts';\nimport {\n JSON_PARSED,\n liteRow,\n type JSONFormat,\n type LiteRow,\n type LiteRowKey,\n type LiteValueType,\n} from '../../types/lite.ts';\nimport {liteTableName} from '../../types/names.ts';\nimport {id} from '../../types/sql.ts';\nimport type {\n BackfillCompleted,\n Change,\n ColumnAdd,\n ColumnDrop,\n ColumnUpdate,\n IndexCreate,\n IndexDrop,\n MessageBackfill,\n MessageCommit,\n MessageDelete,\n MessageInsert,\n MessageRelation,\n MessageTruncate,\n MessageUpdate,\n TableCreate,\n TableDrop,\n TableRename,\n TableUpdateMetadata,\n} from '../change-source/protocol/current/data.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport type {ReplicatorMode} from './replicator.ts';\nimport {ChangeLog, DEL_OP, SET_OP} from './schema/change-log.ts';\nimport {ColumnMetadataStore} from './schema/column-metadata.ts';\nimport {\n ZERO_VERSION_COLUMN_NAME,\n updateReplicationWatermark,\n} from './schema/replication-state.ts';\nimport {TableMetadataTracker} from './schema/table-metadata.ts';\n\nexport type ChangeProcessorMode = ReplicatorMode | 'initial-sync';\n\nexport type CommitResult = {\n watermark: string;\n completedBackfill: DownloadStatus | undefined;\n schemaUpdated: boolean;\n changeLogUpdated: boolean;\n};\n\n/**\n * The ChangeProcessor partitions the stream of messages into transactions\n * by creating a {@link TransactionProcessor} when a transaction begins, and dispatching\n * messages to it until the commit is received.\n *\n * From https://www.postgresql.org/docs/current/protocol-logical-replication.html#PROTOCOL-LOGICAL-MESSAGES-FLOW :\n *\n * \"The logical replication protocol sends individual transactions one by one.\n * This means that all messages between a pair of Begin and Commit messages\n * belong to the same transaction.\"\n */\nexport class ChangeProcessor {\n readonly #db: StatementRunner;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #mode: ChangeProcessorMode;\n readonly #failService: (lc: LogContext, err: unknown) => void;\n\n // The TransactionProcessor lazily loads table specs into this Map,\n // and reloads them after a schema change. It is cached here to avoid\n // reading them from the DB on every transaction.\n readonly #tableSpecs = new Map<string, LiteTableSpec>();\n\n #currentTx: TransactionProcessor | null = null;\n\n #failure: Error | undefined;\n\n constructor(\n db: StatementRunner,\n mode: ChangeProcessorMode,\n failService: (lc: LogContext, err: unknown) => void,\n ) {\n this.#db = db;\n this.#changeLog = new ChangeLog(db.db);\n this.#tableMetadata = new TableMetadataTracker(db.db);\n this.#mode = mode;\n this.#failService = failService;\n }\n\n #fail(lc: LogContext, err: unknown) {\n if (!this.#failure) {\n this.#currentTx?.abort(lc); // roll back any pending transaction.\n\n this.#failure = ensureError(err);\n\n if (!(err instanceof AbortError)) {\n // Propagate the failure up to the service.\n lc.error?.('Message Processing failed:', this.#failure);\n this.#failService(lc, this.#failure);\n }\n }\n }\n\n abort(lc: LogContext) {\n this.#fail(lc, new AbortError());\n }\n\n /** @return If a transaction was committed. */\n processMessage(\n lc: LogContext,\n downstream: ChangeStreamData,\n ): CommitResult | null {\n const [type, message] = downstream;\n if (this.#failure) {\n lc.debug?.(`Dropping ${message.tag}`);\n return null;\n }\n try {\n const watermark =\n type === 'begin'\n ? downstream[2].commitWatermark\n : type === 'commit'\n ? downstream[2].watermark\n : undefined;\n return this.#processMessage(lc, message, watermark);\n } catch (e) {\n this.#fail(lc, e);\n }\n return null;\n }\n\n #beginTransaction(\n lc: LogContext,\n commitVersion: string,\n jsonFormat: JSONFormat,\n ): TransactionProcessor {\n const start = Date.now();\n\n // litestream can technically hold the lock for an arbitrary amount of time\n // when checkpointing a large commit. Crashing on the busy-timeout in this\n // scenario will either produce a corrupt backup or otherwise prevent\n // replication from proceeding.\n //\n // Instead, retry the lock acquisition indefinitely. If this masks\n // an unknown deadlock situation, manual intervention will be necessary.\n for (let i = 0; ; i++) {\n try {\n return new TransactionProcessor(\n lc,\n this.#db,\n this.#mode,\n this.#changeLog,\n this.#tableMetadata,\n this.#tableSpecs,\n commitVersion,\n jsonFormat,\n );\n } catch (e) {\n if (e instanceof SqliteError && e.code === 'SQLITE_BUSY') {\n lc.warn?.(\n `SQLITE_BUSY for ${Date.now() - start} ms (attempt ${i + 1}). ` +\n `This is only expected if litestream is performing a large ` +\n `checkpoint.`,\n e,\n );\n continue;\n }\n throw e;\n }\n }\n }\n\n /** @return If a transaction was committed. */\n #processMessage(\n lc: LogContext,\n msg: Change,\n watermark: string | undefined,\n ): CommitResult | null {\n if (msg.tag === 'begin') {\n if (this.#currentTx) {\n throw new Error(`Already in a transaction ${stringify(msg)}`);\n }\n this.#currentTx = this.#beginTransaction(\n lc,\n must(watermark),\n msg.json ?? JSON_PARSED,\n );\n return null;\n }\n\n // For non-begin messages, there should be a #currentTx set.\n const tx = this.#currentTx;\n if (!tx) {\n throw new Error(\n `Received message outside of transaction: ${stringify(msg)}`,\n );\n }\n\n if (msg.tag === 'commit') {\n // Undef this.#currentTx to allow the assembly of the next transaction.\n this.#currentTx = null;\n\n assert(watermark, 'watermark is required for commit messages');\n return tx.processCommit(msg, watermark);\n }\n\n if (msg.tag === 'rollback') {\n this.#currentTx?.abort(lc);\n this.#currentTx = null;\n return null;\n }\n\n switch (msg.tag) {\n case 'insert':\n tx.processInsert(msg);\n break;\n case 'update':\n tx.processUpdate(msg);\n break;\n case 'delete':\n tx.processDelete(msg);\n break;\n case 'truncate':\n tx.processTruncate(msg);\n break;\n case 'create-table':\n tx.processCreateTable(msg);\n break;\n case 'rename-table':\n tx.processRenameTable(msg);\n break;\n case 'update-table-metadata':\n tx.processTableMetadata(msg);\n break;\n case 'add-column':\n tx.processAddColumn(msg);\n break;\n case 'update-column':\n tx.processUpdateColumn(msg);\n break;\n case 'drop-column':\n tx.processDropColumn(msg);\n break;\n case 'drop-table':\n tx.processDropTable(msg);\n break;\n case 'create-index':\n tx.processCreateIndex(msg);\n break;\n case 'drop-index':\n tx.processDropIndex(msg);\n break;\n case 'backfill':\n tx.processBackfill(msg);\n break;\n case 'backfill-completed':\n tx.processBackfillCompleted(msg);\n break;\n default:\n unreachable(msg);\n }\n\n return null;\n }\n}\n\n/**\n * The {@link TransactionProcessor} handles the sequence of messages from\n * upstream, from `BEGIN` to `COMMIT` and executes the corresponding mutations\n * on the {@link postgres.TransactionSql} on the replica.\n *\n * When applying row contents to the replica, the `_0_version` column is added / updated,\n * and a corresponding entry in the `ChangeLog` is added. The version value is derived\n * from the watermark of the preceding transaction (stored as the `nextStateVersion` in the\n * `ReplicationState` table).\n *\n * Side note: For non-streaming Postgres transactions, the commitEndLsn (and thus\n * commit watermark) is available in the `begin` message, so it could theoretically\n * be used for the row version of changes within the transaction. However, the\n * commitEndLsn is not available in the streaming (in-progress) transaction\n * protocol, and may not be available for CDC streams of other upstream types.\n * Therefore, the zero replication protocol is designed to not require the commit\n * watermark when a transaction begins.\n *\n * Also of interest is the fact that all INSERT Messages are logically applied as\n * UPSERTs. See {@link processInsert} for the underlying motivation.\n */\nclass TransactionProcessor {\n readonly #lc: LogContext;\n readonly #startMs: number;\n readonly #db: StatementRunner;\n readonly #mode: ChangeProcessorMode;\n readonly #version: LexiVersion;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>;\n readonly #jsonFormat: JSONFormat;\n readonly #columnMetadata: ColumnMetadataStore;\n\n #pos = 0;\n #schemaChanged = false;\n #numChangeLogEntries = 0;\n\n constructor(\n lc: LogContext,\n db: StatementRunner,\n mode: ChangeProcessorMode,\n changeLog: ChangeLog,\n tableMetadata: TableMetadataTracker,\n tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>,\n commitVersion: LexiVersion,\n jsonFormat: JSONFormat,\n ) {\n this.#startMs = Date.now();\n this.#mode = mode;\n this.#jsonFormat = jsonFormat;\n\n switch (mode) {\n case 'serving':\n // Although the Replicator / Incremental Syncer is the only writer of the replica,\n // a `BEGIN CONCURRENT` transaction is used to allow View Syncers to simulate\n // (i.e. and `ROLLBACK`) changes on historic snapshots of the database for the\n // purpose of IVM).\n //\n // This TransactionProcessor is the only logic that will actually\n // `COMMIT` any transactions to the replica.\n db.beginConcurrent();\n break;\n case 'backup':\n // For the backup-replicator (i.e. replication-manager), there are no View Syncers\n // and thus BEGIN CONCURRENT is not necessary. In fact, BEGIN CONCURRENT can cause\n // deadlocks with forced wal-checkpoints (which `litestream replicate` performs),\n // so it is important to use vanilla transactions in this configuration.\n db.beginImmediate();\n break;\n case 'initial-sync':\n // When the ChangeProcessor is used for initial-sync, the calling code\n // handles the transaction boundaries.\n break;\n default:\n unreachable();\n }\n this.#db = db;\n this.#version = commitVersion;\n this.#lc = lc.withContext('version', commitVersion);\n this.#changeLog = changeLog;\n this.#tableMetadata = tableMetadata;\n this.#tableSpecs = tableSpecs;\n // The column_metadata table is guaranteed to exist since the\n // replica-schema.ts migration to v8.\n this.#columnMetadata = must(ColumnMetadataStore.getInstance(db.db));\n\n if (this.#tableSpecs.size === 0) {\n this.#reloadTableSpecs();\n }\n }\n\n #reloadTableSpecs() {\n this.#tableSpecs.clear();\n // zqlSpecs include the primary key derived from unique indexes\n const zqlSpecs = computeZqlSpecs(this.#lc, this.#db.db, {\n includeBackfillingColumns: true,\n });\n for (let spec of listTables(this.#db.db)) {\n if (!spec.primaryKey) {\n spec = {\n ...spec,\n primaryKey: [\n ...(zqlSpecs.get(spec.name)?.tableSpec.primaryKey ?? []),\n ],\n };\n }\n this.#tableSpecs.set(spec.name, spec);\n }\n }\n\n #tableSpec(name: string) {\n return must(this.#tableSpecs.get(name), `Unknown table ${name}`);\n }\n\n #getKey(\n {row, numCols}: {row: LiteRow; numCols: number},\n {relation}: {relation: MessageRelation},\n ): LiteRowKey {\n const keyColumns =\n relation.rowKey.type !== 'full'\n ? relation.rowKey.columns // already a suitable key\n : this.#tableSpec(liteTableName(relation)).primaryKey;\n if (!keyColumns?.length) {\n throw new Error(\n `Cannot replicate table \"${relation.name}\" without a PRIMARY KEY or UNIQUE INDEX`,\n );\n }\n // For the common case (replica identity default), the row is already the\n // key for deletes and updates, in which case a new object can be avoided.\n if (numCols === keyColumns.length) {\n return row;\n }\n const key: Record<string, LiteValueType> = {};\n for (const col of keyColumns) {\n key[col] = row[col];\n }\n return key;\n }\n\n processInsert(insert: MessageInsert) {\n const table = liteTableName(insert.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(insert.new, tableSpec, this.#jsonFormat);\n\n this.#upsert(table, {\n ...newRow.row,\n [ZERO_VERSION_COLUMN_NAME]: this.#version,\n });\n\n if (insert.relation.rowKey.columns.length === 0) {\n // INSERTs can be replicated for rows without a PRIMARY KEY or a\n // UNIQUE INDEX. These are written to the replica but not recorded\n // in the changeLog, because these rows cannot participate in IVM.\n //\n // (Once the table schema has been corrected to include a key, the\n // associated schema change will reset pipelines and data can be\n // loaded via hydration.)\n return;\n }\n const key = this.#getKey(newRow, insert);\n this.#logSetOp(table, key, getBackfilledColumns(newRow.row, tableSpec));\n }\n\n #upsert(table: string, row: LiteRow) {\n const columns = Object.keys(row).map(c => id(c));\n this.#db.run(\n `\n INSERT OR REPLACE INTO ${id(table)} (${columns.join(',')})\n VALUES (${Array.from({length: columns.length}).fill('?').join(',')})\n `,\n Object.values(row),\n );\n }\n\n // Updates by default are applied as UPDATE commands to support partial\n // row specifications from the change source. In particular, this is needed\n // to handle updates for which unchanged TOASTed values are not sent:\n //\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-TUPLEDATA\n //\n // However, in certain cases an UPDATE may be received for a row that\n // was not initially synced, such as when, an existing table is added\n // to the app's publication.\n //\n // In order to facilitate \"resumptive\" replication, the logic falls back to\n // an INSERT if the update did not change any rows.\n processUpdate(update: MessageUpdate) {\n const table = liteTableName(update.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(update.new, tableSpec, this.#jsonFormat);\n const row = {...newRow.row, [ZERO_VERSION_COLUMN_NAME]: this.#version};\n\n // update.key is set with the old values if the key has changed.\n const oldKey = update.key\n ? this.#getKey(\n liteRow(update.key, this.#tableSpec(table), this.#jsonFormat),\n update,\n )\n : null;\n const newKey = this.#getKey(newRow, update);\n\n if (oldKey) {\n this.#logDeleteOp(table, oldKey, tableSpec.backfilling);\n }\n this.#logSetOp(table, newKey, getBackfilledColumns(newRow.row, tableSpec));\n\n const currKey = oldKey ?? newKey;\n const conds = Object.keys(currKey).map(col => `${id(col)}=?`);\n const setExprs = Object.keys(row).map(col => `${id(col)}=?`);\n\n const {changes} = this.#db.run(\n `\n UPDATE ${id(table)}\n SET ${setExprs.join(',')}\n WHERE ${conds.join(' AND ')}\n `,\n [...Object.values(row), ...Object.values(currKey)],\n );\n\n // If the UPDATE did not affect any rows, perform an UPSERT of the\n // new row for resumptive replication.\n if (changes === 0) {\n this.#upsert(table, row);\n }\n }\n\n processDelete(del: MessageDelete) {\n const table = liteTableName(del.relation);\n const tableSpec = this.#tableSpec(table);\n const rowKey = this.#getKey(\n liteRow(del.key, tableSpec, this.#jsonFormat),\n del,\n );\n\n this.#delete(table, rowKey);\n this.#logDeleteOp(table, rowKey, tableSpec.backfilling);\n }\n\n #delete(table: string, rowKey: LiteRowKey) {\n const conds = Object.keys(rowKey).map(col => `${id(col)}=?`);\n this.#db.run(\n `DELETE FROM ${id(table)} WHERE ${conds.join(' AND ')}`,\n Object.values(rowKey),\n );\n }\n\n processTruncate(truncate: MessageTruncate) {\n for (const relation of truncate.relations) {\n const table = liteTableName(relation);\n // Update replica data.\n this.#db.run(`DELETE FROM ${id(table)}`);\n\n // Update change log.\n this.#logTruncateOp(table);\n }\n }\n\n processCreateTable(create: TableCreate) {\n if (create.metadata) {\n this.#tableMetadata.set(create.spec, create.metadata);\n }\n const table = mapPostgresToLite(create.spec);\n this.#db.db.exec(createLiteTableStatement(table));\n\n // Write to metadata table\n for (const [colName, colSpec] of Object.entries(create.spec.columns)) {\n this.#columnMetadata.insert(\n table.name,\n colName,\n colSpec,\n create.backfill?.[colName],\n );\n }\n\n if (\n Object.keys(create.backfill ?? {}).length ===\n Object.keys(create.spec.columns).length\n ) {\n this.#reloadTableSpecs();\n } else {\n // Make the table visible immediately unless all of the columns are\n // being backfilled. In the backfill case, the version bump will happen\n // with the backfill is complete.\n this.#logResetOp(table.name);\n }\n this.#lc.info?.(create.tag, table.name);\n }\n\n processTableMetadata(msg: TableUpdateMetadata) {\n this.#tableMetadata.set(msg.table, msg.new);\n }\n\n processRenameTable(rename: TableRename) {\n this.#tableMetadata.rename(rename.old, rename.new);\n\n const oldName = liteTableName(rename.old);\n const newName = liteTableName(rename.new);\n this.#db.db.exec(`ALTER TABLE ${id(oldName)} RENAME TO ${id(newName)}`);\n\n // Rename in metadata table\n this.#columnMetadata.renameTable(oldName, newName);\n\n this.#bumpVersions(newName);\n this.#logResetOp(oldName);\n this.#lc.info?.(rename.tag, oldName, newName);\n }\n\n processAddColumn(msg: ColumnAdd) {\n if (msg.tableMetadata) {\n this.#tableMetadata.set(msg.table, msg.tableMetadata);\n }\n const table = liteTableName(msg.table);\n const {name} = msg.column;\n const spec = mapPostgresToLiteColumn(table, msg.column);\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} ADD ${id(name)} ${liteColumnDef(spec)}`,\n );\n\n // Write to metadata table\n this.#columnMetadata.insert(table, name, msg.column.spec, msg.backfill);\n\n if (msg.backfill) {\n this.#reloadTableSpecs();\n } else {\n // Make the new column visible immediately if it's not being backfilled.\n // Otherwise, the version bump will happen with the backfill is complete.\n this.#bumpVersions(table);\n }\n this.#lc.info?.(msg.tag, table, msg.column);\n }\n\n processUpdateColumn(msg: ColumnUpdate) {\n const table = liteTableName(msg.table);\n let oldName = msg.old.name;\n const newName = msg.new.name;\n\n // update-column can ignore defaults because it does not change the values\n // in existing rows.\n //\n // https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-SET-DROP-DEFAULT\n //\n // \"The new default value will only apply in subsequent INSERT or UPDATE\n // commands; it does not cause rows already in the table to change.\"\n //\n // This allows support for _changing_ column defaults to any expression,\n // since it does not affect what the replica needs to do.\n const oldSpec = mapPostgresToLiteColumn(table, msg.old, 'ignore-default');\n const newSpec = mapPostgresToLiteColumn(table, msg.new, 'ignore-default');\n\n // The only updates that are relevant are the column name and the data type.\n if (oldName === newName && oldSpec.dataType === newSpec.dataType) {\n this.#lc.info?.(msg.tag, 'no thing to update', oldSpec, newSpec);\n return;\n }\n // If the data type changes, we have to make a new column with the new data type\n // and copy the values over.\n if (oldSpec.dataType !== newSpec.dataType) {\n // Remember (and drop) the indexes that reference the column.\n const indexes = listIndexes(this.#db.db).filter(\n idx => idx.tableName === table && oldName in idx.columns,\n );\n const stmts = indexes.map(idx => `DROP INDEX IF EXISTS ${id(idx.name)};`);\n const tmpName = `tmp.${newName}`;\n stmts.push(`\n ALTER TABLE ${id(table)} ADD ${id(tmpName)} ${liteColumnDef(newSpec)};\n UPDATE ${id(table)} SET ${id(tmpName)} = ${id(oldName)};\n ALTER TABLE ${id(table)} DROP ${id(oldName)};\n `);\n for (const idx of indexes) {\n // Re-create the indexes to reference the new column.\n idx.columns[tmpName] = idx.columns[oldName];\n delete idx.columns[oldName];\n stmts.push(createLiteIndexStatement(idx));\n }\n this.#db.db.exec(stmts.join(''));\n oldName = tmpName;\n }\n if (oldName !== newName) {\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} RENAME ${id(oldName)} TO ${id(newName)}`,\n );\n }\n\n // Update metadata table\n this.#columnMetadata.update(\n table,\n msg.old.name,\n msg.new.name,\n msg.new.spec,\n );\n\n this.#bumpVersions(table);\n this.#lc.info?.(msg.tag, table, msg.new);\n }\n\n processDropColumn(msg: ColumnDrop) {\n const table = liteTableName(msg.table);\n const {column} = msg;\n this.#db.db.exec(`ALTER TABLE ${id(table)} DROP ${id(column)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteColumn(table, column);\n\n this.#bumpVersions(table);\n this.#lc.info?.(msg.tag, table, column);\n }\n\n processDropTable(drop: TableDrop) {\n this.#tableMetadata.drop(drop.id);\n\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP TABLE IF EXISTS ${id(name)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteTable(name);\n\n this.#logResetOp(name);\n this.#lc.info?.(drop.tag, name);\n }\n\n processCreateIndex(create: IndexCreate) {\n const index = mapPostgresToLiteIndex(create.spec);\n this.#db.db.exec(createLiteIndexStatement(index));\n\n // indexes affect tables visibility (e.g. sync-ability is gated on\n // having a unique index), so reset pipelines to refresh table schemas.\n // However, the reset is not necessary if the index is for a table\n // that is not yet visible due to backfilling.\n const tableSpec = must(this.#tableSpecs.get(index.tableName));\n if (\n (tableSpec.backfilling ?? []).length ===\n Object.entries(tableSpec.columns).length - 1 // don't count _0_version\n ) {\n this.#reloadTableSpecs();\n } else {\n this.#logResetOp(index.tableName);\n }\n this.#lc.info?.(create.tag, index.name);\n }\n\n processDropIndex(drop: IndexDrop) {\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP INDEX IF EXISTS ${id(name)}`);\n this.#lc.info?.(drop.tag, name);\n }\n\n #bumpVersions(table: string) {\n this.#db.run(\n `UPDATE ${id(table)} SET ${id(ZERO_VERSION_COLUMN_NAME)} = ?`,\n this.#version,\n );\n this.#logResetOp(table);\n }\n\n /**\n * @param backfilledColumns `backfilling` columns for which values were set\n */\n #logSetOp(\n table: string,\n key: LiteRowKey,\n backfilledColumns: string[] | undefined,\n ) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilledColumns !== undefined) {\n this.#changeLog.logSetOp(\n this.#version,\n this.#pos++,\n table,\n key,\n backfilledColumns,\n );\n this.#numChangeLogEntries++;\n }\n }\n\n #logDeleteOp(table: string, key: LiteRowKey, backfilling?: string[]) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilling?.length) {\n this.#changeLog.logDeleteOp(this.#version, this.#pos++, table, key);\n this.#numChangeLogEntries++;\n }\n }\n\n #logTruncateOp(table: string) {\n if (this.#mode === 'serving') {\n this.#changeLog.logTruncateOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n }\n\n #logResetOp(table: string) {\n this.#schemaChanged = true;\n if (this.#mode === 'serving') {\n this.#changeLog.logResetOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n this.#reloadTableSpecs();\n }\n\n processBackfill({relation, watermark, columns, rowValues}: MessageBackfill) {\n const tableName = liteTableName(relation);\n const tableSpec = must(this.#tableSpecs.get(tableName));\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n // Common parts of the INSERT sql statement.\n const insertColsStr = [...cols, ZERO_VERSION_COLUMN_NAME].map(id).join(',');\n const qMarks = Array.from({length: cols.length + 1}, () => '?').join(',');\n const rowKeyColsStr = rowKeyCols.map(id).join(',');\n\n let backfilled = 0;\n let skipped = 0;\n for (const v of rowValues) {\n const row = liteRow(\n Object.fromEntries(cols.map((c, i) => [c, v[i]])),\n tableSpec,\n this.#jsonFormat,\n );\n const rowKey = this.#getKey(row, {relation});\n const rowOp = this.#changeLog.getLatestRowOp(tableName, rowKey);\n if (rowOp?.op === DEL_OP && rowOp.stateVersion > watermark) {\n skipped++;\n continue; // the row was deleted after the backfill snapshot\n }\n const updates =\n rowOp?.op === SET_OP\n ? cols.filter(\n c => (rowOp.backfillingColumnVersions[c] ?? '') <= watermark,\n )\n : cols;\n if (updates.length === 0) {\n // row already has newer values for all backfilling columns.\n skipped++;\n continue;\n }\n const updateStmts = updates.map(col => `${id(col)}=excluded.${id(col)}`);\n this.#db.run(\n /*sql*/ `\n INSERT INTO ${id(tableName)} (${insertColsStr}) VALUES (${qMarks})\n ON CONFLICT (${rowKeyColsStr})\n DO UPDATE SET ${updateStmts.join(',')};\n `,\n ...Object.values(row.row),\n watermark, // the _0_version for new rows (i.e. table backfill)\n );\n backfilled++;\n }\n\n this.#lc.debug?.(\n `backfilled ${backfilled} rows (skipped ${skipped}) into ${tableName}`,\n );\n }\n\n #completedBackfill: DownloadStatus | undefined;\n\n processBackfillCompleted({relation, columns, status}: BackfillCompleted) {\n const tableName = liteTableName(relation);\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n const columnMetadata = must(ColumnMetadataStore.getInstance(this.#db.db));\n for (const col of cols) {\n columnMetadata.clearBackfilling(tableName, col);\n }\n // Given that new columns are being exposed for every row in the table, bump the\n // row version for all rows.\n this.#bumpVersions(tableName);\n if (status) {\n this.#completedBackfill = {table: tableName, columns: cols, ...status};\n }\n this.#lc.info?.(`finished backfilling ${tableName}`);\n\n // Note that there is no need to clear the backfillingColumnVersions values\n // in the changeLog. It could theoretically be done for clarity but:\n // (1) it could be non-trivial in terms of latency introduced and\n // (2) the data must be preserved if _other_ columns are in the process\n // of being backfilled\n //\n // Thus, for speed and simplicity, the values are left as is. (Note that\n // subsequent replicated changes to those rows will clear the values if\n // no backfills are in progress).\n }\n\n processCommit(commit: MessageCommit, watermark: string): CommitResult {\n if (watermark !== this.#version) {\n throw new Error(\n `'commit' version ${watermark} does not match 'begin' version ${\n this.#version\n }: ${stringify(commit)}`,\n );\n }\n updateReplicationWatermark(this.#db, watermark);\n\n if (this.#schemaChanged) {\n const start = Date.now();\n this.#db.db.pragma('optimize');\n this.#lc.info?.(\n `PRAGMA optimized after schema change (${Date.now() - start} ms)`,\n );\n }\n\n if (this.#mode !== 'initial-sync') {\n this.#db.commit();\n }\n\n const elapsedMs = Date.now() - this.#startMs;\n this.#lc.debug?.(`Committed tx@${this.#version} (${elapsedMs} ms)`);\n\n return {\n watermark,\n completedBackfill: this.#completedBackfill,\n schemaUpdated: this.#schemaChanged,\n changeLogUpdated: this.#numChangeLogEntries > 0,\n };\n }\n\n abort(lc: LogContext) {\n lc.info?.(`aborting transaction ${this.#version}`);\n this.#db.rollback();\n }\n}\n\nfunction getBackfilledColumns(\n row: LiteRow,\n {backfilling}: LiteTableSpecWithReplicationStatus,\n): string[] | undefined {\n if (!backfilling?.length) {\n return undefined; // common case\n }\n return backfilling.filter(col => col in row);\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"change-processor.js","sources":["../../../../../../zero-cache/src/services/replicator/change-processor.ts"],"sourcesContent":["import type {LogContext} from '@rocicorp/logger';\nimport {SqliteError} from '@rocicorp/zero-sqlite3';\nimport {AbortError} from '../../../../shared/src/abort-error.ts';\nimport {assert, unreachable} from '../../../../shared/src/asserts.ts';\nimport {stringify} from '../../../../shared/src/bigint-json.ts';\nimport {must} from '../../../../shared/src/must.ts';\nimport type {DownloadStatus} from '../../../../zero-events/src/status.ts';\nimport {\n createLiteIndexStatement,\n createLiteTableStatement,\n liteColumnDef,\n} from '../../db/create.ts';\nimport {\n computeZqlSpecs,\n listIndexes,\n listTables,\n type LiteTableSpecWithReplicationStatus,\n} from '../../db/lite-tables.ts';\nimport {\n mapPostgresToLite,\n mapPostgresToLiteColumn,\n mapPostgresToLiteIndex,\n} from '../../db/pg-to-lite.ts';\nimport type {StatementRunner} from '../../db/statements.ts';\nimport type {LexiVersion} from '../../types/lexi-version.ts';\nimport {\n JSON_PARSED,\n liteRow,\n type JSONFormat,\n type LiteRow,\n type LiteRowKey,\n type LiteValueType,\n} from '../../types/lite.ts';\nimport {liteTableName} from '../../types/names.ts';\nimport {id} from '../../types/sql.ts';\nimport type {\n BackfillCompleted,\n Change,\n ColumnAdd,\n ColumnDrop,\n ColumnUpdate,\n Identifier,\n IndexCreate,\n IndexDrop,\n MessageBackfill,\n MessageCommit,\n MessageDelete,\n MessageInsert,\n MessageRelation,\n MessageTruncate,\n MessageUpdate,\n TableCreate,\n TableDrop,\n TableRename,\n TableUpdateMetadata,\n} from '../change-source/protocol/current/data.ts';\nimport type {ChangeStreamData} from '../change-source/protocol/current/downstream.ts';\nimport type {ReplicatorMode} from './replicator.ts';\nimport {ChangeLog, DEL_OP, SET_OP} from './schema/change-log.ts';\nimport {ColumnMetadataStore} from './schema/column-metadata.ts';\nimport {\n ZERO_VERSION_COLUMN_NAME,\n updateReplicationWatermark,\n} from './schema/replication-state.ts';\nimport {TableMetadataTracker} from './schema/table-metadata.ts';\n\nexport type ChangeProcessorMode = ReplicatorMode | 'initial-sync';\n\nexport type CommitResult = {\n watermark: string;\n completedBackfill: DownloadStatus | undefined;\n schemaUpdated: boolean;\n changeLogUpdated: boolean;\n};\n\n/**\n * The ChangeProcessor partitions the stream of messages into transactions\n * by creating a {@link TransactionProcessor} when a transaction begins, and dispatching\n * messages to it until the commit is received.\n *\n * From https://www.postgresql.org/docs/current/protocol-logical-replication.html#PROTOCOL-LOGICAL-MESSAGES-FLOW :\n *\n * \"The logical replication protocol sends individual transactions one by one.\n * This means that all messages between a pair of Begin and Commit messages\n * belong to the same transaction.\"\n */\nexport class ChangeProcessor {\n readonly #db: StatementRunner;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #mode: ChangeProcessorMode;\n readonly #failService: (lc: LogContext, err: unknown) => void;\n\n // The TransactionProcessor lazily loads table specs into this Map,\n // and reloads them after a schema change. It is cached here to avoid\n // reading them from the DB on every transaction.\n readonly #tableSpecs = new Map<string, LiteTableSpecWithReplicationStatus>();\n\n #currentTx: TransactionProcessor | null = null;\n\n #failure: Error | undefined;\n\n constructor(\n db: StatementRunner,\n mode: ChangeProcessorMode,\n failService: (lc: LogContext, err: unknown) => void,\n ) {\n this.#db = db;\n this.#changeLog = new ChangeLog(db.db);\n this.#tableMetadata = new TableMetadataTracker(db.db);\n this.#mode = mode;\n this.#failService = failService;\n }\n\n #fail(lc: LogContext, err: unknown) {\n if (!this.#failure) {\n this.#currentTx?.abort(lc); // roll back any pending transaction.\n\n this.#failure = ensureError(err);\n\n if (!(err instanceof AbortError)) {\n // Propagate the failure up to the service.\n lc.error?.('Message Processing failed:', this.#failure);\n this.#failService(lc, this.#failure);\n }\n }\n }\n\n abort(lc: LogContext) {\n this.#fail(lc, new AbortError());\n }\n\n /** @return If a transaction was committed. */\n processMessage(\n lc: LogContext,\n downstream: ChangeStreamData,\n ): CommitResult | null {\n const [type, message] = downstream;\n if (this.#failure) {\n lc.debug?.(`Dropping ${message.tag}`);\n return null;\n }\n try {\n const watermark =\n type === 'begin'\n ? downstream[2].commitWatermark\n : type === 'commit'\n ? downstream[2].watermark\n : undefined;\n return this.#processMessage(lc, message, watermark);\n } catch (e) {\n this.#fail(lc, e);\n }\n return null;\n }\n\n #beginTransaction(\n lc: LogContext,\n commitVersion: string,\n jsonFormat: JSONFormat,\n ): TransactionProcessor {\n const start = Date.now();\n\n // litestream can technically hold the lock for an arbitrary amount of time\n // when checkpointing a large commit. Crashing on the busy-timeout in this\n // scenario will either produce a corrupt backup or otherwise prevent\n // replication from proceeding.\n //\n // Instead, retry the lock acquisition indefinitely. If this masks\n // an unknown deadlock situation, manual intervention will be necessary.\n for (let i = 0; ; i++) {\n try {\n return new TransactionProcessor(\n lc,\n this.#db,\n this.#mode,\n this.#changeLog,\n this.#tableMetadata,\n this.#tableSpecs,\n commitVersion,\n jsonFormat,\n );\n } catch (e) {\n if (e instanceof SqliteError && e.code === 'SQLITE_BUSY') {\n lc.warn?.(\n `SQLITE_BUSY for ${Date.now() - start} ms (attempt ${i + 1}). ` +\n `This is only expected if litestream is performing a large ` +\n `checkpoint.`,\n e,\n );\n continue;\n }\n throw e;\n }\n }\n }\n\n /** @return If a transaction was committed. */\n #processMessage(\n lc: LogContext,\n msg: Change,\n watermark: string | undefined,\n ): CommitResult | null {\n if (msg.tag === 'begin') {\n if (this.#currentTx) {\n throw new Error(`Already in a transaction ${stringify(msg)}`);\n }\n this.#currentTx = this.#beginTransaction(\n lc,\n must(watermark),\n msg.json ?? JSON_PARSED,\n );\n return null;\n }\n\n // For non-begin messages, there should be a #currentTx set.\n const tx = this.#currentTx;\n if (!tx) {\n throw new Error(\n `Received message outside of transaction: ${stringify(msg)}`,\n );\n }\n\n if (msg.tag === 'commit') {\n // Undef this.#currentTx to allow the assembly of the next transaction.\n this.#currentTx = null;\n\n assert(watermark, 'watermark is required for commit messages');\n return tx.processCommit(msg, watermark);\n }\n\n if (msg.tag === 'rollback') {\n this.#currentTx?.abort(lc);\n this.#currentTx = null;\n return null;\n }\n\n switch (msg.tag) {\n case 'insert':\n tx.processInsert(msg);\n break;\n case 'update':\n tx.processUpdate(msg);\n break;\n case 'delete':\n tx.processDelete(msg);\n break;\n case 'truncate':\n tx.processTruncate(msg);\n break;\n case 'create-table':\n tx.processCreateTable(msg);\n break;\n case 'rename-table':\n tx.processRenameTable(msg);\n break;\n case 'update-table-metadata':\n tx.processTableMetadata(msg);\n break;\n case 'add-column':\n tx.processAddColumn(msg);\n break;\n case 'update-column':\n tx.processUpdateColumn(msg);\n break;\n case 'drop-column':\n tx.processDropColumn(msg);\n break;\n case 'drop-table':\n tx.processDropTable(msg);\n break;\n case 'create-index':\n tx.processCreateIndex(msg);\n break;\n case 'drop-index':\n tx.processDropIndex(msg);\n break;\n case 'backfill':\n tx.processBackfill(msg);\n break;\n case 'backfill-completed':\n tx.processBackfillCompleted(msg);\n break;\n default:\n unreachable(msg);\n }\n\n return null;\n }\n}\n\n/**\n * The {@link TransactionProcessor} handles the sequence of messages from\n * upstream, from `BEGIN` to `COMMIT` and executes the corresponding mutations\n * on the {@link postgres.TransactionSql} on the replica.\n *\n * When applying row contents to the replica, the `_0_version` column is added / updated,\n * and a corresponding entry in the `ChangeLog` is added. The version value is derived\n * from the watermark of the preceding transaction (stored as the `nextStateVersion` in the\n * `ReplicationState` table).\n *\n * Side note: For non-streaming Postgres transactions, the commitEndLsn (and thus\n * commit watermark) is available in the `begin` message, so it could theoretically\n * be used for the row version of changes within the transaction. However, the\n * commitEndLsn is not available in the streaming (in-progress) transaction\n * protocol, and may not be available for CDC streams of other upstream types.\n * Therefore, the zero replication protocol is designed to not require the commit\n * watermark when a transaction begins.\n *\n * Also of interest is the fact that all INSERT Messages are logically applied as\n * UPSERTs. See {@link processInsert} for the underlying motivation.\n */\nclass TransactionProcessor {\n readonly #lc: LogContext;\n readonly #startMs: number;\n readonly #db: StatementRunner;\n readonly #mode: ChangeProcessorMode;\n readonly #version: LexiVersion;\n readonly #changeLog: ChangeLog;\n readonly #tableMetadata: TableMetadataTracker;\n readonly #tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>;\n readonly #jsonFormat: JSONFormat;\n readonly #columnMetadata: ColumnMetadataStore;\n\n #pos = 0;\n #schemaChanged = false;\n #numChangeLogEntries = 0;\n\n constructor(\n lc: LogContext,\n db: StatementRunner,\n mode: ChangeProcessorMode,\n changeLog: ChangeLog,\n tableMetadata: TableMetadataTracker,\n tableSpecs: Map<string, LiteTableSpecWithReplicationStatus>,\n commitVersion: LexiVersion,\n jsonFormat: JSONFormat,\n ) {\n this.#startMs = Date.now();\n this.#mode = mode;\n this.#jsonFormat = jsonFormat;\n\n switch (mode) {\n case 'serving':\n // Although the Replicator / Incremental Syncer is the only writer of the replica,\n // a `BEGIN CONCURRENT` transaction is used to allow View Syncers to simulate\n // (i.e. and `ROLLBACK`) changes on historic snapshots of the database for the\n // purpose of IVM).\n //\n // This TransactionProcessor is the only logic that will actually\n // `COMMIT` any transactions to the replica.\n db.beginConcurrent();\n break;\n case 'backup':\n // For the backup-replicator (i.e. replication-manager), there are no View Syncers\n // and thus BEGIN CONCURRENT is not necessary. In fact, BEGIN CONCURRENT can cause\n // deadlocks with forced wal-checkpoints (which `litestream replicate` performs),\n // so it is important to use vanilla transactions in this configuration.\n db.beginImmediate();\n break;\n case 'initial-sync':\n // When the ChangeProcessor is used for initial-sync, the calling code\n // handles the transaction boundaries.\n break;\n default:\n unreachable();\n }\n this.#db = db;\n this.#version = commitVersion;\n this.#lc = lc.withContext('version', commitVersion);\n this.#changeLog = changeLog;\n this.#tableMetadata = tableMetadata;\n this.#tableSpecs = tableSpecs;\n // The column_metadata table is guaranteed to exist since the\n // replica-schema.ts migration to v8.\n this.#columnMetadata = must(ColumnMetadataStore.getInstance(db.db));\n\n if (this.#tableSpecs.size === 0) {\n this.#reloadTableSpecs();\n }\n }\n\n #reloadTableSpecs() {\n this.#tableSpecs.clear();\n // zqlSpecs include the primary key derived from unique indexes\n const zqlSpecs = computeZqlSpecs(this.#lc, this.#db.db, {\n includeBackfillingColumns: true,\n });\n for (let spec of listTables(this.#db.db)) {\n if (!spec.primaryKey) {\n spec = {\n ...spec,\n primaryKey: [\n ...(zqlSpecs.get(spec.name)?.tableSpec.primaryKey ?? []),\n ],\n };\n }\n this.#tableSpecs.set(spec.name, spec);\n }\n }\n\n #tableSpec(name: string) {\n return must(this.#tableSpecs.get(name), `Unknown table ${name}`);\n }\n\n #getKey(\n {row, numCols}: {row: LiteRow; numCols: number},\n {relation}: {relation: MessageRelation},\n ): LiteRowKey {\n const keyColumns =\n relation.rowKey.type !== 'full'\n ? relation.rowKey.columns // already a suitable key\n : this.#tableSpec(liteTableName(relation)).primaryKey;\n if (!keyColumns?.length) {\n throw new Error(\n `Cannot replicate table \"${relation.name}\" without a PRIMARY KEY or UNIQUE INDEX`,\n );\n }\n // For the common case (replica identity default), the row is already the\n // key for deletes and updates, in which case a new object can be avoided.\n if (numCols === keyColumns.length) {\n return row;\n }\n const key: Record<string, LiteValueType> = {};\n for (const col of keyColumns) {\n key[col] = row[col];\n }\n return key;\n }\n\n processInsert(insert: MessageInsert) {\n const table = liteTableName(insert.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(insert.new, tableSpec, this.#jsonFormat);\n\n this.#upsert(table, {\n ...newRow.row,\n [ZERO_VERSION_COLUMN_NAME]: this.#version,\n });\n\n if (insert.relation.rowKey.columns.length === 0) {\n // INSERTs can be replicated for rows without a PRIMARY KEY or a\n // UNIQUE INDEX. These are written to the replica but not recorded\n // in the changeLog, because these rows cannot participate in IVM.\n //\n // (Once the table schema has been corrected to include a key, the\n // associated schema change will reset pipelines and data can be\n // loaded via hydration.)\n return;\n }\n const key = this.#getKey(newRow, insert);\n this.#logSetOp(table, key, getBackfilledColumns(newRow.row, tableSpec));\n }\n\n #upsert(table: string, row: LiteRow) {\n const columns = Object.keys(row).map(c => id(c));\n this.#db.run(\n `\n INSERT OR REPLACE INTO ${id(table)} (${columns.join(',')})\n VALUES (${Array.from({length: columns.length}).fill('?').join(',')})\n `,\n Object.values(row),\n );\n }\n\n // Updates by default are applied as UPDATE commands to support partial\n // row specifications from the change source. In particular, this is needed\n // to handle updates for which unchanged TOASTed values are not sent:\n //\n // https://www.postgresql.org/docs/current/protocol-logicalrep-message-formats.html#PROTOCOL-LOGICALREP-MESSAGE-FORMATS-TUPLEDATA\n //\n // However, in certain cases an UPDATE may be received for a row that\n // was not initially synced, such as when, an existing table is added\n // to the app's publication.\n //\n // In order to facilitate \"resumptive\" replication, the logic falls back to\n // an INSERT if the update did not change any rows.\n processUpdate(update: MessageUpdate) {\n const table = liteTableName(update.relation);\n const tableSpec = this.#tableSpec(table);\n const newRow = liteRow(update.new, tableSpec, this.#jsonFormat);\n const row = {...newRow.row, [ZERO_VERSION_COLUMN_NAME]: this.#version};\n\n // update.key is set with the old values if the key has changed.\n const oldKey = update.key\n ? this.#getKey(\n liteRow(update.key, this.#tableSpec(table), this.#jsonFormat),\n update,\n )\n : null;\n const newKey = this.#getKey(newRow, update);\n\n if (oldKey) {\n this.#logDeleteOp(table, oldKey, tableSpec.backfilling);\n }\n this.#logSetOp(table, newKey, getBackfilledColumns(newRow.row, tableSpec));\n\n const currKey = oldKey ?? newKey;\n const conds = Object.keys(currKey).map(col => `${id(col)}=?`);\n const setExprs = Object.keys(row).map(col => `${id(col)}=?`);\n\n const {changes} = this.#db.run(\n `\n UPDATE ${id(table)}\n SET ${setExprs.join(',')}\n WHERE ${conds.join(' AND ')}\n `,\n [...Object.values(row), ...Object.values(currKey)],\n );\n\n // If the UPDATE did not affect any rows, perform an UPSERT of the\n // new row for resumptive replication.\n if (changes === 0) {\n this.#upsert(table, row);\n }\n }\n\n processDelete(del: MessageDelete) {\n const table = liteTableName(del.relation);\n const tableSpec = this.#tableSpec(table);\n const rowKey = this.#getKey(\n liteRow(del.key, tableSpec, this.#jsonFormat),\n del,\n );\n\n this.#delete(table, rowKey);\n this.#logDeleteOp(table, rowKey, tableSpec.backfilling);\n }\n\n #delete(table: string, rowKey: LiteRowKey) {\n const conds = Object.keys(rowKey).map(col => `${id(col)}=?`);\n this.#db.run(\n `DELETE FROM ${id(table)} WHERE ${conds.join(' AND ')}`,\n Object.values(rowKey),\n );\n }\n\n processTruncate(truncate: MessageTruncate) {\n for (const relation of truncate.relations) {\n const table = liteTableName(relation);\n // Update replica data.\n this.#db.run(`DELETE FROM ${id(table)}`);\n\n // Update change log.\n this.#logTruncateOp(table);\n }\n }\n\n processCreateTable(create: TableCreate) {\n if (create.metadata) {\n this.#tableMetadata.setUpstreamMetadata(create.spec, create.metadata);\n }\n const table = mapPostgresToLite(create.spec);\n this.#db.db.exec(createLiteTableStatement(table));\n\n // Write to metadata table\n for (const [colName, colSpec] of Object.entries(create.spec.columns)) {\n this.#columnMetadata.insert(\n table.name,\n colName,\n colSpec,\n create.backfill?.[colName],\n );\n }\n\n if (\n Object.keys(create.backfill ?? {}).length ===\n Object.keys(create.spec.columns).length\n ) {\n this.#reloadTableSpecs();\n } else {\n // Make the table visible immediately unless all of the columns are\n // being backfilled. In the backfill case, the version bump will happen\n // with the backfill is complete.\n this.#logResetOp(table.name);\n }\n this.#lc.info?.(create.tag, table.name);\n }\n\n processTableMetadata(msg: TableUpdateMetadata) {\n this.#tableMetadata.setUpstreamMetadata(msg.table, msg.new);\n }\n\n processRenameTable(rename: TableRename) {\n this.#tableMetadata.rename(rename.old, rename.new);\n\n const oldName = liteTableName(rename.old);\n const newName = liteTableName(rename.new);\n this.#db.db.exec(`ALTER TABLE ${id(oldName)} RENAME TO ${id(newName)}`);\n\n // Rename in metadata table\n this.#columnMetadata.renameTable(oldName, newName);\n\n this.#bumpVersions(rename.new);\n this.#logResetOp(oldName);\n this.#lc.info?.(rename.tag, oldName, newName);\n }\n\n processAddColumn(msg: ColumnAdd) {\n if (msg.tableMetadata) {\n this.#tableMetadata.setUpstreamMetadata(msg.table, msg.tableMetadata);\n }\n const table = liteTableName(msg.table);\n const {name} = msg.column;\n const spec = mapPostgresToLiteColumn(table, msg.column);\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} ADD ${id(name)} ${liteColumnDef(spec)}`,\n );\n\n // Write to metadata table\n this.#columnMetadata.insert(table, name, msg.column.spec, msg.backfill);\n\n if (msg.backfill) {\n this.#reloadTableSpecs();\n } else {\n // Make the new column visible immediately if it's not being backfilled.\n // Otherwise, the version bump will happen with the backfill is complete.\n this.#bumpVersions(msg.table);\n }\n this.#lc.info?.(msg.tag, table, msg.column);\n }\n\n processUpdateColumn(msg: ColumnUpdate) {\n const table = liteTableName(msg.table);\n let oldName = msg.old.name;\n const newName = msg.new.name;\n\n // update-column can ignore defaults because it does not change the values\n // in existing rows.\n //\n // https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-DESC-SET-DROP-DEFAULT\n //\n // \"The new default value will only apply in subsequent INSERT or UPDATE\n // commands; it does not cause rows already in the table to change.\"\n //\n // This allows support for _changing_ column defaults to any expression,\n // since it does not affect what the replica needs to do.\n const oldSpec = mapPostgresToLiteColumn(table, msg.old, 'ignore-default');\n const newSpec = mapPostgresToLiteColumn(table, msg.new, 'ignore-default');\n\n // The only updates that are relevant are the column name and the data type.\n if (oldName === newName && oldSpec.dataType === newSpec.dataType) {\n this.#lc.info?.(msg.tag, 'no thing to update', oldSpec, newSpec);\n return;\n }\n // If the data type changes, we have to make a new column with the new data type\n // and copy the values over.\n if (oldSpec.dataType !== newSpec.dataType) {\n // Remember (and drop) the indexes that reference the column.\n const indexes = listIndexes(this.#db.db).filter(\n idx => idx.tableName === table && oldName in idx.columns,\n );\n const stmts = indexes.map(idx => `DROP INDEX IF EXISTS ${id(idx.name)};`);\n const tmpName = `tmp.${newName}`;\n stmts.push(`\n ALTER TABLE ${id(table)} ADD ${id(tmpName)} ${liteColumnDef(newSpec)};\n UPDATE ${id(table)} SET ${id(tmpName)} = ${id(oldName)};\n ALTER TABLE ${id(table)} DROP ${id(oldName)};\n `);\n for (const idx of indexes) {\n // Re-create the indexes to reference the new column.\n idx.columns[tmpName] = idx.columns[oldName];\n delete idx.columns[oldName];\n stmts.push(createLiteIndexStatement(idx));\n }\n this.#db.db.exec(stmts.join(''));\n oldName = tmpName;\n }\n if (oldName !== newName) {\n this.#db.db.exec(\n `ALTER TABLE ${id(table)} RENAME ${id(oldName)} TO ${id(newName)}`,\n );\n }\n\n // Update metadata table\n this.#columnMetadata.update(\n table,\n msg.old.name,\n msg.new.name,\n msg.new.spec,\n );\n\n this.#bumpVersions(msg.table);\n this.#lc.info?.(msg.tag, table, msg.new);\n }\n\n processDropColumn(msg: ColumnDrop) {\n const table = liteTableName(msg.table);\n const {column} = msg;\n this.#db.db.exec(`ALTER TABLE ${id(table)} DROP ${id(column)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteColumn(table, column);\n\n this.#bumpVersions(msg.table);\n this.#lc.info?.(msg.tag, table, column);\n }\n\n processDropTable(drop: TableDrop) {\n this.#tableMetadata.drop(drop.id);\n\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP TABLE IF EXISTS ${id(name)}`);\n\n // Delete from metadata table\n this.#columnMetadata.deleteTable(name);\n\n this.#logResetOp(name);\n this.#lc.info?.(drop.tag, name);\n }\n\n processCreateIndex(create: IndexCreate) {\n const index = mapPostgresToLiteIndex(create.spec);\n this.#db.db.exec(createLiteIndexStatement(index));\n\n // indexes affect tables visibility (e.g. sync-ability is gated on\n // having a unique index), so reset pipelines to refresh table schemas.\n // However, the reset is not necessary if the index is for a table\n // that is not yet visible due to backfilling.\n const tableSpec = must(this.#tableSpecs.get(index.tableName));\n if (\n (tableSpec.backfilling ?? []).length ===\n Object.entries(tableSpec.columns).length - 1 // don't count _0_version\n ) {\n this.#reloadTableSpecs();\n } else {\n this.#logResetOp(index.tableName);\n }\n this.#lc.info?.(create.tag, index.name);\n }\n\n processDropIndex(drop: IndexDrop) {\n const name = liteTableName(drop.id);\n this.#db.db.exec(`DROP INDEX IF EXISTS ${id(name)}`);\n this.#lc.info?.(drop.tag, name);\n }\n\n #bumpVersions(table: Identifier) {\n this.#tableMetadata.setMinRowVersion(table, this.#version);\n this.#logResetOp(liteTableName(table));\n }\n\n /**\n * @param backfilledColumns `backfilling` columns for which values were set\n */\n #logSetOp(\n table: string,\n key: LiteRowKey,\n backfilledColumns: string[] | undefined,\n ) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilledColumns !== undefined) {\n this.#changeLog.logSetOp(\n this.#version,\n this.#pos++,\n table,\n key,\n backfilledColumns,\n );\n this.#numChangeLogEntries++;\n }\n }\n\n #logDeleteOp(table: string, key: LiteRowKey, backfilling?: string[]) {\n // The \"serving\" replicator always writes to the change-log (for IVM).\n // The \"backup\" replicator only needs to write to the change log\n // when writing columns that are being backfilled.\n if (this.#mode === 'serving' || backfilling?.length) {\n this.#changeLog.logDeleteOp(this.#version, this.#pos++, table, key);\n this.#numChangeLogEntries++;\n }\n }\n\n #logTruncateOp(table: string) {\n if (this.#mode === 'serving') {\n this.#changeLog.logTruncateOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n }\n\n #logResetOp(table: string) {\n this.#schemaChanged = true;\n if (this.#mode === 'serving') {\n this.#changeLog.logResetOp(this.#version, table);\n this.#numChangeLogEntries++;\n }\n this.#reloadTableSpecs();\n }\n\n processBackfill({relation, watermark, columns, rowValues}: MessageBackfill) {\n const tableName = liteTableName(relation);\n const tableSpec = must(this.#tableSpecs.get(tableName));\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n // Common parts of the INSERT sql statement.\n const insertColsStr = [...cols, ZERO_VERSION_COLUMN_NAME].map(id).join(',');\n const qMarks = Array.from({length: cols.length + 1}, () => '?').join(',');\n const rowKeyColsStr = rowKeyCols.map(id).join(',');\n\n let backfilled = 0;\n let skipped = 0;\n for (const v of rowValues) {\n const row = liteRow(\n Object.fromEntries(cols.map((c, i) => [c, v[i]])),\n tableSpec,\n this.#jsonFormat,\n );\n const rowKey = this.#getKey(row, {relation});\n const rowOp = this.#changeLog.getLatestRowOp(tableName, rowKey);\n if (rowOp?.op === DEL_OP && rowOp.stateVersion > watermark) {\n skipped++;\n continue; // the row was deleted after the backfill snapshot\n }\n const updates =\n rowOp?.op === SET_OP\n ? cols.filter(\n c => (rowOp.backfillingColumnVersions[c] ?? '') <= watermark,\n )\n : cols;\n if (updates.length === 0) {\n // row already has newer values for all backfilling columns.\n skipped++;\n continue;\n }\n const updateStmts = updates.map(col => `${id(col)}=excluded.${id(col)}`);\n this.#db.run(\n /*sql*/ `\n INSERT INTO ${id(tableName)} (${insertColsStr}) VALUES (${qMarks})\n ON CONFLICT (${rowKeyColsStr})\n DO UPDATE SET ${updateStmts.join(',')};\n `,\n ...Object.values(row.row),\n watermark, // the _0_version for new rows (i.e. table backfill)\n );\n backfilled++;\n }\n\n this.#lc.debug?.(\n `backfilled ${backfilled} rows (skipped ${skipped}) into ${tableName}`,\n );\n }\n\n #completedBackfill: DownloadStatus | undefined;\n\n processBackfillCompleted({relation, columns, status}: BackfillCompleted) {\n const tableName = liteTableName(relation);\n const rowKeyCols = relation.rowKey.columns;\n const cols = [...rowKeyCols, ...columns];\n\n const columnMetadata = must(ColumnMetadataStore.getInstance(this.#db.db));\n for (const col of cols) {\n columnMetadata.clearBackfilling(tableName, col);\n }\n // Given that new columns are being exposed for every row in the table, bump the\n // row version for all rows.\n this.#bumpVersions(relation);\n if (status) {\n this.#completedBackfill = {table: tableName, columns: cols, ...status};\n }\n this.#lc.info?.(`finished backfilling ${tableName}`);\n\n // Note that there is no need to clear the backfillingColumnVersions values\n // in the changeLog. It could theoretically be done for clarity but:\n // (1) it could be non-trivial in terms of latency introduced and\n // (2) the data must be preserved if _other_ columns are in the process\n // of being backfilled\n //\n // Thus, for speed and simplicity, the values are left as is. (Note that\n // subsequent replicated changes to those rows will clear the values if\n // no backfills are in progress).\n }\n\n processCommit(commit: MessageCommit, watermark: string): CommitResult {\n if (watermark !== this.#version) {\n throw new Error(\n `'commit' version ${watermark} does not match 'begin' version ${\n this.#version\n }: ${stringify(commit)}`,\n );\n }\n updateReplicationWatermark(this.#db, watermark);\n\n if (this.#schemaChanged) {\n const start = Date.now();\n this.#db.db.pragma('optimize');\n this.#lc.info?.(\n `PRAGMA optimized after schema change (${Date.now() - start} ms)`,\n );\n }\n\n if (this.#mode !== 'initial-sync') {\n this.#db.commit();\n }\n\n const elapsedMs = Date.now() - this.#startMs;\n this.#lc.debug?.(`Committed tx@${this.#version} (${elapsedMs} ms)`);\n\n return {\n watermark,\n completedBackfill: this.#completedBackfill,\n schemaUpdated: this.#schemaChanged,\n changeLogUpdated: this.#numChangeLogEntries > 0,\n };\n }\n\n abort(lc: LogContext) {\n lc.info?.(`aborting transaction ${this.#version}`);\n this.#db.rollback();\n }\n}\n\nfunction getBackfilledColumns(\n row: LiteRow,\n {backfilling}: LiteTableSpecWithReplicationStatus,\n): string[] | undefined {\n if (!backfilling?.length) {\n return undefined; // common case\n }\n return backfilling.filter(col => col in row);\n}\n\nfunction ensureError(err: unknown): Error {\n if (err instanceof Error) {\n return err;\n }\n const error = new Error();\n error.cause = err;\n return error;\n}\n"],"names":[],"mappings":"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"}
@@ -1,11 +1,30 @@
1
1
  import type { Database } from '../../../../../zqlite/src/db.ts';
2
2
  import type { Identifier, TableMetadata } from '../../change-source/protocol/current.ts';
3
3
  /**
4
- * Replica-level analog of tableMetadata in change-streamer/schema.
5
- * Per the requirement of the backfill protocol, backfill metadata
6
- * must be tracked outside of the change source (otherwise the change
7
- * source would have to be able to compute the state of the metadata at
8
- * arbitrary points in the past).
4
+ * Table-level controls for handling replicated data.
5
+ *
6
+ * ### Columns
7
+ *
8
+ * `minRowVersion`: the minimum `_0_version` value to apply to
9
+ * all rows in the table. This overrides any per-row
10
+ * `_0_version` value that is smaller (i.e. earlier).
11
+ * The `minRowVersion` column is used to force a re-download
12
+ * of all rows after a table-wide schema change (by giving
13
+ * each row a version that's newer than what's in any CVR).
14
+ * The naive, brute-force method of updating all of the rows
15
+ * requires re-writing the entire table into the WAL as one
16
+ * SQLite operation, which is too costly from both latency
17
+ * and storage space.
18
+ *
19
+ * `upstreamMetadata`: the replica-level analog of tableMetadata in
20
+ * change-streamer/schema. Per the requirement of the backfill
21
+ * protocol, backfill metadata must be tracked outside of the
22
+ * change source (otherwise the change source would have to be
23
+ * able to compute the state of the metadata at arbitrary points
24
+ * in the past).
25
+ *
26
+ * `metadata`: the previous name of the `upstreamMetadata` column,
27
+ * kept for backwards compatibility.
9
28
  *
10
29
  * This tracking is done:
11
30
  * 1. at the Change DB level, by the change-streamer
@@ -13,11 +32,13 @@ import type { Identifier, TableMetadata } from '../../change-source/protocol/cur
13
32
  * of ephemeral Change DBs (on SQLite) that are initialized from data
14
33
  * in the replica.
15
34
  */
16
- export declare const CREATE_TABLE_METADATA_TABLE = "\n CREATE TABLE \"_zero.tableMetadata\" (\n \"schema\" TEXT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"metadata\" TEXT NOT NULL,\n PRIMARY KEY (\"schema\", \"table\")\n );\n";
35
+ export declare const CREATE_TABLE_METADATA_TABLE = "\n CREATE TABLE \"_zero.tableMetadata\" (\n \"schema\" TEXT NOT NULL,\n \"table\" TEXT NOT NULL,\n \"minRowVersion\" TEXT NOT NULL DEFAULT \"00\",\n \"upstreamMetadata\" TEXT,\n \"metadata\" TEXT, -- deprecated\n PRIMARY KEY (\"schema\", \"table\")\n );\n";
17
36
  export declare class TableMetadataTracker {
18
37
  #private;
19
38
  constructor(db: Database);
20
- set({ schema, name }: Identifier, metadata: TableMetadata): void;
39
+ setUpstreamMetadata({ schema, name }: Identifier, metadata: TableMetadata): void;
40
+ setMinRowVersion({ schema, name }: Identifier, version: string): void;
41
+ getMinRowVersions(): Map<string, string>;
21
42
  rename(oldTable: Identifier, newTable: Identifier): void;
22
43
  drop({ schema, name }: Identifier): void;
23
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"table-metadata.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/replicator/schema/table-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,iCAAiC,CAAC;AACzE,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACd,MAAM,yCAAyC,CAAC;AAEjD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,2BAA2B,mMAOvC,CAAC;AAEF,qBAAa,oBAAoB;;gBAKnB,EAAE,EAAE,QAAQ;IAcxB,GAAG,CAAC,EAAC,MAAM,EAAE,IAAI,EAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa;IAIvD,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU;IASjD,IAAI,CAAC,EAAC,MAAM,EAAE,IAAI,EAAC,EAAE,UAAU;CAGhC"}
1
+ {"version":3,"file":"table-metadata.d.ts","sourceRoot":"","sources":["../../../../../../../zero-cache/src/services/replicator/schema/table-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,QAAQ,EAAY,MAAM,iCAAiC,CAAC;AAEzE,OAAO,KAAK,EACV,UAAU,EACV,aAAa,EACd,MAAM,yCAAyC,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,2BAA2B,yTASvC,CAAC;AAEF,qBAAa,oBAAoB;;gBAUnB,EAAE,EAAE,QAAQ;IAIxB,mBAAmB,CAAC,EAAC,MAAM,EAAE,IAAI,EAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,aAAa;IAavE,gBAAgB,CAAC,EAAC,MAAM,EAAE,IAAI,EAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM;IAS5D,iBAAiB,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IAYxC,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU;IAOjD,IAAI,CAAC,EAAC,MAAM,EAAE,IAAI,EAAC,EAAE,UAAU;CAKhC"}