orez 0.2.27 → 0.2.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cf-do/worker.d.ts +3 -0
- package/dist/cf-do/worker.d.ts.map +1 -1
- package/dist/cf-do/worker.js +37 -15
- package/dist/cf-do/worker.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
- package/src/admin/admin-data.test.ts +0 -348
- package/src/admin/http-proxy.ts +0 -252
- package/src/admin/log-store.ts +0 -192
- package/src/admin/server.ts +0 -471
- package/src/admin/ui.ts +0 -1322
- package/src/bench/proxy-throughput.bench.ts +0 -343
- package/src/bench/serial-mutations.bench.ts +0 -270
- package/src/browser.ts +0 -203
- package/src/cf-do/.wrangler/cache/cf.json +0 -1
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/cache/miniflare-CacheObject/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/0ffaabee41a60e04dd0eb7db3073f0a40139e6a97ccd26823967acb652b89a7b.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-shm +0 -0
- package/src/cf-do/.wrangler/state/v3/do/zero-do-ZeroDO/metadata.sqlite-wal +0 -0
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-0z4CpE/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-insertion-facade.js +0 -11
- package/src/cf-do/.wrangler/tmp/bundle-vYmw0E/middleware-loader.entry.ts +0 -134
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-cbILNo/worker.js.map +0 -8
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js +0 -1059
- package/src/cf-do/.wrangler/tmp/dev-qbho19/worker.js.map +0 -8
- package/src/cf-do/ARCHITECTURE.md +0 -93
- package/src/cf-do/CHAT_E2E.md +0 -213
- package/src/cf-do/watermark.test.ts +0 -103
- package/src/cf-do/watermark.ts +0 -118
- package/src/cf-do/worker.ts +0 -1041
- package/src/cf-do/wrangler.toml +0 -11
- package/src/cf-pglite/README.md +0 -19
- package/src/change-tracking.ts +0 -25
- package/src/child-process.test.ts +0 -147
- package/src/child-process.ts +0 -90
- package/src/cli-entry.ts +0 -72
- package/src/cli.test.ts +0 -40
- package/src/cli.ts +0 -1214
- package/src/config.ts +0 -150
- package/src/do-sql-tracking.test.ts +0 -19
- package/src/do-sql-tracking.ts +0 -19
- package/src/index.ts +0 -1215
- package/src/integration/integration.test.ts +0 -517
- package/src/integration/native-binary.guard.test.ts +0 -13
- package/src/integration/native-startup.test.ts +0 -44
- package/src/integration/replication-latency.test.ts +0 -428
- package/src/integration/restore-live-stress.test.ts +0 -433
- package/src/integration/restore-reset.test.ts +0 -400
- package/src/integration/restore.test.ts +0 -274
- package/src/integration/test-permissions.ts +0 -147
- package/src/load-config.ts +0 -46
- package/src/log.ts +0 -96
- package/src/mutex.ts +0 -47
- package/src/pg-proxy-browser.singledb.test.ts +0 -233
- package/src/pg-proxy-browser.ts +0 -2022
- package/src/pg-proxy-do-backend.test.ts +0 -3890
- package/src/pg-proxy-do-backend.ts +0 -7191
- package/src/pg-proxy.ts +0 -1087
- package/src/pg-sqlite-compiler/README.md +0 -53
- package/src/pg-sqlite-compiler/catalog/seed.ts +0 -524
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/arithmetic.json +0 -307
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/array.json +0 -377
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/cast.json +0 -12
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/catalog.json +0 -447
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/create-table.json +0 -32
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/datetime.json +0 -397
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/enum.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/insert.json +0 -337
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/json.json +0 -537
- package/src/pg-sqlite-compiler/fixtures/pgsqlite/misc.json +0 -1837
- package/src/pg-sqlite-compiler/index.ts +0 -73
- package/src/pg-sqlite-compiler/integration.test.ts +0 -136
- package/src/pg-sqlite-compiler/passes/ast-utils.ts +0 -113
- package/src/pg-sqlite-compiler/passes/catalog.ts +0 -65
- package/src/pg-sqlite-compiler/passes/datetime.ts +0 -74
- package/src/pg-sqlite-compiler/passes/index.ts +0 -49
- package/src/pg-sqlite-compiler/passes/types.ts +0 -156
- package/src/pg-sqlite-compiler/smoke.test.ts +0 -69
- package/src/pg-sqlite-compiler/test/catalog.test.ts +0 -171
- package/src/pg-sqlite-compiler/test/corpus.test.ts +0 -161
- package/src/pg-sqlite-compiler/test/datetime.oracle.test.ts +0 -102
- package/src/pg-sqlite-compiler/test/oracle.ts +0 -237
- package/src/pg-sqlite-compiler/test/types.test.ts +0 -109
- package/src/pg-sqlite-compiler/types.ts +0 -63
- package/src/pglite-ipc.test.ts +0 -116
- package/src/pglite-ipc.ts +0 -266
- package/src/pglite-manager.ts +0 -557
- package/src/pglite-web-proxy.test.ts +0 -57
- package/src/pglite-web-proxy.ts +0 -221
- package/src/pglite-web-worker.ts +0 -152
- package/src/pglite-worker-thread.ts +0 -253
- package/src/port.ts +0 -25
- package/src/process-title.ts +0 -9
- package/src/recovery.ts +0 -155
- package/src/replication/change-tracker.test.ts +0 -357
- package/src/replication/change-tracker.ts +0 -279
- package/src/replication/handler.test.ts +0 -511
- package/src/replication/handler.ts +0 -1190
- package/src/replication/pgoutput-encoder.test.ts +0 -697
- package/src/replication/pgoutput-encoder.ts +0 -373
- package/src/replication/tcp-replication.test.ts +0 -876
- package/src/replication/zero-compat.test.ts +0 -1150
- package/src/restore-stress.test.ts +0 -188
- package/src/s3-local.ts +0 -203
- package/src/shim/hooks.mjs +0 -120
- package/src/shim/register.mjs +0 -4
- package/src/sqlite-mode/apply-mode.ts +0 -224
- package/src/sqlite-mode/index.ts +0 -15
- package/src/sqlite-mode/native-binary.ts +0 -89
- package/src/sqlite-mode/package-resolve.ts +0 -17
- package/src/sqlite-mode/resolve-mode.ts +0 -80
- package/src/sqlite-mode/shim-template.ts +0 -159
- package/src/sqlite-mode/sqlite-mode.test.ts +0 -427
- package/src/sqlite-mode/types.ts +0 -30
- package/src/vite-plugin.ts +0 -67
- package/src/wasm-sqlite.test.ts +0 -537
- package/src/worker/browser-admin.ts +0 -52
- package/src/worker/browser-build-config.test.ts +0 -71
- package/src/worker/browser-build-config.ts +0 -109
- package/src/worker/browser-embed-admin.test.ts +0 -75
- package/src/worker/browser-embed.ts +0 -345
- package/src/worker/cf-patches.ts +0 -384
- package/src/worker/embed-integration.test.ts +0 -321
- package/src/worker/index.ts +0 -138
- package/src/worker/shims/fastify.test.ts +0 -255
- package/src/worker/shims/fastify.ts +0 -306
- package/src/worker/shims/http-service.test.ts +0 -355
- package/src/worker/shims/http-service.ts +0 -293
- package/src/worker/shims/node-stub.ts +0 -290
- package/src/worker/shims/oxfmt.ts +0 -3
- package/src/worker/shims/postgres-browser.ts +0 -59
- package/src/worker/shims/postgres-socket.test.ts +0 -576
- package/src/worker/shims/postgres-socket.ts +0 -310
- package/src/worker/shims/postgres.test.ts +0 -364
- package/src/worker/shims/postgres.ts +0 -1454
- package/src/worker/shims/sqlite-browser.test.ts +0 -233
- package/src/worker/shims/sqlite-browser.ts +0 -175
- package/src/worker/shims/sqlite.test.ts +0 -786
- package/src/worker/shims/sqlite.ts +0 -978
- package/src/worker/shims/stream-browser.ts +0 -15
- package/src/worker/shims/ws-browser.test.ts +0 -205
- package/src/worker/shims/ws-browser.ts +0 -248
- package/src/worker/shims/ws.test.ts +0 -288
- package/src/worker/shims/ws.ts +0 -467
- package/src/worker/shims/zero-process-env.ts +0 -11
- package/src/worker/types.ts +0 -75
- package/src/worker/worker-integration.test.ts +0 -223
- package/src/worker/worker.test.ts +0 -136
- package/src/worker/zero-cache-embed-cf.ts +0 -463
- package/src/worker/zero-cache-embed.ts +0 -277
|
@@ -1,697 +0,0 @@
|
|
|
1
|
-
import { join } from 'node:path'
|
|
2
|
-
|
|
3
|
-
import { beforeAll, describe, expect, it } from 'vitest'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
encodeBegin,
|
|
7
|
-
encodeCommit,
|
|
8
|
-
encodeRelation,
|
|
9
|
-
encodeInsert,
|
|
10
|
-
encodeUpdate,
|
|
11
|
-
encodeDelete,
|
|
12
|
-
encodeKeepalive,
|
|
13
|
-
wrapXLogData,
|
|
14
|
-
wrapCopyData,
|
|
15
|
-
getTableOid,
|
|
16
|
-
inferColumns,
|
|
17
|
-
type ColumnInfo,
|
|
18
|
-
} from './pgoutput-encoder'
|
|
19
|
-
|
|
20
|
-
// pg epoch: 2000-01-01 in microseconds from unix epoch
|
|
21
|
-
const PG_EPOCH_MICROS = 946684800000000n
|
|
22
|
-
|
|
23
|
-
// mini decoder helpers
|
|
24
|
-
function r16(buf: Uint8Array, off: number) {
|
|
25
|
-
return new DataView(buf.buffer, buf.byteOffset).getInt16(off)
|
|
26
|
-
}
|
|
27
|
-
function r32(buf: Uint8Array, off: number) {
|
|
28
|
-
return new DataView(buf.buffer, buf.byteOffset).getInt32(off)
|
|
29
|
-
}
|
|
30
|
-
function r64(buf: Uint8Array, off: number) {
|
|
31
|
-
return new DataView(buf.buffer, buf.byteOffset).getBigInt64(off)
|
|
32
|
-
}
|
|
33
|
-
function rCStr(buf: Uint8Array, off: number): [string, number] {
|
|
34
|
-
let end = off
|
|
35
|
-
while (end < buf.length && buf[end] !== 0) end++
|
|
36
|
-
return [new TextDecoder().decode(buf.subarray(off, end)), end + 1]
|
|
37
|
-
}
|
|
38
|
-
function rText(buf: Uint8Array, off: number): [string, number] {
|
|
39
|
-
const len = r32(buf, off)
|
|
40
|
-
const str = new TextDecoder().decode(buf.subarray(off + 4, off + 4 + len))
|
|
41
|
-
return [str, off + 4 + len]
|
|
42
|
-
}
|
|
43
|
-
function rTupleTextValues(buf: Uint8Array, off: number): [Array<string | null>, number] {
|
|
44
|
-
const values: Array<string | null> = []
|
|
45
|
-
const count = r16(buf, off)
|
|
46
|
-
let pos = off + 2
|
|
47
|
-
for (let i = 0; i < count; i++) {
|
|
48
|
-
const kind = buf[pos++]
|
|
49
|
-
if (kind === 0x6e) {
|
|
50
|
-
values.push(null)
|
|
51
|
-
continue
|
|
52
|
-
}
|
|
53
|
-
expect(kind).toBe(0x74)
|
|
54
|
-
const len = r32(buf, pos)
|
|
55
|
-
pos += 4
|
|
56
|
-
values.push(new TextDecoder().decode(buf.subarray(pos, pos + len)))
|
|
57
|
-
pos += len
|
|
58
|
-
}
|
|
59
|
-
return [values, pos]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
describe('pgoutput-encoder', () => {
|
|
63
|
-
describe('encodeBegin', () => {
|
|
64
|
-
it('produces correct binary layout', () => {
|
|
65
|
-
const lsn = 0x1000100n
|
|
66
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
67
|
-
const xid = 42
|
|
68
|
-
|
|
69
|
-
const buf = encodeBegin(lsn, ts, xid)
|
|
70
|
-
|
|
71
|
-
expect(buf.length).toBe(21)
|
|
72
|
-
expect(buf[0]).toBe(0x42) // 'B'
|
|
73
|
-
expect(r64(buf, 1)).toBe(lsn)
|
|
74
|
-
expect(r64(buf, 9)).toBe(ts - PG_EPOCH_MICROS)
|
|
75
|
-
expect(r32(buf, 17)).toBe(xid)
|
|
76
|
-
})
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
describe('encodeCommit', () => {
|
|
80
|
-
it('produces correct binary layout', () => {
|
|
81
|
-
const lsn = 0x1000100n
|
|
82
|
-
const endLsn = 0x1000200n
|
|
83
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
84
|
-
|
|
85
|
-
const buf = encodeCommit(0, lsn, endLsn, ts)
|
|
86
|
-
|
|
87
|
-
expect(buf.length).toBe(26)
|
|
88
|
-
expect(buf[0]).toBe(0x43) // 'C'
|
|
89
|
-
expect(buf[1]).toBe(0)
|
|
90
|
-
expect(r64(buf, 2)).toBe(lsn)
|
|
91
|
-
expect(r64(buf, 10)).toBe(endLsn)
|
|
92
|
-
expect(r64(buf, 18)).toBe(ts - PG_EPOCH_MICROS)
|
|
93
|
-
})
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
describe('encodeRelation', () => {
|
|
97
|
-
it('encodes table with columns', () => {
|
|
98
|
-
const cols: ColumnInfo[] = [
|
|
99
|
-
{ name: 'id', typeOid: 23, typeMod: -1 },
|
|
100
|
-
{ name: 'name', typeOid: 25, typeMod: -1 },
|
|
101
|
-
]
|
|
102
|
-
|
|
103
|
-
const buf = encodeRelation(16384, 'public', 'users', 0x64, cols)
|
|
104
|
-
|
|
105
|
-
expect(buf[0]).toBe(0x52) // 'R'
|
|
106
|
-
expect(r32(buf, 1)).toBe(16384)
|
|
107
|
-
|
|
108
|
-
let pos = 5
|
|
109
|
-
const [schema, p1] = rCStr(buf, pos)
|
|
110
|
-
expect(schema).toBe('public')
|
|
111
|
-
pos = p1
|
|
112
|
-
|
|
113
|
-
const [table, p2] = rCStr(buf, pos)
|
|
114
|
-
expect(table).toBe('users')
|
|
115
|
-
pos = p2
|
|
116
|
-
|
|
117
|
-
expect(buf[pos]).toBe(0x64) // replica identity
|
|
118
|
-
pos++
|
|
119
|
-
expect(r16(buf, pos)).toBe(2)
|
|
120
|
-
pos += 2
|
|
121
|
-
|
|
122
|
-
// col 0
|
|
123
|
-
expect(buf[pos++]).toBe(0) // flags
|
|
124
|
-
const [c1, p3] = rCStr(buf, pos)
|
|
125
|
-
expect(c1).toBe('id')
|
|
126
|
-
pos = p3
|
|
127
|
-
expect(r32(buf, pos)).toBe(23)
|
|
128
|
-
pos += 4
|
|
129
|
-
expect(r32(buf, pos)).toBe(-1)
|
|
130
|
-
pos += 4
|
|
131
|
-
|
|
132
|
-
// col 1
|
|
133
|
-
expect(buf[pos++]).toBe(0)
|
|
134
|
-
const [c2, p4] = rCStr(buf, pos)
|
|
135
|
-
expect(c2).toBe('name')
|
|
136
|
-
pos = p4
|
|
137
|
-
expect(r32(buf, pos)).toBe(25)
|
|
138
|
-
pos += 4
|
|
139
|
-
expect(r32(buf, pos)).toBe(-1)
|
|
140
|
-
pos += 4
|
|
141
|
-
|
|
142
|
-
expect(pos).toBe(buf.length)
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
it('handles zero columns', () => {
|
|
146
|
-
const buf = encodeRelation(16384, 'public', 'empty', 0x64, [])
|
|
147
|
-
expect(buf[0]).toBe(0x52)
|
|
148
|
-
let pos = 5
|
|
149
|
-
;[, pos] = rCStr(buf, pos)
|
|
150
|
-
;[, pos] = rCStr(buf, pos)
|
|
151
|
-
pos++ // replica identity
|
|
152
|
-
expect(r16(buf, pos)).toBe(0)
|
|
153
|
-
})
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
describe('encodeInsert', () => {
|
|
157
|
-
const cols: ColumnInfo[] = [
|
|
158
|
-
{ name: 'id', typeOid: 25, typeMod: -1 },
|
|
159
|
-
{ name: 'name', typeOid: 25, typeMod: -1 },
|
|
160
|
-
]
|
|
161
|
-
|
|
162
|
-
it('encodes row values', () => {
|
|
163
|
-
const buf = encodeInsert(16384, { id: '1', name: 'alice' }, cols)
|
|
164
|
-
|
|
165
|
-
expect(buf[0]).toBe(0x49) // 'I'
|
|
166
|
-
expect(r32(buf, 1)).toBe(16384)
|
|
167
|
-
expect(buf[5]).toBe(0x4e) // 'N'
|
|
168
|
-
|
|
169
|
-
expect(r16(buf, 6)).toBe(2) // num cols
|
|
170
|
-
let pos = 8
|
|
171
|
-
expect(buf[pos]).toBe(0x74) // 't'
|
|
172
|
-
const [v1, p1] = rText(buf, pos + 1)
|
|
173
|
-
expect(v1).toBe('1')
|
|
174
|
-
pos = p1 + 1 // +1 for 't' byte
|
|
175
|
-
// hmm wait, let me recalculate
|
|
176
|
-
|
|
177
|
-
// actually the tuple format is: numCols(2) + for each: type(1) + if text: len(4)+data
|
|
178
|
-
// so at offset 8: type byte, then len, then data
|
|
179
|
-
pos = 8
|
|
180
|
-
expect(buf[pos]).toBe(0x74) // 't'
|
|
181
|
-
pos++
|
|
182
|
-
expect(r32(buf, pos)).toBe(1) // length of '1'
|
|
183
|
-
pos += 4
|
|
184
|
-
expect(new TextDecoder().decode(buf.subarray(pos, pos + 1))).toBe('1')
|
|
185
|
-
pos += 1
|
|
186
|
-
|
|
187
|
-
expect(buf[pos]).toBe(0x74)
|
|
188
|
-
pos++
|
|
189
|
-
expect(r32(buf, pos)).toBe(5)
|
|
190
|
-
pos += 4
|
|
191
|
-
expect(new TextDecoder().decode(buf.subarray(pos, pos + 5))).toBe('alice')
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
it('encodes null values', () => {
|
|
195
|
-
const buf = encodeInsert(16384, { id: '1', name: null }, cols)
|
|
196
|
-
|
|
197
|
-
let pos = 8 // start of first value
|
|
198
|
-
expect(buf[pos]).toBe(0x74) // first val: text
|
|
199
|
-
pos += 1 + 4 + 1 // 't' + len + '1'
|
|
200
|
-
expect(buf[pos]).toBe(0x6e) // second val: null
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('encodes unicode strings', () => {
|
|
204
|
-
const buf = encodeInsert(16384, { id: '1', name: '日本語 🎉' }, cols)
|
|
205
|
-
|
|
206
|
-
// find second value
|
|
207
|
-
let pos = 8
|
|
208
|
-
pos++ // 't'
|
|
209
|
-
const len1 = r32(buf, pos)
|
|
210
|
-
pos += 4 + len1
|
|
211
|
-
|
|
212
|
-
expect(buf[pos]).toBe(0x74)
|
|
213
|
-
pos++
|
|
214
|
-
const len2 = r32(buf, pos)
|
|
215
|
-
pos += 4
|
|
216
|
-
const decoded = new TextDecoder().decode(buf.subarray(pos, pos + len2))
|
|
217
|
-
expect(decoded).toBe('日本語 🎉')
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it('encodes object values as JSON', () => {
|
|
221
|
-
const metaCols: ColumnInfo[] = [{ name: 'meta', typeOid: 25, typeMod: -1 }]
|
|
222
|
-
const buf = encodeInsert(16384, { meta: { foo: 'bar', n: 42 } }, metaCols)
|
|
223
|
-
|
|
224
|
-
let pos = 8 // first value
|
|
225
|
-
expect(buf[pos]).toBe(0x74)
|
|
226
|
-
pos++
|
|
227
|
-
const len = r32(buf, pos)
|
|
228
|
-
pos += 4
|
|
229
|
-
const decoded = new TextDecoder().decode(buf.subarray(pos, pos + len))
|
|
230
|
-
expect(JSON.parse(decoded)).toEqual({ foo: 'bar', n: 42 })
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
it('encodes empty string', () => {
|
|
234
|
-
const buf = encodeInsert(16384, { id: '', name: '' }, cols)
|
|
235
|
-
|
|
236
|
-
let pos = 8
|
|
237
|
-
expect(buf[pos]).toBe(0x74)
|
|
238
|
-
pos++
|
|
239
|
-
expect(r32(buf, pos)).toBe(0) // empty string length
|
|
240
|
-
})
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
describe('encodeUpdate', () => {
|
|
244
|
-
const cols: ColumnInfo[] = [
|
|
245
|
-
{ name: 'id', typeOid: 25, typeMod: -1 },
|
|
246
|
-
{ name: 'val', typeOid: 25, typeMod: -1 },
|
|
247
|
-
]
|
|
248
|
-
|
|
249
|
-
it('includes old tuple when provided', () => {
|
|
250
|
-
const buf = encodeUpdate(
|
|
251
|
-
16384,
|
|
252
|
-
{ id: '1', val: 'new' },
|
|
253
|
-
{ id: '1', val: 'old' },
|
|
254
|
-
cols
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
expect(buf[0]).toBe(0x55) // 'U'
|
|
258
|
-
expect(r32(buf, 1)).toBe(16384)
|
|
259
|
-
expect(buf[5]).toBe(0x4f) // 'O' old tuple
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('skips old tuple when null', () => {
|
|
263
|
-
const buf = encodeUpdate(16384, { id: '1', val: 'new' }, null, cols)
|
|
264
|
-
|
|
265
|
-
expect(buf[0]).toBe(0x55)
|
|
266
|
-
expect(buf[5]).toBe(0x4e) // 'N' directly
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('old tuple precedes new tuple', () => {
|
|
270
|
-
const buf = encodeUpdate(
|
|
271
|
-
16384,
|
|
272
|
-
{ id: '1', val: 'new' },
|
|
273
|
-
{ id: '1', val: 'old' },
|
|
274
|
-
cols
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
// 'O' at offset 5, then old tuple, then 'N', then new tuple
|
|
278
|
-
expect(buf[5]).toBe(0x4f)
|
|
279
|
-
// find 'N' marker after old tuple
|
|
280
|
-
// old tuple: numCols(2) + 2 values
|
|
281
|
-
let pos = 6 // start of old tuple
|
|
282
|
-
const numOldCols = r16(buf, pos)
|
|
283
|
-
expect(numOldCols).toBe(2)
|
|
284
|
-
pos += 2
|
|
285
|
-
// skip values
|
|
286
|
-
for (let i = 0; i < numOldCols; i++) {
|
|
287
|
-
if (buf[pos] === 0x6e) {
|
|
288
|
-
pos++
|
|
289
|
-
} else {
|
|
290
|
-
pos++ // 't'
|
|
291
|
-
const len = r32(buf, pos)
|
|
292
|
-
pos += 4 + len
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
expect(buf[pos]).toBe(0x4e) // 'N' new tuple marker
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
it('encodes sqlite integer booleans as postgres bool text', () => {
|
|
299
|
-
const boolCols: ColumnInfo[] = [
|
|
300
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
301
|
-
{ name: 'completed', typeOid: 16, typeMod: -1 },
|
|
302
|
-
]
|
|
303
|
-
const buf = encodeUpdate(
|
|
304
|
-
16384,
|
|
305
|
-
{ id: '1', completed: 1 },
|
|
306
|
-
{ id: '1', completed: 0 },
|
|
307
|
-
boolCols
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
expect(buf[5]).toBe(0x4f) // 'O' old tuple
|
|
311
|
-
const [oldValues, afterOld] = rTupleTextValues(buf, 6)
|
|
312
|
-
expect(oldValues).toEqual(['1', 'f'])
|
|
313
|
-
expect(buf[afterOld]).toBe(0x4e) // 'N' new tuple
|
|
314
|
-
const [newValues] = rTupleTextValues(buf, afterOld + 1)
|
|
315
|
-
expect(newValues).toEqual(['1', 't'])
|
|
316
|
-
})
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
describe('encodeDelete', () => {
|
|
320
|
-
it('encodes with key tuple marker', () => {
|
|
321
|
-
const cols: ColumnInfo[] = [{ name: 'id', typeOid: 25, typeMod: -1 }]
|
|
322
|
-
const buf = encodeDelete(16384, { id: '42' }, cols)
|
|
323
|
-
|
|
324
|
-
expect(buf[0]).toBe(0x44) // 'D'
|
|
325
|
-
expect(r32(buf, 1)).toBe(16384)
|
|
326
|
-
expect(buf[5]).toBe(0x4b) // 'K'
|
|
327
|
-
})
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
describe('wrapXLogData', () => {
|
|
331
|
-
it('wraps payload with wal positions', () => {
|
|
332
|
-
const payload = new Uint8Array([1, 2, 3])
|
|
333
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
334
|
-
|
|
335
|
-
const buf = wrapXLogData(0x100n, 0x200n, ts, payload)
|
|
336
|
-
|
|
337
|
-
expect(buf[0]).toBe(0x77) // 'w'
|
|
338
|
-
expect(r64(buf, 1)).toBe(0x100n)
|
|
339
|
-
expect(r64(buf, 9)).toBe(0x200n)
|
|
340
|
-
expect(r64(buf, 17)).toBe(ts - PG_EPOCH_MICROS)
|
|
341
|
-
expect(Array.from(buf.subarray(25))).toEqual([1, 2, 3])
|
|
342
|
-
})
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
describe('wrapCopyData', () => {
|
|
346
|
-
it('wraps with length prefix', () => {
|
|
347
|
-
const inner = new Uint8Array([0xaa, 0xbb])
|
|
348
|
-
|
|
349
|
-
const buf = wrapCopyData(inner)
|
|
350
|
-
|
|
351
|
-
expect(buf[0]).toBe(0x64) // 'd'
|
|
352
|
-
expect(r32(buf, 1)).toBe(4 + 2) // length includes the 4 bytes of the length field itself
|
|
353
|
-
expect(Array.from(buf.subarray(5))).toEqual([0xaa, 0xbb])
|
|
354
|
-
})
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
describe('encodeKeepalive', () => {
|
|
358
|
-
it('wraps keepalive in CopyData', () => {
|
|
359
|
-
const walEnd = 0x1000200n
|
|
360
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
361
|
-
|
|
362
|
-
const buf = encodeKeepalive(walEnd, ts, false)
|
|
363
|
-
|
|
364
|
-
expect(buf[0]).toBe(0x64) // outer CopyData
|
|
365
|
-
expect(buf[5]).toBe(0x6b) // inner 'k' keepalive
|
|
366
|
-
expect(r64(buf, 6)).toBe(walEnd)
|
|
367
|
-
expect(r64(buf, 14)).toBe(ts - PG_EPOCH_MICROS)
|
|
368
|
-
expect(buf[22]).toBe(0)
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
it('sets reply-requested flag', () => {
|
|
372
|
-
const buf = encodeKeepalive(0n, BigInt(Date.now()) * 1000n, true)
|
|
373
|
-
expect(buf[22]).toBe(1)
|
|
374
|
-
})
|
|
375
|
-
})
|
|
376
|
-
|
|
377
|
-
describe('inferColumns', () => {
|
|
378
|
-
it('maps keys to text columns', () => {
|
|
379
|
-
const cols = inferColumns({ id: 1, name: 'test', active: true })
|
|
380
|
-
expect(cols).toEqual([
|
|
381
|
-
{ name: 'id', typeOid: 25, typeMod: -1 },
|
|
382
|
-
{ name: 'name', typeOid: 25, typeMod: -1 },
|
|
383
|
-
{ name: 'active', typeOid: 25, typeMod: -1 },
|
|
384
|
-
])
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
it('handles empty object', () => {
|
|
388
|
-
expect(inferColumns({})).toEqual([])
|
|
389
|
-
})
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
describe('getTableOid', () => {
|
|
393
|
-
it('returns stable oid for same table', () => {
|
|
394
|
-
const a = getTableOid('oid_test_stable')
|
|
395
|
-
const b = getTableOid('oid_test_stable')
|
|
396
|
-
expect(a).toBe(b)
|
|
397
|
-
expect(a).toBeGreaterThanOrEqual(16384)
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
it('returns unique oids for different tables', () => {
|
|
401
|
-
const a = getTableOid('oid_test_x')
|
|
402
|
-
const b = getTableOid('oid_test_y')
|
|
403
|
-
expect(a).not.toBe(b)
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
it('matches DoBackend catalog oids for flattened schema tables', () => {
|
|
407
|
-
expect(getTableOid('public.todo')).toBe(4392680)
|
|
408
|
-
expect(getTableOid('todo_0.clients')).toBe(9663976)
|
|
409
|
-
expect(getTableOid('todo_0.mutations')).toBe(8519194)
|
|
410
|
-
})
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
// roundtrip tests: encode with orez → parse with zero-cache's parser
|
|
414
|
-
// this validates the fundamental contract between orez and zero-cache
|
|
415
|
-
describe('roundtrip: orez encoder → zero-cache parser', () => {
|
|
416
|
-
// relative path bypasses package.json exports restriction
|
|
417
|
-
const parserPath = join(
|
|
418
|
-
import.meta.dirname,
|
|
419
|
-
'../../node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/logical-replication/pgoutput-parser.js'
|
|
420
|
-
)
|
|
421
|
-
|
|
422
|
-
// mock type parsers: unknown OIDs default to String (identity for text)
|
|
423
|
-
const typeParsers = { getTypeParser: () => String }
|
|
424
|
-
|
|
425
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
-
let PgoutputParser: any
|
|
427
|
-
beforeAll(async () => {
|
|
428
|
-
PgoutputParser = (await import(parserPath)).PgoutputParser
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
432
|
-
function makeParser(parserOverrides: any = typeParsers) {
|
|
433
|
-
return new PgoutputParser(parserOverrides)
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function makeBoolAwareParser() {
|
|
437
|
-
return makeParser({
|
|
438
|
-
getTypeParser: (oid: number) =>
|
|
439
|
-
oid === 16 ? (value: string) => value === 't' : String,
|
|
440
|
-
})
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
it('BEGIN roundtrip', () => {
|
|
444
|
-
const lsn = 0x1000200n
|
|
445
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
446
|
-
const parser = makeParser()
|
|
447
|
-
const parsed = parser.parse(encodeBegin(lsn, ts, 42))
|
|
448
|
-
|
|
449
|
-
expect(parsed.tag).toBe('begin')
|
|
450
|
-
expect(parsed.commitLsn).toBe('00000000/01000200')
|
|
451
|
-
expect(parsed.xid).toBe(42)
|
|
452
|
-
expect(parsed.commitTime).toBe(ts)
|
|
453
|
-
})
|
|
454
|
-
|
|
455
|
-
it('COMMIT roundtrip', () => {
|
|
456
|
-
const lsn = 0x1000200n
|
|
457
|
-
const endLsn = 0x1000300n
|
|
458
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
459
|
-
const parser = makeParser()
|
|
460
|
-
const parsed = parser.parse(encodeCommit(0, lsn, endLsn, ts))
|
|
461
|
-
|
|
462
|
-
expect(parsed.tag).toBe('commit')
|
|
463
|
-
expect(parsed.commitLsn).toBe('00000000/01000200')
|
|
464
|
-
expect(parsed.commitEndLsn).toBe('00000000/01000300')
|
|
465
|
-
expect(parsed.commitTime).toBe(ts)
|
|
466
|
-
})
|
|
467
|
-
|
|
468
|
-
it('RELATION roundtrip', () => {
|
|
469
|
-
const oid = getTableOid('rt.rel_test')
|
|
470
|
-
const cols: ColumnInfo[] = [
|
|
471
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
472
|
-
{ name: 'name', typeOid: 25, typeMod: -1 },
|
|
473
|
-
]
|
|
474
|
-
const parser = makeParser()
|
|
475
|
-
const parsed = parser.parse(encodeRelation(oid, 'public', 'rel_test', 0x64, cols))
|
|
476
|
-
|
|
477
|
-
expect(parsed.tag).toBe('relation')
|
|
478
|
-
expect(parsed.schema).toBe('public')
|
|
479
|
-
expect(parsed.name).toBe('rel_test')
|
|
480
|
-
expect(parsed.columns).toHaveLength(2)
|
|
481
|
-
expect(parsed.keyColumns).toEqual(['id'])
|
|
482
|
-
})
|
|
483
|
-
|
|
484
|
-
it('INSERT roundtrip', () => {
|
|
485
|
-
const oid = getTableOid('rt.ins_test')
|
|
486
|
-
const cols: ColumnInfo[] = [
|
|
487
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
488
|
-
{ name: 'val', typeOid: 25, typeMod: -1 },
|
|
489
|
-
]
|
|
490
|
-
const parser = makeParser()
|
|
491
|
-
parser.parse(encodeRelation(oid, 'public', 'ins_test', 0x64, cols))
|
|
492
|
-
|
|
493
|
-
const parsed = parser.parse(encodeInsert(oid, { id: 'abc', val: 'hello' }, cols))
|
|
494
|
-
expect(parsed.tag).toBe('insert')
|
|
495
|
-
expect(parsed.new.id).toBe('abc')
|
|
496
|
-
expect(parsed.new.val).toBe('hello')
|
|
497
|
-
})
|
|
498
|
-
|
|
499
|
-
it('INSERT with null', () => {
|
|
500
|
-
const oid = getTableOid('rt.null_test')
|
|
501
|
-
const cols: ColumnInfo[] = [
|
|
502
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
503
|
-
{ name: 'opt', typeOid: 25, typeMod: -1 },
|
|
504
|
-
]
|
|
505
|
-
const parser = makeParser()
|
|
506
|
-
parser.parse(encodeRelation(oid, 'public', 'null_test', 0x64, cols))
|
|
507
|
-
|
|
508
|
-
const parsed = parser.parse(encodeInsert(oid, { id: 'x', opt: null }, cols))
|
|
509
|
-
expect(parsed.new.opt).toBeNull()
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
it('UPDATE with old row roundtrip', () => {
|
|
513
|
-
const oid = getTableOid('rt.upd_test')
|
|
514
|
-
const cols: ColumnInfo[] = [
|
|
515
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
516
|
-
{ name: 'val', typeOid: 25, typeMod: -1 },
|
|
517
|
-
]
|
|
518
|
-
const parser = makeParser()
|
|
519
|
-
parser.parse(encodeRelation(oid, 'public', 'upd_test', 0x64, cols))
|
|
520
|
-
|
|
521
|
-
const parsed = parser.parse(
|
|
522
|
-
encodeUpdate(oid, { id: '1', val: 'new' }, { id: '1', val: 'old' }, cols)
|
|
523
|
-
)
|
|
524
|
-
expect(parsed.tag).toBe('update')
|
|
525
|
-
expect(parsed.new.val).toBe('new')
|
|
526
|
-
expect(parsed.old.val).toBe('old')
|
|
527
|
-
})
|
|
528
|
-
|
|
529
|
-
it('UPDATE without old row', () => {
|
|
530
|
-
const oid = getTableOid('rt.upd_no_old')
|
|
531
|
-
const cols: ColumnInfo[] = [
|
|
532
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
533
|
-
{ name: 'val', typeOid: 25, typeMod: -1 },
|
|
534
|
-
]
|
|
535
|
-
const parser = makeParser()
|
|
536
|
-
parser.parse(encodeRelation(oid, 'public', 'upd_no_old', 0x64, cols))
|
|
537
|
-
|
|
538
|
-
const parsed = parser.parse(encodeUpdate(oid, { id: '1', val: 'v' }, null, cols))
|
|
539
|
-
expect(parsed.tag).toBe('update')
|
|
540
|
-
expect(parsed.new.val).toBe('v')
|
|
541
|
-
expect(parsed.old).toBeNull()
|
|
542
|
-
expect(parsed.key).toBeNull()
|
|
543
|
-
})
|
|
544
|
-
|
|
545
|
-
it('UPDATE roundtrip decodes sqlite boolean integers through zero-cache parser', () => {
|
|
546
|
-
const oid = getTableOid('rt.bool_update')
|
|
547
|
-
const cols: ColumnInfo[] = [
|
|
548
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
549
|
-
{ name: 'completed', typeOid: 16, typeMod: -1 },
|
|
550
|
-
]
|
|
551
|
-
const parser = makeBoolAwareParser()
|
|
552
|
-
parser.parse(encodeRelation(oid, 'public', 'bool_update', 0x64, cols))
|
|
553
|
-
|
|
554
|
-
const parsed = parser.parse(
|
|
555
|
-
encodeUpdate(oid, { id: '1', completed: 1 }, { id: '1', completed: 0 }, cols)
|
|
556
|
-
)
|
|
557
|
-
expect(parsed.tag).toBe('update')
|
|
558
|
-
expect(parsed.old.completed).toBe(false)
|
|
559
|
-
expect(parsed.new.completed).toBe(true)
|
|
560
|
-
})
|
|
561
|
-
|
|
562
|
-
it('DELETE roundtrip', () => {
|
|
563
|
-
const oid = getTableOid('rt.del_test')
|
|
564
|
-
const cols: ColumnInfo[] = [
|
|
565
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
566
|
-
{ name: 'val', typeOid: 25, typeMod: -1 },
|
|
567
|
-
]
|
|
568
|
-
const parser = makeParser()
|
|
569
|
-
parser.parse(encodeRelation(oid, 'public', 'del_test', 0x64, cols))
|
|
570
|
-
|
|
571
|
-
const parsed = parser.parse(encodeDelete(oid, { id: 'gone', val: 'x' }, cols))
|
|
572
|
-
expect(parsed.tag).toBe('delete')
|
|
573
|
-
expect(parsed.key.id).toBe('gone')
|
|
574
|
-
})
|
|
575
|
-
|
|
576
|
-
it('full transaction: BEGIN → RELATION → INSERT → COMMIT', () => {
|
|
577
|
-
const parser = makeParser()
|
|
578
|
-
const lsn = 0x2000000n
|
|
579
|
-
const endLsn = 0x2000100n
|
|
580
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
581
|
-
|
|
582
|
-
const begin = parser.parse(encodeBegin(lsn, ts, 1))
|
|
583
|
-
expect(begin.commitLsn).toBe('00000000/02000000')
|
|
584
|
-
|
|
585
|
-
const oid = getTableOid('rt.full_tx')
|
|
586
|
-
const cols: ColumnInfo[] = [
|
|
587
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
588
|
-
{ name: 'data', typeOid: 25, typeMod: -1 },
|
|
589
|
-
]
|
|
590
|
-
parser.parse(encodeRelation(oid, 'public', 'full_tx', 0x64, cols))
|
|
591
|
-
|
|
592
|
-
const ins = parser.parse(encodeInsert(oid, { id: '1', data: 'test' }, cols))
|
|
593
|
-
expect(ins.new.id).toBe('1')
|
|
594
|
-
|
|
595
|
-
const commit = parser.parse(encodeCommit(0, lsn, endLsn, ts))
|
|
596
|
-
expect(commit.commitLsn).toBe(begin.commitLsn)
|
|
597
|
-
})
|
|
598
|
-
|
|
599
|
-
it('XLogData + CopyData wrapper roundtrip with parser', () => {
|
|
600
|
-
const lsn = 0x3000000n
|
|
601
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
602
|
-
const pgoutput = encodeBegin(lsn, ts, 1)
|
|
603
|
-
const xlog = wrapXLogData(lsn, lsn, ts, pgoutput)
|
|
604
|
-
const frame = wrapCopyData(xlog)
|
|
605
|
-
|
|
606
|
-
// unwrap CopyData
|
|
607
|
-
const copyLen = r32(frame, 1)
|
|
608
|
-
const inner = frame.subarray(5, 1 + copyLen)
|
|
609
|
-
|
|
610
|
-
// parse like stream.js
|
|
611
|
-
expect(inner[0]).toBe(0x77)
|
|
612
|
-
const streamLsn = new DataView(inner.buffer, inner.byteOffset).getBigUint64(1)
|
|
613
|
-
expect(streamLsn).toBe(lsn)
|
|
614
|
-
|
|
615
|
-
// parse pgoutput
|
|
616
|
-
const parser = makeParser()
|
|
617
|
-
const parsed = parser.parse(inner.subarray(25))
|
|
618
|
-
expect(parsed.tag).toBe('begin')
|
|
619
|
-
expect(parsed.commitLsn).toBe('00000000/03000000')
|
|
620
|
-
})
|
|
621
|
-
|
|
622
|
-
it('shard schema encoding', () => {
|
|
623
|
-
const oid = getTableOid('rt.chat_0.clients')
|
|
624
|
-
const cols: ColumnInfo[] = [
|
|
625
|
-
{ name: 'id', typeOid: 25, typeMod: -1, isKey: true },
|
|
626
|
-
{ name: 'lastMutationID', typeOid: 20, typeMod: -1 },
|
|
627
|
-
]
|
|
628
|
-
const parser = makeParser()
|
|
629
|
-
const rel = parser.parse(encodeRelation(oid, 'chat_0', 'clients', 0x64, cols))
|
|
630
|
-
expect(rel.schema).toBe('chat_0')
|
|
631
|
-
expect(rel.name).toBe('clients')
|
|
632
|
-
})
|
|
633
|
-
|
|
634
|
-
it('LSN ordering: slot < streaming changes', async () => {
|
|
635
|
-
// validates that streaming changes will be seen as "new" by zero-cache
|
|
636
|
-
let testLsn = 0x1000000n
|
|
637
|
-
const next = () => {
|
|
638
|
-
testLsn += 0x100n
|
|
639
|
-
return testLsn
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const slotLsn = next() // CREATE_REPLICATION_SLOT
|
|
643
|
-
const beginLsn = next() // first streaming BEGIN
|
|
644
|
-
const commitLsn = next() // first streaming COMMIT
|
|
645
|
-
|
|
646
|
-
expect(beginLsn).toBeGreaterThan(slotLsn)
|
|
647
|
-
expect(commitLsn).toBeGreaterThan(beginLsn)
|
|
648
|
-
|
|
649
|
-
// verify lexi version ordering is preserved
|
|
650
|
-
const lexiPath = join(
|
|
651
|
-
import.meta.dirname,
|
|
652
|
-
'../../node_modules/@rocicorp/zero/out/zero-cache/src/types/lexi-version.js'
|
|
653
|
-
)
|
|
654
|
-
const { versionToLexi } = await import(lexiPath)
|
|
655
|
-
const lsnPath = join(
|
|
656
|
-
import.meta.dirname,
|
|
657
|
-
'../../node_modules/@rocicorp/zero/out/zero-cache/src/services/change-source/pg/lsn.js'
|
|
658
|
-
)
|
|
659
|
-
const { toBigInt: lsnToBigInt } = await import(lsnPath)
|
|
660
|
-
|
|
661
|
-
const slotHex = `00000000/${slotLsn.toString(16).padStart(8, '0')}`.toUpperCase()
|
|
662
|
-
const beginHex = `00000000/${beginLsn.toString(16).padStart(8, '0')}`.toUpperCase()
|
|
663
|
-
|
|
664
|
-
const slotVersion = versionToLexi(lsnToBigInt(slotHex))
|
|
665
|
-
const beginVersion = versionToLexi(lsnToBigInt(beginHex))
|
|
666
|
-
|
|
667
|
-
// lexi versions must maintain ordering
|
|
668
|
-
expect(beginVersion > slotVersion).toBe(true)
|
|
669
|
-
})
|
|
670
|
-
})
|
|
671
|
-
|
|
672
|
-
describe('double-wrap: CopyData(XLogData(message))', () => {
|
|
673
|
-
// this is the exact framing zero-cache expects for every replication message
|
|
674
|
-
it('produces parseable nested structure', () => {
|
|
675
|
-
const ts = BigInt(Date.now()) * 1000n
|
|
676
|
-
const lsn = 0x1000000n
|
|
677
|
-
const inner = encodeBegin(lsn, ts, 1)
|
|
678
|
-
const xlog = wrapXLogData(lsn, lsn, ts, inner)
|
|
679
|
-
const frame = wrapCopyData(xlog)
|
|
680
|
-
|
|
681
|
-
// parse back
|
|
682
|
-
expect(frame[0]).toBe(0x64) // CopyData
|
|
683
|
-
const copyLen = r32(frame, 1)
|
|
684
|
-
expect(frame.length).toBe(1 + copyLen)
|
|
685
|
-
|
|
686
|
-
// inside CopyData: XLogData
|
|
687
|
-
expect(frame[5]).toBe(0x77) // XLogData
|
|
688
|
-
expect(r64(frame, 6)).toBe(lsn) // walStart
|
|
689
|
-
expect(r64(frame, 14)).toBe(lsn) // walEnd
|
|
690
|
-
|
|
691
|
-
// inside XLogData: Begin
|
|
692
|
-
expect(frame[30]).toBe(0x42) // Begin
|
|
693
|
-
expect(r64(frame, 31)).toBe(lsn) // begin LSN
|
|
694
|
-
expect(r32(frame, 47)).toBe(1) // xid
|
|
695
|
-
})
|
|
696
|
-
})
|
|
697
|
-
})
|