@supalive/core 1.9.0 → 1.11.0
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/db-ClCicssQ.js.map +1 -1
- package/dist/index-BQRlJY_1.d.ts +2192 -0
- package/dist/index-BQRlJY_1.d.ts.map +1 -0
- package/dist/index-C_Zew1wH.d.ts +2209 -0
- package/dist/index-C_Zew1wH.d.ts.map +1 -0
- package/dist/index-D8O39FoZ.d.ts +2192 -0
- package/dist/index-D8O39FoZ.d.ts.map +1 -0
- package/dist/index-WMhtVzJA.d.ts +2192 -0
- package/dist/index-WMhtVzJA.d.ts.map +1 -0
- package/dist/mysql-BXy4mk-9.d.ts +113 -0
- package/dist/mysql-BXy4mk-9.d.ts.map +1 -0
- package/dist/mysql-CBSuONm5.d.ts +109 -0
- package/dist/mysql-CBSuONm5.d.ts.map +1 -0
- package/dist/mysql-CDenA4XE.d.ts +109 -0
- package/dist/mysql-CDenA4XE.d.ts.map +1 -0
- package/dist/mysql-D-PGYc1L.js +609 -0
- package/dist/mysql-D-PGYc1L.js.map +1 -0
- package/dist/mysql-DAfFK5v1.d.ts +109 -0
- package/dist/mysql-DAfFK5v1.d.ts.map +1 -0
- package/dist/mysql-txOOzccX.js +609 -0
- package/dist/mysql-txOOzccX.js.map +1 -0
- package/dist/overlap-checker-BoWNco2y.js +258 -0
- package/dist/overlap-checker-BoWNco2y.js.map +1 -0
- package/dist/overlap-checker-DvRqQOTe.js +265 -0
- package/dist/overlap-checker-DvRqQOTe.js.map +1 -0
- package/dist/postgres-BKGLPrKt.d.ts +113 -0
- package/dist/postgres-BKGLPrKt.d.ts.map +1 -0
- package/dist/postgres-CKBWXyY8.d.ts +113 -0
- package/dist/postgres-CKBWXyY8.d.ts.map +1 -0
- package/dist/postgres-CapFFTTn.d.ts +117 -0
- package/dist/postgres-CapFFTTn.d.ts.map +1 -0
- package/dist/postgres-Cn9vTBX7.js +864 -0
- package/dist/postgres-Cn9vTBX7.js.map +1 -0
- package/dist/postgres-DQxKs5Nu.d.ts +113 -0
- package/dist/postgres-DQxKs5Nu.d.ts.map +1 -0
- package/dist/postgres-tLkdxhFu.js +864 -0
- package/dist/postgres-tLkdxhFu.js.map +1 -0
- package/dist/src/client/index.d.ts +1 -1
- package/dist/src/exports/mysql.d.ts +1 -1
- package/dist/src/exports/mysql.js +1 -1
- package/dist/src/exports/postgres.d.ts +1 -1
- package/dist/src/exports/postgres.js +1 -1
- package/dist/src/exports/procedure.d.ts +1 -1
- package/dist/src/exports/schema-sql.d.ts +1 -1
- package/dist/src/exports/server.d.ts +24 -124
- package/dist/src/exports/server.d.ts.map +1 -1
- package/dist/src/exports/server.js +137 -32
- package/dist/src/exports/server.js.map +1 -1
- package/dist/src/exports/sub-manager-worker-entry.d.ts +1 -0
- package/dist/src/exports/sub-manager-worker-entry.js +71 -0
- package/dist/src/exports/sub-manager-worker-entry.js.map +1 -0
- package/dist/src/exports/subscription-manager-worker-entry.js +3 -49
- package/dist/src/exports/subscription-manager-worker-entry.js.map +1 -1
- package/dist/src/exports/types.d.ts +2 -2
- package/dist/sub-worker-dispatch-Cokz99D1.js +810 -0
- package/dist/sub-worker-dispatch-Cokz99D1.js.map +1 -0
- package/dist/sub-worker-dispatch-DohrszOw.js +810 -0
- package/dist/sub-worker-dispatch-DohrszOw.js.map +1 -0
- package/dist/sub-worker-dispatch-G8V8asfR.js +810 -0
- package/dist/sub-worker-dispatch-G8V8asfR.js.map +1 -0
- package/dist/subscription-worker-dispatch-CSQV-PO3.js +810 -0
- package/dist/subscription-worker-dispatch-CSQV-PO3.js.map +1 -0
- package/dist/types_server-AQ8Jl2_R.d.ts +521 -0
- package/dist/types_server-AQ8Jl2_R.d.ts.map +1 -0
- package/dist/types_server-BB2WvXEA.d.ts +521 -0
- package/dist/types_server-BB2WvXEA.d.ts.map +1 -0
- package/dist/types_server-DU9JMSXd.d.ts +521 -0
- package/dist/types_server-DU9JMSXd.d.ts.map +1 -0
- package/dist/types_server-SPElhjga.d.ts +521 -0
- package/dist/types_server-SPElhjga.d.ts.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { n as utf8Decode, t as bytesEqual } from "./bytes-AuEkyxeU.js";
|
|
2
|
+
import { a as jsonPathExtract, i as jsonPathExists, r as jsonContains } from "./query-ur-jCtVr.js";
|
|
3
|
+
import { C as normalizeIdToBytes } from "./types_db-B039N8Em.js";
|
|
4
|
+
//#region src/server/overlap-checker.ts
|
|
5
|
+
function mapReadEntryToRaw(entry) {
|
|
6
|
+
if (entry.kind === "point") return {
|
|
7
|
+
kind: "point",
|
|
8
|
+
tableId: entry.tableId,
|
|
9
|
+
id: normalizeIdToBytes(entry.id),
|
|
10
|
+
table: entry.table
|
|
11
|
+
};
|
|
12
|
+
else return {
|
|
13
|
+
kind: "range",
|
|
14
|
+
tableId: entry.tableId,
|
|
15
|
+
table: entry.table,
|
|
16
|
+
predicate: entry.predicate,
|
|
17
|
+
columns: entry.columns,
|
|
18
|
+
offset: entry.offset,
|
|
19
|
+
orderBy: entry.orderBy,
|
|
20
|
+
limit: entry.limit
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function mapReadSetToRaw(readSet) {
|
|
24
|
+
return readSet.map(mapReadEntryToRaw);
|
|
25
|
+
}
|
|
26
|
+
function mapWriteEntryToRaw(entry) {
|
|
27
|
+
return {
|
|
28
|
+
table: entry.table,
|
|
29
|
+
tableId: entry.tableId,
|
|
30
|
+
id: normalizeIdToBytes(entry.id),
|
|
31
|
+
op: entry.op,
|
|
32
|
+
data: entry.data,
|
|
33
|
+
prevData: entry.prevData
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function mapWriteSetToRaw(writeSet) {
|
|
37
|
+
return writeSet.map(mapWriteEntryToRaw);
|
|
38
|
+
}
|
|
39
|
+
function evaluatePredicateAgainstRow(rowData, predicate) {
|
|
40
|
+
switch (predicate.kind) {
|
|
41
|
+
case "and": {
|
|
42
|
+
let hasUnknown = false;
|
|
43
|
+
for (const child of predicate.children) {
|
|
44
|
+
const r = evaluatePredicateAgainstRow(rowData, child);
|
|
45
|
+
if (r === "no") return "no";
|
|
46
|
+
if (r === "unknown") hasUnknown = true;
|
|
47
|
+
}
|
|
48
|
+
return hasUnknown ? "unknown" : "yes";
|
|
49
|
+
}
|
|
50
|
+
case "or": {
|
|
51
|
+
let hasUnknown = false;
|
|
52
|
+
for (const child of predicate.children) {
|
|
53
|
+
const r = evaluatePredicateAgainstRow(rowData, child);
|
|
54
|
+
if (r === "yes") return "yes";
|
|
55
|
+
if (r === "unknown") hasUnknown = true;
|
|
56
|
+
}
|
|
57
|
+
return hasUnknown ? "unknown" : "no";
|
|
58
|
+
}
|
|
59
|
+
case "leaf": {
|
|
60
|
+
if (!(predicate.column in rowData)) return "unknown";
|
|
61
|
+
const val = rowData[predicate.column];
|
|
62
|
+
switch (predicate.operator) {
|
|
63
|
+
case "=": return val === predicate.value ? "yes" : "no";
|
|
64
|
+
case "!=": return val !== predicate.value ? "yes" : "no";
|
|
65
|
+
case ">": return val > predicate.value ? "yes" : "no";
|
|
66
|
+
case ">=": return val >= predicate.value ? "yes" : "no";
|
|
67
|
+
case "<": return val < predicate.value ? "yes" : "no";
|
|
68
|
+
case "<=": return val <= predicate.value ? "yes" : "no";
|
|
69
|
+
case "between": return val >= predicate.value && val <= predicate.value2 ? "yes" : "no";
|
|
70
|
+
case "in": return Array.isArray(predicate.value) && predicate.value.includes(val) ? "yes" : "no";
|
|
71
|
+
case "like":
|
|
72
|
+
case "contains":
|
|
73
|
+
case "ilike":
|
|
74
|
+
case "icontains": {
|
|
75
|
+
if (typeof val !== "string" || typeof predicate.value !== "string") return "no";
|
|
76
|
+
const haystack = predicate.operator === "ilike" || predicate.operator === "icontains" ? val.toLowerCase() : val;
|
|
77
|
+
const needle = predicate.operator === "ilike" || predicate.operator === "icontains" ? predicate.value.toLowerCase() : predicate.value;
|
|
78
|
+
const pattern = predicate.operator === "contains" || predicate.operator === "icontains" ? `%${needle}%` : needle;
|
|
79
|
+
return new RegExp("^" + pattern.replace(/%/g, ".*").replace(/_/g, ".") + "$").test(haystack) ? "yes" : "no";
|
|
80
|
+
}
|
|
81
|
+
case "jsonContains": {
|
|
82
|
+
const candidate = decodeJsonLeafValue(predicate);
|
|
83
|
+
const target = predicate.path !== void 0 ? jsonPathExtract(val, predicate.path) : val;
|
|
84
|
+
if (target === void 0) return "no";
|
|
85
|
+
return jsonContains(target, candidate) ? "yes" : "no";
|
|
86
|
+
}
|
|
87
|
+
case "jsonContainedBy": {
|
|
88
|
+
const candidate = decodeJsonLeafValue(predicate);
|
|
89
|
+
const target = predicate.path !== void 0 ? jsonPathExtract(val, predicate.path) : val;
|
|
90
|
+
if (target === void 0) return "no";
|
|
91
|
+
return jsonContains(candidate, target) ? "yes" : "no";
|
|
92
|
+
}
|
|
93
|
+
case "jsonHasKey": {
|
|
94
|
+
if (predicate.mode && Array.isArray(predicate.value)) {
|
|
95
|
+
const paths = predicate.value;
|
|
96
|
+
if (paths.length === 0) return "no";
|
|
97
|
+
return (predicate.mode === "all" ? paths.every((p) => jsonPathExists(val, p)) : paths.some((p) => jsonPathExists(val, p))) ? "yes" : "no";
|
|
98
|
+
}
|
|
99
|
+
if (predicate.raw) return jsonPathExists(val, predicate.value) ? "yes" : "no";
|
|
100
|
+
const key = predicate.value;
|
|
101
|
+
if (val === null || typeof val !== "object") return "no";
|
|
102
|
+
if (Array.isArray(val)) return val.includes(key) ? "yes" : "no";
|
|
103
|
+
return key in val ? "yes" : "no";
|
|
104
|
+
}
|
|
105
|
+
case "jsonArrayContains": {
|
|
106
|
+
const candidate = decodeJsonLeafValue(predicate);
|
|
107
|
+
const target = predicate.path !== void 0 ? jsonPathExtract(val, predicate.path) : val;
|
|
108
|
+
if (!Array.isArray(target)) return "no";
|
|
109
|
+
return target.some((el) => jsonContains(el, candidate)) ? "yes" : "no";
|
|
110
|
+
}
|
|
111
|
+
case "isNull": return val === null ? "yes" : "no";
|
|
112
|
+
case "isNotNull": return val !== null ? "yes" : "no";
|
|
113
|
+
default: return "unknown";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function decodeJsonLeafValue(leaf) {
|
|
119
|
+
if (!leaf.raw) return leaf.value;
|
|
120
|
+
if (typeof leaf.value !== "string") return leaf.value;
|
|
121
|
+
try {
|
|
122
|
+
return JSON.parse(leaf.value);
|
|
123
|
+
} catch {
|
|
124
|
+
return leaf.value;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function parseLogData(log) {
|
|
128
|
+
if (log._parsedData !== void 0) return log._parsedData;
|
|
129
|
+
const data = log.data.length > 0 ? JSON.parse(utf8Decode(log.data)) : null;
|
|
130
|
+
log._parsedData = data;
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
function checkLogsAffectReadSet(logs, rawReadSet) {
|
|
134
|
+
for (const log of logs) for (const rawRead of rawReadSet) {
|
|
135
|
+
if (!bytesEqual(log.tableId, rawRead.tableId)) continue;
|
|
136
|
+
if (rawRead.kind === "point" ? checkPointReadOverlap(log, rawRead) : checkRangeReadOverlap(log, rawRead)) return true;
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Stateless freshness check shared by the subscription worker's `register` and
|
|
142
|
+
* the main server's one-shot query path. Decides whether a query's cached
|
|
143
|
+
* result (described by `metadata`) is still valid at `currentTs`.
|
|
144
|
+
*
|
|
145
|
+
* It is a pure function of Redis (`metadata`, `minRetainedTs`) + the DB
|
|
146
|
+
* commit-log probes (`hasCommitForPoints` for point-only tables,
|
|
147
|
+
* `getCommitLogsBetweenTs` for range tables) + the read set — it holds no
|
|
148
|
+
* worker-local state, so it is safe to run outside the sub-manager.
|
|
149
|
+
*
|
|
150
|
+
* `minRetainedTs` is the retention watermark: the oldest commit-log ts still
|
|
151
|
+
* retained after prune.
|
|
152
|
+
*
|
|
153
|
+
* Two ways a cache entry is deemed stale:
|
|
154
|
+
* 1. **Retention guard** — `metadata.lastSnapshotTs < minRetainedTs` (or the
|
|
155
|
+
* watermark is unknown). The between-ts scan would start below the oldest
|
|
156
|
+
* retained commit log, so it could silently miss pruned commits → we cannot
|
|
157
|
+
* prove freshness and must recompute. This is the case that a one-shot
|
|
158
|
+
* (untracked by any subscription, so nothing holds the retention watermark
|
|
159
|
+
* down) can hit.
|
|
160
|
+
* 2. **Overlap** — a commit in `(lastSnapshotTs, currentTs]` touched the read
|
|
161
|
+
* set.
|
|
162
|
+
*/
|
|
163
|
+
async function evaluateCacheFreshness(db, metadata, currentTs, minRetainedTs) {
|
|
164
|
+
if (!metadata) return {
|
|
165
|
+
fresh: false,
|
|
166
|
+
readSet: [],
|
|
167
|
+
rawReadSet: []
|
|
168
|
+
};
|
|
169
|
+
const readSet = metadata.readSet;
|
|
170
|
+
const rawReadSet = mapReadSetToRaw(readSet);
|
|
171
|
+
if (minRetainedTs === null || metadata.lastSnapshotTs < minRetainedTs) return {
|
|
172
|
+
fresh: false,
|
|
173
|
+
readSet,
|
|
174
|
+
rawReadSet
|
|
175
|
+
};
|
|
176
|
+
if (rawReadSet.length === 0) return {
|
|
177
|
+
fresh: true,
|
|
178
|
+
readSet,
|
|
179
|
+
rawReadSet
|
|
180
|
+
};
|
|
181
|
+
const beginTs = metadata.lastSnapshotTs + 1n;
|
|
182
|
+
const endTs = currentTs + 1n;
|
|
183
|
+
const rangeTables = /* @__PURE__ */ new Map();
|
|
184
|
+
for (const r of rawReadSet) if (r.kind === "range") rangeTables.set(tableKey(r.tableId), r.tableId);
|
|
185
|
+
const pointProbes = [];
|
|
186
|
+
for (const r of rawReadSet) if (r.kind === "point" && !rangeTables.has(tableKey(r.tableId))) pointProbes.push({
|
|
187
|
+
tableId: r.tableId,
|
|
188
|
+
id: r.id
|
|
189
|
+
});
|
|
190
|
+
if (pointProbes.length > 0) {
|
|
191
|
+
if (await db.hasCommitForPoints(beginTs, endTs, pointProbes)) return {
|
|
192
|
+
fresh: false,
|
|
193
|
+
readSet,
|
|
194
|
+
rawReadSet
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (rangeTables.size > 0) {
|
|
198
|
+
if (checkLogsAffectReadSet(await db.getCommitLogsBetweenTs(beginTs, endTs, [...rangeTables.values()]), rawReadSet)) return {
|
|
199
|
+
fresh: false,
|
|
200
|
+
readSet,
|
|
201
|
+
rawReadSet
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
return {
|
|
205
|
+
fresh: true,
|
|
206
|
+
readSet,
|
|
207
|
+
rawReadSet
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** Stable hex key for a table-id byte string, for use as a Map/Set key (byte
|
|
211
|
+
* arrays compare by identity, not value). */
|
|
212
|
+
function tableKey(bytes) {
|
|
213
|
+
let s = "";
|
|
214
|
+
for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, "0");
|
|
215
|
+
return s;
|
|
216
|
+
}
|
|
217
|
+
function checkPointReadOverlap(log, rawRead) {
|
|
218
|
+
return bytesEqual(log.id, rawRead.id);
|
|
219
|
+
}
|
|
220
|
+
function checkRangeReadOverlap(log, rawRead) {
|
|
221
|
+
const logData = parseLogData(log);
|
|
222
|
+
if (!logData) return true;
|
|
223
|
+
const op = logData.op ?? "update";
|
|
224
|
+
const data = logData.data;
|
|
225
|
+
const prevData = logData.prevData;
|
|
226
|
+
return checkRowWriteAffectsRange(op, prevData, data, rawRead.predicate);
|
|
227
|
+
}
|
|
228
|
+
function checkWriteSetAffectsReadSet(writeSet, rawReadSet) {
|
|
229
|
+
for (const write of writeSet) for (const rawRead of rawReadSet) {
|
|
230
|
+
if (!bytesEqual(write.tableId, rawRead.tableId)) continue;
|
|
231
|
+
if (checkWriteAffectsRawReadEntry(write, rawRead)) return true;
|
|
232
|
+
}
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
function checkWriteAffectsRawReadEntry(write, rawRead) {
|
|
236
|
+
if (rawRead.kind === "point") return bytesEqual(write.id, rawRead.id);
|
|
237
|
+
return checkRowWriteAffectsRange(write.op, write.prevData, write.data, rawRead.predicate);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Evaluate whether a write affects a range subscription, given the row's
|
|
241
|
+
* prior and post state. The write is considered affecting if EITHER side
|
|
242
|
+
* matched the predicate — that catches:
|
|
243
|
+
* - rows entering the result set (prev "no", post "yes")
|
|
244
|
+
* - rows leaving the result set (prev "yes", post "no")
|
|
245
|
+
* - rows changing while still in the set (prev "yes", post "yes")
|
|
246
|
+
* Skipped only if both sides are a definite "no".
|
|
247
|
+
*
|
|
248
|
+
* When prevData is missing (e.g. inserts, or commit logs predating this
|
|
249
|
+
* change), we fall back to conservative "unknown" on that side.
|
|
250
|
+
*/
|
|
251
|
+
function checkRowWriteAffectsRange(op, prevData, data, predicate) {
|
|
252
|
+
const prevVerdict = prevData ? evaluatePredicateAgainstRow(prevData, predicate) : op === "insert" ? "no" : "unknown";
|
|
253
|
+
let postVerdict;
|
|
254
|
+
if (op === "delete") postVerdict = "no";
|
|
255
|
+
else if (data) postVerdict = evaluatePredicateAgainstRow(prevData ? {
|
|
256
|
+
...prevData,
|
|
257
|
+
...data
|
|
258
|
+
} : data, predicate);
|
|
259
|
+
else postVerdict = "unknown";
|
|
260
|
+
return prevVerdict !== "no" || postVerdict !== "no";
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
export { mapWriteSetToRaw as a, mapReadSetToRaw as i, checkWriteSetAffectsReadSet as n, evaluateCacheFreshness as r, checkLogsAffectReadSet as t };
|
|
264
|
+
|
|
265
|
+
//# sourceMappingURL=overlap-checker-DvRqQOTe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlap-checker-DvRqQOTe.js","names":[],"sources":["../src/server/overlap-checker.ts"],"sourcesContent":["import type {\n ReadEntry, CommitLogEntry,\n Predicate, WriteEntry, WriteOp,\n RawReadEntry, RawPointRead, RawRangeRead, LeafPredicate,\n QueryCacheMetadata,\n} from \"../db/types_db\";\nimport { normalizeIdToBytes } from \"../db/types_db\";\nimport { bytesEqual, utf8Decode } from \"../db/bytes\";\nimport { jsonContains, jsonPathExists, jsonPathExtract } from \"../db/query\";\nimport type { Database } from \"../db/db\";\n\nexport interface RawWriteEntry {\n table: string;\n tableId: Uint8Array;\n id: Uint8Array;\n op: WriteOp;\n data?: Record<string, unknown>;\n prevData?: Record<string, unknown>;\n}\n\nexport function mapReadEntryToRaw(entry: ReadEntry): RawReadEntry {\n if (entry.kind === \"point\") {\n return {\n kind: \"point\",\n tableId: entry.tableId,\n id: normalizeIdToBytes(entry.id),\n table: entry.table,\n };\n } else {\n return {\n kind: \"range\",\n tableId: entry.tableId,\n table: entry.table,\n predicate: entry.predicate,\n columns: entry.columns,\n offset: entry.offset,\n orderBy: entry.orderBy,\n limit: entry.limit,\n };\n }\n}\n\nexport function mapReadSetToRaw(readSet: ReadEntry[]): RawReadEntry[] {\n return readSet.map(mapReadEntryToRaw);\n}\n\nexport function mapWriteEntryToRaw(entry: WriteEntry): RawWriteEntry {\n return {\n table: entry.table,\n tableId: entry.tableId,\n id: normalizeIdToBytes(entry.id),\n op: entry.op,\n data: entry.data,\n prevData: entry.prevData,\n };\n}\n\nexport function mapWriteSetToRaw(writeSet: WriteEntry[]): RawWriteEntry[] {\n return writeSet.map(mapWriteEntryToRaw);\n}\n\nexport type OverlapVerdict = \"yes\" | \"no\" | \"unknown\";\n\nexport function evaluatePredicateAgainstRow(\n rowData: Record<string, unknown>,\n predicate: Predicate\n): OverlapVerdict {\n switch (predicate.kind) {\n case \"and\": {\n let hasUnknown = false;\n for (const child of predicate.children) {\n const r = evaluatePredicateAgainstRow(rowData, child);\n if (r === \"no\") return \"no\";\n if (r === \"unknown\") hasUnknown = true;\n }\n return hasUnknown ? \"unknown\" : \"yes\";\n }\n case \"or\": {\n let hasUnknown = false;\n for (const child of predicate.children) {\n const r = evaluatePredicateAgainstRow(rowData, child);\n if (r === \"yes\") return \"yes\";\n if (r === \"unknown\") hasUnknown = true;\n }\n return hasUnknown ? \"unknown\" : \"no\";\n }\n case \"leaf\": {\n if (!(predicate.column in rowData)) return \"unknown\";\n const val = rowData[predicate.column];\n switch (predicate.operator) {\n case \"=\": return val === predicate.value ? \"yes\" : \"no\";\n case \"!=\": return val !== predicate.value ? \"yes\" : \"no\";\n case \">\": return (val as number) > (predicate.value as number) ? \"yes\" : \"no\";\n case \">=\": return (val as number) >= (predicate.value as number) ? \"yes\" : \"no\";\n case \"<\": return (val as number) < (predicate.value as number) ? \"yes\" : \"no\";\n case \"<=\": return (val as number) <= (predicate.value as number) ? \"yes\" : \"no\";\n case \"between\":\n return (val as number) >= (predicate.value as number) &&\n (val as number) <= (predicate.value2 as number) ? \"yes\" : \"no\";\n case \"in\":\n return Array.isArray(predicate.value) && (predicate.value as unknown[]).includes(val) ? \"yes\" : \"no\";\n case \"like\":\n case \"contains\":\n case \"ilike\":\n case \"icontains\": {\n if (typeof val !== \"string\" || typeof predicate.value !== \"string\") return \"no\";\n const haystack = (predicate.operator === \"ilike\" || predicate.operator === \"icontains\")\n ? val.toLowerCase() : val;\n const needle = (predicate.operator === \"ilike\" || predicate.operator === \"icontains\")\n ? (predicate.value as string).toLowerCase() : predicate.value as string;\n const pattern = (predicate.operator === \"contains\" || predicate.operator === \"icontains\")\n ? `%${needle}%` : needle;\n const regex = new RegExp(\"^\" + pattern.replace(/%/g, \".*\").replace(/_/g, \".\") + \"$\");\n return regex.test(haystack) ? \"yes\" : \"no\";\n }\n case \"jsonContains\": {\n const candidate = decodeJsonLeafValue(predicate);\n const target = predicate.path !== undefined ? jsonPathExtract(val, predicate.path) : val;\n if (target === undefined) return \"no\";\n return jsonContains(target, candidate) ? \"yes\" : \"no\";\n }\n case \"jsonContainedBy\": {\n const candidate = decodeJsonLeafValue(predicate);\n const target = predicate.path !== undefined ? jsonPathExtract(val, predicate.path) : val;\n if (target === undefined) return \"no\";\n return jsonContains(candidate, target) ? \"yes\" : \"no\";\n }\n case \"jsonHasKey\": {\n if (predicate.mode && Array.isArray(predicate.value)) {\n const paths = predicate.value as string[];\n if (paths.length === 0) return \"no\";\n const verdict = predicate.mode === \"all\"\n ? paths.every(p => jsonPathExists(val, p))\n : paths.some(p => jsonPathExists(val, p));\n return verdict ? \"yes\" : \"no\";\n }\n if (predicate.raw) {\n return jsonPathExists(val, predicate.value as string) ? \"yes\" : \"no\";\n }\n const key = predicate.value as string;\n if (val === null || typeof val !== \"object\") return \"no\";\n if (Array.isArray(val)) return (val as unknown[]).includes(key) ? \"yes\" : \"no\";\n return key in (val as object) ? \"yes\" : \"no\";\n }\n case \"jsonArrayContains\": {\n const candidate = decodeJsonLeafValue(predicate);\n const target = predicate.path !== undefined ? jsonPathExtract(val, predicate.path) : val;\n if (!Array.isArray(target)) return \"no\";\n return (target as unknown[]).some(el => jsonContains(el, candidate)) ? \"yes\" : \"no\";\n }\n case \"isNull\": return val === null ? \"yes\" : \"no\";\n case \"isNotNull\": return val !== null ? \"yes\" : \"no\";\n default: return \"unknown\";\n }\n }\n }\n}\n\nfunction decodeJsonLeafValue(leaf: LeafPredicate): unknown {\n if (!leaf.raw) return leaf.value;\n if (typeof leaf.value !== \"string\") return leaf.value;\n try { return JSON.parse(leaf.value); } catch { return leaf.value; }\n}\n\nfunction parseLogData(log: CommitLogEntry): Record<string, unknown> | null {\n if (log._parsedData !== undefined) {\n return log._parsedData;\n }\n const data = log.data.length > 0\n ? JSON.parse(utf8Decode(log.data)) as Record<string, unknown>\n : null;\n log._parsedData = data;\n return data;\n}\n\n\nexport function checkLogsAffectReadSet(\n logs: CommitLogEntry[],\n rawReadSet: RawReadEntry[]\n): boolean {\n for (const log of logs) {\n for (const rawRead of rawReadSet) {\n if (!bytesEqual(log.tableId, rawRead.tableId)) continue;\n\n const res = rawRead.kind === \"point\" ?\n checkPointReadOverlap(log, rawRead) :\n checkRangeReadOverlap(log, rawRead);\n\n if (res) {\n return true;\n }\n }\n }\n return false;\n}\n\nexport interface CacheFreshness {\n /** true → the cached data for this key is still valid as of `currentTs`. */\n fresh: boolean;\n /** The cached read set (empty when there is no usable metadata). */\n readSet: ReadEntry[];\n rawReadSet: RawReadEntry[];\n}\n\n/**\n * Stateless freshness check shared by the subscription worker's `register` and\n * the main server's one-shot query path. Decides whether a query's cached\n * result (described by `metadata`) is still valid at `currentTs`.\n *\n * It is a pure function of Redis (`metadata`, `minRetainedTs`) + the DB\n * commit-log probes (`hasCommitForPoints` for point-only tables,\n * `getCommitLogsBetweenTs` for range tables) + the read set — it holds no\n * worker-local state, so it is safe to run outside the sub-manager.\n *\n * `minRetainedTs` is the retention watermark: the oldest commit-log ts still\n * retained after prune.\n *\n * Two ways a cache entry is deemed stale:\n * 1. **Retention guard** — `metadata.lastSnapshotTs < minRetainedTs` (or the\n * watermark is unknown). The between-ts scan would start below the oldest\n * retained commit log, so it could silently miss pruned commits → we cannot\n * prove freshness and must recompute. This is the case that a one-shot\n * (untracked by any subscription, so nothing holds the retention watermark\n * down) can hit.\n * 2. **Overlap** — a commit in `(lastSnapshotTs, currentTs]` touched the read\n * set.\n */\nexport async function evaluateCacheFreshness(\n db: Pick<Database, \"getCommitLogsBetweenTs\" | \"hasCommitForPoints\">,\n metadata: QueryCacheMetadata | null,\n currentTs: bigint,\n minRetainedTs: bigint | null,\n): Promise<CacheFreshness> {\n if (!metadata) return { fresh: false, readSet: [], rawReadSet: [] };\n\n const readSet = metadata.readSet;\n const rawReadSet = mapReadSetToRaw(readSet);\n\n if (minRetainedTs === null || metadata.lastSnapshotTs < minRetainedTs) {\n return { fresh: false, readSet, rawReadSet };\n }\n\n // A query that read nothing can't be invalidated by any commit — permanently\n // fresh (until TTL eviction). Skip the commit-log scan entirely.\n if (rawReadSet.length === 0) return { fresh: true, readSet, rawReadSet };\n\n // Window (lastSnapshotTs, currentTs]: commits strictly AFTER the cached\n // snapshot (the data already reflects everything <= lastSnapshotTs) up to and\n // INCLUDING currentTs. The DB scans take a half-open [begin, end), so +1n on\n // both ends models that inclusive upper bound. Missing the upper bound would\n // drop a mutation committed at exactly currentTs — and a one-shot query has no\n // push-invalidation backstop, so it would serve stale.\n const beginTs = metadata.lastSnapshotTs + 1n;\n const endTs = currentTs + 1n;\n\n // Partition the read set by how precisely a commit-log check can be pushed to\n // the DB:\n // • range tables — a commit only matters if the row's prev/post state\n // satisfies the predicate, which only the in-app evaluator can decide, so\n // the row `data` for these tables must be fetched.\n // • point-only tables (carry no range read) — overlap is a pure\n // (table_id, id) key match, answerable by an existence probe with no blob\n // fetch or JSON parse.\n // A point read whose table ALSO carries a range read is subsumed by that\n // table's row fetch (checkLogsAffectReadSet re-checks it against those rows),\n // so it is intentionally excluded from the point probe.\n const rangeTables = new Map<string, Uint8Array>();\n for (const r of rawReadSet) {\n if (r.kind === \"range\") rangeTables.set(tableKey(r.tableId), r.tableId);\n }\n\n const pointProbes: { tableId: Uint8Array; id: Uint8Array }[] = [];\n for (const r of rawReadSet) {\n if (r.kind === \"point\" && !rangeTables.has(tableKey(r.tableId))) {\n pointProbes.push({ tableId: r.tableId, id: r.id });\n }\n }\n\n // Point-only tables: one existence probe on exact (table_id, id) pairs — no\n // data blobs fetched, no predicates evaluated.\n if (pointProbes.length > 0) {\n if (await db.hasCommitForPoints(beginTs, endTs, pointProbes)) {\n return { fresh: false, readSet, rawReadSet };\n }\n }\n\n // Range tables: fetch the window's logs for just those tables and evaluate\n // predicates (and any point reads subsumed onto them) in-app.\n if (rangeTables.size > 0) {\n const logs = await db.getCommitLogsBetweenTs(beginTs, endTs, [...rangeTables.values()]);\n if (checkLogsAffectReadSet(logs, rawReadSet)) {\n return { fresh: false, readSet, rawReadSet };\n }\n }\n\n return { fresh: true, readSet, rawReadSet };\n}\n\n/** Stable hex key for a table-id byte string, for use as a Map/Set key (byte\n * arrays compare by identity, not value). */\nfunction tableKey(bytes: Uint8Array): string {\n let s = \"\";\n for (let i = 0; i < bytes.length; i++) s += bytes[i].toString(16).padStart(2, \"0\");\n return s;\n}\n\nfunction checkPointReadOverlap(\n log: CommitLogEntry,\n rawRead: RawPointRead,\n): boolean {\n return bytesEqual(log.id, rawRead.id);\n}\n\nfunction checkRangeReadOverlap(\n log: CommitLogEntry,\n rawRead: RawRangeRead\n): boolean {\n const logData = parseLogData(log);\n\n // No envelope at all → can't reason, must conflict-out.\n if (!logData) { \n return true;\n }\n\n // Commit logs serialize the full WriteEntry (see PgDatabase.insertCommitLogs),\n // so extract the row state from it. Same prev-OR-post check as the\n // in-memory invalidator path.\n const op = (logData.op as WriteOp | undefined) ?? \"update\";\n const data = logData.data as Record<string, unknown> | undefined;\n const prevData = logData.prevData as Record<string, unknown> | undefined;\n\n return checkRowWriteAffectsRange(op, prevData, data, rawRead.predicate);\n}\n\nexport function checkWriteSetAffectsReadSet(\n writeSet: RawWriteEntry[],\n rawReadSet: RawReadEntry[]\n): boolean {\n for (const write of writeSet) {\n for (const rawRead of rawReadSet) {\n if (!bytesEqual(write.tableId, rawRead.tableId)) continue;\n\n if (checkWriteAffectsRawReadEntry(write, rawRead)) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction checkWriteAffectsRawReadEntry(\n write: RawWriteEntry,\n rawRead: RawReadEntry\n): boolean {\n if (rawRead.kind === \"point\") {\n return bytesEqual(write.id, rawRead.id);\n }\n return checkRowWriteAffectsRange(\n write.op,\n write.prevData,\n write.data,\n rawRead.predicate,\n );\n}\n\n/**\n * Evaluate whether a write affects a range subscription, given the row's\n * prior and post state. The write is considered affecting if EITHER side\n * matched the predicate — that catches:\n * - rows entering the result set (prev \"no\", post \"yes\")\n * - rows leaving the result set (prev \"yes\", post \"no\")\n * - rows changing while still in the set (prev \"yes\", post \"yes\")\n * Skipped only if both sides are a definite \"no\".\n *\n * When prevData is missing (e.g. inserts, or commit logs predating this\n * change), we fall back to conservative \"unknown\" on that side.\n */\nfunction checkRowWriteAffectsRange(\n op: WriteOp,\n prevData: Record<string, unknown> | undefined,\n data: Record<string, unknown> | undefined,\n predicate: RawRangeRead[\"predicate\"],\n): boolean {\n const prevVerdict: OverlapVerdict = prevData\n ? evaluatePredicateAgainstRow(prevData, predicate)\n : (op === \"insert\" ? \"no\" : \"unknown\");\n\n let postVerdict: OverlapVerdict;\n if (op === \"delete\") {\n postVerdict = \"no\";\n } else if (data) {\n // Merge over prevData so unchanged columns carry over — otherwise a\n // partial update (e.g. {title: \"x\"}) would be \"unknown\" on every\n // predicate column it doesn't touch.\n const post = prevData ? { ...prevData, ...data } : data;\n postVerdict = evaluatePredicateAgainstRow(post, predicate);\n } else {\n postVerdict = \"unknown\";\n }\n\n return prevVerdict !== \"no\" || postVerdict !== \"no\";\n}"],"mappings":"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"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { $r as WriteEntry, Gn as DbType, Hn as CoreInitLockCtx, Jn as PreparedQueries, Kn as LazyCommitTsParam, Qn as CacheLayer, Un as Database, Wn as DbQueryResult, Xn as SqlBuilder, Xr as ReadEntry, Zn as TxDatabase, qn as PooledClient, yr as CommitLogEntry } from "./index-BQRlJY_1.js";
|
|
2
|
+
import { Pool, PoolConfig } from "pg";
|
|
3
|
+
import { Logger } from "pino";
|
|
4
|
+
|
|
5
|
+
//#region src/db/postgres.d.ts
|
|
6
|
+
declare class PgDatabase implements Database {
|
|
7
|
+
constructor(logger: Logger, config: PoolConfig, pool?: Pool | undefined);
|
|
8
|
+
private logger;
|
|
9
|
+
config: PoolConfig;
|
|
10
|
+
private pool;
|
|
11
|
+
readonly sqlBuilder: SqlBuilder;
|
|
12
|
+
readonly dbType: DbType;
|
|
13
|
+
columnTypes: Map<string, Map<string, string>>;
|
|
14
|
+
protected sqlCache: Map<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetches actual column types from Postgres for `table` and returns a
|
|
17
|
+
* map of DB column name → PG type string (as produced by `format_type`,
|
|
18
|
+
* e.g. `bigint`, `integer`, `character varying(32)`, `text[]`, `jsonb`).
|
|
19
|
+
* No caching here — see {@link getColumnTypes} for the cached path.
|
|
20
|
+
*/
|
|
21
|
+
loadColumnTypesFromPg(table: string): Promise<Map<string, string>>;
|
|
22
|
+
/**
|
|
23
|
+
* Synchronous column-type lookup. The apply path calls this on every
|
|
24
|
+
* batched write, so it can't await — populate the cache via
|
|
25
|
+
* {@link bootstrapColumnTypes} at server start. Throws if `table` is
|
|
26
|
+
* missing (programmer error / bootstrap skipped).
|
|
27
|
+
*/
|
|
28
|
+
getColumnTypes(table: string): Map<string, string> | null;
|
|
29
|
+
/**
|
|
30
|
+
* Eagerly load column types into the in-memory cache. With a Redis
|
|
31
|
+
* client supplied, attempts the cache key first and falls through to
|
|
32
|
+
* Postgres on miss (writing the result back to Redis). This is the
|
|
33
|
+
* "one server queries PG, the rest read from Redis" pattern for cold
|
|
34
|
+
* starts in a multi-instance deploy. Without Redis, always queries PG.
|
|
35
|
+
*
|
|
36
|
+
* Pass `tables` to limit the load to a specific list; otherwise every
|
|
37
|
+
* user table in the connected database is loaded.
|
|
38
|
+
*
|
|
39
|
+
* The Redis cache key should encode whatever invalidates types in your
|
|
40
|
+
* environment (typically a schema version / deploy version). The
|
|
41
|
+
* subscriber in supalive-server.ts is expected to publish a reload
|
|
42
|
+
* signal after migrations and invoke {@link invalidateColumnTypes}.
|
|
43
|
+
*/
|
|
44
|
+
bootstrapColumnTypes(opts: {
|
|
45
|
+
redis?: CacheLayer;
|
|
46
|
+
cacheKey: string;
|
|
47
|
+
tables?: string[];
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Invalidate cached types (and dependent SQL) for `table`, or the entire
|
|
51
|
+
* cache if `table` is omitted. The Redis-pubsub reload signal in
|
|
52
|
+
* supalive-server.ts is expected to call this on schema changes.
|
|
53
|
+
*/
|
|
54
|
+
private invalidateColumnTypes;
|
|
55
|
+
/** Enumerate the user-defined tables in the connected database — used
|
|
56
|
+
* by bootstrapColumnTypes() when no explicit list is provided. */
|
|
57
|
+
private listUserTables;
|
|
58
|
+
getCoreVersion(): Promise<bigint>;
|
|
59
|
+
coreMigrationStatements(toVersion: bigint): string[];
|
|
60
|
+
withCoreInitLock(fn: (ctx: CoreInitLockCtx) => Promise<void>): Promise<void>;
|
|
61
|
+
query<T = any>(sql: string, params?: unknown[]): Promise<DbQueryResult<T>>;
|
|
62
|
+
getClient(): Promise<PooledClient>;
|
|
63
|
+
getTransactionClient(): Promise<TxDatabase>;
|
|
64
|
+
/** Internal: replace this instance's caches with shared references from
|
|
65
|
+
* a parent. Used by PgDatabase.getTransactionClient. */
|
|
66
|
+
adoptCaches(columnTypes: Map<string, Map<string, string>>, sqlCache: Map<string, string>): void;
|
|
67
|
+
getLatestSnapshotTimestamp(): Promise<bigint>;
|
|
68
|
+
updateLatestSnapshotTimestamp(commitTs: bigint): Promise<void>;
|
|
69
|
+
getMinSnapshotTimestamp(): Promise<bigint>;
|
|
70
|
+
updateMinSnapshotTimestamp(ts: bigint): Promise<void>;
|
|
71
|
+
getNextTimestamp(): Promise<bigint>;
|
|
72
|
+
attachPrevDataToWriteSet(writeSet: WriteEntry[], readSet: ReadEntry[]): Promise<void>;
|
|
73
|
+
prepareCommitWrites(readSet: ReadEntry[], writeSet: WriteEntry[], commitTs: LazyCommitTsParam): Promise<PreparedQueries>;
|
|
74
|
+
private pgPrepareBatchWrites;
|
|
75
|
+
private pgPrepareInsertSingle;
|
|
76
|
+
private pgPrepareInsertBatch;
|
|
77
|
+
private pgPrepareUpdateSingle;
|
|
78
|
+
private pgPrepareUpdateBatch;
|
|
79
|
+
private pgPrepareDeleteSingle;
|
|
80
|
+
private pgPrepareDeleteBatch;
|
|
81
|
+
private buildOrGetInsertSql;
|
|
82
|
+
private buildOrGetUpdateSql;
|
|
83
|
+
private buildOrGetDeleteSql;
|
|
84
|
+
private pgPrepareCommitLogs;
|
|
85
|
+
/**
|
|
86
|
+
* Point-only OCC check. For each genuine point read, compare the row's
|
|
87
|
+
* current commit_ts against the value captured at read time. A `null`
|
|
88
|
+
* expectedCommitTs means "row didn't exist when we read"; any row found
|
|
89
|
+
* now is a conflict. Reads whose row is being written in this same txn
|
|
90
|
+
* are skipped — the CAS UPDATE/DELETE already validated those.
|
|
91
|
+
*/
|
|
92
|
+
private pgPrepareValidatePointReadsByCommitTs;
|
|
93
|
+
commitWrites(beginTs: bigint, queries: PreparedQueries, commitTs: bigint): Promise<{
|
|
94
|
+
success: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
private pgApplyWrites;
|
|
97
|
+
/**
|
|
98
|
+
* Point-only OCC check. For each genuine point read, compare the row's
|
|
99
|
+
* current commit_ts against the value captured at read time. A `null`
|
|
100
|
+
* expectedCommitTs means "row didn't exist when we read"; any row found
|
|
101
|
+
* now is a conflict. Reads whose row is being written in this same txn
|
|
102
|
+
* are skipped — the CAS UPDATE/DELETE already validated those.
|
|
103
|
+
*/
|
|
104
|
+
private validatePointReadsByCommitTs;
|
|
105
|
+
pruneCommitLogsBefore(ts: bigint): Promise<bigint>;
|
|
106
|
+
getMinCommitLogTs(): Promise<bigint | null>;
|
|
107
|
+
getCommitLogsBetweenTs(beginTs: bigint, endTs: bigint, tableIds: Uint8Array[]): Promise<CommitLogEntry[]>;
|
|
108
|
+
getCommitLogsSinceTs(sinceTs: bigint, excludeTs: bigint, tableIds: Uint8Array[]): Promise<CommitLogEntry[]>;
|
|
109
|
+
close(): Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { PgDatabase as t };
|
|
113
|
+
//# sourceMappingURL=postgres-BKGLPrKt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-BKGLPrKt.d.ts","names":[],"sources":["../src/db/postgres.ts"],"mappings":";;;;;cAiBa,UAAA,YAAsB,QAAA;cAE/B,MAAA,EAAQ,MAAA,EACR,MAAA,EAAQ,UAAA,EACR,IAAA,GAAM,IAAA;EAAA,QAWA,MAAA;EACD,MAAA,EAAQ,UAAA;EAAA,QACP,IAAA;EAAA,SACC,UAAA,EAAY,UAAA;EAAA,SACZ,MAAA,EAAQ,MAAA;EAMV,WAAA,EAAa,GAAA,SAAY,GAAA;EAAA,UAGtB,QAAA,EAAU,GAAA;EAHY;;;;;;EAW1B,qBAAA,CAAsB,KAAA,WAAgB,OAAA,CAAQ,GAAA;EAoDhD;;;;;;EA5BJ,cAAA,CAAe,KAAA,WAAgB,GAAA;EAyJ4B;;;;;;;;;;;;;;;EAjIrD,oBAAA,CAAqB,IAAA;IACzB,KAAA,GAAQ,UAAA;IACR,QAAA;IACA,MAAA;EAAA,IACE,OAAA;EAyRD;;;;;EAAA,QA/OK,qBAAA;EA23BsF;;EAAA,QA72BhF,cAAA;EAUR,cAAA,IAAkB,OAAA;EAWxB,uBAAA,CAAwB,SAAA;EAUlB,gBAAA,CAAiB,EAAA,GAAK,GAAA,EAAK,eAAA,KAAoB,OAAA,SAAgB,OAAA;EAsC/D,KAAA,UAAe,GAAA,UAAa,MAAA,eAAyB,OAAA,CAAQ,aAAA,CAAc,CAAA;EAe3E,SAAA,IAAa,OAAA,CAAQ,YAAA;EAIrB,oBAAA,IAAwB,OAAA,CAAQ,UAAA;EAxOL;;EAoPjC,WAAA,CACE,WAAA,EAAa,GAAA,SAAY,GAAA,mBACzB,QAAA,EAAU,GAAA;EAMN,0BAAA,IAA8B,OAAA;EAK9B,6BAAA,CAA8B,QAAA,WAAmB,OAAA;EAOjD,uBAAA,IAA2B,OAAA;EAO3B,0BAAA,CAA2B,EAAA,WAAa,OAAA;EAOxC,gBAAA,IAAoB,OAAA;EAuBpB,wBAAA,CAAyB,QAAA,EAAU,UAAA,IAAc,OAAA,EAAS,SAAA,KAAc,OAAA;EAgExE,mBAAA,CACJ,OAAA,EAAS,SAAA,IACT,QAAA,EAAU,UAAA,IACV,QAAA,EAAU,iBAAA,GACT,OAAA,CAAQ,eAAA;EAAA,QAoBG,oBAAA;EAAA,QA2BN,qBAAA;EAAA,QAkBM,oBAAA;EAAA,QA+DN,qBAAA;EAAA,QAwBM,oBAAA;EAAA,QAsEN,qBAAA;EAAA,QAgBM,oBAAA;EAAA,QAiDN,mBAAA;EAAA,QAqBA,mBAAA;EAAA,QA4BA,mBAAA;EAAA,QAcM,mBAAA;EA3qBR;;;;;;;EAAA,QAytBE,qCAAA;EAkDF,YAAA,CACJ,OAAA,UACA,OAAA,EAAS,eAAA,EACT,QAAA,WACC,OAAA;IAAU,OAAA;EAAA;EAAA,QAqCC,aAAA;EApwBa;;;;;;;EAAA,QA+2Bb,4BAAA;EA0CR,qBAAA,CAAsB,EAAA,WAAa,OAAA;EAQnC,iBAAA,IAAqB,OAAA;EAQrB,sBAAA,CAAuB,OAAA,UAAiB,KAAA,UAAe,QAAA,EAAU,UAAA,KAAe,OAAA,CAAQ,cAAA;EAmBxF,oBAAA,CAAqB,OAAA,UAAiB,SAAA,UAAmB,QAAA,EAAU,UAAA,KAAe,OAAA,CAAQ,cAAA;EAmB1F,KAAA,IAAS,OAAA;AAAA"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { $r as WriteEntry, Gn as DbType, Hn as CoreInitLockCtx, Jn as PreparedQueries, Kn as LazyCommitTsParam, Qn as CacheLayer, Un as Database, Wn as DbQueryResult, Xn as SqlBuilder, Xr as ReadEntry, Zn as TxDatabase, qn as PooledClient, yr as CommitLogEntry } from "./index-D8O39FoZ.js";
|
|
2
|
+
import { Pool, PoolConfig } from "pg";
|
|
3
|
+
import { Logger } from "pino";
|
|
4
|
+
|
|
5
|
+
//#region src/db/postgres.d.ts
|
|
6
|
+
declare class PgDatabase implements Database {
|
|
7
|
+
constructor(logger: Logger, config: PoolConfig, pool?: Pool | undefined);
|
|
8
|
+
private logger;
|
|
9
|
+
config: PoolConfig;
|
|
10
|
+
private pool;
|
|
11
|
+
readonly sqlBuilder: SqlBuilder;
|
|
12
|
+
readonly dbType: DbType;
|
|
13
|
+
columnTypes: Map<string, Map<string, string>>;
|
|
14
|
+
protected sqlCache: Map<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Fetches actual column types from Postgres for `table` and returns a
|
|
17
|
+
* map of DB column name → PG type string (as produced by `format_type`,
|
|
18
|
+
* e.g. `bigint`, `integer`, `character varying(32)`, `text[]`, `jsonb`).
|
|
19
|
+
* No caching here — see {@link getColumnTypes} for the cached path.
|
|
20
|
+
*/
|
|
21
|
+
loadColumnTypesFromPg(table: string): Promise<Map<string, string>>;
|
|
22
|
+
/**
|
|
23
|
+
* Synchronous column-type lookup. The apply path calls this on every
|
|
24
|
+
* batched write, so it can't await — populate the cache via
|
|
25
|
+
* {@link bootstrapColumnTypes} at server start. Throws if `table` is
|
|
26
|
+
* missing (programmer error / bootstrap skipped).
|
|
27
|
+
*/
|
|
28
|
+
getColumnTypes(table: string): Map<string, string> | null;
|
|
29
|
+
/**
|
|
30
|
+
* Eagerly load column types into the in-memory cache. With a Redis
|
|
31
|
+
* client supplied, attempts the cache key first and falls through to
|
|
32
|
+
* Postgres on miss (writing the result back to Redis). This is the
|
|
33
|
+
* "one server queries PG, the rest read from Redis" pattern for cold
|
|
34
|
+
* starts in a multi-instance deploy. Without Redis, always queries PG.
|
|
35
|
+
*
|
|
36
|
+
* Pass `tables` to limit the load to a specific list; otherwise every
|
|
37
|
+
* user table in the connected database is loaded.
|
|
38
|
+
*
|
|
39
|
+
* The Redis cache key should encode whatever invalidates types in your
|
|
40
|
+
* environment (typically a schema version / deploy version). The
|
|
41
|
+
* subscriber in supalive-server.ts is expected to publish a reload
|
|
42
|
+
* signal after migrations and invoke {@link invalidateColumnTypes}.
|
|
43
|
+
*/
|
|
44
|
+
bootstrapColumnTypes(opts: {
|
|
45
|
+
redis?: CacheLayer;
|
|
46
|
+
cacheKey: string;
|
|
47
|
+
tables?: string[];
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Invalidate cached types (and dependent SQL) for `table`, or the entire
|
|
51
|
+
* cache if `table` is omitted. The Redis-pubsub reload signal in
|
|
52
|
+
* supalive-server.ts is expected to call this on schema changes.
|
|
53
|
+
*/
|
|
54
|
+
private invalidateColumnTypes;
|
|
55
|
+
/** Enumerate the user-defined tables in the connected database — used
|
|
56
|
+
* by bootstrapColumnTypes() when no explicit list is provided. */
|
|
57
|
+
private listUserTables;
|
|
58
|
+
getCoreVersion(): Promise<bigint>;
|
|
59
|
+
coreMigrationStatements(toVersion: bigint): string[];
|
|
60
|
+
withCoreInitLock(fn: (ctx: CoreInitLockCtx) => Promise<void>): Promise<void>;
|
|
61
|
+
query<T = any>(sql: string, params?: unknown[]): Promise<DbQueryResult<T>>;
|
|
62
|
+
getClient(): Promise<PooledClient>;
|
|
63
|
+
getTransactionClient(): Promise<TxDatabase>;
|
|
64
|
+
/** Internal: replace this instance's caches with shared references from
|
|
65
|
+
* a parent. Used by PgDatabase.getTransactionClient. */
|
|
66
|
+
adoptCaches(columnTypes: Map<string, Map<string, string>>, sqlCache: Map<string, string>): void;
|
|
67
|
+
getLatestSnapshotTimestamp(): Promise<bigint>;
|
|
68
|
+
updateLatestSnapshotTimestamp(commitTs: bigint): Promise<void>;
|
|
69
|
+
getMinSnapshotTimestamp(): Promise<bigint>;
|
|
70
|
+
updateMinSnapshotTimestamp(ts: bigint): Promise<void>;
|
|
71
|
+
getNextTimestamp(): Promise<bigint>;
|
|
72
|
+
attachPrevDataToWriteSet(writeSet: WriteEntry[], readSet: ReadEntry[]): Promise<void>;
|
|
73
|
+
prepareCommitWrites(readSet: ReadEntry[], writeSet: WriteEntry[], commitTs: LazyCommitTsParam): Promise<PreparedQueries>;
|
|
74
|
+
private pgPrepareBatchWrites;
|
|
75
|
+
private pgPrepareInsertSingle;
|
|
76
|
+
private pgPrepareInsertBatch;
|
|
77
|
+
private pgPrepareUpdateSingle;
|
|
78
|
+
private pgPrepareUpdateBatch;
|
|
79
|
+
private pgPrepareDeleteSingle;
|
|
80
|
+
private pgPrepareDeleteBatch;
|
|
81
|
+
private buildOrGetInsertSql;
|
|
82
|
+
private buildOrGetUpdateSql;
|
|
83
|
+
private buildOrGetDeleteSql;
|
|
84
|
+
private pgPrepareCommitLogs;
|
|
85
|
+
/**
|
|
86
|
+
* Point-only OCC check. For each genuine point read, compare the row's
|
|
87
|
+
* current commit_ts against the value captured at read time. A `null`
|
|
88
|
+
* expectedCommitTs means "row didn't exist when we read"; any row found
|
|
89
|
+
* now is a conflict. Reads whose row is being written in this same txn
|
|
90
|
+
* are skipped — the CAS UPDATE/DELETE already validated those.
|
|
91
|
+
*/
|
|
92
|
+
private pgPrepareValidatePointReadsByCommitTs;
|
|
93
|
+
commitWrites(beginTs: bigint, queries: PreparedQueries, commitTs: bigint): Promise<{
|
|
94
|
+
success: boolean;
|
|
95
|
+
}>;
|
|
96
|
+
private pgApplyWrites;
|
|
97
|
+
/**
|
|
98
|
+
* Point-only OCC check. For each genuine point read, compare the row's
|
|
99
|
+
* current commit_ts against the value captured at read time. A `null`
|
|
100
|
+
* expectedCommitTs means "row didn't exist when we read"; any row found
|
|
101
|
+
* now is a conflict. Reads whose row is being written in this same txn
|
|
102
|
+
* are skipped — the CAS UPDATE/DELETE already validated those.
|
|
103
|
+
*/
|
|
104
|
+
private validatePointReadsByCommitTs;
|
|
105
|
+
pruneCommitLogsBefore(ts: bigint): Promise<bigint>;
|
|
106
|
+
getMinCommitLogTs(): Promise<bigint | null>;
|
|
107
|
+
getCommitLogsBetweenTs(beginTs: bigint, endTs: bigint, tableIds: Uint8Array[]): Promise<CommitLogEntry[]>;
|
|
108
|
+
getCommitLogsSinceTs(sinceTs: bigint, excludeTs: bigint, tableIds: Uint8Array[]): Promise<CommitLogEntry[]>;
|
|
109
|
+
close(): Promise<void>;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { PgDatabase as t };
|
|
113
|
+
//# sourceMappingURL=postgres-CKBWXyY8.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"postgres-CKBWXyY8.d.ts","names":[],"sources":["../src/db/postgres.ts"],"mappings":";;;;;cAiBa,UAAA,YAAsB,QAAA;cAE/B,MAAA,EAAQ,MAAA,EACR,MAAA,EAAQ,UAAA,EACR,IAAA,GAAM,IAAA;EAAA,QAWA,MAAA;EACD,MAAA,EAAQ,UAAA;EAAA,QACP,IAAA;EAAA,SACC,UAAA,EAAY,UAAA;EAAA,SACZ,MAAA,EAAQ,MAAA;EAMV,WAAA,EAAa,GAAA,SAAY,GAAA;EAAA,UAGtB,QAAA,EAAU,GAAA;EAHY;;;;;;EAW1B,qBAAA,CAAsB,KAAA,WAAgB,OAAA,CAAQ,GAAA;EAoDhD;;;;;;EA5BJ,cAAA,CAAe,KAAA,WAAgB,GAAA;EAyJ4B;;;;;;;;;;;;;;;EAjIrD,oBAAA,CAAqB,IAAA;IACzB,KAAA,GAAQ,UAAA;IACR,QAAA;IACA,MAAA;EAAA,IACE,OAAA;EAyRD;;;;;EAAA,QA/OK,qBAAA;EA23BsF;;EAAA,QA72BhF,cAAA;EAUR,cAAA,IAAkB,OAAA;EAWxB,uBAAA,CAAwB,SAAA;EAUlB,gBAAA,CAAiB,EAAA,GAAK,GAAA,EAAK,eAAA,KAAoB,OAAA,SAAgB,OAAA;EAsC/D,KAAA,UAAe,GAAA,UAAa,MAAA,eAAyB,OAAA,CAAQ,aAAA,CAAc,CAAA;EAe3E,SAAA,IAAa,OAAA,CAAQ,YAAA;EAIrB,oBAAA,IAAwB,OAAA,CAAQ,UAAA;EAxOL;;EAoPjC,WAAA,CACE,WAAA,EAAa,GAAA,SAAY,GAAA,mBACzB,QAAA,EAAU,GAAA;EAMN,0BAAA,IAA8B,OAAA;EAK9B,6BAAA,CAA8B,QAAA,WAAmB,OAAA;EAOjD,uBAAA,IAA2B,OAAA;EAO3B,0BAAA,CAA2B,EAAA,WAAa,OAAA;EAOxC,gBAAA,IAAoB,OAAA;EAuBpB,wBAAA,CAAyB,QAAA,EAAU,UAAA,IAAc,OAAA,EAAS,SAAA,KAAc,OAAA;EAgExE,mBAAA,CACJ,OAAA,EAAS,SAAA,IACT,QAAA,EAAU,UAAA,IACV,QAAA,EAAU,iBAAA,GACT,OAAA,CAAQ,eAAA;EAAA,QAoBG,oBAAA;EAAA,QA2BN,qBAAA;EAAA,QAkBM,oBAAA;EAAA,QA+DN,qBAAA;EAAA,QAwBM,oBAAA;EAAA,QAsEN,qBAAA;EAAA,QAgBM,oBAAA;EAAA,QAiDN,mBAAA;EAAA,QAqBA,mBAAA;EAAA,QA4BA,mBAAA;EAAA,QAcM,mBAAA;EA3qBR;;;;;;;EAAA,QAytBE,qCAAA;EAkDF,YAAA,CACJ,OAAA,UACA,OAAA,EAAS,eAAA,EACT,QAAA,WACC,OAAA;IAAU,OAAA;EAAA;EAAA,QAqCC,aAAA;EApwBa;;;;;;;EAAA,QA+2Bb,4BAAA;EA0CR,qBAAA,CAAsB,EAAA,WAAa,OAAA;EAQnC,iBAAA,IAAqB,OAAA;EAQrB,sBAAA,CAAuB,OAAA,UAAiB,KAAA,UAAe,QAAA,EAAU,UAAA,KAAe,OAAA,CAAQ,cAAA;EAmBxF,oBAAA,CAAqB,OAAA,UAAiB,SAAA,UAAmB,QAAA,EAAU,UAAA,KAAe,OAAA,CAAQ,cAAA;EAmB1F,KAAA,IAAS,OAAA;AAAA"}
|