@voltro/sql-mysql 0.1.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/CHANGELOG.md +52 -0
- package/LICENSE +57 -0
- package/README.md +26 -0
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +1062 -0
- package/dist/index.d.ts +373 -0
- package/dist/index.js +946 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
import { MysqlClient as e } from "@effect/sql-mysql2";
|
|
2
|
+
import { Config as t, Effect as n, Layer as r, ManagedRuntime as i, Option as a, Redacted as o, Schedule as s } from "effect";
|
|
3
|
+
import { EventEmitter as c } from "node:events";
|
|
4
|
+
import { createLogger as l } from "@voltro/logger";
|
|
5
|
+
import { SqlClient as u, TransactionConnection as d } from "@effect/sql/SqlClient";
|
|
6
|
+
import { attachEagerLoads as f, compileEagerJson as p, compilePredicate as m, compileRawFragment as h, compileSelect as g, decodeRowsFromSchema as _, encodeRowForSchema as v, hasEagerLoads as y, id as b, qualifyTable as x, requireTable as S, table as C, text as w, timestamp as T } from "@voltro/database";
|
|
7
|
+
//#region src/sqlLayer.ts
|
|
8
|
+
var E = (n) => e.layerConfig({
|
|
9
|
+
host: t.succeed(n.host),
|
|
10
|
+
port: t.succeed(n.port),
|
|
11
|
+
username: t.succeed(n.username),
|
|
12
|
+
password: t.succeed(o.make(n.password)),
|
|
13
|
+
database: t.succeed(n.database),
|
|
14
|
+
...n.maxConnections === void 0 ? {} : { maxConnections: t.succeed(n.maxConnections) }
|
|
15
|
+
}), D = (e) => {
|
|
16
|
+
if (e.url) {
|
|
17
|
+
let t = new URL(e.url);
|
|
18
|
+
return {
|
|
19
|
+
host: t.hostname || "localhost",
|
|
20
|
+
port: t.port ? Number(t.port) : 3306,
|
|
21
|
+
username: decodeURIComponent(t.username || "app"),
|
|
22
|
+
password: decodeURIComponent(t.password || "app"),
|
|
23
|
+
database: t.pathname.replace(/^\//, "") || "app",
|
|
24
|
+
...e.maxConnections === void 0 ? {} : { maxConnections: e.maxConnections }
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
host: e.host ?? "localhost",
|
|
29
|
+
port: e.port ?? 3306,
|
|
30
|
+
username: e.username ?? "app",
|
|
31
|
+
password: e.password ?? "app",
|
|
32
|
+
database: e.database ?? "app",
|
|
33
|
+
...e.maxConnections === void 0 ? {} : { maxConnections: e.maxConnections }
|
|
34
|
+
};
|
|
35
|
+
}, O = (e) => E(D(e)), k = (e, t) => e === null ? !1 : t === null ? !0 : e.filename === t.filename ? e.position > t.position : e.filename > t.filename, A = (e) => e.msSinceProgress < e.stallThresholdMs ? "healthy" : e.primary !== null && !k(e.primary, e.reader) ? "idle-caught-up" : "reconnect", j = (e) => (e instanceof Error ? e.message : String(e ?? "")).includes("schema changed between binlog event and metadata fetch"), M = /* @__PURE__ */ new Set([
|
|
36
|
+
"writerows",
|
|
37
|
+
"updaterows",
|
|
38
|
+
"deleterows"
|
|
39
|
+
]), N = /\b(alter|rename|drop|create)\s+(table|column)?/i, P = (e) => new Promise((t) => setTimeout(t, e)), F = async (e) => {
|
|
40
|
+
let t = l({ scope: `voltro:${e.variant}:cdc` }), n;
|
|
41
|
+
try {
|
|
42
|
+
n = (await import("@vlasky/zongji")).default;
|
|
43
|
+
} catch (t) {
|
|
44
|
+
throw Error(`changeStrategy='cdc' on ${e.variant} requires the '@vlasky/zongji' optional dependency. Install it (it ships as an optionalDependency of @voltro/sql-mysql) to enable binlog CDC.`, { cause: t });
|
|
45
|
+
}
|
|
46
|
+
let r = e.includeTables ? new Set(e.includeTables) : null, i = e.connection.database, a = e.startPosition?.filename ?? null, o = e.startPosition ?? null, s = (t, n) => _([n], t, e.variant)[0], c = null, u = !1, d = 0, f = Date.now(), p = !1, m = null, h = e.stallThresholdMs ?? 25e3, g = e.watchdogIntervalMs ?? 15e3, v = () => {
|
|
47
|
+
f = Date.now();
|
|
48
|
+
}, y = (n) => {
|
|
49
|
+
v();
|
|
50
|
+
let l = n.getEventName();
|
|
51
|
+
if (l === "rotate" && n.binlogName) {
|
|
52
|
+
a = n.binlogName;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (l === "query" && n.query && N.test(n.query)) {
|
|
56
|
+
c && (c.tableMap = {});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (n.nextPosition && a && (o = {
|
|
60
|
+
filename: a,
|
|
61
|
+
position: n.nextPosition
|
|
62
|
+
}, e.onPosition?.(o)), !M.has(l)) return;
|
|
63
|
+
let u = n.tableMap[n.tableId];
|
|
64
|
+
if (!u || u.parentSchema !== i) return;
|
|
65
|
+
let d = u.tableName;
|
|
66
|
+
if (!d.startsWith("_voltro_") && !(r && !r.has(d))) try {
|
|
67
|
+
if (l === "writerows") for (let t of n.rows) e.onChange({
|
|
68
|
+
table: d,
|
|
69
|
+
op: "insert",
|
|
70
|
+
old: null,
|
|
71
|
+
new: s(d, t)
|
|
72
|
+
});
|
|
73
|
+
else if (l === "deleterows") for (let t of n.rows) e.onChange({
|
|
74
|
+
table: d,
|
|
75
|
+
op: "delete",
|
|
76
|
+
old: s(d, t),
|
|
77
|
+
new: null
|
|
78
|
+
});
|
|
79
|
+
else for (let t of n.rows) e.onChange({
|
|
80
|
+
table: d,
|
|
81
|
+
op: "update",
|
|
82
|
+
old: s(d, t.before),
|
|
83
|
+
new: s(d, t.after)
|
|
84
|
+
});
|
|
85
|
+
} catch (n) {
|
|
86
|
+
t.warn("cdc: failed to map a row event", {
|
|
87
|
+
table: d,
|
|
88
|
+
name: l
|
|
89
|
+
}, n), e.onError?.(n);
|
|
90
|
+
}
|
|
91
|
+
}, b = (t) => {
|
|
92
|
+
let n = {
|
|
93
|
+
serverId: e.serverId,
|
|
94
|
+
includeEvents: [
|
|
95
|
+
"rotate",
|
|
96
|
+
"tablemap",
|
|
97
|
+
"writerows",
|
|
98
|
+
"updaterows",
|
|
99
|
+
"deleterows",
|
|
100
|
+
"xid",
|
|
101
|
+
"query"
|
|
102
|
+
]
|
|
103
|
+
};
|
|
104
|
+
return e.includeTables && e.includeTables.length > 0 && (n.includeSchema = { [i]: [...e.includeTables] }), t ? (n.filename = t.filename, n.position = t.position) : n.startAtEnd = !0, n;
|
|
105
|
+
}, x = (e) => {
|
|
106
|
+
let t = e;
|
|
107
|
+
return t?.errno === 1236 || String(t?.code ?? "").includes("ER_MASTER_FATAL_ERROR_READING_BINLOG");
|
|
108
|
+
}, S = (r) => new Promise((i, a) => {
|
|
109
|
+
let o = new n({
|
|
110
|
+
host: e.connection.host,
|
|
111
|
+
port: e.connection.port,
|
|
112
|
+
user: e.connection.username,
|
|
113
|
+
password: e.connection.password,
|
|
114
|
+
enableKeepAlive: !0,
|
|
115
|
+
keepAliveInitialDelay: e.keepAliveInitialDelayMs ?? 1e4
|
|
116
|
+
});
|
|
117
|
+
c = o;
|
|
118
|
+
let s = !1;
|
|
119
|
+
o.on("binlog", y), o.on("ready", () => {
|
|
120
|
+
v(), !s && (s = !0, d = 0, t.info("cdc: binlog reader attached", {
|
|
121
|
+
serverId: e.serverId,
|
|
122
|
+
from: r ?? "current-end"
|
|
123
|
+
}), i());
|
|
124
|
+
}), o.on("error", (e) => {
|
|
125
|
+
if (!s) {
|
|
126
|
+
s = !0, a(e instanceof Error ? e : Error(String(e)));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
C("binlog reader error", e);
|
|
130
|
+
}), o.start(b(r));
|
|
131
|
+
}), C = async (n, r) => {
|
|
132
|
+
if (!(u || p)) {
|
|
133
|
+
p = !0, t.warn(`cdc: reconnecting — ${n}`, {}, r instanceof Error ? r : void 0), e.onError?.(r);
|
|
134
|
+
try {
|
|
135
|
+
let n = r;
|
|
136
|
+
for (; !u;) {
|
|
137
|
+
try {
|
|
138
|
+
c?.stop();
|
|
139
|
+
} catch {}
|
|
140
|
+
if (d++, await P(Math.min(3e4, 500 * 2 ** Math.min(d, 6))), u) return;
|
|
141
|
+
let r = o, i = x(n), s = !i && j(n);
|
|
142
|
+
(i || s) && (t.warn(s ? "cdc: un-replayable backlog event (schema moved past it) — jumping to current end + self-heal" : "cdc: binlog gap (purged/failover) — jumping to current end + self-heal"), r = e.resolveStartPosition ? await e.resolveStartPosition().catch(() => null) : null, a = r?.filename ?? null, e.onResync?.());
|
|
143
|
+
try {
|
|
144
|
+
await S(r), d = 0, v();
|
|
145
|
+
return;
|
|
146
|
+
} catch (e) {
|
|
147
|
+
n = e;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} finally {
|
|
151
|
+
p = !1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
await S(e.startPosition ?? null);
|
|
157
|
+
} catch (n) {
|
|
158
|
+
if (x(n) && e.startPosition) {
|
|
159
|
+
t.warn("cdc: persisted offset purged — starting at current end + self-heal");
|
|
160
|
+
try {
|
|
161
|
+
c?.stop();
|
|
162
|
+
} catch {}
|
|
163
|
+
let n = e.resolveStartPosition ? await e.resolveStartPosition().catch(() => null) : null;
|
|
164
|
+
a = n?.filename ?? null, e.onResync?.(), await P(500), await S(n);
|
|
165
|
+
} else throw n;
|
|
166
|
+
}
|
|
167
|
+
let w = async () => {
|
|
168
|
+
if (u || p || Date.now() - f < h) return;
|
|
169
|
+
let n = null;
|
|
170
|
+
if (e.resolveStartPosition && (n = await e.resolveStartPosition().catch(() => null)), u || p) return;
|
|
171
|
+
let r = A({
|
|
172
|
+
msSinceProgress: Date.now() - f,
|
|
173
|
+
stallThresholdMs: h,
|
|
174
|
+
primary: n,
|
|
175
|
+
reader: o
|
|
176
|
+
});
|
|
177
|
+
if (r !== "reconnect") {
|
|
178
|
+
r === "idle-caught-up" && v();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
t.warn("cdc: watchdog detected a stalled reader — forcing reconnect", {
|
|
182
|
+
reader: o,
|
|
183
|
+
primary: n,
|
|
184
|
+
stalledForMs: Date.now() - f
|
|
185
|
+
}), C("watchdog: stream stalled (no events while primary advanced)", /* @__PURE__ */ Error("cdc watchdog stall"));
|
|
186
|
+
};
|
|
187
|
+
return m = setInterval(() => {
|
|
188
|
+
w();
|
|
189
|
+
}, g), m.unref && m.unref(), {
|
|
190
|
+
stop: async () => {
|
|
191
|
+
u = !0, m &&= (clearInterval(m), null);
|
|
192
|
+
try {
|
|
193
|
+
c?.stop();
|
|
194
|
+
} catch (e) {
|
|
195
|
+
t.warn("cdc: error stopping binlog reader", {}, e);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
currentPosition: () => o
|
|
199
|
+
};
|
|
200
|
+
}, I = "_voltro_cdc_offsets", L = C(I, {
|
|
201
|
+
id: b({ prefix: "cdcoff" }),
|
|
202
|
+
replicaId: w(),
|
|
203
|
+
streamName: w(),
|
|
204
|
+
binlogFile: w(),
|
|
205
|
+
binlogPosition: w(),
|
|
206
|
+
updatedAt: T()
|
|
207
|
+
}).index(["replicaId"]), R = /* @__PURE__ */ new Set(["1213", "1205"]), z = (e) => {
|
|
208
|
+
let t = e;
|
|
209
|
+
for (let e = 0; e < 5 && typeof t == "object" && t; e++) {
|
|
210
|
+
let e = t.errno;
|
|
211
|
+
if (typeof e == "number") return String(e);
|
|
212
|
+
let n = t.code;
|
|
213
|
+
if (typeof n == "string") return n;
|
|
214
|
+
t = t.cause;
|
|
215
|
+
}
|
|
216
|
+
}, B = (e) => {
|
|
217
|
+
let t = z(e);
|
|
218
|
+
return t !== void 0 && R.has(t);
|
|
219
|
+
}, V = (e) => B(e) ? "retry" : "noRetry", H = (e) => {
|
|
220
|
+
if (e == null) return "null";
|
|
221
|
+
let t = typeof e;
|
|
222
|
+
if (t === "bigint") return `${e}n`;
|
|
223
|
+
if (t !== "object") return JSON.stringify(e);
|
|
224
|
+
if (e instanceof Date) return `"${e.toISOString()}"`;
|
|
225
|
+
if (Array.isArray(e)) return `[${e.map(H).join(",")}]`;
|
|
226
|
+
let n = e;
|
|
227
|
+
return `{${Object.keys(n).sort().map((e) => `${JSON.stringify(e)}:${H(n[e])}`).join(",")}}`;
|
|
228
|
+
}, U = (e) => {
|
|
229
|
+
let t = H(e), n = 2166136261;
|
|
230
|
+
for (let e = 0; e < t.length; e++) n ^= t.charCodeAt(e), n = Math.imul(n, 16777619);
|
|
231
|
+
return (n >>> 0).toString(36);
|
|
232
|
+
}, W = (e, t) => {
|
|
233
|
+
let n = setTimeout(e, t);
|
|
234
|
+
typeof n.unref == "function" && n.unref();
|
|
235
|
+
}, G = class {
|
|
236
|
+
variant;
|
|
237
|
+
ttlMs;
|
|
238
|
+
schedule;
|
|
239
|
+
seen = /* @__PURE__ */ new Map();
|
|
240
|
+
constructor(e, t = 6e4, n = W) {
|
|
241
|
+
this.variant = e, this.ttlMs = t, this.schedule = n;
|
|
242
|
+
}
|
|
243
|
+
key(e) {
|
|
244
|
+
let t = e.op === "delete" ? e.old : e.new;
|
|
245
|
+
if (t == null) return null;
|
|
246
|
+
let n = t.id;
|
|
247
|
+
if (n == null) return null;
|
|
248
|
+
let r = t;
|
|
249
|
+
try {
|
|
250
|
+
r = _([t], e.table, this.variant)[0] ?? t;
|
|
251
|
+
} catch {
|
|
252
|
+
r = t;
|
|
253
|
+
}
|
|
254
|
+
return `${e.table} ${e.op} ${String(n)} ${U(r)}`;
|
|
255
|
+
}
|
|
256
|
+
admit(e) {
|
|
257
|
+
let t = this.key(e);
|
|
258
|
+
if (t === null) return !0;
|
|
259
|
+
let n = this.seen.get(t) ?? 0;
|
|
260
|
+
return n > 0 ? (n <= 1 ? this.seen.delete(t) : this.seen.set(t, n - 1), !1) : (this.seen.set(t, 1), this.schedule(() => this.expireOne(t), this.ttlMs), !0);
|
|
261
|
+
}
|
|
262
|
+
expireOne(e) {
|
|
263
|
+
let t = this.seen.get(e);
|
|
264
|
+
t !== void 0 && (t <= 1 ? this.seen.delete(e) : this.seen.set(e, t - 1));
|
|
265
|
+
}
|
|
266
|
+
get pending() {
|
|
267
|
+
return this.seen.size;
|
|
268
|
+
}
|
|
269
|
+
}, K = async (e) => {
|
|
270
|
+
let t = e.variant ?? "mysql", n = l({ scope: `voltro:${t}` }), a = e.changeStrategy ?? "inline", o = a;
|
|
271
|
+
a === "cdc" && !e.cdcConfig && (n.warn("changeStrategy='cdc' requires cdcConfig (serverId + connection); falling back to 'inline'."), o = "inline");
|
|
272
|
+
let s = e.tracerLayer ? r.mergeAll(e.sqlLayer, e.tracerLayer) : e.sqlLayer, c = i.make(s), d = new q(await c.runPromise(u), c, t, o);
|
|
273
|
+
return o === "cdc" && e.cdcConfig && await d.startCdcConsumer(e.cdcConfig), d;
|
|
274
|
+
}, q = class e {
|
|
275
|
+
sql;
|
|
276
|
+
runtime;
|
|
277
|
+
variant;
|
|
278
|
+
changeStrategy;
|
|
279
|
+
namespace;
|
|
280
|
+
emitter;
|
|
281
|
+
inflightTxns = 0;
|
|
282
|
+
log;
|
|
283
|
+
cdcHandle = null;
|
|
284
|
+
cdcCheckpointTimer = null;
|
|
285
|
+
cdcPendingPosition = null;
|
|
286
|
+
cdcReplicaId = "";
|
|
287
|
+
cdcStreamName = "default";
|
|
288
|
+
cdcGate;
|
|
289
|
+
constructor(e, t, n, r = "inline", i = null, a, o) {
|
|
290
|
+
this.sql = e, this.runtime = t, this.variant = n, this.changeStrategy = r, this.namespace = i, this.log = l({ scope: `voltro:${n}` }), this.emitter = a ?? new c(), this.cdcGate = o ?? new G(n);
|
|
291
|
+
}
|
|
292
|
+
withNamespace(t) {
|
|
293
|
+
return t === this.namespace ? this : new e(this.sql, this.runtime, this.variant, this.changeStrategy, t, this.emitter, this.cdcGate);
|
|
294
|
+
}
|
|
295
|
+
nsT(e) {
|
|
296
|
+
return x(this.namespace, e);
|
|
297
|
+
}
|
|
298
|
+
get dialectId() {
|
|
299
|
+
return this.variant;
|
|
300
|
+
}
|
|
301
|
+
get __mysqlReplicationFriend() {
|
|
302
|
+
return {
|
|
303
|
+
runEffect: (e) => this.runtime.runPromise(e),
|
|
304
|
+
variant: this.variant
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async executeQuery(e, t) {
|
|
308
|
+
let r = g(e, this.sql, this.namespace), i = t ? n.provideService(r, d, t) : r;
|
|
309
|
+
return _(await this.runtime.runPromise(i), e.table, this.variant);
|
|
310
|
+
}
|
|
311
|
+
get supportsInsertReturning() {
|
|
312
|
+
return this.variant === "mariadb";
|
|
313
|
+
}
|
|
314
|
+
get supportsDeleteReturning() {
|
|
315
|
+
return this.variant === "mariadb";
|
|
316
|
+
}
|
|
317
|
+
get supportsUpdateReturning() {
|
|
318
|
+
return !1;
|
|
319
|
+
}
|
|
320
|
+
async executeInsert(e, t, r, i) {
|
|
321
|
+
let a = this.sql;
|
|
322
|
+
if (this.supportsInsertReturning) {
|
|
323
|
+
let o = a`INSERT INTO ${a(this.nsT(e))} ${a.insert(v(t, e))} RETURNING *`, s = r ? n.provideService(o, d, r) : o, c = (await this.runtime.runPromise(s))[0];
|
|
324
|
+
if (!c) throw Error(`MysqlStore.insert: no row returned for table '${e}'`);
|
|
325
|
+
return this.routeEvent({
|
|
326
|
+
table: e,
|
|
327
|
+
op: "insert",
|
|
328
|
+
old: null,
|
|
329
|
+
new: c
|
|
330
|
+
}, i), c;
|
|
331
|
+
}
|
|
332
|
+
let o = v(t, e), s = t.id;
|
|
333
|
+
if (s === void 0) {
|
|
334
|
+
let t = await this.insertRecoverAutoId(e, o, r);
|
|
335
|
+
return this.routeEvent({
|
|
336
|
+
table: e,
|
|
337
|
+
op: "insert",
|
|
338
|
+
old: null,
|
|
339
|
+
new: t
|
|
340
|
+
}, i), t;
|
|
341
|
+
}
|
|
342
|
+
let c = a`INSERT INTO ${a(this.nsT(e))} ${a.insert(o)}`, l = r ? n.provideService(c, d, r) : c;
|
|
343
|
+
await this.runtime.runPromise(l);
|
|
344
|
+
let u = a`SELECT * FROM ${a(this.nsT(e))} WHERE ${a("id")} = ${s}`, f = r ? n.provideService(u, d, r) : u, p = (await this.runtime.runPromise(f))[0];
|
|
345
|
+
if (!p) throw Error(`MysqlStore.insert: row not found post-insert in '${e}'`);
|
|
346
|
+
return this.routeEvent({
|
|
347
|
+
table: e,
|
|
348
|
+
op: "insert",
|
|
349
|
+
old: null,
|
|
350
|
+
new: p
|
|
351
|
+
}, i), p;
|
|
352
|
+
}
|
|
353
|
+
async insertRecoverAutoId(e, t, r) {
|
|
354
|
+
let i = this.sql;
|
|
355
|
+
return this.runPinned(r, (r) => n.gen(this, function* () {
|
|
356
|
+
let a = i`INSERT INTO ${i(this.nsT(e))} ${i.insert(t)}`;
|
|
357
|
+
yield* n.provideService(a, d, r);
|
|
358
|
+
let o = (yield* n.provideService(i`SELECT LAST_INSERT_ID() AS ${i("lastId")}`, d, r))[0]?.lastId;
|
|
359
|
+
return o === void 0 || Number(o) === 0 ? yield* n.fail(/* @__PURE__ */ Error(`MysqlStore.insert: LAST_INSERT_ID() returned no id for '${e}' — is the primary key AUTO_INCREMENT?`)) : (yield* n.provideService(i`SELECT * FROM ${i(this.nsT(e))} WHERE ${i("id")} = ${o}`, d, r))[0] || (yield* n.fail(/* @__PURE__ */ Error(`MysqlStore.insert: row not found post-insert in '${e}'`)));
|
|
360
|
+
}), "insert");
|
|
361
|
+
}
|
|
362
|
+
async runPinned(e, t, r) {
|
|
363
|
+
if (e) return this.runtime.runPromise(t(e));
|
|
364
|
+
this.inflightTxns++;
|
|
365
|
+
try {
|
|
366
|
+
let e = n.suspend(() => this.sql.withTransaction(n.flatMap(n.serviceOption(d), (e) => a.isNone(e) ? n.fail(/* @__PURE__ */ Error("MysqlStore.insert: TransactionConnection missing.")) : t(e.value)))), i = s.exponential("10 millis").pipe(s.compose(s.recurs(3)), s.whileInput(B)), o = e.pipe(n.retry(i), n.withSpan("store.insert", { attributes: {
|
|
367
|
+
"db.system": this.variant,
|
|
368
|
+
"db.operation": r
|
|
369
|
+
} }));
|
|
370
|
+
return await this.runtime.runPromise(o);
|
|
371
|
+
} finally {
|
|
372
|
+
this.inflightTxns--;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
async executeInsertMany(e, t, r, i) {
|
|
376
|
+
if (t.length === 0) return [];
|
|
377
|
+
let a = this.sql, o = t.map((t) => v(t, e));
|
|
378
|
+
if (this.supportsInsertReturning) {
|
|
379
|
+
let t = a`INSERT INTO ${a(this.nsT(e))} ${a.insert(o)} RETURNING *`, s = r ? n.provideService(t, d, r) : t, c = await this.runtime.runPromise(s);
|
|
380
|
+
for (let t of c) this.routeEvent({
|
|
381
|
+
table: e,
|
|
382
|
+
op: "insert",
|
|
383
|
+
old: null,
|
|
384
|
+
new: t
|
|
385
|
+
}, i);
|
|
386
|
+
return c;
|
|
387
|
+
}
|
|
388
|
+
let s = t.map((e) => e.id);
|
|
389
|
+
if (s.every((e) => e === void 0)) {
|
|
390
|
+
let t = await this.insertManyRecoverAutoIds(e, o, r);
|
|
391
|
+
for (let n of t) this.routeEvent({
|
|
392
|
+
table: e,
|
|
393
|
+
op: "insert",
|
|
394
|
+
old: null,
|
|
395
|
+
new: n
|
|
396
|
+
}, i);
|
|
397
|
+
return t;
|
|
398
|
+
}
|
|
399
|
+
if (s.some((e) => e === void 0)) throw Error("MysqlStore.insertMany: rows mix client-supplied and missing 'id's — supply an id for every row or none (AUTO_INCREMENT recovery).");
|
|
400
|
+
let c = a`INSERT INTO ${a(this.nsT(e))} ${a.insert(o)}`, l = r ? n.provideService(c, d, r) : c;
|
|
401
|
+
await this.runtime.runPromise(l);
|
|
402
|
+
let u = a`SELECT * FROM ${a(this.nsT(e))} WHERE ${a("id")} IN ${a.in(s)}`, f = r ? n.provideService(u, d, r) : u, p = await this.runtime.runPromise(f), m = new Map(p.map((e) => [e.id, e])), h = s.map((e) => m.get(e)).filter((e) => e !== void 0);
|
|
403
|
+
for (let t of h) this.routeEvent({
|
|
404
|
+
table: e,
|
|
405
|
+
op: "insert",
|
|
406
|
+
old: null,
|
|
407
|
+
new: t
|
|
408
|
+
}, i);
|
|
409
|
+
return h;
|
|
410
|
+
}
|
|
411
|
+
async insertManyRecoverAutoIds(e, t, r) {
|
|
412
|
+
let i = this.sql, a = t.length;
|
|
413
|
+
return this.runPinned(r, (r) => n.gen(this, function* () {
|
|
414
|
+
let o = i`INSERT INTO ${i(this.nsT(e))} ${i.insert(t)}`;
|
|
415
|
+
yield* n.provideService(o, d, r);
|
|
416
|
+
let s = (yield* n.provideService(i`SELECT LAST_INSERT_ID() AS ${i("firstId")}`, d, r))[0]?.firstId;
|
|
417
|
+
if (s === void 0 || Number(s) === 0) return yield* n.fail(/* @__PURE__ */ Error(`MysqlStore.insertMany: LAST_INSERT_ID() returned no id for '${e}' — is the primary key AUTO_INCREMENT?`));
|
|
418
|
+
let c = Number(s);
|
|
419
|
+
return yield* n.provideService(i`SELECT * FROM ${i(this.nsT(e))} WHERE ${i("id")} BETWEEN ${c} AND ${c + a - 1} ORDER BY ${i("id")} ASC`, d, r);
|
|
420
|
+
}), "insert");
|
|
421
|
+
}
|
|
422
|
+
async executePatchJson(e, t, r, i, a, o) {
|
|
423
|
+
let s = this.sql, c = r.split("."), l = c[0], u = c.slice(1), f = JSON.stringify(i ?? null), p = u.length === 0 ? "$" : `$.${u.join(".")}`, m = s`UPDATE ${s(this.nsT(e))} SET ${s(l)} = JSON_SET(COALESCE(${s(l)}, '{}'), ${p}, CAST(${f} AS JSON)) WHERE ${s("id")} = ${t}`, h = a ? n.provideService(m, d, a) : m, g = await this.runtime.runPromise(h);
|
|
424
|
+
if (g && typeof g.affectedRows == "number" && g.affectedRows === 0) return null;
|
|
425
|
+
let _ = s`SELECT * FROM ${s(this.nsT(e))} WHERE ${s("id")} = ${t}`, v = a ? n.provideService(_, d, a) : _, y = (await this.runtime.runPromise(v))[0];
|
|
426
|
+
return y ? (this.routeEvent({
|
|
427
|
+
table: e,
|
|
428
|
+
op: "update",
|
|
429
|
+
old: null,
|
|
430
|
+
new: y
|
|
431
|
+
}, o), y) : null;
|
|
432
|
+
}
|
|
433
|
+
async executeUpdate(e, t, r, i, a) {
|
|
434
|
+
let o = this.sql;
|
|
435
|
+
if (this.supportsUpdateReturning) {
|
|
436
|
+
let s = o`UPDATE ${o(this.nsT(e))} SET ${o.update(v(r, e))} WHERE ${o("id")} = ${t} RETURNING *`, c = i ? n.provideService(s, d, i) : s, l = (await this.runtime.runPromise(c))[0];
|
|
437
|
+
return l ? (this.routeEvent({
|
|
438
|
+
table: e,
|
|
439
|
+
op: "update",
|
|
440
|
+
old: null,
|
|
441
|
+
new: l
|
|
442
|
+
}, a), l) : null;
|
|
443
|
+
}
|
|
444
|
+
let s = o`UPDATE ${o(this.nsT(e))} SET ${o.update(v(r, e))} WHERE ${o("id")} = ${t}`, c = i ? n.provideService(s, d, i) : s, l = await this.runtime.runPromise(c);
|
|
445
|
+
if (l && typeof l.affectedRows == "number" && l.affectedRows === 0) return null;
|
|
446
|
+
let u = o`SELECT * FROM ${o(this.nsT(e))} WHERE ${o("id")} = ${t}`, f = i ? n.provideService(u, d, i) : u, p = (await this.runtime.runPromise(f))[0];
|
|
447
|
+
return p ? (this.routeEvent({
|
|
448
|
+
table: e,
|
|
449
|
+
op: "update",
|
|
450
|
+
old: null,
|
|
451
|
+
new: p
|
|
452
|
+
}, a), p) : null;
|
|
453
|
+
}
|
|
454
|
+
async executeDelete(e, t, r, i) {
|
|
455
|
+
let a = this.sql;
|
|
456
|
+
if (this.supportsDeleteReturning) {
|
|
457
|
+
let o = a`DELETE FROM ${a(this.nsT(e))} WHERE ${a("id")} = ${t} RETURNING *`, s = r ? n.provideService(o, d, r) : o, c = (await this.runtime.runPromise(s))[0];
|
|
458
|
+
return c ? (this.routeEvent({
|
|
459
|
+
table: e,
|
|
460
|
+
op: "delete",
|
|
461
|
+
old: c,
|
|
462
|
+
new: null
|
|
463
|
+
}, i), !0) : !1;
|
|
464
|
+
}
|
|
465
|
+
let o = a`SELECT * FROM ${a(this.nsT(e))} WHERE ${a("id")} = ${t}`, s = r ? n.provideService(o, d, r) : o, c = (await this.runtime.runPromise(s))[0];
|
|
466
|
+
if (!c) return !1;
|
|
467
|
+
let l = a`DELETE FROM ${a(this.nsT(e))} WHERE ${a("id")} = ${t}`, u = r ? n.provideService(l, d, r) : l;
|
|
468
|
+
return await this.runtime.runPromise(u), this.routeEvent({
|
|
469
|
+
table: e,
|
|
470
|
+
op: "delete",
|
|
471
|
+
old: c,
|
|
472
|
+
new: null
|
|
473
|
+
}, i), !0;
|
|
474
|
+
}
|
|
475
|
+
routeEvent(e, t) {
|
|
476
|
+
if (t !== null) {
|
|
477
|
+
t.push(e);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
this.emitChange(e);
|
|
481
|
+
}
|
|
482
|
+
async executeUpsert(e, t, n, r, i) {
|
|
483
|
+
if (this.variant === "mariadb" && typeof n.update != "function") return this.executeMariadbUpsert(e, t, {
|
|
484
|
+
conflictColumns: n.conflictColumns,
|
|
485
|
+
...n.update === void 0 ? {} : { update: n.update }
|
|
486
|
+
}, r, i);
|
|
487
|
+
let a = await this.findByConflict(e, t, n.conflictColumns, r);
|
|
488
|
+
if (a) {
|
|
489
|
+
let o;
|
|
490
|
+
if (typeof n.update == "function") o = n.update(a);
|
|
491
|
+
else if (Array.isArray(n.update)) {
|
|
492
|
+
let e = {};
|
|
493
|
+
for (let r of n.update) r in t && (e[r] = t[r]);
|
|
494
|
+
o = e;
|
|
495
|
+
} else {
|
|
496
|
+
let e = {};
|
|
497
|
+
for (let [r, i] of Object.entries(t)) r !== "id" && (n.conflictColumns.includes(r) || (e[r] = i));
|
|
498
|
+
o = e;
|
|
499
|
+
}
|
|
500
|
+
return await this.executeUpdate(e, a.id, o, r, i) ?? a;
|
|
501
|
+
}
|
|
502
|
+
return this.executeInsert(e, t, r, i);
|
|
503
|
+
}
|
|
504
|
+
async executeMariadbUpsert(e, t, r, i, a) {
|
|
505
|
+
let o = this.sql, s = v(t, e), c = Object.keys(s).filter((e) => s[e] !== void 0), l = r.update === void 0 ? c.filter((e) => e !== "id" && !r.conflictColumns.includes(e)) : r.update, u = l.length > 0 ? o.csv(l.map((e) => o`${o(e)} = VALUES(${o(e)})`)) : o`${o(r.conflictColumns[0])} = VALUES(${o(r.conflictColumns[0])})`, f = o`INSERT INTO ${o(this.nsT(e))} ${o.insert(s)} ON DUPLICATE KEY UPDATE ${u} RETURNING *`, p = i ? n.provideService(f, d, i) : f, m = (await this.runtime.runPromise(p))[0];
|
|
506
|
+
if (!m) throw Error(`MysqlStore.upsert: no row returned for table '${e}'`);
|
|
507
|
+
let h = t.id !== void 0 && t.id === m.id ? "insert" : "update";
|
|
508
|
+
return this.routeEvent({
|
|
509
|
+
table: e,
|
|
510
|
+
op: h,
|
|
511
|
+
old: null,
|
|
512
|
+
new: m
|
|
513
|
+
}, a), m;
|
|
514
|
+
}
|
|
515
|
+
async executeInsertIgnore(e, t, r, i, a) {
|
|
516
|
+
if (this.variant === "mariadb") {
|
|
517
|
+
let o = this.sql, s = o`INSERT IGNORE INTO ${o(this.nsT(e))} ${o.insert(v(t, e))} RETURNING *`, c = i ? n.provideService(s, d, i) : s, l = (await this.runtime.runPromise(c))[0];
|
|
518
|
+
if (l) return this.routeEvent({
|
|
519
|
+
table: e,
|
|
520
|
+
op: "insert",
|
|
521
|
+
old: null,
|
|
522
|
+
new: l
|
|
523
|
+
}, a), l;
|
|
524
|
+
let u = await this.findByConflict(e, t, r.conflictColumns, i);
|
|
525
|
+
if (!u) throw Error("MysqlStore.insertIgnore: row conflicted but lookup found nothing");
|
|
526
|
+
return u;
|
|
527
|
+
}
|
|
528
|
+
return await this.findByConflict(e, t, r.conflictColumns, i) || this.executeInsert(e, t, i, a);
|
|
529
|
+
}
|
|
530
|
+
async findByConflict(e, t, r, i) {
|
|
531
|
+
if (r.length === 0) return;
|
|
532
|
+
let a = this.sql, o = r.map((e) => a`${a(e)} = ${t[e]}`), s = a`SELECT * FROM ${a(this.nsT(e))} WHERE ${a.and(o)} LIMIT 1`, c = i ? n.provideService(s, d, i) : s;
|
|
533
|
+
return (await this.runtime.runPromise(c))[0];
|
|
534
|
+
}
|
|
535
|
+
query(e) {
|
|
536
|
+
return this.runWithEager(e, null);
|
|
537
|
+
}
|
|
538
|
+
raw(e, t) {
|
|
539
|
+
let n = h(e, this.sql);
|
|
540
|
+
return this.runtime.runPromise(n);
|
|
541
|
+
}
|
|
542
|
+
async runWithEager(e, t) {
|
|
543
|
+
if (!y(e)) return this.executeQuery(e, t);
|
|
544
|
+
let r = this.variant === "mariadb" ? "mariadb" : "mysql", i = this.namespace === null ? p(e, this.sql, r) : null;
|
|
545
|
+
if (i !== null) try {
|
|
546
|
+
let e = t ? n.provideService(i.fragment, d, t) : i.fragment, r = await this.runtime.runPromise(e);
|
|
547
|
+
return i.decode(r);
|
|
548
|
+
} catch (e) {
|
|
549
|
+
this.log.warn(`${this.variant} JSON-agg eager-load failed; falling back to walker`, { err: e });
|
|
550
|
+
}
|
|
551
|
+
return f(await this.executeQuery(e, t), e.eager, e.sourceTable ?? S(e.table), (e) => this.executeQuery(e, t));
|
|
552
|
+
}
|
|
553
|
+
getInternalRunWithEager() {
|
|
554
|
+
return this.runWithEager.bind(this);
|
|
555
|
+
}
|
|
556
|
+
insert(e, t) {
|
|
557
|
+
return this.executeInsert(e, t, null, null);
|
|
558
|
+
}
|
|
559
|
+
insertMany(e, t) {
|
|
560
|
+
return this.executeInsertMany(e, t, null, null);
|
|
561
|
+
}
|
|
562
|
+
patchJson(e, t, n, r) {
|
|
563
|
+
return this.executePatchJson(e, t, n, r, null, null);
|
|
564
|
+
}
|
|
565
|
+
upsert(e, t, n) {
|
|
566
|
+
return this.executeUpsert(e, t, n, null, null);
|
|
567
|
+
}
|
|
568
|
+
insertIgnore(e, t, n) {
|
|
569
|
+
return this.executeInsertIgnore(e, t, n, null, null);
|
|
570
|
+
}
|
|
571
|
+
update(e, t, n) {
|
|
572
|
+
return this.executeUpdate(e, t, n, null, null);
|
|
573
|
+
}
|
|
574
|
+
delete(e, t) {
|
|
575
|
+
return this.executeDelete(e, t, null, null);
|
|
576
|
+
}
|
|
577
|
+
async updateMany(e, t, r) {
|
|
578
|
+
if (this.supportsUpdateReturning) {
|
|
579
|
+
let n = this.sql, i = m(r.where, n, this.namespace), a = n`UPDATE ${n(this.nsT(e))} SET ${n.update(v(t, e))} WHERE ${i} RETURNING *`, o = await this.runtime.runPromise(a);
|
|
580
|
+
for (let t of o) this.routeEvent({
|
|
581
|
+
table: e,
|
|
582
|
+
op: "update",
|
|
583
|
+
old: null,
|
|
584
|
+
new: t
|
|
585
|
+
}, null);
|
|
586
|
+
return o.length;
|
|
587
|
+
}
|
|
588
|
+
let i = this.sql, o = m(r.where, i, this.namespace);
|
|
589
|
+
this.inflightTxns++;
|
|
590
|
+
try {
|
|
591
|
+
let r = n.suspend(() => this.sql.withTransaction(n.flatMap(n.serviceOption(d), (r) => {
|
|
592
|
+
if (a.isNone(r)) return n.fail(/* @__PURE__ */ Error("MysqlStore.updateMany: TransactionConnection missing."));
|
|
593
|
+
let s = r.value, c = i`SELECT id FROM ${i(this.nsT(e))} WHERE ${o} FOR UPDATE`, l = i`UPDATE ${i(this.nsT(e))} SET ${i.update(v(t, e))} WHERE ${o}`;
|
|
594
|
+
return n.flatMap(n.provideService(c, d, s), (t) => {
|
|
595
|
+
if (t.length === 0) return n.succeed([]);
|
|
596
|
+
let r = t.map((e) => e.id), a = i`SELECT * FROM ${i(this.nsT(e))} WHERE ${i("id")} IN ${i.in(r)}`;
|
|
597
|
+
return n.flatMap(n.provideService(l, d, s), () => n.provideService(a, d, s));
|
|
598
|
+
});
|
|
599
|
+
}))), c = s.exponential("10 millis").pipe(s.compose(s.recurs(3)), s.whileInput(B)), l = r.pipe(n.retry(c), n.withSpan("store.updateMany", { attributes: {
|
|
600
|
+
"db.system": this.variant,
|
|
601
|
+
"db.operation": "update"
|
|
602
|
+
} })), u = await this.runtime.runPromise(l);
|
|
603
|
+
if (u.length === 0) return 0;
|
|
604
|
+
let f = _(u, e, this.variant);
|
|
605
|
+
for (let t of f) this.routeEvent({
|
|
606
|
+
table: e,
|
|
607
|
+
op: "update",
|
|
608
|
+
old: null,
|
|
609
|
+
new: t
|
|
610
|
+
}, null);
|
|
611
|
+
return f.length;
|
|
612
|
+
} finally {
|
|
613
|
+
this.inflightTxns--;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async deleteMany(e, t) {
|
|
617
|
+
let r = this.sql, i = m(t.where, r, this.namespace);
|
|
618
|
+
if (this.supportsDeleteReturning) {
|
|
619
|
+
let t = r`DELETE FROM ${r(this.nsT(e))} WHERE ${i} RETURNING *`, n = _(await this.runtime.runPromise(t), e, this.variant);
|
|
620
|
+
for (let t of n) this.routeEvent({
|
|
621
|
+
table: e,
|
|
622
|
+
op: "delete",
|
|
623
|
+
old: t,
|
|
624
|
+
new: null
|
|
625
|
+
}, null);
|
|
626
|
+
return n.length;
|
|
627
|
+
}
|
|
628
|
+
this.inflightTxns++;
|
|
629
|
+
try {
|
|
630
|
+
let t = n.suspend(() => this.sql.withTransaction(n.flatMap(n.serviceOption(d), (t) => {
|
|
631
|
+
if (a.isNone(t)) return n.fail(/* @__PURE__ */ Error("MysqlStore.deleteMany: TransactionConnection missing."));
|
|
632
|
+
let o = t.value, s = r`SELECT * FROM ${r(this.nsT(e))} WHERE ${i} FOR UPDATE`, c = r`DELETE FROM ${r(this.nsT(e))} WHERE ${i}`;
|
|
633
|
+
return n.flatMap(n.provideService(s, d, o), (e) => e.length === 0 ? n.succeed(e) : n.as(n.provideService(c, d, o), e));
|
|
634
|
+
}))), o = s.exponential("10 millis").pipe(s.compose(s.recurs(3)), s.whileInput(B)), c = t.pipe(n.retry(o), n.withSpan("store.deleteMany", { attributes: {
|
|
635
|
+
"db.system": this.variant,
|
|
636
|
+
"db.operation": "delete"
|
|
637
|
+
} })), l = await this.runtime.runPromise(c);
|
|
638
|
+
if (l.length === 0) return 0;
|
|
639
|
+
let u = _(l, e, this.variant);
|
|
640
|
+
for (let t of u) this.routeEvent({
|
|
641
|
+
table: e,
|
|
642
|
+
op: "delete",
|
|
643
|
+
old: t,
|
|
644
|
+
new: null
|
|
645
|
+
}, null);
|
|
646
|
+
return u.length;
|
|
647
|
+
} finally {
|
|
648
|
+
this.inflightTxns--;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
emitChange(e) {
|
|
652
|
+
this.changeStrategy === "cdc" && !this.cdcGate.admit(e) || this.emitter.emit("change", e);
|
|
653
|
+
}
|
|
654
|
+
async startCdcConsumer(e) {
|
|
655
|
+
if (this.cdcHandle) return;
|
|
656
|
+
await this.assertBinlogConfig(), this.cdcReplicaId = e.replicaId;
|
|
657
|
+
let t = await this.readCdcOffset(e.replicaId) ?? await this.resolveBinlogEnd();
|
|
658
|
+
this.cdcHandle = await F({
|
|
659
|
+
connection: e.connection,
|
|
660
|
+
serverId: e.serverId,
|
|
661
|
+
variant: this.variant,
|
|
662
|
+
...e.includeTables ? { includeTables: e.includeTables } : {},
|
|
663
|
+
...e.watchdogIntervalMs === void 0 ? {} : { watchdogIntervalMs: e.watchdogIntervalMs },
|
|
664
|
+
...e.stallThresholdMs === void 0 ? {} : { stallThresholdMs: e.stallThresholdMs },
|
|
665
|
+
...e.keepAliveInitialDelayMs === void 0 ? {} : { keepAliveInitialDelayMs: e.keepAliveInitialDelayMs },
|
|
666
|
+
startPosition: t,
|
|
667
|
+
onChange: (e) => this.injectExternalChange(e),
|
|
668
|
+
onPosition: (e) => {
|
|
669
|
+
this.cdcPendingPosition = e;
|
|
670
|
+
},
|
|
671
|
+
onError: (e) => this.log.warn("cdc: consumer error", {}, e),
|
|
672
|
+
resolveStartPosition: () => this.resolveBinlogEnd(),
|
|
673
|
+
onResync: () => {
|
|
674
|
+
this.log.warn("cdc: resynced to current binlog end after a gap — subscriptions self-heal on the next change (purge/failover recovery)");
|
|
675
|
+
}
|
|
676
|
+
}), this.cdcCheckpointTimer = setInterval(() => {
|
|
677
|
+
this.flushCdcOffset();
|
|
678
|
+
}, 1e3), this.cdcCheckpointTimer.unref && this.cdcCheckpointTimer.unref();
|
|
679
|
+
}
|
|
680
|
+
async assertBinlogConfig() {
|
|
681
|
+
let e = this.sql, t = (await this.runtime.runPromise(e`
|
|
682
|
+
SELECT @@binlog_format AS format, @@binlog_row_image AS rowImage, @@log_bin AS logBin`))[0];
|
|
683
|
+
if (!t || Number(t.logBin) !== 1) throw Error(`${this.variant} binlog CDC requires log_bin=ON. Start ${this.variant} with --log-bin (and --binlog-format=ROW --binlog-row-image=FULL), or set CDC=0 to use inline emit. (MySQL 8.0+ enables log_bin by default; MariaDB does not.)`);
|
|
684
|
+
if (t.format !== "ROW") throw Error(`${this.variant} binlog CDC requires binlog_format=ROW (got '${t.format}'). Set --binlog-format=ROW, or CDC=0 for inline emit.`);
|
|
685
|
+
t.rowImage !== "FULL" && this.log.warn(`cdc: binlog_row_image='${t.rowImage}' (expected FULL) — UPDATE/DELETE before-images may be incomplete. Set --binlog-row-image=FULL.`);
|
|
686
|
+
}
|
|
687
|
+
async resolveBinlogEnd() {
|
|
688
|
+
let e = this.sql, t = this.variant === "mysql" ? e`SHOW BINARY LOG STATUS`.unprepared : e`SHOW MASTER STATUS`.unprepared, n = this.variant === "mysql" ? e`SHOW MASTER STATUS`.unprepared : e`SHOW BINARY LOG STATUS`.unprepared, r = async (e) => {
|
|
689
|
+
let t = (await this.runtime.runPromise(e))[0];
|
|
690
|
+
return t?.File ? {
|
|
691
|
+
filename: t.File,
|
|
692
|
+
position: Number(t.Position)
|
|
693
|
+
} : null;
|
|
694
|
+
};
|
|
695
|
+
try {
|
|
696
|
+
return await r(t);
|
|
697
|
+
} catch {
|
|
698
|
+
try {
|
|
699
|
+
return await r(n);
|
|
700
|
+
} catch {
|
|
701
|
+
return this.log.debug("cdc: binlog-status query unavailable (needs REPLICATION CLIENT); using startAtEnd"), null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
async readCdcOffset(e) {
|
|
706
|
+
try {
|
|
707
|
+
let t = this.sql, n = (await this.runtime.runPromise(t`
|
|
708
|
+
SELECT ${t("binlogFile")}, ${t("binlogPosition")}
|
|
709
|
+
FROM ${t(I)}
|
|
710
|
+
WHERE ${t("id")} = ${e} LIMIT 1`))[0];
|
|
711
|
+
if (!n) return null;
|
|
712
|
+
let r = Number(n.binlogPosition);
|
|
713
|
+
return !n.binlogFile || !Number.isFinite(r) ? null : (this.log.info("cdc: resuming from persisted offset", {
|
|
714
|
+
replicaId: e,
|
|
715
|
+
file: n.binlogFile,
|
|
716
|
+
position: r
|
|
717
|
+
}), {
|
|
718
|
+
filename: n.binlogFile,
|
|
719
|
+
position: r
|
|
720
|
+
});
|
|
721
|
+
} catch (t) {
|
|
722
|
+
return this.log.warn("cdc: no readable offset; starting at binlog end", { replicaId: e }, t), null;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
async flushCdcOffset() {
|
|
726
|
+
let e = this.cdcPendingPosition;
|
|
727
|
+
if (e) {
|
|
728
|
+
this.cdcPendingPosition = null;
|
|
729
|
+
try {
|
|
730
|
+
let t = this.sql, n = this.cdcReplicaId;
|
|
731
|
+
await this.runtime.runPromise(t`INSERT INTO ${t(I)} ${t.insert({
|
|
732
|
+
id: n,
|
|
733
|
+
replicaId: n,
|
|
734
|
+
streamName: this.cdcStreamName,
|
|
735
|
+
binlogFile: e.filename,
|
|
736
|
+
binlogPosition: String(e.position),
|
|
737
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
738
|
+
})}
|
|
739
|
+
ON DUPLICATE KEY UPDATE
|
|
740
|
+
${t("binlogFile")} = ${e.filename},
|
|
741
|
+
${t("binlogPosition")} = ${String(e.position)},
|
|
742
|
+
${t("updatedAt")} = ${/* @__PURE__ */ new Date()}`);
|
|
743
|
+
} catch (t) {
|
|
744
|
+
this.cdcPendingPosition = e, this.log.warn("cdc: checkpoint write failed", {}, t);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
getInternalExecuteQuery() {
|
|
749
|
+
return this.executeQuery.bind(this);
|
|
750
|
+
}
|
|
751
|
+
getInternalExecuteInsert() {
|
|
752
|
+
return this.executeInsert.bind(this);
|
|
753
|
+
}
|
|
754
|
+
getInternalExecuteInsertMany() {
|
|
755
|
+
return this.executeInsertMany.bind(this);
|
|
756
|
+
}
|
|
757
|
+
getInternalExecutePatchJson() {
|
|
758
|
+
return this.executePatchJson.bind(this);
|
|
759
|
+
}
|
|
760
|
+
getInternalExecuteUpdate() {
|
|
761
|
+
return this.executeUpdate.bind(this);
|
|
762
|
+
}
|
|
763
|
+
getInternalExecuteDelete() {
|
|
764
|
+
return this.executeDelete.bind(this);
|
|
765
|
+
}
|
|
766
|
+
getInternalExecuteUpsert() {
|
|
767
|
+
return this.executeUpsert.bind(this);
|
|
768
|
+
}
|
|
769
|
+
getInternalExecuteInsertIgnore() {
|
|
770
|
+
return this.executeInsertIgnore.bind(this);
|
|
771
|
+
}
|
|
772
|
+
async transactional(e) {
|
|
773
|
+
this.inflightTxns++;
|
|
774
|
+
let t = 0, r = n.suspend(() => {
|
|
775
|
+
let r = ++t;
|
|
776
|
+
return this.sql.withTransaction(n.flatMap(n.serviceOption(d), (t) => {
|
|
777
|
+
if (a.isNone(t)) return n.fail(/* @__PURE__ */ Error("MysqlStore.transactional: TransactionConnection missing."));
|
|
778
|
+
let i = new J(this, t.value);
|
|
779
|
+
return n.tryPromise({
|
|
780
|
+
try: () => e(i).then((e) => ({
|
|
781
|
+
result: e,
|
|
782
|
+
view: i,
|
|
783
|
+
attempt: r
|
|
784
|
+
})),
|
|
785
|
+
catch: (e) => e
|
|
786
|
+
});
|
|
787
|
+
}));
|
|
788
|
+
}), i = s.exponential("10 millis").pipe(s.compose(s.recurs(3)), s.whileInput(B)), o = r.pipe(n.retry(i), n.withSpan("store.transactional", { attributes: {
|
|
789
|
+
"db.system": this.variant,
|
|
790
|
+
"db.operation": "transaction"
|
|
791
|
+
} }));
|
|
792
|
+
try {
|
|
793
|
+
let e = await this.runtime.runPromise(o);
|
|
794
|
+
return e.view.commitEvents(), e.result;
|
|
795
|
+
} finally {
|
|
796
|
+
this.inflightTxns--;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
onChange(e) {
|
|
800
|
+
return this.emitter.on("change", e), () => {
|
|
801
|
+
this.emitter.off("change", e);
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
get changeScope() {
|
|
805
|
+
return this.changeStrategy === "cdc" ? "fleet" : "local";
|
|
806
|
+
}
|
|
807
|
+
injectExternalChange(e) {
|
|
808
|
+
this.changeStrategy === "cdc" && !this.cdcGate.admit(e) || this.emitter.emit("change", {
|
|
809
|
+
...e,
|
|
810
|
+
origin: "injected"
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
run(e) {
|
|
814
|
+
return this.runtime.runPromise(e);
|
|
815
|
+
}
|
|
816
|
+
async close(e = 5e3) {
|
|
817
|
+
if (this.cdcCheckpointTimer &&= (clearInterval(this.cdcCheckpointTimer), null), this.cdcHandle) {
|
|
818
|
+
let e = this.cdcHandle.currentPosition();
|
|
819
|
+
e && (this.cdcPendingPosition = e), await this.flushCdcOffset(), await this.cdcHandle.stop(), this.cdcHandle = null;
|
|
820
|
+
}
|
|
821
|
+
if (this.inflightTxns > 0) {
|
|
822
|
+
let t = Date.now() + e;
|
|
823
|
+
for (; this.inflightTxns > 0 && Date.now() < t;) await new Promise((e) => setTimeout(e, 25));
|
|
824
|
+
this.inflightTxns > 0 && this.log.warn("close: grace period expired with in-flight transactions", {
|
|
825
|
+
gracePeriodMs: e,
|
|
826
|
+
inflight: this.inflightTxns
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
await this.runtime.dispose();
|
|
830
|
+
}
|
|
831
|
+
async ping() {
|
|
832
|
+
await this.runtime.runPromise(this.sql`SELECT 1`);
|
|
833
|
+
}
|
|
834
|
+
}, J = class {
|
|
835
|
+
parent;
|
|
836
|
+
txn;
|
|
837
|
+
events = [];
|
|
838
|
+
committed = !1;
|
|
839
|
+
constructor(e, t) {
|
|
840
|
+
this.parent = e, this.txn = t;
|
|
841
|
+
}
|
|
842
|
+
query(e) {
|
|
843
|
+
return this.parent.getInternalRunWithEager()(e, this.txn);
|
|
844
|
+
}
|
|
845
|
+
insert(e, t) {
|
|
846
|
+
return this.parent.getInternalExecuteInsert()(e, t, this.txn, this.events);
|
|
847
|
+
}
|
|
848
|
+
insertMany(e, t) {
|
|
849
|
+
return this.parent.getInternalExecuteInsertMany()(e, t, this.txn, this.events);
|
|
850
|
+
}
|
|
851
|
+
patchJson(e, t, n, r) {
|
|
852
|
+
return this.parent.getInternalExecutePatchJson()(e, t, n, r, this.txn, this.events);
|
|
853
|
+
}
|
|
854
|
+
update(e, t, n) {
|
|
855
|
+
return this.parent.getInternalExecuteUpdate()(e, t, n, this.txn, this.events);
|
|
856
|
+
}
|
|
857
|
+
delete(e, t) {
|
|
858
|
+
return this.parent.getInternalExecuteDelete()(e, t, this.txn, this.events);
|
|
859
|
+
}
|
|
860
|
+
async updateMany(e, t, n) {
|
|
861
|
+
let r = await this.query({
|
|
862
|
+
table: e,
|
|
863
|
+
predicate: n.where,
|
|
864
|
+
order: [],
|
|
865
|
+
take: void 0,
|
|
866
|
+
skip: void 0,
|
|
867
|
+
projection: ["id"]
|
|
868
|
+
}), i = 0;
|
|
869
|
+
for (let n of r) await this.update(e, n.id, t) && i++;
|
|
870
|
+
return i;
|
|
871
|
+
}
|
|
872
|
+
async deleteMany(e, t) {
|
|
873
|
+
let n = await this.query({
|
|
874
|
+
table: e,
|
|
875
|
+
predicate: t.where,
|
|
876
|
+
order: [],
|
|
877
|
+
take: void 0,
|
|
878
|
+
skip: void 0,
|
|
879
|
+
projection: ["id"]
|
|
880
|
+
}), r = 0;
|
|
881
|
+
for (let t of n) await this.delete(e, t.id) && r++;
|
|
882
|
+
return r;
|
|
883
|
+
}
|
|
884
|
+
upsert(e, t, n) {
|
|
885
|
+
return this.parent.getInternalExecuteUpsert()(e, t, n, this.txn, this.events);
|
|
886
|
+
}
|
|
887
|
+
insertIgnore(e, t, n) {
|
|
888
|
+
return this.parent.getInternalExecuteInsertIgnore()(e, t, n, this.txn, this.events);
|
|
889
|
+
}
|
|
890
|
+
transactional(e) {
|
|
891
|
+
return Promise.reject(/* @__PURE__ */ Error("MysqlStore.transactional: nested transactions are not supported."));
|
|
892
|
+
}
|
|
893
|
+
onChange(e) {
|
|
894
|
+
throw Error("MysqlTransactionalView.onChange: not supported inside a transaction.");
|
|
895
|
+
}
|
|
896
|
+
injectExternalChange(e) {
|
|
897
|
+
throw Error("MysqlTransactionalView.injectExternalChange: not supported inside a transaction.");
|
|
898
|
+
}
|
|
899
|
+
commitEvents() {
|
|
900
|
+
if (!this.committed) {
|
|
901
|
+
this.committed = !0;
|
|
902
|
+
for (let e of this.events) this.parent.emitChange(e);
|
|
903
|
+
this.events.length = 0;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}, Y = (e) => e.__mysqlReplicationFriend ?? null, X = () => ({
|
|
907
|
+
async capturePrimaryPosition(e) {
|
|
908
|
+
let t = Y(e);
|
|
909
|
+
if (t === null) throw Error("mysqlReplicationAdapter: primary is not a MysqlStore (missing __mysqlReplicationFriend).");
|
|
910
|
+
return t.runEffect(n.gen(function* () {
|
|
911
|
+
let e = yield* u, r = (t.variant === "mariadb" ? yield* e`SELECT @@global.gtid_current_pos AS gtid` : yield* e`SELECT @@global.gtid_executed AS gtid`)[0]?.gtid;
|
|
912
|
+
return typeof r == "string" ? r : yield* n.die("mysql/mariadb did not return a GTID set");
|
|
913
|
+
}));
|
|
914
|
+
},
|
|
915
|
+
async probeReplicaPosition(e) {
|
|
916
|
+
let t = Y(e);
|
|
917
|
+
if (t === null) throw Error("mysqlReplicationAdapter: replica is not a MysqlStore.");
|
|
918
|
+
return t.runEffect(n.gen(function* () {
|
|
919
|
+
let e = yield* u, r = (t.variant === "mariadb" ? yield* e`SELECT @@global.gtid_current_pos AS gtid` : yield* e`SELECT @@global.gtid_executed AS gtid`)[0]?.gtid;
|
|
920
|
+
return typeof r == "string" ? r : yield* n.die("mysql/mariadb replica did not return a GTID set");
|
|
921
|
+
}));
|
|
922
|
+
},
|
|
923
|
+
compare(e, t) {
|
|
924
|
+
return "behind";
|
|
925
|
+
}
|
|
926
|
+
}), Z = {
|
|
927
|
+
id: "mysql",
|
|
928
|
+
makeSqlLayer: (e) => O(e),
|
|
929
|
+
makeStore: (e) => K({
|
|
930
|
+
...e,
|
|
931
|
+
variant: "mysql"
|
|
932
|
+
}),
|
|
933
|
+
compileContains: (e, t, n) => n`${e} LIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
|
|
934
|
+
retryFilter: V
|
|
935
|
+
}, Q = {
|
|
936
|
+
id: "mariadb",
|
|
937
|
+
makeSqlLayer: (e) => O(e),
|
|
938
|
+
makeStore: (e) => K({
|
|
939
|
+
...e,
|
|
940
|
+
variant: "mariadb"
|
|
941
|
+
}),
|
|
942
|
+
compileContains: (e, t, n) => n`${e} LIKE ${`%${t.replace(/[\\%_]/g, (e) => `\\${e}`)}%`} ESCAPE '\\'`,
|
|
943
|
+
retryFilter: V
|
|
944
|
+
};
|
|
945
|
+
//#endregion
|
|
946
|
+
export { I as CDC_OFFSETS_TABLE, e as MysqlClient, L as _voltroCdcOffsetsTable, D as connectionFromConfig, E as makeMysqlSqlLayer, O as makeMysqlSqlLayerFromConfig, K as makeMysqlStore, Q as mariadbDialect, Z as mysqlDialect, X as mysqlReplicationAdapter, V as mysqlRetryFilter, F as startBinlogCdc };
|