@crvouga/mockingbird-service-postgres 1.5.1 → 1.7.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/AGENTS.md +5 -6
- package/CHANGELOG.md +27 -0
- package/COMPATIBILITY.md +14 -5
- package/README.md +30 -13
- package/dist/admin.js +191 -54
- package/dist/admin.js.map +4 -4
- package/dist/ast/nodes.d.ts +1 -0
- package/dist/index.js +118 -32
- package/dist/index.js.map +3 -3
- package/dist/runtime/database-context.d.ts +4 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +25808 -0
- package/dist/server.js.map +7 -0
- package/dist/sql/deparse.d.ts +1 -0
- package/dist/unstable.js +96 -31
- package/dist/unstable.js.map +3 -3
- package/dist/wire/cli.js +1766 -173
- package/dist/wire/cli.js.map +4 -4
- package/dist/wire/cluster.d.ts +23 -8
- package/dist/wire/connection.d.ts +25 -1
- package/dist/wire/fleet.d.ts +80 -0
- package/dist/wire/index.d.ts +11 -1
- package/dist/wire/index.js +608 -78
- package/dist/wire/index.js.map +3 -3
- package/dist/wire/probe.d.ts +2 -0
- package/dist/wire/protocol.d.ts +4 -0
- package/package.json +13 -2
package/dist/wire/cli.js
CHANGED
|
@@ -1,7 +1,70 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
import {
|
|
3
|
+
// ../../adapters/node/dist/cli.js
|
|
4
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { resolve } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { parseArgs } from "node:util";
|
|
9
|
+
|
|
10
|
+
// ../../adapters/node/dist/fleet.js
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { chmod, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
13
|
+
|
|
14
|
+
// ../core/dist/clock.js
|
|
15
|
+
var createClock = (source = Date.now) => {
|
|
16
|
+
let offsetMs = 0;
|
|
17
|
+
let frozenAt;
|
|
18
|
+
const now = () => frozenAt ?? source() + offsetMs;
|
|
19
|
+
return {
|
|
20
|
+
now,
|
|
21
|
+
set: (epochMs) => {
|
|
22
|
+
if (frozenAt !== void 0)
|
|
23
|
+
frozenAt = epochMs;
|
|
24
|
+
else
|
|
25
|
+
offsetMs = epochMs - source();
|
|
26
|
+
},
|
|
27
|
+
advance: (deltaMs) => {
|
|
28
|
+
if (frozenAt !== void 0)
|
|
29
|
+
frozenAt += deltaMs;
|
|
30
|
+
else
|
|
31
|
+
offsetMs += deltaMs;
|
|
32
|
+
},
|
|
33
|
+
freeze: () => {
|
|
34
|
+
frozenAt = now();
|
|
35
|
+
},
|
|
36
|
+
unfreeze: () => {
|
|
37
|
+
if (frozenAt === void 0)
|
|
38
|
+
return;
|
|
39
|
+
offsetMs = frozenAt - source();
|
|
40
|
+
frozenAt = void 0;
|
|
41
|
+
},
|
|
42
|
+
reset: () => {
|
|
43
|
+
offsetMs = 0;
|
|
44
|
+
frozenAt = void 0;
|
|
45
|
+
},
|
|
46
|
+
state: () => ({ now: now(), frozen: frozenAt !== void 0, offsetMs })
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ../core/dist/control.js
|
|
51
|
+
var UNITS = {
|
|
52
|
+
ms: 1,
|
|
53
|
+
s: 1e3,
|
|
54
|
+
m: 6e4,
|
|
55
|
+
h: 36e5,
|
|
56
|
+
d: 864e5
|
|
57
|
+
};
|
|
58
|
+
var parseDuration = (value) => {
|
|
59
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
60
|
+
return value;
|
|
61
|
+
if (typeof value !== "string")
|
|
62
|
+
return void 0;
|
|
63
|
+
const match = /^(-?\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/.exec(value.trim());
|
|
64
|
+
if (!match)
|
|
65
|
+
return void 0;
|
|
66
|
+
return Number(match[1]) * UNITS[match[2]];
|
|
67
|
+
};
|
|
5
68
|
|
|
6
69
|
// ../../core/dist/timeline.js
|
|
7
70
|
var BRANCH_PATTERN = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
@@ -164,6 +227,764 @@ var Timeline = class {
|
|
|
164
227
|
}
|
|
165
228
|
};
|
|
166
229
|
|
|
230
|
+
// ../../adapters/node/dist/serve.js
|
|
231
|
+
import { createServer } from "node:http";
|
|
232
|
+
var serve = async (api, options2 = {}) => {
|
|
233
|
+
const server2 = createServer(async (req, res) => {
|
|
234
|
+
const chunks = [];
|
|
235
|
+
for await (const chunk of req) {
|
|
236
|
+
chunks.push(Buffer.from(chunk));
|
|
237
|
+
}
|
|
238
|
+
const body = Buffer.concat(chunks);
|
|
239
|
+
const address = server2.address();
|
|
240
|
+
const port = typeof address === "object" && address !== null ? address.port : void 0;
|
|
241
|
+
const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
|
|
242
|
+
const raw = req.url ?? "/";
|
|
243
|
+
const url = new URL(raw.replace(/^\/+/, "/"), base);
|
|
244
|
+
const method = req.method ?? "GET";
|
|
245
|
+
const init = { method, headers: req.headers };
|
|
246
|
+
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
|
247
|
+
init.body = body;
|
|
248
|
+
}
|
|
249
|
+
const aborted = new AbortController();
|
|
250
|
+
res.once("close", () => {
|
|
251
|
+
if (!res.writableFinished)
|
|
252
|
+
aborted.abort();
|
|
253
|
+
});
|
|
254
|
+
init.signal = aborted.signal;
|
|
255
|
+
const request = new Request(url, init);
|
|
256
|
+
let response;
|
|
257
|
+
try {
|
|
258
|
+
response = await api.fetch(request);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (error.code === "MOCKINGBIRD_DROP") {
|
|
261
|
+
req.socket.destroy();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
res.writeHead(500, { "content-type": "application/json" });
|
|
265
|
+
res.end(JSON.stringify({
|
|
266
|
+
error: {
|
|
267
|
+
type: "mockingbird_internal",
|
|
268
|
+
message: error instanceof Error ? error.message : String(error)
|
|
269
|
+
}
|
|
270
|
+
}));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const headers = Object.fromEntries(response.headers);
|
|
274
|
+
const cookies = response.headers.getSetCookie();
|
|
275
|
+
if (cookies.length > 0)
|
|
276
|
+
headers["set-cookie"] = cookies;
|
|
277
|
+
if (!response.body) {
|
|
278
|
+
res.writeHead(response.status, headers);
|
|
279
|
+
res.end();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
res.writeHead(response.status, headers);
|
|
283
|
+
res.flushHeaders();
|
|
284
|
+
const reader = response.body.getReader();
|
|
285
|
+
try {
|
|
286
|
+
for (; ; ) {
|
|
287
|
+
const { done, value } = await reader.read();
|
|
288
|
+
if (done)
|
|
289
|
+
break;
|
|
290
|
+
if (!res.write(value))
|
|
291
|
+
await new Promise((resolve2) => res.once("drain", resolve2));
|
|
292
|
+
}
|
|
293
|
+
res.end();
|
|
294
|
+
} catch {
|
|
295
|
+
res.destroy();
|
|
296
|
+
} finally {
|
|
297
|
+
reader.releaseLock();
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
await new Promise((resolve2, reject) => {
|
|
301
|
+
server2.once("error", reject);
|
|
302
|
+
server2.listen(options2.port ?? 0, options2.host, resolve2);
|
|
303
|
+
});
|
|
304
|
+
return server2;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// ../../adapters/node/dist/listen.js
|
|
308
|
+
var listen = async (api, options2 = {}) => {
|
|
309
|
+
const host = options2.host ?? "127.0.0.1";
|
|
310
|
+
const server2 = await serve(api, { port: options2.port ?? 0, host });
|
|
311
|
+
const address = server2.address();
|
|
312
|
+
const port = typeof address === "object" && address !== null ? address.port : options2.port ?? 0;
|
|
313
|
+
const shown = host.includes(":") ? `[${host}]` : host;
|
|
314
|
+
return {
|
|
315
|
+
url: `http://${shown}:${port}`,
|
|
316
|
+
port,
|
|
317
|
+
host,
|
|
318
|
+
server: server2,
|
|
319
|
+
close: () => new Promise((resolve2, reject) => {
|
|
320
|
+
server2.close((error) => {
|
|
321
|
+
if (error && error.code !== "ERR_SERVER_NOT_RUNNING")
|
|
322
|
+
reject(error);
|
|
323
|
+
else
|
|
324
|
+
resolve2();
|
|
325
|
+
});
|
|
326
|
+
server2.closeAllConnections?.();
|
|
327
|
+
})
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
// ../../adapters/node/dist/fleet.js
|
|
332
|
+
var EMPTY = {
|
|
333
|
+
activeRequests: 0,
|
|
334
|
+
activeConnections: 0,
|
|
335
|
+
blockedCommands: 0,
|
|
336
|
+
pendingJobs: 0,
|
|
337
|
+
pendingWebhooks: 0,
|
|
338
|
+
unmatchedRequests: 0
|
|
339
|
+
};
|
|
340
|
+
var NS = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
341
|
+
var json = (status, value) => Response.json(value, { status });
|
|
342
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
343
|
+
var pending = (d) => d.activeRequests + d.blockedCommands + d.pendingJobs + d.pendingWebhooks;
|
|
344
|
+
var publicUrl = (raw) => {
|
|
345
|
+
const url = new URL(raw);
|
|
346
|
+
url.password = "";
|
|
347
|
+
return url.href;
|
|
348
|
+
};
|
|
349
|
+
var atomicWrite = async (path, value) => {
|
|
350
|
+
const temp = `${path}.${randomUUID()}.tmp`;
|
|
351
|
+
try {
|
|
352
|
+
await writeFile(temp, `${JSON.stringify(value, null, 2)}
|
|
353
|
+
`, { mode: 384, flag: "wx" });
|
|
354
|
+
await chmod(temp, 384);
|
|
355
|
+
await rename(temp, path);
|
|
356
|
+
} finally {
|
|
357
|
+
await rm(temp, { force: true });
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var removeOwned = async (path, id) => {
|
|
361
|
+
if (!path)
|
|
362
|
+
return;
|
|
363
|
+
try {
|
|
364
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
365
|
+
if (isObject(value) && value.id === id)
|
|
366
|
+
await rm(path, { force: true });
|
|
367
|
+
} catch (error) {
|
|
368
|
+
if (error.code !== "ENOENT")
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
var httpChild = async (target, entry, options2) => {
|
|
373
|
+
const runtime = await target.create(entry.options ?? {}, {
|
|
374
|
+
adminKey: entry.adminKey,
|
|
375
|
+
seed: entry.seed,
|
|
376
|
+
onLog: options2.onLog
|
|
377
|
+
});
|
|
378
|
+
runtime.isolateNamespaces();
|
|
379
|
+
const locked = /* @__PURE__ */ new Set();
|
|
380
|
+
const active = /* @__PURE__ */ new Map();
|
|
381
|
+
const api = {
|
|
382
|
+
async fetch(request) {
|
|
383
|
+
const namespace = runtime.namespaceOf(request);
|
|
384
|
+
if (locked.has(namespace))
|
|
385
|
+
return json(409, { error: "fleet control in progress", namespace });
|
|
386
|
+
active.set(namespace, (active.get(namespace) ?? 0) + 1);
|
|
387
|
+
try {
|
|
388
|
+
return await runtime.fetch(request);
|
|
389
|
+
} finally {
|
|
390
|
+
active.set(namespace, (active.get(namespace) ?? 1) - 1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
let server2;
|
|
395
|
+
try {
|
|
396
|
+
server2 = await listen(api, {
|
|
397
|
+
port: entry.port ?? target.defaultPort,
|
|
398
|
+
host: entry.host ?? "127.0.0.1"
|
|
399
|
+
});
|
|
400
|
+
target.listening?.(server2);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
runtime.stop?.();
|
|
403
|
+
await server2?.close();
|
|
404
|
+
runtime.webhooks?.clear();
|
|
405
|
+
await runtime.webhooks?.idle();
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
return {
|
|
409
|
+
protocol: "http",
|
|
410
|
+
url: server2.url,
|
|
411
|
+
healthUrl: `${server2.url}/health`,
|
|
412
|
+
adminUrl: `${server2.url}/__admin`,
|
|
413
|
+
namespaces: { header: "x-mockingbird-namespace", path: "/ns/{name}" },
|
|
414
|
+
ready: async () => {
|
|
415
|
+
try {
|
|
416
|
+
return (await fetch(`${server2.url}/health`, { signal: AbortSignal.timeout(2e3) })).ok;
|
|
417
|
+
} catch {
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
async close() {
|
|
422
|
+
runtime.stop?.();
|
|
423
|
+
runtime.webhooks?.clear();
|
|
424
|
+
await server2.close();
|
|
425
|
+
await runtime.webhooks?.idle();
|
|
426
|
+
},
|
|
427
|
+
lock(namespace) {
|
|
428
|
+
if (locked.has(namespace))
|
|
429
|
+
throw new Error("namespace already locked");
|
|
430
|
+
locked.add(namespace);
|
|
431
|
+
return () => locked.delete(namespace);
|
|
432
|
+
},
|
|
433
|
+
diagnostics(namespace) {
|
|
434
|
+
return {
|
|
435
|
+
...EMPTY,
|
|
436
|
+
activeRequests: active.get(namespace) ?? 0,
|
|
437
|
+
pendingWebhooks: runtime.webhooks?.pending(namespace) ?? 0,
|
|
438
|
+
unmatchedRequests: runtime.metrics.report(namespace).unmatched.reduce((n, r) => n + r.count, 0)
|
|
439
|
+
};
|
|
440
|
+
},
|
|
441
|
+
async checkpoint(namespace) {
|
|
442
|
+
return runtime.fleetSnapshot(namespace);
|
|
443
|
+
},
|
|
444
|
+
async restore(namespace, checkpoint) {
|
|
445
|
+
runtime.fleetRestore(checkpoint, namespace);
|
|
446
|
+
},
|
|
447
|
+
reset: (namespace) => runtime.reset(namespace),
|
|
448
|
+
clock: (namespace) => runtime.namespaceClock(namespace),
|
|
449
|
+
async settle(namespace) {
|
|
450
|
+
const instance = runtime.instance(namespace);
|
|
451
|
+
instance.tick?.();
|
|
452
|
+
await runtime.webhooks?.idle();
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
async function startFleet(config, options2) {
|
|
457
|
+
const id = randomUUID();
|
|
458
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
459
|
+
const children = /* @__PURE__ */ new Map();
|
|
460
|
+
const clocks = /* @__PURE__ */ new Map();
|
|
461
|
+
const checkpoints = /* @__PURE__ */ new Map();
|
|
462
|
+
let supervisor;
|
|
463
|
+
let stopped = false;
|
|
464
|
+
let controlling = false;
|
|
465
|
+
const close = async () => {
|
|
466
|
+
if (stopped)
|
|
467
|
+
return;
|
|
468
|
+
stopped = true;
|
|
469
|
+
await Promise.allSettled([...children.values()].map((child) => child.close()));
|
|
470
|
+
await supervisor?.close();
|
|
471
|
+
await removeOwned(options2.readyFile, id);
|
|
472
|
+
await removeOwned(options2.connectionsFile, id);
|
|
473
|
+
};
|
|
474
|
+
if (options2.readyFile)
|
|
475
|
+
await rm(options2.readyFile, { force: true });
|
|
476
|
+
if (options2.connectionsFile)
|
|
477
|
+
await rm(options2.connectionsFile, { force: true });
|
|
478
|
+
const clock = (namespace) => {
|
|
479
|
+
let value = clocks.get(namespace);
|
|
480
|
+
if (!value) {
|
|
481
|
+
value = createClock();
|
|
482
|
+
clocks.set(namespace, value);
|
|
483
|
+
}
|
|
484
|
+
return value;
|
|
485
|
+
};
|
|
486
|
+
const select = (body) => {
|
|
487
|
+
if (body.services === void 0)
|
|
488
|
+
return children;
|
|
489
|
+
if (!Array.isArray(body.services) || body.services.length === 0)
|
|
490
|
+
throw new Error("services must be a nonempty list");
|
|
491
|
+
const selected = /* @__PURE__ */ new Map();
|
|
492
|
+
for (const name of body.services) {
|
|
493
|
+
if (typeof name !== "string" || !children.has(name))
|
|
494
|
+
throw new Error("unknown service in selection");
|
|
495
|
+
selected.set(name, children.get(name));
|
|
496
|
+
}
|
|
497
|
+
return selected;
|
|
498
|
+
};
|
|
499
|
+
const diagnostics = (namespace) => Object.fromEntries([...children].map(([name, child]) => [name, child.diagnostics(namespace)]));
|
|
500
|
+
const transaction = async (namespace, selected, run) => {
|
|
501
|
+
if (controlling)
|
|
502
|
+
return json(409, { error: "another fleet operation is in progress" });
|
|
503
|
+
controlling = true;
|
|
504
|
+
const release = [];
|
|
505
|
+
const before = /* @__PURE__ */ new Map();
|
|
506
|
+
const results = {};
|
|
507
|
+
let failure;
|
|
508
|
+
try {
|
|
509
|
+
for (const [name, child] of selected) {
|
|
510
|
+
failure = name;
|
|
511
|
+
release.push(child.lock(namespace));
|
|
512
|
+
}
|
|
513
|
+
const busy = Object.fromEntries([...selected].filter(([, child]) => {
|
|
514
|
+
const state = child.diagnostics(namespace);
|
|
515
|
+
return state.activeRequests + state.blockedCommands + state.pendingWebhooks + (state.activeJobs ?? 0) > 0;
|
|
516
|
+
}).map(([name, child]) => [name, child.diagnostics(namespace)]));
|
|
517
|
+
if (Object.keys(busy).length)
|
|
518
|
+
return json(409, { error: "namespace is not quiescent", services: busy });
|
|
519
|
+
for (const [name, child] of selected) {
|
|
520
|
+
failure = name;
|
|
521
|
+
before.set(name, await child.checkpoint(namespace));
|
|
522
|
+
}
|
|
523
|
+
for (const [name, child] of selected) {
|
|
524
|
+
failure = name;
|
|
525
|
+
results[name] = { status: "ok", result: await run(name, child) };
|
|
526
|
+
}
|
|
527
|
+
return json(200, { status: "ok", namespace, services: results });
|
|
528
|
+
} catch {
|
|
529
|
+
const rollback = {};
|
|
530
|
+
for (const [name, point] of before) {
|
|
531
|
+
try {
|
|
532
|
+
await selected.get(name)?.restore(namespace, point);
|
|
533
|
+
rollback[name] = "restored";
|
|
534
|
+
} catch {
|
|
535
|
+
rollback[name] = "failed";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return json(500, {
|
|
539
|
+
status: "failed",
|
|
540
|
+
namespace,
|
|
541
|
+
failedService: failure,
|
|
542
|
+
services: results,
|
|
543
|
+
rollback,
|
|
544
|
+
rolledBack: before.size > 0 && Object.values(rollback).every((value) => value === "restored")
|
|
545
|
+
});
|
|
546
|
+
} finally {
|
|
547
|
+
for (const unlock of release.reverse())
|
|
548
|
+
unlock();
|
|
549
|
+
controlling = false;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
const api = {
|
|
553
|
+
async fetch(request) {
|
|
554
|
+
const url = new URL(request.url);
|
|
555
|
+
if (url.pathname === "/health" && request.method === "GET") {
|
|
556
|
+
const states = await Promise.all([...children].map(async ([name, child]) => {
|
|
557
|
+
let protocolReady = false;
|
|
558
|
+
try {
|
|
559
|
+
protocolReady = await child.ready();
|
|
560
|
+
} catch {
|
|
561
|
+
}
|
|
562
|
+
return [
|
|
563
|
+
name,
|
|
564
|
+
{
|
|
565
|
+
processReady: !stopped,
|
|
566
|
+
protocolReady,
|
|
567
|
+
status: protocolReady ? "ready" : "failed",
|
|
568
|
+
url: publicUrl(child.url)
|
|
569
|
+
}
|
|
570
|
+
];
|
|
571
|
+
}));
|
|
572
|
+
const ready = !stopped && states.every(([, state]) => state.protocolReady);
|
|
573
|
+
return json(ready ? 200 : 503, {
|
|
574
|
+
status: ready ? "ready" : "failed",
|
|
575
|
+
id,
|
|
576
|
+
services: Object.fromEntries(states)
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (!url.pathname.startsWith("/__fleet/"))
|
|
580
|
+
return json(404, { error: "not found" });
|
|
581
|
+
const adminKey = config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
582
|
+
if (adminKey !== void 0 && request.headers.get("x-mockingbird-admin-key") !== adminKey)
|
|
583
|
+
return json(401, { error: "admin key required" });
|
|
584
|
+
const route = /^\/__fleet\/namespaces\/([^/]+)\/(reset|snapshots)(?:\/([^/]+)\/restore)?$/.exec(url.pathname);
|
|
585
|
+
const namespace = route ? decodeURIComponent(route[1]) : url.searchParams.get("namespace") ?? request.headers.get("x-mockingbird-namespace") ?? config.namespace ?? "default";
|
|
586
|
+
if (!NS.test(namespace))
|
|
587
|
+
return json(400, { error: "invalid namespace" });
|
|
588
|
+
if (request.method === "GET" && url.pathname === "/__fleet/metrics")
|
|
589
|
+
return json(200, { namespace, services: diagnostics(namespace) });
|
|
590
|
+
if (request.method === "GET" && url.pathname === "/__fleet/clock")
|
|
591
|
+
return json(200, {
|
|
592
|
+
namespace,
|
|
593
|
+
...clock(namespace).state(),
|
|
594
|
+
services: Object.fromEntries([...children].map(([name, child]) => [name, child.clock(namespace).state()]))
|
|
595
|
+
});
|
|
596
|
+
if (request.method !== "POST")
|
|
597
|
+
return json(405, { error: "method not allowed" });
|
|
598
|
+
let body;
|
|
599
|
+
try {
|
|
600
|
+
const text = await request.text();
|
|
601
|
+
const value = text ? JSON.parse(text) : {};
|
|
602
|
+
if (!isObject(value))
|
|
603
|
+
throw new Error("expected object");
|
|
604
|
+
body = value;
|
|
605
|
+
} catch {
|
|
606
|
+
return json(400, { error: "expected JSON object" });
|
|
607
|
+
}
|
|
608
|
+
let selected;
|
|
609
|
+
try {
|
|
610
|
+
selected = select(body);
|
|
611
|
+
} catch (error) {
|
|
612
|
+
return json(400, { error: error instanceof Error ? error.message : "bad selection" });
|
|
613
|
+
}
|
|
614
|
+
if (url.pathname === "/__fleet/wait-until-idle") {
|
|
615
|
+
const services = diagnostics(namespace);
|
|
616
|
+
const idle = Object.values(services).every((d) => pending(d) === 0);
|
|
617
|
+
return json(idle ? 200 : 409, { status: idle ? "idle" : "busy", namespace, services });
|
|
618
|
+
}
|
|
619
|
+
if (url.pathname === "/__fleet/clock") {
|
|
620
|
+
const current = clock(namespace);
|
|
621
|
+
let target = current.now();
|
|
622
|
+
if (body.set !== void 0) {
|
|
623
|
+
target = typeof body.set === "number" ? body.set : typeof body.set === "string" ? Date.parse(body.set) : NaN;
|
|
624
|
+
if (!Number.isFinite(target))
|
|
625
|
+
return json(400, { error: "set must be epoch ms or ISO-8601" });
|
|
626
|
+
}
|
|
627
|
+
if (body.advance !== void 0) {
|
|
628
|
+
const delta = parseDuration(body.advance);
|
|
629
|
+
if (delta === void 0)
|
|
630
|
+
return json(400, { error: "invalid advance" });
|
|
631
|
+
target += delta;
|
|
632
|
+
}
|
|
633
|
+
const frozen = body.freeze === void 0 ? current.state().frozen : body.freeze;
|
|
634
|
+
if (typeof frozen !== "boolean")
|
|
635
|
+
return json(400, { error: "freeze must be boolean" });
|
|
636
|
+
const response2 = await transaction(namespace, selected, async (_name, child) => {
|
|
637
|
+
const childClock = child.clock(namespace);
|
|
638
|
+
childClock.freeze();
|
|
639
|
+
childClock.set(target);
|
|
640
|
+
if (!frozen)
|
|
641
|
+
childClock.unfreeze();
|
|
642
|
+
await child.settle?.(namespace);
|
|
643
|
+
return childClock.state();
|
|
644
|
+
});
|
|
645
|
+
if (response2.ok) {
|
|
646
|
+
current.freeze();
|
|
647
|
+
current.set(target);
|
|
648
|
+
if (!frozen)
|
|
649
|
+
current.unfreeze();
|
|
650
|
+
}
|
|
651
|
+
return response2;
|
|
652
|
+
}
|
|
653
|
+
if (!route)
|
|
654
|
+
return json(404, { error: "not found" });
|
|
655
|
+
if (route[2] === "reset") {
|
|
656
|
+
if (route[3])
|
|
657
|
+
return json(404, { error: "not found" });
|
|
658
|
+
const response2 = await transaction(namespace, selected, async (_name, child) => child.reset(namespace));
|
|
659
|
+
if (response2.ok && selected.size === children.size)
|
|
660
|
+
clock(namespace).reset();
|
|
661
|
+
return response2;
|
|
662
|
+
}
|
|
663
|
+
if (route[3]) {
|
|
664
|
+
const point = checkpoints.get(route[3]);
|
|
665
|
+
if (!point || point.namespace !== namespace)
|
|
666
|
+
return json(404, { error: "no snapshot in this namespace" });
|
|
667
|
+
const restored = new Map([...point.values].map(([name]) => [name, children.get(name)]));
|
|
668
|
+
const response2 = await transaction(namespace, restored, async (name, child) => child.restore(namespace, point.values.get(name)));
|
|
669
|
+
if (response2.ok) {
|
|
670
|
+
const current = clock(namespace);
|
|
671
|
+
current.freeze();
|
|
672
|
+
current.set(point.clock.now);
|
|
673
|
+
if (!point.clock.frozen)
|
|
674
|
+
current.unfreeze();
|
|
675
|
+
}
|
|
676
|
+
return response2;
|
|
677
|
+
}
|
|
678
|
+
const snapshotId = randomUUID();
|
|
679
|
+
const values = /* @__PURE__ */ new Map();
|
|
680
|
+
const response = await transaction(namespace, selected, async (name, child) => {
|
|
681
|
+
values.set(name, await child.checkpoint(namespace));
|
|
682
|
+
return { checkpoint: `${snapshotId}:${name}` };
|
|
683
|
+
});
|
|
684
|
+
if (!response.ok)
|
|
685
|
+
return response;
|
|
686
|
+
checkpoints.set(snapshotId, { namespace, values, clock: clock(namespace).state() });
|
|
687
|
+
return json(201, {
|
|
688
|
+
...await response.json(),
|
|
689
|
+
id: snapshotId,
|
|
690
|
+
label: body.label
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
try {
|
|
695
|
+
if (!isObject(config.services) || Object.keys(config.services).length === 0)
|
|
696
|
+
throw new Error("config must contain at least one service");
|
|
697
|
+
for (const [name, entry] of Object.entries(config.services)) {
|
|
698
|
+
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name))
|
|
699
|
+
throw new Error("invalid service name");
|
|
700
|
+
try {
|
|
701
|
+
const target = await options2.load(name);
|
|
702
|
+
if (entry.protocol && entry.protocol !== ("protocol" in target ? target.protocol : "http"))
|
|
703
|
+
throw new Error("configured protocol does not match target");
|
|
704
|
+
const adminKey = entry.adminKey ?? config.adminKey ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
705
|
+
const child = "start" in target ? await target.start(entry) : await httpChild(target, { ...entry, ...adminKey !== void 0 ? { adminKey } : {} }, options2);
|
|
706
|
+
children.set(name, child);
|
|
707
|
+
if (!await child.ready())
|
|
708
|
+
throw new Error("protocol readiness failed");
|
|
709
|
+
} catch {
|
|
710
|
+
throw new Error(`service ${name} failed to start; all started listeners were closed`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
supervisor = await listen(api, {
|
|
714
|
+
port: config.control?.port ?? 0,
|
|
715
|
+
host: config.control?.host ?? "127.0.0.1"
|
|
716
|
+
});
|
|
717
|
+
const manifest = {
|
|
718
|
+
version: 1,
|
|
719
|
+
state: "ready",
|
|
720
|
+
pid: process.pid,
|
|
721
|
+
id,
|
|
722
|
+
startedAt,
|
|
723
|
+
adminBase: `${supervisor.url}/__fleet`,
|
|
724
|
+
healthUrl: `${supervisor.url}/health`,
|
|
725
|
+
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
726
|
+
name,
|
|
727
|
+
{
|
|
728
|
+
protocol: child.protocol,
|
|
729
|
+
url: publicUrl(child.url),
|
|
730
|
+
healthUrl: child.healthUrl ?? `${supervisor?.url}/health`,
|
|
731
|
+
adminUrl: child.adminUrl ?? `${supervisor?.url}/__fleet`,
|
|
732
|
+
namespaces: child.namespaces,
|
|
733
|
+
processReady: true,
|
|
734
|
+
protocolReady: true
|
|
735
|
+
}
|
|
736
|
+
]))
|
|
737
|
+
};
|
|
738
|
+
if (options2.connectionsFile)
|
|
739
|
+
await atomicWrite(options2.connectionsFile, {
|
|
740
|
+
version: 1,
|
|
741
|
+
id,
|
|
742
|
+
pid: process.pid,
|
|
743
|
+
services: Object.fromEntries([...children].map(([name, child]) => [
|
|
744
|
+
name,
|
|
745
|
+
{ url: child.connection ?? child.url, namespaces: child.connections }
|
|
746
|
+
]))
|
|
747
|
+
});
|
|
748
|
+
if (options2.readyFile)
|
|
749
|
+
await atomicWrite(options2.readyFile, manifest);
|
|
750
|
+
options2.onReady?.(manifest);
|
|
751
|
+
return { manifest, children, fetch: api.fetch, close };
|
|
752
|
+
} catch (error) {
|
|
753
|
+
await close();
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// ../../adapters/node/dist/cli.js
|
|
759
|
+
var optionHelp = (options2) => Object.entries(options2).map(([name, option]) => {
|
|
760
|
+
const flag = `--${name}${option.type === "string" ? ` ${option.value ?? "<value>"}` : ""}`;
|
|
761
|
+
const fallback2 = option.default !== void 0 ? ` (default: ${String(option.default)})` : "";
|
|
762
|
+
return ` ${flag.padEnd(30)} ${option.description}${fallback2}`;
|
|
763
|
+
});
|
|
764
|
+
var help = (spec) => [
|
|
765
|
+
`${spec.bin} \u2014 ${spec.description}`,
|
|
766
|
+
"",
|
|
767
|
+
"Usage:",
|
|
768
|
+
` ${spec.bin} <command> [options]`,
|
|
769
|
+
"",
|
|
770
|
+
"Commands:",
|
|
771
|
+
...Object.entries(spec.commands).map(([name, c]) => ` ${name.padEnd(30)} ${c.summary}`),
|
|
772
|
+
"",
|
|
773
|
+
`Run \`${spec.bin} <command> --help\` for a command's options.`
|
|
774
|
+
].join("\n");
|
|
775
|
+
var commandHelp = (spec, name, command2) => [
|
|
776
|
+
`${spec.bin} ${name} \u2014 ${command2.summary}`,
|
|
777
|
+
"",
|
|
778
|
+
"Usage:",
|
|
779
|
+
` ${command2.usage ?? `${spec.bin} ${name} [options]`}`,
|
|
780
|
+
...command2.options ? ["", "Options:", ...optionHelp(command2.options)] : []
|
|
781
|
+
].join("\n");
|
|
782
|
+
var runCli = async (spec, argv) => {
|
|
783
|
+
const pair = argv.length >= 2 ? `${argv[0]} ${argv[1]}` : void 0;
|
|
784
|
+
const words = pair !== void 0 && spec.commands[pair] ? 2 : 1;
|
|
785
|
+
const name = words === 2 ? pair : argv[0];
|
|
786
|
+
const rest = argv.slice(words);
|
|
787
|
+
if (name === void 0 || name === "--help" || name === "-h" || name === "help") {
|
|
788
|
+
console.log(help(spec));
|
|
789
|
+
return 0;
|
|
790
|
+
}
|
|
791
|
+
const command2 = spec.commands[name];
|
|
792
|
+
if (!command2) {
|
|
793
|
+
console.error(`${spec.bin}: unknown command ${JSON.stringify(name)}
|
|
794
|
+
|
|
795
|
+
${help(spec)}`);
|
|
796
|
+
return 2;
|
|
797
|
+
}
|
|
798
|
+
if (rest.includes("--help") || rest.includes("-h")) {
|
|
799
|
+
console.log(commandHelp(spec, name, command2));
|
|
800
|
+
return 0;
|
|
801
|
+
}
|
|
802
|
+
let parsed;
|
|
803
|
+
try {
|
|
804
|
+
parsed = parseArgs({
|
|
805
|
+
args: rest,
|
|
806
|
+
allowPositionals: true,
|
|
807
|
+
strict: true,
|
|
808
|
+
options: Object.fromEntries(Object.entries(command2.options ?? {}).map(([key, option]) => [
|
|
809
|
+
key,
|
|
810
|
+
{
|
|
811
|
+
type: option.type,
|
|
812
|
+
...option.default !== void 0 ? { default: option.default } : {}
|
|
813
|
+
}
|
|
814
|
+
]))
|
|
815
|
+
});
|
|
816
|
+
} catch (error) {
|
|
817
|
+
console.error(`${spec.bin} ${name}: ${error instanceof Error ? error.message : String(error)}
|
|
818
|
+
|
|
819
|
+
${commandHelp(spec, name, command2)}`);
|
|
820
|
+
return 2;
|
|
821
|
+
}
|
|
822
|
+
return command2.run(parsed.values, parsed.positionals);
|
|
823
|
+
};
|
|
824
|
+
var COMMON_SERVE_OPTIONS = {
|
|
825
|
+
port: { type: "string", value: "<port>", description: "Port to listen on" },
|
|
826
|
+
host: { type: "string", value: "<host>", description: "Interface to bind", default: "127.0.0.1" },
|
|
827
|
+
"admin-key": {
|
|
828
|
+
type: "string",
|
|
829
|
+
value: "<key>",
|
|
830
|
+
description: "Require x-mockingbird-admin-key on /__admin/* (env MOCKINGBIRD_ADMIN_KEY)"
|
|
831
|
+
},
|
|
832
|
+
seed: { type: "string", value: "<seed>", description: "Seed for every random choice" },
|
|
833
|
+
log: {
|
|
834
|
+
type: "string",
|
|
835
|
+
value: "<pretty|json|off>",
|
|
836
|
+
description: "Request log format",
|
|
837
|
+
default: "pretty"
|
|
838
|
+
},
|
|
839
|
+
"log-requests": {
|
|
840
|
+
type: "boolean",
|
|
841
|
+
description: "One JSON line per request: namespace, operationId, status, ids touched (never bodies). Same as --log json"
|
|
842
|
+
},
|
|
843
|
+
config: {
|
|
844
|
+
type: "string",
|
|
845
|
+
value: "<file>",
|
|
846
|
+
description: "Serve every service in a mockingbird.json config instead"
|
|
847
|
+
},
|
|
848
|
+
"ready-file": {
|
|
849
|
+
type: "string",
|
|
850
|
+
description: "Atomically publish fleet endpoint JSON",
|
|
851
|
+
value: "<file>"
|
|
852
|
+
},
|
|
853
|
+
"connections-file": {
|
|
854
|
+
type: "string",
|
|
855
|
+
description: "Write private connection URLs with mode 0600",
|
|
856
|
+
value: "<file>"
|
|
857
|
+
},
|
|
858
|
+
"ready-json": {
|
|
859
|
+
type: "boolean",
|
|
860
|
+
description: "Emit one public JSON record after fleet readiness"
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
var formatLog = (format) => {
|
|
864
|
+
if (format === "off")
|
|
865
|
+
return void 0;
|
|
866
|
+
if (format === "json")
|
|
867
|
+
return (entry) => console.log(JSON.stringify(entry));
|
|
868
|
+
return (entry) => {
|
|
869
|
+
const op = entry.operationId ?? (entry.unmatched ? "UNMATCHED" : "-");
|
|
870
|
+
const ns = entry.namespace === "default" ? "" : ` [${entry.namespace}]`;
|
|
871
|
+
const fault = entry.faultId ? ` fault=${entry.faultId}` : "";
|
|
872
|
+
const adopted = entry.adopted ? " adopted" : "";
|
|
873
|
+
const received = entry.request ? ` received=${entry.request.bodyBytes === null ? "?" : `${entry.request.bodyBytes}B`} ct=${entry.request.contentType ?? "-"}${entry.request.transferEncoding ? ` te=${entry.request.transferEncoding}` : ""}` : "";
|
|
874
|
+
const issues = entry.issues?.length ? ` issues=${entry.issues.slice(0, 3).map((issue) => `${issue.path || "$"}: ${issue.message}`).join("; ")}${entry.issues.length > 3 ? ` (+${entry.issues.length - 3})` : ""}` : "";
|
|
875
|
+
console.log(`${entry.service} ${entry.method} ${entry.path} ${entry.status} ${op} ${entry.durationMs}ms${ns}${fault}${adopted}${received}${issues}`);
|
|
876
|
+
};
|
|
877
|
+
};
|
|
878
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
879
|
+
var loadTarget = async (name, own) => {
|
|
880
|
+
if (name === own.name)
|
|
881
|
+
return own;
|
|
882
|
+
const specifier = `@crvouga/mockingbird-service-${name}/server`;
|
|
883
|
+
try {
|
|
884
|
+
let mod;
|
|
885
|
+
try {
|
|
886
|
+
mod = await import(specifier);
|
|
887
|
+
} catch {
|
|
888
|
+
const fromProject = createRequire(resolve(process.cwd(), "package.json"));
|
|
889
|
+
mod = await import(pathToFileURL(fromProject.resolve(specifier)).href);
|
|
890
|
+
}
|
|
891
|
+
if (!mod.serveTarget)
|
|
892
|
+
throw new Error(`${specifier} exports no serveTarget`);
|
|
893
|
+
return mod.serveTarget;
|
|
894
|
+
} catch (error) {
|
|
895
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
896
|
+
throw new Error(`cannot load service "${name}": ${reason}. Install @crvouga/mockingbird-service-${name}.`);
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
var start = async (target, values, config) => {
|
|
900
|
+
const runtime = await target.create(values, {
|
|
901
|
+
adminKey: config.adminKey,
|
|
902
|
+
seed: config.seed,
|
|
903
|
+
onLog: formatLog(config.log)
|
|
904
|
+
});
|
|
905
|
+
const listening = await listen(runtime, { port: config.port, host: config.host });
|
|
906
|
+
target.listening?.(listening);
|
|
907
|
+
console.log(`${target.name} mock listening on ${listening.url}`);
|
|
908
|
+
console.log(`${target.name} health: GET ${listening.url}/health`);
|
|
909
|
+
console.log(`${target.name} admin: ${listening.url}/__admin (${config.adminKey ? "x-mockingbird-admin-key required" : "open \u2014 pass --admin-key to lock"})`);
|
|
910
|
+
console.log(`${target.name} admin ui: ${listening.url}/__admin/ui`);
|
|
911
|
+
for (const line of target.banner?.(runtime) ?? [])
|
|
912
|
+
console.log(`${target.name} ${line}`);
|
|
913
|
+
return listening;
|
|
914
|
+
};
|
|
915
|
+
var untilSignal = async (servers) => new Promise((resolve2) => {
|
|
916
|
+
const stop2 = async () => {
|
|
917
|
+
;
|
|
918
|
+
process.removeListener("SIGINT", stop2);
|
|
919
|
+
process.removeListener("SIGTERM", stop2);
|
|
920
|
+
await Promise.allSettled(servers.map((s) => s.close()));
|
|
921
|
+
resolve2(0);
|
|
922
|
+
};
|
|
923
|
+
process.once("SIGINT", stop2);
|
|
924
|
+
process.once("SIGTERM", stop2);
|
|
925
|
+
});
|
|
926
|
+
var serveCommand = (target) => ({
|
|
927
|
+
summary: `Serve the ${target.name} mock or a configured fleet`,
|
|
928
|
+
options: { ...COMMON_SERVE_OPTIONS, ..."options" in target ? target.options : {} },
|
|
929
|
+
async run(values) {
|
|
930
|
+
const log2 = values["log-requests"] === true ? "json" : asString(values.log) ?? "pretty";
|
|
931
|
+
if (!["pretty", "json", "off"].includes(log2)) {
|
|
932
|
+
console.error(`--log must be pretty, json or off (got ${log2})`);
|
|
933
|
+
return 2;
|
|
934
|
+
}
|
|
935
|
+
const configPath = asString(values.config);
|
|
936
|
+
if (configPath !== void 0) {
|
|
937
|
+
try {
|
|
938
|
+
const config = JSON.parse(await readFile2(configPath, "utf8"));
|
|
939
|
+
const adminKey2 = config.adminKey ?? asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
940
|
+
if (adminKey2 !== void 0)
|
|
941
|
+
config.adminKey = adminKey2;
|
|
942
|
+
const fleet = await startFleet(config, {
|
|
943
|
+
load: (name) => loadTarget(name, target),
|
|
944
|
+
onLog: formatLog(config.log ?? log2),
|
|
945
|
+
...asString(values["ready-file"]) ? { readyFile: asString(values["ready-file"]) } : {},
|
|
946
|
+
...asString(values["connections-file"]) ? { connectionsFile: asString(values["connections-file"]) } : {},
|
|
947
|
+
onReady: (manifest) => {
|
|
948
|
+
if (values["ready-json"])
|
|
949
|
+
console.log(JSON.stringify(manifest));
|
|
950
|
+
else
|
|
951
|
+
for (const [name, endpoint] of Object.entries(manifest.services))
|
|
952
|
+
console.log(`${name} mock listening on ${endpoint.url}`);
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
return untilSignal([fleet]);
|
|
956
|
+
} catch (error) {
|
|
957
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
958
|
+
return 1;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
if ("start" in target) {
|
|
962
|
+
console.error("Use --config to supervise protocol services with this command");
|
|
963
|
+
return 2;
|
|
964
|
+
}
|
|
965
|
+
const port = asString(values.port);
|
|
966
|
+
const adminKey = asString(values["admin-key"]) ?? process.env.MOCKINGBIRD_ADMIN_KEY;
|
|
967
|
+
const seed = asString(values.seed);
|
|
968
|
+
let listening;
|
|
969
|
+
try {
|
|
970
|
+
listening = await start(target, values, {
|
|
971
|
+
port: port === void 0 ? target.defaultPort : Number.parseInt(port, 10),
|
|
972
|
+
host: asString(values.host) ?? "127.0.0.1",
|
|
973
|
+
...adminKey !== void 0 ? { adminKey } : {},
|
|
974
|
+
...seed !== void 0 ? { seed } : {},
|
|
975
|
+
log: log2
|
|
976
|
+
});
|
|
977
|
+
} catch (error) {
|
|
978
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
979
|
+
return 1;
|
|
980
|
+
}
|
|
981
|
+
return untilSignal([listening]);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// src/wire/index.ts
|
|
986
|
+
import { createServer as createServer2 } from "node:net";
|
|
987
|
+
|
|
167
988
|
// src/errors/error.ts
|
|
168
989
|
var CATEGORY_TO_SQLSTATE = {
|
|
169
990
|
syntax: "42601",
|
|
@@ -2344,11 +3165,11 @@ var JsonParser = class {
|
|
|
2344
3165
|
}
|
|
2345
3166
|
}
|
|
2346
3167
|
parseNumber() {
|
|
2347
|
-
const
|
|
3168
|
+
const start2 = this.pos;
|
|
2348
3169
|
const re = /-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?/y;
|
|
2349
3170
|
re.lastIndex = this.pos;
|
|
2350
3171
|
const m = re.exec(this.text);
|
|
2351
|
-
if (!m || m.index !==
|
|
3172
|
+
if (!m || m.index !== start2) this.fail("bad number");
|
|
2352
3173
|
this.pos = re.lastIndex;
|
|
2353
3174
|
return jsonbNum(parseNumeric(m[0]));
|
|
2354
3175
|
}
|
|
@@ -3465,7 +4286,13 @@ var DatabaseState = class _DatabaseState {
|
|
|
3465
4286
|
this.settings.set("lc_numeric", "C");
|
|
3466
4287
|
this.settings.set("lc_time", "C");
|
|
3467
4288
|
this.settings.set("default_transaction_isolation", "read committed");
|
|
4289
|
+
this.settings.set("lock_timeout", "0");
|
|
4290
|
+
this.settings.set("idle_in_transaction_session_timeout", "0");
|
|
4291
|
+
this.settings.set("transaction_timeout", "0");
|
|
3468
4292
|
this.settings.set("statement_timeout", "0");
|
|
4293
|
+
this.settings.set("check_function_bodies", "on");
|
|
4294
|
+
this.settings.set("xmloption", "content");
|
|
4295
|
+
this.settings.set("row_security", "on");
|
|
3469
4296
|
this.settings.set("array_nulls", "on");
|
|
3470
4297
|
this.settings.set("backslash_quote", "safe_encoding");
|
|
3471
4298
|
}
|
|
@@ -4694,18 +5521,18 @@ function isoWeek(days) {
|
|
|
4694
5521
|
return jan4 - (isoDw - 1);
|
|
4695
5522
|
};
|
|
4696
5523
|
let year = y;
|
|
4697
|
-
let
|
|
4698
|
-
if (days <
|
|
5524
|
+
let start2 = week1Monday(y);
|
|
5525
|
+
if (days < start2) {
|
|
4699
5526
|
year = y - 1;
|
|
4700
|
-
|
|
5527
|
+
start2 = week1Monday(year);
|
|
4701
5528
|
} else {
|
|
4702
5529
|
const nextStart = week1Monday(y + 1);
|
|
4703
5530
|
if (days >= nextStart) {
|
|
4704
5531
|
year = y + 1;
|
|
4705
|
-
|
|
5532
|
+
start2 = nextStart;
|
|
4706
5533
|
}
|
|
4707
5534
|
}
|
|
4708
|
-
return { week: Math.floor((days -
|
|
5535
|
+
return { week: Math.floor((days - start2) / 7) + 1, year };
|
|
4709
5536
|
}
|
|
4710
5537
|
function dateTruncDays(field, days) {
|
|
4711
5538
|
const { y, m } = daysToCivil(days);
|
|
@@ -5275,9 +6102,9 @@ function getArrayFunctions() {
|
|
|
5275
6102
|
}
|
|
5276
6103
|
const elemT = arrayElemType(a.t);
|
|
5277
6104
|
const target = castTo(ctx, args2[1], elemT, {});
|
|
5278
|
-
const
|
|
6105
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
5279
6106
|
const lb = lbsOf(arr)[0] ?? 1;
|
|
5280
|
-
for (let i = Math.max(
|
|
6107
|
+
for (let i = Math.max(start2 - lb, 0); i < arr.items.length; i++) {
|
|
5281
6108
|
const item = arr.items[i];
|
|
5282
6109
|
const eq = item === null ? target.v === null : target.v !== null && datumEquals(elemT, item, target.v, ctx);
|
|
5283
6110
|
if (eq) return tv("int4", i + lb);
|
|
@@ -5757,12 +6584,12 @@ function parseByFormat(input, fmt) {
|
|
|
5757
6584
|
};
|
|
5758
6585
|
const readNum = (maxLen) => {
|
|
5759
6586
|
skipInputSeparators();
|
|
5760
|
-
const
|
|
5761
|
-
while (i < input.length && /[0-9]/.test(input[i]) && i -
|
|
5762
|
-
if (i ===
|
|
6587
|
+
const start2 = i;
|
|
6588
|
+
while (i < input.length && /[0-9]/.test(input[i]) && i - start2 < maxLen) i++;
|
|
6589
|
+
if (i === start2) {
|
|
5763
6590
|
throw pgError("invalid_datetime", `invalid value for field in source string "${input}"`, "22007");
|
|
5764
6591
|
}
|
|
5765
|
-
return Number(input.slice(
|
|
6592
|
+
return Number(input.slice(start2, i));
|
|
5766
6593
|
};
|
|
5767
6594
|
const readMonthName = () => {
|
|
5768
6595
|
skipInputSeparators();
|
|
@@ -7067,6 +7894,22 @@ function exactDegTan(x) {
|
|
|
7067
7894
|
return Math.tan(x * Math.PI / 180);
|
|
7068
7895
|
}
|
|
7069
7896
|
|
|
7897
|
+
// src/sql/deparse.ts
|
|
7898
|
+
function quoteIdent(name) {
|
|
7899
|
+
if (/^[a-z_][a-z0-9_$]*$/.test(name)) return name;
|
|
7900
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
7901
|
+
}
|
|
7902
|
+
|
|
7903
|
+
// src/runtime/database-context.ts
|
|
7904
|
+
var contexts = /* @__PURE__ */ new WeakMap();
|
|
7905
|
+
var fallback = { name: "postgres", names: () => ["postgres"] };
|
|
7906
|
+
function databaseCatalogContext(state) {
|
|
7907
|
+
return contexts.get(state) ?? fallback;
|
|
7908
|
+
}
|
|
7909
|
+
function setDatabaseCatalogContext(state, context) {
|
|
7910
|
+
contexts.set(state, context);
|
|
7911
|
+
}
|
|
7912
|
+
|
|
7070
7913
|
// src/functions/misc-fns.ts
|
|
7071
7914
|
var PG_VERSION_TEXT = "PostgreSQL 18.3 (postgres-mem) on TypeScript, in-memory engine";
|
|
7072
7915
|
function findSequenceForCall(ctx, name) {
|
|
@@ -7150,8 +7993,8 @@ function sequenceNextval(ctx, seq) {
|
|
|
7150
7993
|
function getMiscFunctions() {
|
|
7151
7994
|
const m = /* @__PURE__ */ new Map();
|
|
7152
7995
|
m.set("version", () => tv("text", PG_VERSION_TEXT));
|
|
7153
|
-
m.set("current_database", () => tv("name",
|
|
7154
|
-
m.set("current_catalog", () => tv("name",
|
|
7996
|
+
m.set("current_database", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
|
|
7997
|
+
m.set("current_catalog", (ctx) => tv("name", databaseCatalogContext(ctx.state).name));
|
|
7155
7998
|
m.set("current_schema", (ctx) => {
|
|
7156
7999
|
const path = ctx.state.effectiveSearchPath();
|
|
7157
8000
|
const first = path.find((s) => ctx.state.schemas.has(s));
|
|
@@ -7273,7 +8116,7 @@ function getMiscFunctions() {
|
|
|
7273
8116
|
if (!col.identity) return tv("text", null);
|
|
7274
8117
|
return tv(
|
|
7275
8118
|
"text",
|
|
7276
|
-
col.identity.sequence.split(".").map(
|
|
8119
|
+
col.identity.sequence.split(".").map(quoteIdent).join(".")
|
|
7277
8120
|
);
|
|
7278
8121
|
})
|
|
7279
8122
|
);
|
|
@@ -7581,15 +8424,15 @@ var decoderUtf8 = new TextDecoder("utf-8", { fatal: false });
|
|
|
7581
8424
|
function chars(s) {
|
|
7582
8425
|
return [...s];
|
|
7583
8426
|
}
|
|
7584
|
-
function substrImpl(s,
|
|
8427
|
+
function substrImpl(s, start2, len) {
|
|
7585
8428
|
const cs = chars(s);
|
|
7586
8429
|
if (len === void 0) {
|
|
7587
|
-
const from2 = Math.max(
|
|
8430
|
+
const from2 = Math.max(start2, 1);
|
|
7588
8431
|
return cs.slice(from2 - 1).join("");
|
|
7589
8432
|
}
|
|
7590
8433
|
if (len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
|
|
7591
|
-
const end =
|
|
7592
|
-
const from = Math.max(
|
|
8434
|
+
const end = start2 + len;
|
|
8435
|
+
const from = Math.max(start2, 1);
|
|
7593
8436
|
const to = Math.max(end, 1);
|
|
7594
8437
|
if (to <= from) return "";
|
|
7595
8438
|
return cs.slice(from - 1, to - 1).join("");
|
|
@@ -7653,14 +8496,14 @@ function getStringFunctions() {
|
|
|
7653
8496
|
const a = args2[0];
|
|
7654
8497
|
if (a.t === "bytea") {
|
|
7655
8498
|
const bytes = a.v;
|
|
7656
|
-
const
|
|
8499
|
+
const start3 = argInt(ctx, args2[1]);
|
|
7657
8500
|
const len2 = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
|
|
7658
|
-
return tv("bytea", byteaSubstr(bytes,
|
|
8501
|
+
return tv("bytea", byteaSubstr(bytes, start3, len2));
|
|
7659
8502
|
}
|
|
7660
8503
|
const s = argText(ctx, a);
|
|
7661
|
-
const
|
|
8504
|
+
const start2 = argInt(ctx, args2[1]);
|
|
7662
8505
|
const len = args2.length > 2 ? argInt(ctx, args2[2]) : void 0;
|
|
7663
|
-
return tv("text", substrImpl(s,
|
|
8506
|
+
return tv("text", substrImpl(s, start2, len));
|
|
7664
8507
|
})
|
|
7665
8508
|
);
|
|
7666
8509
|
m.set(
|
|
@@ -7956,11 +8799,11 @@ function getStringFunctions() {
|
|
|
7956
8799
|
strict("int4", (ctx, args2) => {
|
|
7957
8800
|
const s = argText(ctx, args2[0]);
|
|
7958
8801
|
const pattern = argText(ctx, args2[1]);
|
|
7959
|
-
const
|
|
8802
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
7960
8803
|
const flags = args2.length > 3 ? argText(ctx, args2[3]) : "";
|
|
7961
|
-
if (
|
|
8804
|
+
if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
|
|
7962
8805
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
7963
|
-
const sub = chars(s).slice(
|
|
8806
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
7964
8807
|
let count = 0;
|
|
7965
8808
|
for (const _ of sub.matchAll(js)) count++;
|
|
7966
8809
|
return tv("int4", count);
|
|
@@ -7981,11 +8824,11 @@ function getStringFunctions() {
|
|
|
7981
8824
|
strict("text", (ctx, args2) => {
|
|
7982
8825
|
const s = argText(ctx, args2[0]);
|
|
7983
8826
|
const pattern = argText(ctx, args2[1]);
|
|
7984
|
-
const
|
|
8827
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
7985
8828
|
const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
|
|
7986
8829
|
const flags = args2.length > 4 ? argText(ctx, args2[4]) : "";
|
|
7987
8830
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
7988
|
-
const sub = chars(s).slice(
|
|
8831
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
7989
8832
|
let i = 0;
|
|
7990
8833
|
for (const match of sub.matchAll(js)) {
|
|
7991
8834
|
i++;
|
|
@@ -7999,21 +8842,21 @@ function getStringFunctions() {
|
|
|
7999
8842
|
strict("int4", (ctx, args2) => {
|
|
8000
8843
|
const s = argText(ctx, args2[0]);
|
|
8001
8844
|
const pattern = argText(ctx, args2[1]);
|
|
8002
|
-
const
|
|
8845
|
+
const start2 = args2.length > 2 ? argInt(ctx, args2[2]) : 1;
|
|
8003
8846
|
const nth = args2.length > 3 ? argInt(ctx, args2[3]) : 1;
|
|
8004
8847
|
const endoption = args2.length > 4 ? argInt(ctx, args2[4]) : 0;
|
|
8005
8848
|
const flags = args2.length > 5 ? argText(ctx, args2[5]) : "";
|
|
8006
|
-
if (
|
|
8849
|
+
if (start2 < 1) throw pgError("invalid_parameter_value", `invalid value for parameter "start": ${start2}`);
|
|
8007
8850
|
if (endoption !== 0 && endoption !== 1) {
|
|
8008
8851
|
throw pgError("invalid_parameter_value", `invalid value for parameter "endoption": ${endoption}`);
|
|
8009
8852
|
}
|
|
8010
8853
|
const js = new RegExp(pgRegexToJs(pattern), `${regexFlags(flags, true)}g`);
|
|
8011
|
-
const sub = chars(s).slice(
|
|
8854
|
+
const sub = chars(s).slice(start2 - 1).join("");
|
|
8012
8855
|
let i = 0;
|
|
8013
8856
|
for (const match of sub.matchAll(js)) {
|
|
8014
8857
|
i++;
|
|
8015
8858
|
if (i === nth) {
|
|
8016
|
-
const base =
|
|
8859
|
+
const base = start2 - 1 + chars(sub.slice(0, match.index)).length;
|
|
8017
8860
|
return tv("int4", endoption === 0 ? base + 1 : base + chars(match[0]).length + 1);
|
|
8018
8861
|
}
|
|
8019
8862
|
}
|
|
@@ -8127,10 +8970,10 @@ function getStringFunctions() {
|
|
|
8127
8970
|
);
|
|
8128
8971
|
return m;
|
|
8129
8972
|
}
|
|
8130
|
-
function byteaSubstr(bytes,
|
|
8973
|
+
function byteaSubstr(bytes, start2, len) {
|
|
8131
8974
|
if (len !== void 0 && len < 0) throw pgError("substring_error", "negative substring length not allowed", "22011");
|
|
8132
|
-
const from = Math.max(
|
|
8133
|
-
const to = len === void 0 ? bytes.length : Math.max(
|
|
8975
|
+
const from = Math.max(start2, 1) - 1;
|
|
8976
|
+
const to = len === void 0 ? bytes.length : Math.max(start2 + len - 1, 0);
|
|
8134
8977
|
return bytes.slice(from, to);
|
|
8135
8978
|
}
|
|
8136
8979
|
function pad(s, len, fill, left) {
|
|
@@ -10692,7 +11535,7 @@ function evalOverlaps(ctx, scope, args2) {
|
|
|
10692
11535
|
};
|
|
10693
11536
|
const [s1r, e1r] = pair(args2[0]);
|
|
10694
11537
|
const [s2r, e2r] = pair(args2[1]);
|
|
10695
|
-
const resolveEnd = (
|
|
11538
|
+
const resolveEnd = (start2, end) => end.t === "interval" ? evalBinary(ctx, "+", start2, end) : end;
|
|
10696
11539
|
const e1 = resolveEnd(s1r, e1r);
|
|
10697
11540
|
const e2 = resolveEnd(s2r, e2r);
|
|
10698
11541
|
const target = [s1r, e1, s2r, e2].map((x) => x.t).find((t) => t !== "unknown") ?? "timestamp";
|
|
@@ -11951,7 +12794,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11951
12794
|
}
|
|
11952
12795
|
if (a.t === "timestamp" || a.t === "timestamptz" || b.t === "timestamp" || b.t === "timestamptz" || a.t === "date") {
|
|
11953
12796
|
const t2 = a.t === "timestamptz" || b.t === "timestamptz" ? "timestamptz" : "timestamp";
|
|
11954
|
-
const
|
|
12797
|
+
const start3 = castTo(ctx, a, t2, { explicit: true }).v;
|
|
11955
12798
|
const stop3 = castTo(ctx, b, t2, { explicit: true }).v;
|
|
11956
12799
|
const stepArg = args2[2];
|
|
11957
12800
|
if (stepArg === void 0 || !isInterval(stepArg.v)) {
|
|
@@ -11966,7 +12809,7 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11966
12809
|
if (step2.months !== 0) {
|
|
11967
12810
|
let i = 0;
|
|
11968
12811
|
for (; ; ) {
|
|
11969
|
-
const { days, tod } = splitTs(
|
|
12812
|
+
const { days, tod } = splitTs(start3);
|
|
11970
12813
|
const c = daysToCivil(days);
|
|
11971
12814
|
const totalMonths = c.y * 12 + (c.m - 1) + step2.months * i;
|
|
11972
12815
|
const y = Math.floor(totalMonths / 12);
|
|
@@ -11983,19 +12826,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
11983
12826
|
const stepMicros = intervalTotalMicros(step2);
|
|
11984
12827
|
if (stepMicros === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
11985
12828
|
if (stepMicros > 0n) {
|
|
11986
|
-
for (let cur =
|
|
12829
|
+
for (let cur = start3; cur <= stop3; cur += stepMicros) rows2.push([cur]);
|
|
11987
12830
|
} else {
|
|
11988
|
-
for (let cur =
|
|
12831
|
+
for (let cur = start3; cur >= stop3; cur += stepMicros) rows2.push([cur]);
|
|
11989
12832
|
}
|
|
11990
12833
|
return { columns: [{ name: alias, type: t2 }], rows: rows2 };
|
|
11991
12834
|
}
|
|
11992
12835
|
if (a.t === "numeric" || b.t === "numeric" || args2[2]?.t === "numeric") {
|
|
11993
|
-
const
|
|
12836
|
+
const start3 = castTo(ctx, a, "numeric", { explicit: true }).v;
|
|
11994
12837
|
const stop3 = castTo(ctx, b, "numeric", { explicit: true }).v;
|
|
11995
12838
|
const step2 = args2[2] ? castTo(ctx, args2[2], "numeric", { explicit: true }).v : castTo(ctx, tv("int4", 1), "numeric", { explicit: true }).v;
|
|
11996
12839
|
if (numericSign(step2) === 0) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
11997
12840
|
const rows2 = [];
|
|
11998
|
-
let cur =
|
|
12841
|
+
let cur = start3;
|
|
11999
12842
|
const ascending = numericSign(step2) > 0;
|
|
12000
12843
|
while (ascending ? numericCmp(cur, stop3) <= 0 : numericCmp(cur, stop3) >= 0) {
|
|
12001
12844
|
rows2.push([cur]);
|
|
@@ -12007,19 +12850,19 @@ SRFS.set("generate_series", (ctx, args2, alias) => {
|
|
|
12007
12850
|
}
|
|
12008
12851
|
const useBig = a.t === "int8" || b.t === "int8" || args2[2]?.t === "int8";
|
|
12009
12852
|
const t = useBig ? "int8" : "int4";
|
|
12010
|
-
const
|
|
12853
|
+
const start2 = castTo(ctx, a, "int8", {}).v;
|
|
12011
12854
|
const stop2 = castTo(ctx, b, "int8", {}).v;
|
|
12012
12855
|
const step = args2[2] ? castTo(ctx, args2[2], "int8", {}).v : 1n;
|
|
12013
12856
|
if (step === 0n) throw pgError("invalid_parameter_value", "step size cannot equal zero", "22023");
|
|
12014
12857
|
const rows = [];
|
|
12015
12858
|
if (step > 0n) {
|
|
12016
|
-
for (let cur =
|
|
12859
|
+
for (let cur = start2; cur <= stop2; cur += step) {
|
|
12017
12860
|
rows.push([useBig ? cur : Number(cur)]);
|
|
12018
12861
|
if (rows.length > 1e7)
|
|
12019
12862
|
throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
|
|
12020
12863
|
}
|
|
12021
12864
|
} else {
|
|
12022
|
-
for (let cur =
|
|
12865
|
+
for (let cur = start2; cur >= stop2; cur += step) {
|
|
12023
12866
|
rows.push([useBig ? cur : Number(cur)]);
|
|
12024
12867
|
if (rows.length > 1e7)
|
|
12025
12868
|
throw pgError("program_limit_exceeded", "generate_series result set too large", "54000");
|
|
@@ -12449,10 +13292,10 @@ function tokenize(sql) {
|
|
|
12449
13292
|
const m = /^\$([A-Za-z_\u0080-\uffff][A-Za-z0-9_\u0080-\uffff]*)?\$/.exec(sql.slice(i));
|
|
12450
13293
|
if (m) {
|
|
12451
13294
|
const open = m[0];
|
|
12452
|
-
const
|
|
12453
|
-
const end = sql.indexOf(open,
|
|
13295
|
+
const start2 = i + open.length;
|
|
13296
|
+
const end = sql.indexOf(open, start2);
|
|
12454
13297
|
if (end === -1) syntaxError("unterminated dollar-quoted string");
|
|
12455
|
-
push("string", sql.slice(
|
|
13298
|
+
push("string", sql.slice(start2, end), i);
|
|
12456
13299
|
i = end + open.length;
|
|
12457
13300
|
continue;
|
|
12458
13301
|
}
|
|
@@ -12519,14 +13362,14 @@ function tokenize(sql) {
|
|
|
12519
13362
|
continue;
|
|
12520
13363
|
}
|
|
12521
13364
|
if (/\d/.test(c) || c === "." && /\d/.test(sql[i + 1] ?? "")) {
|
|
12522
|
-
const
|
|
13365
|
+
const start2 = i;
|
|
12523
13366
|
const radix = /^0[xX][0-9a-fA-F_]+|^0[oO][0-7_]+|^0[bB][01_]+/.exec(sql.slice(i));
|
|
12524
13367
|
if (radix) {
|
|
12525
13368
|
const raw2 = radix[0];
|
|
12526
13369
|
i += raw2.length;
|
|
12527
13370
|
const clean = raw2.replaceAll("_", "");
|
|
12528
13371
|
const v = BigInt(clean);
|
|
12529
|
-
push("number", v.toString(),
|
|
13372
|
+
push("number", v.toString(), start2);
|
|
12530
13373
|
continue;
|
|
12531
13374
|
}
|
|
12532
13375
|
let j = i;
|
|
@@ -12548,7 +13391,7 @@ function tokenize(sql) {
|
|
|
12548
13391
|
if (j < n && IDENT_START.test(sql[j])) {
|
|
12549
13392
|
syntaxError(`trailing junk after numeric literal at or near "${sql.slice(i, j + 1)}"`);
|
|
12550
13393
|
}
|
|
12551
|
-
push("number", raw,
|
|
13394
|
+
push("number", raw, start2);
|
|
12552
13395
|
i = j;
|
|
12553
13396
|
continue;
|
|
12554
13397
|
}
|
|
@@ -12606,9 +13449,9 @@ function tokenize(sql) {
|
|
|
12606
13449
|
push("eof", "", i);
|
|
12607
13450
|
return tokens;
|
|
12608
13451
|
}
|
|
12609
|
-
function scanStandardString(sql,
|
|
13452
|
+
function scanStandardString(sql, start2) {
|
|
12610
13453
|
let out = "";
|
|
12611
|
-
let i =
|
|
13454
|
+
let i = start2 + 1;
|
|
12612
13455
|
const n = sql.length;
|
|
12613
13456
|
for (; ; ) {
|
|
12614
13457
|
if (i >= n) syntaxError("unterminated quoted string");
|
|
@@ -12636,9 +13479,9 @@ function scanStandardString(sql, start) {
|
|
|
12636
13479
|
i++;
|
|
12637
13480
|
}
|
|
12638
13481
|
}
|
|
12639
|
-
function scanEscapeString(sql,
|
|
13482
|
+
function scanEscapeString(sql, start2) {
|
|
12640
13483
|
let out = "";
|
|
12641
|
-
let i =
|
|
13484
|
+
let i = start2 + 1;
|
|
12642
13485
|
const n = sql.length;
|
|
12643
13486
|
for (; ; ) {
|
|
12644
13487
|
if (i >= n) syntaxError("unterminated quoted string");
|
|
@@ -14523,14 +15366,19 @@ var Parser = class {
|
|
|
14523
15366
|
parseIdentityOptions() {
|
|
14524
15367
|
const options2 = {};
|
|
14525
15368
|
if (this.eatPunct("(")) {
|
|
14526
|
-
Object.assign(options2, this.parseSequenceOptions());
|
|
15369
|
+
Object.assign(options2, this.parseSequenceOptions(true));
|
|
14527
15370
|
this.expectPunct(")");
|
|
14528
15371
|
}
|
|
14529
15372
|
return options2;
|
|
14530
15373
|
}
|
|
14531
|
-
parseSequenceOptions() {
|
|
15374
|
+
parseSequenceOptions(identity = false) {
|
|
14532
15375
|
const options2 = {};
|
|
14533
15376
|
for (; ; ) {
|
|
15377
|
+
if (identity && this.eatKw("sequence")) {
|
|
15378
|
+
this.expectKw("name");
|
|
15379
|
+
options2.sequenceName = this.qualifiedName();
|
|
15380
|
+
continue;
|
|
15381
|
+
}
|
|
14534
15382
|
if (this.eatKw("increment")) {
|
|
14535
15383
|
this.eatKw("by");
|
|
14536
15384
|
options2.increment = this.parseSignedBigInt();
|
|
@@ -16373,14 +17221,14 @@ var Parser = class {
|
|
|
16373
17221
|
let frame = null;
|
|
16374
17222
|
if (this.atKw("range") || this.atKw("rows") || this.atKw("groups")) {
|
|
16375
17223
|
const mode = this.next().value;
|
|
16376
|
-
let
|
|
17224
|
+
let start2;
|
|
16377
17225
|
let end = null;
|
|
16378
17226
|
if (this.eatKw("between")) {
|
|
16379
|
-
|
|
17227
|
+
start2 = this.parseFrameBound();
|
|
16380
17228
|
this.expectKw("and");
|
|
16381
17229
|
end = this.parseFrameBound();
|
|
16382
17230
|
} else {
|
|
16383
|
-
|
|
17231
|
+
start2 = this.parseFrameBound();
|
|
16384
17232
|
}
|
|
16385
17233
|
let exclusion = null;
|
|
16386
17234
|
if (this.eatKw("exclude")) {
|
|
@@ -16397,7 +17245,7 @@ var Parser = class {
|
|
|
16397
17245
|
exclusion = "no_others";
|
|
16398
17246
|
}
|
|
16399
17247
|
}
|
|
16400
|
-
frame = { mode, start, end, exclusion };
|
|
17248
|
+
frame = { mode, start: start2, end, exclusion };
|
|
16401
17249
|
}
|
|
16402
17250
|
return { name, partitionBy, orderBy, frame };
|
|
16403
17251
|
}
|
|
@@ -16643,7 +17491,7 @@ var PlParser = class {
|
|
|
16643
17491
|
return { kind: "assign", target, expr: this.parseExprUntilSemi() };
|
|
16644
17492
|
}
|
|
16645
17493
|
parseSqlTextUntilSemi() {
|
|
16646
|
-
const
|
|
17494
|
+
const start2 = this.peek().pos;
|
|
16647
17495
|
let depth = 0;
|
|
16648
17496
|
for (; ; ) {
|
|
16649
17497
|
const t = this.peek();
|
|
@@ -16651,7 +17499,7 @@ var PlParser = class {
|
|
|
16651
17499
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16652
17500
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16653
17501
|
if (t.type === "punct" && t.value === ";" && depth === 0) {
|
|
16654
|
-
const text = this.src.slice(
|
|
17502
|
+
const text = this.src.slice(start2, t.pos).trim();
|
|
16655
17503
|
this.pos++;
|
|
16656
17504
|
return text;
|
|
16657
17505
|
}
|
|
@@ -16659,7 +17507,7 @@ var PlParser = class {
|
|
|
16659
17507
|
}
|
|
16660
17508
|
}
|
|
16661
17509
|
parseExprUntilSemi() {
|
|
16662
|
-
const
|
|
17510
|
+
const start2 = this.peek().pos;
|
|
16663
17511
|
let depth = 0;
|
|
16664
17512
|
for (; ; ) {
|
|
16665
17513
|
const t = this.peek();
|
|
@@ -16667,7 +17515,7 @@ var PlParser = class {
|
|
|
16667
17515
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16668
17516
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16669
17517
|
if (t.type === "punct" && t.value === ";" && depth === 0) {
|
|
16670
|
-
const text = this.src.slice(
|
|
17518
|
+
const text = this.src.slice(start2, t.pos);
|
|
16671
17519
|
this.pos++;
|
|
16672
17520
|
return parseSqlExpr(text);
|
|
16673
17521
|
}
|
|
@@ -16675,7 +17523,7 @@ var PlParser = class {
|
|
|
16675
17523
|
}
|
|
16676
17524
|
}
|
|
16677
17525
|
parseExprUntilKw(kw) {
|
|
16678
|
-
const
|
|
17526
|
+
const start2 = this.peek().pos;
|
|
16679
17527
|
let depth = 0;
|
|
16680
17528
|
let caseDepth = 0;
|
|
16681
17529
|
for (; ; ) {
|
|
@@ -16686,7 +17534,7 @@ var PlParser = class {
|
|
|
16686
17534
|
if (t.type === "ident" && t.value === "case" && depth === 0) caseDepth++;
|
|
16687
17535
|
if (t.type === "ident" && t.value === "end" && depth === 0 && caseDepth > 0) caseDepth--;
|
|
16688
17536
|
if (t.type === "ident" && t.value === kw && depth === 0 && caseDepth === 0) {
|
|
16689
|
-
const text = this.src.slice(
|
|
17537
|
+
const text = this.src.slice(start2, t.pos);
|
|
16690
17538
|
this.pos++;
|
|
16691
17539
|
return parseSqlExpr(text);
|
|
16692
17540
|
}
|
|
@@ -16694,7 +17542,7 @@ var PlParser = class {
|
|
|
16694
17542
|
}
|
|
16695
17543
|
}
|
|
16696
17544
|
parseQueryUntilKw(kw) {
|
|
16697
|
-
const
|
|
17545
|
+
const start2 = this.peek().pos;
|
|
16698
17546
|
let depth = 0;
|
|
16699
17547
|
for (; ; ) {
|
|
16700
17548
|
const t = this.peek();
|
|
@@ -16702,7 +17550,7 @@ var PlParser = class {
|
|
|
16702
17550
|
if (t.type === "punct" && t.value === "(") depth++;
|
|
16703
17551
|
if (t.type === "punct" && t.value === ")") depth--;
|
|
16704
17552
|
if (t.type === "ident" && t.value === kw && depth === 0) {
|
|
16705
|
-
const text = this.src.slice(
|
|
17553
|
+
const text = this.src.slice(start2, t.pos).trim();
|
|
16706
17554
|
this.pos++;
|
|
16707
17555
|
const stmts = parse(text);
|
|
16708
17556
|
const q = stmts[0];
|
|
@@ -18245,7 +19093,7 @@ function buildFrom(env, items, where = null) {
|
|
|
18245
19093
|
}
|
|
18246
19094
|
}
|
|
18247
19095
|
if (cols === null) {
|
|
18248
|
-
const
|
|
19096
|
+
const probe2 = materializeItem(
|
|
18249
19097
|
env,
|
|
18250
19098
|
item,
|
|
18251
19099
|
new RowScope(
|
|
@@ -18255,8 +19103,8 @@ function buildFrom(env, items, where = null) {
|
|
|
18255
19103
|
accVars
|
|
18256
19104
|
)
|
|
18257
19105
|
);
|
|
18258
|
-
cols = [...accRel.columns, ...
|
|
18259
|
-
vars = /* @__PURE__ */ new Set([...accVars, ...
|
|
19106
|
+
cols = [...accRel.columns, ...probe2.rel.columns];
|
|
19107
|
+
vars = /* @__PURE__ */ new Set([...accVars, ...probe2.rangeVars]);
|
|
18260
19108
|
}
|
|
18261
19109
|
acc = { rel: { columns: cols, rows }, rangeVars: vars };
|
|
18262
19110
|
} else {
|
|
@@ -19016,13 +19864,13 @@ function expandTargets(core, columns) {
|
|
|
19016
19864
|
return items;
|
|
19017
19865
|
}
|
|
19018
19866
|
function checkPredicateType(env, columns, rangeVars, e, kind, extras) {
|
|
19019
|
-
const
|
|
19867
|
+
const probe2 = new RowScope(
|
|
19020
19868
|
columns,
|
|
19021
19869
|
columns.map(() => null),
|
|
19022
19870
|
env.outer,
|
|
19023
19871
|
rangeVars
|
|
19024
19872
|
);
|
|
19025
|
-
checkBoolExprType(env.ctx, makeEvalScope(env,
|
|
19873
|
+
checkBoolExprType(env.ctx, makeEvalScope(env, probe2, extras), e, kind);
|
|
19026
19874
|
}
|
|
19027
19875
|
function executeCore(env0, core, orderBy, opts) {
|
|
19028
19876
|
const env = env0;
|
|
@@ -19112,8 +19960,8 @@ function executeCore(env0, core, orderBy, opts) {
|
|
|
19112
19960
|
const groupValues = groupDefs.map((d, di) => {
|
|
19113
19961
|
if (!activeSet[di]) {
|
|
19114
19962
|
if (rows.length > 0) {
|
|
19115
|
-
const
|
|
19116
|
-
return tv(
|
|
19963
|
+
const probe2 = evalScalar(env, repScope, d.expr);
|
|
19964
|
+
return tv(probe2.t, null);
|
|
19117
19965
|
}
|
|
19118
19966
|
return tv(UNKNOWN, null);
|
|
19119
19967
|
}
|
|
@@ -20266,8 +21114,8 @@ function executeTransaction(env, stmt) {
|
|
|
20266
21114
|
var defaultSettings = null;
|
|
20267
21115
|
function getDefaultSettings() {
|
|
20268
21116
|
if (!defaultSettings) {
|
|
20269
|
-
const
|
|
20270
|
-
defaultSettings = new Map(
|
|
21117
|
+
const probe2 = new DatabaseState(new Prng(1), () => /* @__PURE__ */ new Date(0));
|
|
21118
|
+
defaultSettings = new Map(probe2.settings);
|
|
20271
21119
|
}
|
|
20272
21120
|
return defaultSettings;
|
|
20273
21121
|
}
|
|
@@ -20532,6 +21380,27 @@ function parseCsvLine(line, delim, quote) {
|
|
|
20532
21380
|
}
|
|
20533
21381
|
return fields;
|
|
20534
21382
|
}
|
|
21383
|
+
function splitCsvRecords(data, quote) {
|
|
21384
|
+
const records = [];
|
|
21385
|
+
let start2 = 0;
|
|
21386
|
+
let quoted = false;
|
|
21387
|
+
for (let i = 0; i < data.length; i++) {
|
|
21388
|
+
const char = data[i];
|
|
21389
|
+
if (char === quote) {
|
|
21390
|
+
if (quoted && data[i + 1] === quote) i++;
|
|
21391
|
+
else quoted = !quoted;
|
|
21392
|
+
continue;
|
|
21393
|
+
}
|
|
21394
|
+
if (char === "\n" && !quoted) {
|
|
21395
|
+
const end = i > start2 && data[i - 1] === "\r" ? i - 1 : i;
|
|
21396
|
+
records.push(data.slice(start2, end));
|
|
21397
|
+
start2 = i + 1;
|
|
21398
|
+
}
|
|
21399
|
+
}
|
|
21400
|
+
if (quoted) throw pgError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
21401
|
+
if (start2 < data.length) records.push(data.slice(start2));
|
|
21402
|
+
return records;
|
|
21403
|
+
}
|
|
20535
21404
|
function executeCopyFromData(env, stmt, data) {
|
|
20536
21405
|
const format = String(stmt.options.format ?? "text").toLowerCase();
|
|
20537
21406
|
if (format !== "text" && format !== "csv") throw unsupported(`COPY format ${format}`);
|
|
@@ -20542,7 +21411,7 @@ function executeCopyFromData(env, stmt, data) {
|
|
|
20542
21411
|
const nullStr = String(stmt.options.null ?? (format === "csv" ? "" : "\\N"));
|
|
20543
21412
|
const quote = String(stmt.options.quote ?? '"');
|
|
20544
21413
|
const header = stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true";
|
|
20545
|
-
let lines = data.split(/\r?\n/);
|
|
21414
|
+
let lines = format === "csv" ? splitCsvRecords(data, quote) : data.split(/\r?\n/);
|
|
20546
21415
|
const endMarker = lines.indexOf("\\.");
|
|
20547
21416
|
if (endMarker !== -1) lines = lines.slice(0, endMarker);
|
|
20548
21417
|
if (lines.length > 0 && lines[lines.length - 1] === "") lines = lines.slice(0, -1);
|
|
@@ -21276,7 +22145,8 @@ function pgSettings(ctx) {
|
|
|
21276
22145
|
"pg_settings"
|
|
21277
22146
|
);
|
|
21278
22147
|
}
|
|
21279
|
-
function pgDatabase() {
|
|
22148
|
+
function pgDatabase(ctx) {
|
|
22149
|
+
const names = databaseCatalogContext(ctx.state).names();
|
|
21280
22150
|
return rel(
|
|
21281
22151
|
[
|
|
21282
22152
|
["oid", "oid"],
|
|
@@ -21285,7 +22155,7 @@ function pgDatabase() {
|
|
|
21285
22155
|
["datistemplate", "bool"],
|
|
21286
22156
|
["datallowconn", "bool"]
|
|
21287
22157
|
],
|
|
21288
|
-
[
|
|
22158
|
+
names.map((name, index) => [5 + index, name, 6, false, true]),
|
|
21289
22159
|
"pg_database"
|
|
21290
22160
|
);
|
|
21291
22161
|
}
|
|
@@ -21320,13 +22190,14 @@ function pgTrigger(ctx) {
|
|
|
21320
22190
|
"pg_trigger"
|
|
21321
22191
|
);
|
|
21322
22192
|
}
|
|
21323
|
-
var
|
|
22193
|
+
var catalogName = (state) => databaseCatalogContext(state).name;
|
|
21324
22194
|
function infoSchemata(state) {
|
|
22195
|
+
const catalog = catalogName(state);
|
|
21325
22196
|
const rows = [
|
|
21326
|
-
[
|
|
21327
|
-
[
|
|
22197
|
+
[catalog, "pg_catalog", OWNER],
|
|
22198
|
+
[catalog, "information_schema", OWNER]
|
|
21328
22199
|
];
|
|
21329
|
-
for (const s of state.schemas.values()) rows.push([
|
|
22200
|
+
for (const s of state.schemas.values()) rows.push([catalog, s.name, OWNER]);
|
|
21330
22201
|
return rel(
|
|
21331
22202
|
[
|
|
21332
22203
|
["catalog_name", "name"],
|
|
@@ -21338,13 +22209,14 @@ function infoSchemata(state) {
|
|
|
21338
22209
|
);
|
|
21339
22210
|
}
|
|
21340
22211
|
function infoTables(ctx) {
|
|
22212
|
+
const catalog = catalogName(ctx.state);
|
|
21341
22213
|
const rows = [];
|
|
21342
22214
|
for (const schema of ctx.state.schemas.values()) {
|
|
21343
22215
|
for (const t of schema.tables.values()) {
|
|
21344
|
-
rows.push([
|
|
22216
|
+
rows.push([catalog, t.schema, t.name, t.temp ? "LOCAL TEMPORARY" : "BASE TABLE", "YES"]);
|
|
21345
22217
|
}
|
|
21346
22218
|
for (const v of schema.views.values()) {
|
|
21347
|
-
if (!v.materialized) rows.push([
|
|
22219
|
+
if (!v.materialized) rows.push([catalog, v.schema, v.name, "VIEW", "NO"]);
|
|
21348
22220
|
}
|
|
21349
22221
|
}
|
|
21350
22222
|
return rel(
|
|
@@ -21360,6 +22232,7 @@ function infoTables(ctx) {
|
|
|
21360
22232
|
);
|
|
21361
22233
|
}
|
|
21362
22234
|
function infoColumns(ctx) {
|
|
22235
|
+
const catalog = catalogName(ctx.state);
|
|
21363
22236
|
const rows = [];
|
|
21364
22237
|
for (const t of allTables(ctx.state)) {
|
|
21365
22238
|
t.columns.forEach((c, i) => {
|
|
@@ -21370,7 +22243,7 @@ function infoColumns(ctx) {
|
|
|
21370
22243
|
const dataType = isArrayType(id) ? "ARRAY" : isEnumType(id) ? "USER-DEFINED" : typeDisplayName(id);
|
|
21371
22244
|
const udt = isArrayType(id) ? `_${arrayUdt(id)}` : isEnumType(id) ? id.slice(5).split(".").pop() : id;
|
|
21372
22245
|
rows.push([
|
|
21373
|
-
|
|
22246
|
+
catalog,
|
|
21374
22247
|
t.schema,
|
|
21375
22248
|
t.name,
|
|
21376
22249
|
c.name,
|
|
@@ -21415,10 +22288,11 @@ function arrayUdt(t) {
|
|
|
21415
22288
|
return isEnumType(elem) ? elem.slice(5).split(".").pop() : elem;
|
|
21416
22289
|
}
|
|
21417
22290
|
function infoViews(ctx) {
|
|
22291
|
+
const catalog = catalogName(ctx.state);
|
|
21418
22292
|
const rows = [];
|
|
21419
22293
|
for (const schema of ctx.state.schemas.values()) {
|
|
21420
22294
|
for (const v of schema.views.values()) {
|
|
21421
|
-
if (!v.materialized) rows.push([
|
|
22295
|
+
if (!v.materialized) rows.push([catalog, v.schema, v.name, null]);
|
|
21422
22296
|
}
|
|
21423
22297
|
}
|
|
21424
22298
|
return rel(
|
|
@@ -21433,10 +22307,11 @@ function infoViews(ctx) {
|
|
|
21433
22307
|
);
|
|
21434
22308
|
}
|
|
21435
22309
|
function infoSequences(ctx) {
|
|
22310
|
+
const catalog = catalogName(ctx.state);
|
|
21436
22311
|
const rows = [];
|
|
21437
22312
|
for (const s of allSequences(ctx.state)) {
|
|
21438
22313
|
rows.push([
|
|
21439
|
-
|
|
22314
|
+
catalog,
|
|
21440
22315
|
s.schema,
|
|
21441
22316
|
s.name,
|
|
21442
22317
|
typeDisplayName(s.dataType),
|
|
@@ -21464,11 +22339,12 @@ function infoSequences(ctx) {
|
|
|
21464
22339
|
);
|
|
21465
22340
|
}
|
|
21466
22341
|
function infoTableConstraints(ctx) {
|
|
22342
|
+
const catalog = catalogName(ctx.state);
|
|
21467
22343
|
const rows = [];
|
|
21468
22344
|
for (const t of allTables(ctx.state)) {
|
|
21469
22345
|
for (const c of t.constraints) {
|
|
21470
22346
|
const type = c.kind === "primary_key" ? "PRIMARY KEY" : c.kind === "unique" ? "UNIQUE" : c.kind === "check" ? "CHECK" : "FOREIGN KEY";
|
|
21471
|
-
rows.push([
|
|
22347
|
+
rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, type]);
|
|
21472
22348
|
}
|
|
21473
22349
|
}
|
|
21474
22350
|
return rel(
|
|
@@ -21486,12 +22362,13 @@ function infoTableConstraints(ctx) {
|
|
|
21486
22362
|
);
|
|
21487
22363
|
}
|
|
21488
22364
|
function infoKeyColumnUsage(ctx) {
|
|
22365
|
+
const catalog = catalogName(ctx.state);
|
|
21489
22366
|
const rows = [];
|
|
21490
22367
|
for (const t of allTables(ctx.state)) {
|
|
21491
22368
|
for (const c of t.constraints) {
|
|
21492
22369
|
if (c.kind !== "primary_key" && c.kind !== "unique" && c.kind !== "foreign_key") continue;
|
|
21493
22370
|
c.columns.forEach((col, i) => {
|
|
21494
|
-
rows.push([
|
|
22371
|
+
rows.push([catalog, t.schema, c.name, catalog, t.schema, t.name, col, i + 1]);
|
|
21495
22372
|
});
|
|
21496
22373
|
}
|
|
21497
22374
|
}
|
|
@@ -21511,12 +22388,13 @@ function infoKeyColumnUsage(ctx) {
|
|
|
21511
22388
|
);
|
|
21512
22389
|
}
|
|
21513
22390
|
function infoRoutines(ctx) {
|
|
22391
|
+
const catalog = catalogName(ctx.state);
|
|
21514
22392
|
const rows = [];
|
|
21515
22393
|
for (const schema of ctx.state.schemas.values()) {
|
|
21516
22394
|
for (const fns of schema.functions.values()) {
|
|
21517
22395
|
for (const f of fns) {
|
|
21518
22396
|
rows.push([
|
|
21519
|
-
|
|
22397
|
+
catalog,
|
|
21520
22398
|
f.schema,
|
|
21521
22399
|
f.name,
|
|
21522
22400
|
"FUNCTION",
|
|
@@ -21573,7 +22451,7 @@ function buildCatalogRelation(ctx, schema, name) {
|
|
|
21573
22451
|
case "pg_settings":
|
|
21574
22452
|
return pgSettings(ctx);
|
|
21575
22453
|
case "pg_database":
|
|
21576
|
-
return pgDatabase();
|
|
22454
|
+
return pgDatabase(ctx);
|
|
21577
22455
|
case "pg_roles":
|
|
21578
22456
|
case "pg_user":
|
|
21579
22457
|
return pgRoles();
|
|
@@ -21754,6 +22632,11 @@ var MAX_IDENT = 63;
|
|
|
21754
22632
|
function truncIdent(s) {
|
|
21755
22633
|
return s.length > MAX_IDENT ? s.slice(0, MAX_IDENT) : s;
|
|
21756
22634
|
}
|
|
22635
|
+
function identitySequenceTarget(env, tableSchema, defaultName, options2) {
|
|
22636
|
+
const parts = options2.sequenceName;
|
|
22637
|
+
if (!parts || parts.length === 0) return { schema: tableSchema, name: defaultName };
|
|
22638
|
+
return targetSchema(env, parts);
|
|
22639
|
+
}
|
|
21757
22640
|
var SEQ_LIMITS = {
|
|
21758
22641
|
int2: { min: -32768n, max: 32767n },
|
|
21759
22642
|
int4: { min: -2147483648n, max: 2147483647n },
|
|
@@ -21930,8 +22813,8 @@ function buildColumn(env, schema, tableName, def, out, taken) {
|
|
|
21930
22813
|
break;
|
|
21931
22814
|
}
|
|
21932
22815
|
case "generated_identity": {
|
|
21933
|
-
const
|
|
21934
|
-
const seq = buildSequence(env, schema,
|
|
22816
|
+
const target = identitySequenceTarget(env, schema, truncIdent(`${tableName}_${def.name}_seq`), c.options);
|
|
22817
|
+
const seq = buildSequence(env, target.schema, target.name, c.options, false);
|
|
21935
22818
|
if (!c.options.as) {
|
|
21936
22819
|
seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
|
|
21937
22820
|
const limits = SEQ_LIMITS[seq.dataType];
|
|
@@ -21940,7 +22823,7 @@ function buildColumn(env, schema, tableName, def, out, taken) {
|
|
|
21940
22823
|
}
|
|
21941
22824
|
seq.ownedBy = { table: tableName, column: def.name };
|
|
21942
22825
|
out.sequences.push(seq);
|
|
21943
|
-
col.identity = { always: c.always, sequence: `${schema.name}.${
|
|
22826
|
+
col.identity = { always: c.always, sequence: `${target.schema.name}.${target.name}` };
|
|
21944
22827
|
col.notNull = true;
|
|
21945
22828
|
break;
|
|
21946
22829
|
}
|
|
@@ -22076,10 +22959,11 @@ function executeCreateTable(env, stmt) {
|
|
|
22076
22959
|
throw pgError("invalid_table_definition", `multiple primary keys for table "${name}" are not allowed`, "42P16");
|
|
22077
22960
|
}
|
|
22078
22961
|
for (const seq of built.sequences) {
|
|
22079
|
-
|
|
22962
|
+
const seqSchema = state.getSchema(seq.schema);
|
|
22963
|
+
if (seqSchema.hasRelation(seq.name)) {
|
|
22080
22964
|
throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
|
|
22081
22965
|
}
|
|
22082
|
-
|
|
22966
|
+
seqSchema.sequences.set(seq.name, seq);
|
|
22083
22967
|
}
|
|
22084
22968
|
const table = new TableData(schema.name, name, built.columns, state.nextOid(), stmt.temp);
|
|
22085
22969
|
table.constraints = built.constraints;
|
|
@@ -22431,7 +23315,13 @@ function executeAlterTable(env, stmt) {
|
|
|
22431
23315
|
const built = { columns: [], constraints: [], sequences: [] };
|
|
22432
23316
|
const taken = constraintNames(table);
|
|
22433
23317
|
const col = buildColumn(env, schema, table.name, action.column, built, taken);
|
|
22434
|
-
for (const seq of built.sequences)
|
|
23318
|
+
for (const seq of built.sequences) {
|
|
23319
|
+
const seqSchema = state.getSchema(seq.schema);
|
|
23320
|
+
if (seqSchema.hasRelation(seq.name)) {
|
|
23321
|
+
throw pgError("duplicate_table", `relation "${seq.name}" already exists`, "42P07");
|
|
23322
|
+
}
|
|
23323
|
+
seqSchema.sequences.set(seq.name, seq);
|
|
23324
|
+
}
|
|
22435
23325
|
table.columns.push(col);
|
|
22436
23326
|
table.constraints.push(...built.constraints);
|
|
22437
23327
|
for (let i = 0; i < rows.length; i++) {
|
|
@@ -22751,12 +23641,32 @@ function executeAlterTable(env, stmt) {
|
|
|
22751
23641
|
"42P16"
|
|
22752
23642
|
);
|
|
22753
23643
|
}
|
|
22754
|
-
|
|
22755
|
-
|
|
23644
|
+
if (!col.notNull) {
|
|
23645
|
+
throw pgError(
|
|
23646
|
+
"object_not_in_prerequisite_state",
|
|
23647
|
+
`column "${action.column}" of relation "${table.name}" must be declared NOT NULL before identity can be added`,
|
|
23648
|
+
"55000"
|
|
23649
|
+
);
|
|
23650
|
+
}
|
|
23651
|
+
const target = identitySequenceTarget(
|
|
23652
|
+
env,
|
|
23653
|
+
schema,
|
|
23654
|
+
truncIdent(`${table.name}_${action.column}_seq`),
|
|
23655
|
+
action.options
|
|
23656
|
+
);
|
|
23657
|
+
if (target.schema.hasRelation(target.name)) {
|
|
23658
|
+
throw pgError("duplicate_table", `relation "${target.name}" already exists`, "42P07");
|
|
23659
|
+
}
|
|
23660
|
+
const seq = buildSequence(env, target.schema, target.name, action.options, false);
|
|
23661
|
+
if (!action.options.as) {
|
|
23662
|
+
seq.dataType = col.type.id === "int2" || col.type.id === "int4" || col.type.id === "int8" ? col.type.id : "int8";
|
|
23663
|
+
const limits = SEQ_LIMITS[seq.dataType];
|
|
23664
|
+
if (action.options.maxValue === void 0) seq.maxValue = seq.increment > 0n ? limits.max : -1n;
|
|
23665
|
+
if (action.options.minValue === void 0) seq.minValue = seq.increment > 0n ? 1n : limits.min;
|
|
23666
|
+
}
|
|
22756
23667
|
seq.ownedBy = { table: table.name, column: action.column };
|
|
22757
|
-
schema.sequences.set(
|
|
22758
|
-
col.identity = { always:
|
|
22759
|
-
col.notNull = true;
|
|
23668
|
+
target.schema.sequences.set(target.name, seq);
|
|
23669
|
+
col.identity = { always: action.always, sequence: `${target.schema.name}.${target.name}` };
|
|
22760
23670
|
break;
|
|
22761
23671
|
}
|
|
22762
23672
|
case "drop_identity": {
|
|
@@ -23625,11 +24535,11 @@ if (typeof disposeKey === "symbol") {
|
|
|
23625
24535
|
|
|
23626
24536
|
// src/wire/cluster.ts
|
|
23627
24537
|
var Cluster = class {
|
|
23628
|
-
constructor(db) {
|
|
23629
|
-
this.
|
|
23630
|
-
|
|
24538
|
+
constructor(db, defaultDatabaseName = "postgres") {
|
|
24539
|
+
this.defaultDatabaseName = defaultDatabaseName;
|
|
24540
|
+
this.installDatabase(defaultDatabaseName, db);
|
|
23631
24541
|
}
|
|
23632
|
-
|
|
24542
|
+
defaultDatabaseName;
|
|
23633
24543
|
turnHolder = null;
|
|
23634
24544
|
turnQueue = [];
|
|
23635
24545
|
locks = /* @__PURE__ */ new Map();
|
|
@@ -23638,6 +24548,87 @@ var Cluster = class {
|
|
|
23638
24548
|
sessions = /* @__PURE__ */ new Map();
|
|
23639
24549
|
current = null;
|
|
23640
24550
|
nextPid = 1e3;
|
|
24551
|
+
databases = /* @__PURE__ */ new Map();
|
|
24552
|
+
/** The initial database, retained for the server's backwards-compatible public handle. */
|
|
24553
|
+
get db() {
|
|
24554
|
+
return this.requireDatabase(this.defaultDatabaseName);
|
|
24555
|
+
}
|
|
24556
|
+
databaseNames() {
|
|
24557
|
+
return [...this.databases.keys()].sort();
|
|
24558
|
+
}
|
|
24559
|
+
snapshotAll() {
|
|
24560
|
+
return new Map([...this.databases].map(([name, db]) => [name, db.snapshot()]));
|
|
24561
|
+
}
|
|
24562
|
+
restoreAll(points) {
|
|
24563
|
+
if (this.sessions.size > 0) throw new Error("cannot restore a catalog with active sessions");
|
|
24564
|
+
if (!points.has(this.defaultDatabaseName)) throw new Error("snapshot lacks default database");
|
|
24565
|
+
for (const db of this.databases.values()) db.close();
|
|
24566
|
+
this.databases.clear();
|
|
24567
|
+
for (const [name, point] of points) this.installDatabase(name, point.open());
|
|
24568
|
+
}
|
|
24569
|
+
getDatabase(name) {
|
|
24570
|
+
return this.databases.get(name);
|
|
24571
|
+
}
|
|
24572
|
+
requireDatabase(name) {
|
|
24573
|
+
const db = this.databases.get(name);
|
|
24574
|
+
if (!db) throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
|
|
24575
|
+
return db;
|
|
24576
|
+
}
|
|
24577
|
+
createDatabase(name, template) {
|
|
24578
|
+
if (this.databases.has(name)) {
|
|
24579
|
+
throw new PostgresError("duplicate_object", `database "${name}" already exists`, "42P04");
|
|
24580
|
+
}
|
|
24581
|
+
const db = template === void 0 ? new Database({ now: this.db.now, seed: this.db.seed }) : this.requireTemplate(template).snapshot().open();
|
|
24582
|
+
this.installDatabase(name, db);
|
|
24583
|
+
}
|
|
24584
|
+
dropDatabase(name, requester) {
|
|
24585
|
+
if (name === requester.databaseName || this.activeConnections(name) > 0) {
|
|
24586
|
+
throw new PostgresError(
|
|
24587
|
+
"object_not_in_prerequisite_state",
|
|
24588
|
+
`database "${name}" is being accessed by other users`,
|
|
24589
|
+
"55006"
|
|
24590
|
+
);
|
|
24591
|
+
}
|
|
24592
|
+
if (!this.databases.delete(name)) {
|
|
24593
|
+
throw new PostgresError("undefined_object", `database "${name}" does not exist`, "3D000");
|
|
24594
|
+
}
|
|
24595
|
+
}
|
|
24596
|
+
renameDatabase(from, to, requester) {
|
|
24597
|
+
if (this.databases.has(to)) {
|
|
24598
|
+
throw new PostgresError("duplicate_object", `database "${to}" already exists`, "42P04");
|
|
24599
|
+
}
|
|
24600
|
+
const db = this.requireDatabase(from);
|
|
24601
|
+
if (from === requester.databaseName || this.activeConnections(from) > 0) {
|
|
24602
|
+
throw new PostgresError(
|
|
24603
|
+
"object_not_in_prerequisite_state",
|
|
24604
|
+
`database "${from}" is being accessed by other users`,
|
|
24605
|
+
"55006"
|
|
24606
|
+
);
|
|
24607
|
+
}
|
|
24608
|
+
this.databases.delete(from);
|
|
24609
|
+
this.installDatabase(to, db);
|
|
24610
|
+
}
|
|
24611
|
+
requireTemplate(name) {
|
|
24612
|
+
if (this.activeConnections(name) > 0) {
|
|
24613
|
+
throw new PostgresError(
|
|
24614
|
+
"object_not_in_prerequisite_state",
|
|
24615
|
+
`source database "${name}" is being accessed by other users`,
|
|
24616
|
+
"55006"
|
|
24617
|
+
);
|
|
24618
|
+
}
|
|
24619
|
+
return this.requireDatabase(name);
|
|
24620
|
+
}
|
|
24621
|
+
activeConnections(name) {
|
|
24622
|
+
let count = 0;
|
|
24623
|
+
for (const session of this.sessions.values()) if (session.databaseName === name) count++;
|
|
24624
|
+
return count;
|
|
24625
|
+
}
|
|
24626
|
+
installDatabase(name, db) {
|
|
24627
|
+
const context = { name, names: () => this.databaseNames() };
|
|
24628
|
+
setDatabaseCatalogContext(db.state, context);
|
|
24629
|
+
this.databases.set(name, db);
|
|
24630
|
+
registerSessionFunctions(this, db);
|
|
24631
|
+
}
|
|
23641
24632
|
newPid() {
|
|
23642
24633
|
return this.nextPid++;
|
|
23643
24634
|
}
|
|
@@ -23651,8 +24642,8 @@ var Cluster = class {
|
|
|
23651
24642
|
if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
|
|
23652
24643
|
return Promise.reject(deadlock());
|
|
23653
24644
|
}
|
|
23654
|
-
return new Promise((
|
|
23655
|
-
const waiter = { session, resolve, reject };
|
|
24645
|
+
return new Promise((resolve2, reject) => {
|
|
24646
|
+
const waiter = { session, resolve: resolve2, reject };
|
|
23656
24647
|
session.waitingForTurn = waiter;
|
|
23657
24648
|
this.turnQueue.push(waiter);
|
|
23658
24649
|
});
|
|
@@ -23683,8 +24674,8 @@ var Cluster = class {
|
|
|
23683
24674
|
if (holder.waitingForLock !== null && this.locks.get(holder.waitingForLock)?.holder === session) {
|
|
23684
24675
|
return Promise.reject(deadlock());
|
|
23685
24676
|
}
|
|
23686
|
-
return new Promise((
|
|
23687
|
-
const waiter = { session, resolve, reject };
|
|
24677
|
+
return new Promise((resolve2, reject) => {
|
|
24678
|
+
const waiter = { session, resolve: resolve2, reject };
|
|
23688
24679
|
session.waitingForLock = key;
|
|
23689
24680
|
const queue = this.lockQueues.get(key) ?? [];
|
|
23690
24681
|
queue.push(waiter);
|
|
@@ -23729,7 +24720,7 @@ var Cluster = class {
|
|
|
23729
24720
|
}
|
|
23730
24721
|
this.unlockAll(session);
|
|
23731
24722
|
if (this.turnHolder === session) {
|
|
23732
|
-
if (
|
|
24723
|
+
if (session.database.transactions.inTransaction) session.database.transactions.rollback();
|
|
23733
24724
|
this.releaseTurn(session);
|
|
23734
24725
|
}
|
|
23735
24726
|
}
|
|
@@ -23761,8 +24752,7 @@ var LockWait = class extends Error {
|
|
|
23761
24752
|
key;
|
|
23762
24753
|
};
|
|
23763
24754
|
var lockKey = (args2) => args2.map((a) => String(a)).join(":");
|
|
23764
|
-
var registerSessionFunctions = (cluster) => {
|
|
23765
|
-
const db = cluster.db;
|
|
24755
|
+
var registerSessionFunctions = (cluster, db) => {
|
|
23766
24756
|
const me = () => {
|
|
23767
24757
|
if (!cluster.current) throw new PostgresError("internal", "no session is executing", "XX000");
|
|
23768
24758
|
return cluster.current;
|
|
@@ -23929,13 +24919,24 @@ var backend = {
|
|
|
23929
24919
|
for (const oid of oids) w.i32(oid);
|
|
23930
24920
|
}),
|
|
23931
24921
|
notificationResponse: (pid, channel, payload) => message("A", (w) => w.i32(pid).cstring(channel).cstring(payload)),
|
|
24922
|
+
copyInResponse: (columns) => message("G", (w) => {
|
|
24923
|
+
w.u8(0).i16(columns);
|
|
24924
|
+
for (let i = 0; i < columns; i++) w.i16(0);
|
|
24925
|
+
}),
|
|
24926
|
+
copyOutResponse: (columns) => message("H", (w) => {
|
|
24927
|
+
w.u8(0).i16(columns);
|
|
24928
|
+
for (let i = 0; i < columns; i++) w.i16(0);
|
|
24929
|
+
}),
|
|
24930
|
+
copyData: (data) => message("d", (w) => w.bytes(data)),
|
|
24931
|
+
copyDone: () => message("c", () => {
|
|
24932
|
+
}),
|
|
23932
24933
|
/** The one-byte answers to SSLRequest / GSSENCRequest: no encryption here. */
|
|
23933
24934
|
no: () => new Uint8Array([78])
|
|
23934
24935
|
};
|
|
23935
24936
|
var ByteReader = class {
|
|
23936
|
-
constructor(bytes,
|
|
24937
|
+
constructor(bytes, start2 = 0) {
|
|
23937
24938
|
this.bytes = bytes;
|
|
23938
|
-
this.pos =
|
|
24939
|
+
this.pos = start2;
|
|
23939
24940
|
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
23940
24941
|
}
|
|
23941
24942
|
bytes;
|
|
@@ -24095,11 +25096,11 @@ var ScramServer = class {
|
|
|
24095
25096
|
// src/wire/split.ts
|
|
24096
25097
|
var splitStatements = (script) => {
|
|
24097
25098
|
const out = [];
|
|
24098
|
-
let
|
|
25099
|
+
let start2 = 0;
|
|
24099
25100
|
let i = 0;
|
|
24100
25101
|
const n = script.length;
|
|
24101
25102
|
const push = (end) => {
|
|
24102
|
-
const text = script.slice(
|
|
25103
|
+
const text = script.slice(start2, end).trim();
|
|
24103
25104
|
if (text.length > 0) out.push(text);
|
|
24104
25105
|
};
|
|
24105
25106
|
while (i < n) {
|
|
@@ -24155,7 +25156,7 @@ var splitStatements = (script) => {
|
|
|
24155
25156
|
}
|
|
24156
25157
|
if (c === ";") {
|
|
24157
25158
|
push(i);
|
|
24158
|
-
|
|
25159
|
+
start2 = i + 1;
|
|
24159
25160
|
}
|
|
24160
25161
|
i++;
|
|
24161
25162
|
}
|
|
@@ -24170,6 +25171,23 @@ var LISTEN = /^listen\s+("?)([A-Za-z_][\w$]*)\1\s*$/i;
|
|
|
24170
25171
|
var UNLISTEN = /^unlisten\s+(\*|"?[A-Za-z_][\w$]*"?)\s*$/i;
|
|
24171
25172
|
var NOTIFY = /^notify\s+("?)([A-Za-z_][\w$]*)\1\s*(?:,\s*'((?:[^']|'')*)')?\s*$/i;
|
|
24172
25173
|
var RETURNS_ROWS = /^(select|with|values|show|table|explain)\b/i;
|
|
25174
|
+
var CLUSTER_DDL = /^(create|drop|alter)\s+database\b/i;
|
|
25175
|
+
var IDENT = '(?:"((?:[^"]|"")+)"|([A-Za-z_][A-Za-z0-9_$]*))';
|
|
25176
|
+
var CREATE_DATABASE = new RegExp(
|
|
25177
|
+
`^create\\s+database\\s+${IDENT}(?:\\s+with)?(?:\\s+template\\s*(?:=\\s*)?${IDENT})?\\s*$`,
|
|
25178
|
+
"i"
|
|
25179
|
+
);
|
|
25180
|
+
var DROP_DATABASE = new RegExp(
|
|
25181
|
+
`^drop\\s+database\\s+(if\\s+exists\\s+)?${IDENT}(?:\\s+with\\s*\\(\\s*force\\s*\\))?\\s*$`,
|
|
25182
|
+
"i"
|
|
25183
|
+
);
|
|
25184
|
+
var RENAME_DATABASE = new RegExp(`^alter\\s+database\\s+${IDENT}\\s+rename\\s+to\\s+${IDENT}\\s*$`, "i");
|
|
25185
|
+
var identifier = (quoted, plain) => quoted === void 0 ? plain.toLowerCase() : quoted.replaceAll('""', '"');
|
|
25186
|
+
var ROW_LOCK = /\s+for\s+(?:no\s+key\s+update|key\s+share|update|share)(?:\s+of\s+(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s*,\s*(?:(?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?))*)?(?:\s+(nowait|skip\s+locked))?\s*$/i;
|
|
25187
|
+
var LIMIT = /\s+limit\s+(\d+)(?:\s+offset\s+(\d+))?\s*$/i;
|
|
25188
|
+
var FROM_TABLE = /\bfrom\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)/i;
|
|
25189
|
+
var UPDATE_TARGET = /^update\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?\s+set\s+[\s\S]*?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
|
|
25190
|
+
var DELETE_TARGET = /^delete\s+from\s+((?:"[^"]+"|[A-Za-z_][\w$]*)(?:\.(?:"[^"]+"|[A-Za-z_][\w$]*))?)(?:\s+(?:as\s+)?(?:"[^"]+"|[A-Za-z_][\w$]*))?(?:\s+where\s+([\s\S]*?))?(?:\s+returning\s+[\s\S]*)?$/i;
|
|
24173
25191
|
var TYPLEN = {
|
|
24174
25192
|
bool: 1,
|
|
24175
25193
|
int2: 2,
|
|
@@ -24262,6 +25280,7 @@ var binaryValue = (type, text) => {
|
|
|
24262
25280
|
}
|
|
24263
25281
|
};
|
|
24264
25282
|
var binaryEncodable = (type) => TEXT_TYPES2.has(type) || ["bool", "int2", "int4", "oid", "int8", "float4", "float8", "bytea", "uuid"].includes(type);
|
|
25283
|
+
var mutatesDatabase = (sql) => parse(sql).some((stmt) => !["select", "show", "explain", "transaction", "comment", "no_op"].includes(stmt.type));
|
|
24265
25284
|
var decodeParam = (oid, format, bytes) => {
|
|
24266
25285
|
if (bytes === null) return null;
|
|
24267
25286
|
if (format === 0) return utf83.decode(bytes);
|
|
@@ -24310,6 +25329,8 @@ var Connection = class {
|
|
|
24310
25329
|
this.socket = socket;
|
|
24311
25330
|
this.cluster = cluster;
|
|
24312
25331
|
this.options = options2;
|
|
25332
|
+
this.database = cluster.db;
|
|
25333
|
+
this.databaseName = cluster.defaultDatabaseName;
|
|
24313
25334
|
this.pid = cluster.newPid();
|
|
24314
25335
|
this.secret = Math.floor(Math.random() * 2147483647);
|
|
24315
25336
|
cluster.sessions.set(this.pid, this);
|
|
@@ -24325,6 +25346,8 @@ var Connection = class {
|
|
|
24325
25346
|
waitingForTurn = null;
|
|
24326
25347
|
waitingForLock = null;
|
|
24327
25348
|
pendingNotifies = [];
|
|
25349
|
+
database;
|
|
25350
|
+
databaseName;
|
|
24328
25351
|
frames = new FrameParser();
|
|
24329
25352
|
inbox = [];
|
|
24330
25353
|
pumping = false;
|
|
@@ -24341,6 +25364,10 @@ var Connection = class {
|
|
|
24341
25364
|
prepared = /* @__PURE__ */ new Map();
|
|
24342
25365
|
portals = /* @__PURE__ */ new Map();
|
|
24343
25366
|
notifications = [];
|
|
25367
|
+
copyIn = null;
|
|
25368
|
+
copyDrain = false;
|
|
25369
|
+
/** Successful writes in this session's READ COMMITTED transaction workspace. */
|
|
25370
|
+
transaction = null;
|
|
24344
25371
|
closed = false;
|
|
24345
25372
|
receive(chunk) {
|
|
24346
25373
|
let messages;
|
|
@@ -24418,6 +25445,20 @@ var Connection = class {
|
|
|
24418
25445
|
this.fatal("08P01", "expected startup or password message");
|
|
24419
25446
|
return;
|
|
24420
25447
|
}
|
|
25448
|
+
if (this.copyIn && message2.kind === "typed") {
|
|
25449
|
+
await this.handleCopyIn(message2.type, message2.body);
|
|
25450
|
+
return;
|
|
25451
|
+
}
|
|
25452
|
+
if (this.copyDrain && message2.kind === "typed") {
|
|
25453
|
+
if (message2.type === "c" || message2.type === "f") {
|
|
25454
|
+
this.copyDrain = false;
|
|
25455
|
+
this.readyForQuery();
|
|
25456
|
+
} else if (message2.type === "X") {
|
|
25457
|
+
this.socket.end();
|
|
25458
|
+
this.dispose();
|
|
25459
|
+
}
|
|
25460
|
+
return;
|
|
25461
|
+
}
|
|
24421
25462
|
if (this.skipUntilSync && message2.type !== "S" && message2.type !== "X") return;
|
|
24422
25463
|
try {
|
|
24423
25464
|
switch (message2.type) {
|
|
@@ -24468,10 +25509,13 @@ var Connection = class {
|
|
|
24468
25509
|
}
|
|
24469
25510
|
this.user = parameters.user ?? "postgres";
|
|
24470
25511
|
const requested = parameters.database && parameters.database.length > 0 ? parameters.database : this.user;
|
|
24471
|
-
|
|
25512
|
+
const database = this.cluster.getDatabase(requested);
|
|
25513
|
+
if (!database || this.options.database !== void 0 && requested !== this.options.database) {
|
|
24472
25514
|
this.fatal("3D000", `database "${requested}" does not exist`);
|
|
24473
25515
|
return;
|
|
24474
25516
|
}
|
|
25517
|
+
this.database = database;
|
|
25518
|
+
this.databaseName = requested;
|
|
24475
25519
|
if (this.options.password !== void 0) {
|
|
24476
25520
|
this.scram = new ScramServer(this.options.password);
|
|
24477
25521
|
this.write(backend.authenticationSASL(["SCRAM-SHA-256"]));
|
|
@@ -24535,7 +25579,7 @@ var Connection = class {
|
|
|
24535
25579
|
}
|
|
24536
25580
|
// --- execution ----------------------------------------------------------------------------
|
|
24537
25581
|
get inTransaction() {
|
|
24538
|
-
return this.
|
|
25582
|
+
return this.transaction !== null;
|
|
24539
25583
|
}
|
|
24540
25584
|
status() {
|
|
24541
25585
|
if (this.aborted) return "E";
|
|
@@ -24558,6 +25602,7 @@ var Connection = class {
|
|
|
24558
25602
|
return outcome;
|
|
24559
25603
|
} catch (error) {
|
|
24560
25604
|
if (this.inTransaction) this.aborted = true;
|
|
25605
|
+
if (error instanceof PostgresError && error.sqlState === "40P01") this.cluster.unlockAll(this, true);
|
|
24561
25606
|
const status = error instanceof PostgresError ? error.sqlState : "XX000";
|
|
24562
25607
|
this.options.onLog?.({ pid: this.pid, sql: text, durationMs: performance.now() - started, status });
|
|
24563
25608
|
throw error;
|
|
@@ -24572,8 +25617,8 @@ var Connection = class {
|
|
|
24572
25617
|
"25P02"
|
|
24573
25618
|
);
|
|
24574
25619
|
}
|
|
24575
|
-
const
|
|
24576
|
-
if (
|
|
25620
|
+
const listen2 = LISTEN.exec(text);
|
|
25621
|
+
if (listen2) return this.listen(listen2[2]);
|
|
24577
25622
|
const unlisten = UNLISTEN.exec(text);
|
|
24578
25623
|
if (unlisten) return this.unlisten(unlisten[1].replaceAll('"', ""));
|
|
24579
25624
|
const notify = NOTIFY.exec(text);
|
|
@@ -24582,7 +25627,7 @@ var Connection = class {
|
|
|
24582
25627
|
if (!this.inTransaction) this.flushNotifies();
|
|
24583
25628
|
return command("NOTIFY");
|
|
24584
25629
|
}
|
|
24585
|
-
|
|
25630
|
+
if (CLUSTER_DDL.test(text)) return this.executeClusterDdl(text);
|
|
24586
25631
|
for (; ; ) {
|
|
24587
25632
|
if (this.cancelled) throw pgError2("internal", "canceling statement due to user request", "57014");
|
|
24588
25633
|
await this.cluster.acquireTurn(this);
|
|
@@ -24590,9 +25635,9 @@ var Connection = class {
|
|
|
24590
25635
|
if (faults.delayStatementMs !== void 0) {
|
|
24591
25636
|
const ms = faults.delayStatementMs;
|
|
24592
25637
|
faults.delayStatementMs = void 0;
|
|
24593
|
-
await new Promise((
|
|
25638
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
24594
25639
|
}
|
|
24595
|
-
if (faults.dropConnection &&
|
|
25640
|
+
if (faults.dropConnection && this.inTransaction) {
|
|
24596
25641
|
faults.dropConnection = void 0;
|
|
24597
25642
|
this.destroy();
|
|
24598
25643
|
throw new ConnectionDropped();
|
|
@@ -24604,7 +25649,7 @@ var Connection = class {
|
|
|
24604
25649
|
} catch (error) {
|
|
24605
25650
|
if (error instanceof LockWait) {
|
|
24606
25651
|
const key = error.key;
|
|
24607
|
-
|
|
25652
|
+
this.cluster.releaseTurn(this);
|
|
24608
25653
|
await this.waitForLock(key);
|
|
24609
25654
|
continue;
|
|
24610
25655
|
}
|
|
@@ -24614,7 +25659,7 @@ var Connection = class {
|
|
|
24614
25659
|
}
|
|
24615
25660
|
}
|
|
24616
25661
|
waitForLock(key) {
|
|
24617
|
-
return new Promise((
|
|
25662
|
+
return new Promise((resolve2, reject) => {
|
|
24618
25663
|
this.lockWaiter = (error) => {
|
|
24619
25664
|
this.lockWaiter = null;
|
|
24620
25665
|
this.waitingForLock = null;
|
|
@@ -24623,7 +25668,7 @@ var Connection = class {
|
|
|
24623
25668
|
this.cluster.waitLock(this, key).then(
|
|
24624
25669
|
() => {
|
|
24625
25670
|
this.lockWaiter = null;
|
|
24626
|
-
|
|
25671
|
+
resolve2();
|
|
24627
25672
|
},
|
|
24628
25673
|
(error) => {
|
|
24629
25674
|
this.lockWaiter = null;
|
|
@@ -24634,57 +25679,196 @@ var Connection = class {
|
|
|
24634
25679
|
}
|
|
24635
25680
|
/** The engine call itself: statement-atomic, and transaction control handled as the server sees it. */
|
|
24636
25681
|
runStatement(text, params, control) {
|
|
24637
|
-
const db = this.cluster.db;
|
|
24638
|
-
const tx = db.transactions;
|
|
24639
25682
|
const faults = this.options.faults;
|
|
24640
25683
|
if (control && COMMIT.test(text)) {
|
|
24641
25684
|
if (this.aborted) {
|
|
24642
|
-
|
|
25685
|
+
this.transaction = null;
|
|
24643
25686
|
this.aborted = false;
|
|
24644
25687
|
return command("ROLLBACK");
|
|
24645
25688
|
}
|
|
24646
25689
|
if (faults.failCommit !== void 0) {
|
|
24647
25690
|
const code = faults.failCommit;
|
|
24648
25691
|
faults.failCommit = void 0;
|
|
24649
|
-
|
|
25692
|
+
this.transaction = null;
|
|
24650
25693
|
throw pgError2("internal", "could not serialize access due to concurrent update", code);
|
|
24651
25694
|
}
|
|
24652
25695
|
}
|
|
24653
25696
|
if (control && this.aborted && /^(rollback|abort)\b/i.test(text) && !/\bto\b/i.test(text)) {
|
|
24654
|
-
|
|
25697
|
+
this.transaction = null;
|
|
24655
25698
|
this.aborted = false;
|
|
24656
25699
|
return command("ROLLBACK");
|
|
24657
25700
|
}
|
|
24658
|
-
|
|
24659
|
-
|
|
24660
|
-
|
|
24661
|
-
|
|
24662
|
-
|
|
24663
|
-
|
|
24664
|
-
|
|
24665
|
-
|
|
24666
|
-
|
|
24667
|
-
|
|
24668
|
-
|
|
24669
|
-
return outcome;
|
|
24670
|
-
} catch (error) {
|
|
24671
|
-
tx.rollbackToSavepoint(name);
|
|
24672
|
-
tx.releaseSavepoint(name);
|
|
24673
|
-
throw error;
|
|
25701
|
+
if (control) return this.transactionControl(text);
|
|
25702
|
+
const workspace = this.transaction === null ? null : this.openWorkspace();
|
|
25703
|
+
const db = workspace ?? this.database;
|
|
25704
|
+
try {
|
|
25705
|
+
const run = () => {
|
|
25706
|
+
const result = this.cluster.as(this, () => this.runQuery(db, text, params));
|
|
25707
|
+
return { empty: false, result, tag: commandTag(result) };
|
|
25708
|
+
};
|
|
25709
|
+
const outcome = db.transaction(run);
|
|
25710
|
+
if (this.transaction && mutatesDatabase(text)) {
|
|
25711
|
+
this.transaction.statements.push({ kind: "sql", sql: text, params: params.slice() });
|
|
24674
25712
|
}
|
|
25713
|
+
return outcome;
|
|
25714
|
+
} finally {
|
|
25715
|
+
workspace?.close();
|
|
24675
25716
|
}
|
|
24676
|
-
return db.transaction(run);
|
|
24677
25717
|
}
|
|
24678
|
-
/** Release the turn
|
|
25718
|
+
/** Release the statement turn; transaction-scoped locks live until COMMIT / ROLLBACK. */
|
|
24679
25719
|
afterStatement() {
|
|
24680
|
-
if (this.cluster.db.transactions.inTransaction) return;
|
|
24681
|
-
this.cluster.unlockAll(this, true);
|
|
24682
25720
|
this.cluster.releaseTurn(this);
|
|
24683
|
-
this.
|
|
25721
|
+
if (!this.inTransaction) {
|
|
25722
|
+
this.cluster.unlockAll(this, true);
|
|
25723
|
+
this.flushNotifies();
|
|
25724
|
+
}
|
|
25725
|
+
}
|
|
25726
|
+
transactionControl(text) {
|
|
25727
|
+
const stmt = parse(text)[0];
|
|
25728
|
+
if (stmt?.type !== "transaction") throw pgError2("syntax", "invalid transaction command", "42601");
|
|
25729
|
+
switch (stmt.action) {
|
|
25730
|
+
case "begin":
|
|
25731
|
+
this.transaction ??= { statements: [], savepoints: [] };
|
|
25732
|
+
return command("BEGIN");
|
|
25733
|
+
case "commit": {
|
|
25734
|
+
const transaction = this.transaction;
|
|
25735
|
+
this.transaction = null;
|
|
25736
|
+
if (transaction) {
|
|
25737
|
+
this.database.transaction(() => {
|
|
25738
|
+
for (const logged of transaction.statements) this.replay(this.database, logged);
|
|
25739
|
+
});
|
|
25740
|
+
}
|
|
25741
|
+
return command("COMMIT");
|
|
25742
|
+
}
|
|
25743
|
+
case "rollback":
|
|
25744
|
+
this.transaction = null;
|
|
25745
|
+
return command("ROLLBACK");
|
|
25746
|
+
case "savepoint": {
|
|
25747
|
+
if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
|
|
25748
|
+
this.transaction.savepoints.push({
|
|
25749
|
+
name: stmt.savepointName,
|
|
25750
|
+
statementCount: this.transaction.statements.length
|
|
25751
|
+
});
|
|
25752
|
+
return command("SAVEPOINT");
|
|
25753
|
+
}
|
|
25754
|
+
case "release": {
|
|
25755
|
+
const index = this.findSavepoint(stmt.savepointName);
|
|
25756
|
+
this.transaction?.savepoints.splice(index);
|
|
25757
|
+
return command("RELEASE");
|
|
25758
|
+
}
|
|
25759
|
+
case "rollback_to": {
|
|
25760
|
+
const index = this.findSavepoint(stmt.savepointName);
|
|
25761
|
+
const savepoint = this.transaction?.savepoints[index];
|
|
25762
|
+
if (!this.transaction || !savepoint) throw pgError2("internal", "savepoint does not exist", "3B001");
|
|
25763
|
+
this.transaction.statements.splice(savepoint.statementCount);
|
|
25764
|
+
this.transaction.savepoints.splice(index + 1);
|
|
25765
|
+
this.aborted = false;
|
|
25766
|
+
return command("ROLLBACK");
|
|
25767
|
+
}
|
|
25768
|
+
}
|
|
25769
|
+
}
|
|
25770
|
+
findSavepoint(name) {
|
|
25771
|
+
if (!this.transaction) throw pgError2("internal", "SAVEPOINT can only be used in transaction blocks", "25P01");
|
|
25772
|
+
for (let i = this.transaction.savepoints.length - 1; i >= 0; i--) {
|
|
25773
|
+
if (this.transaction.savepoints[i]?.name === name) return i;
|
|
25774
|
+
}
|
|
25775
|
+
throw pgError2("internal", `savepoint "${name}" does not exist`, "3B001");
|
|
25776
|
+
}
|
|
25777
|
+
/** Rebase this transaction's successful writes onto the latest committed state. */
|
|
25778
|
+
openWorkspace() {
|
|
25779
|
+
const workspace = this.database.branch();
|
|
25780
|
+
setDatabaseCatalogContext(workspace.state, {
|
|
25781
|
+
name: this.databaseName,
|
|
25782
|
+
names: () => this.cluster.databaseNames()
|
|
25783
|
+
});
|
|
25784
|
+
for (const logged of this.transaction?.statements ?? []) {
|
|
25785
|
+
workspace.transaction(() => this.replay(workspace, logged));
|
|
25786
|
+
}
|
|
25787
|
+
return workspace;
|
|
25788
|
+
}
|
|
25789
|
+
replay(db, logged) {
|
|
25790
|
+
this.cluster.as(this, () => {
|
|
25791
|
+
if (logged.kind === "sql") db.prepare(logged.sql).textResult(...logged.params);
|
|
25792
|
+
else {
|
|
25793
|
+
const env = { ctx: new EngineCtx(db.state), params: null, ctes: /* @__PURE__ */ new Map(), outer: null };
|
|
25794
|
+
executeCopyFromData(env, logged.stmt, logged.data);
|
|
25795
|
+
}
|
|
25796
|
+
});
|
|
25797
|
+
}
|
|
25798
|
+
runQuery(db, text, params) {
|
|
25799
|
+
const lock = ROW_LOCK.exec(text);
|
|
25800
|
+
if (!lock) {
|
|
25801
|
+
const mutation = UPDATE_TARGET.exec(text) ?? DELETE_TARGET.exec(text);
|
|
25802
|
+
if (mutation) {
|
|
25803
|
+
const lockQuery = `SELECT * FROM ${mutation[1]}${mutation[2] ? ` WHERE ${mutation[2]}` : ""}`;
|
|
25804
|
+
this.lockRows(db, lockQuery, params, "wait");
|
|
25805
|
+
}
|
|
25806
|
+
return db.prepare(text).textResult(...params);
|
|
25807
|
+
}
|
|
25808
|
+
let query = text.slice(0, lock.index).trimEnd();
|
|
25809
|
+
const limitMatch = LIMIT.exec(query);
|
|
25810
|
+
const limit = limitMatch ? Number(limitMatch[1]) : Number.POSITIVE_INFINITY;
|
|
25811
|
+
const offset = limitMatch ? Number(limitMatch[2] ?? 0) : 0;
|
|
25812
|
+
if (limitMatch) query = query.slice(0, limitMatch.index).trimEnd();
|
|
25813
|
+
return this.lockRows(db, query, params, lock[1]?.toLowerCase().replace(/\s+/g, "_") ?? "wait", limit, offset);
|
|
25814
|
+
}
|
|
25815
|
+
lockRows(db, query, params, policy, limit = Number.POSITIVE_INFINITY, offset = 0) {
|
|
25816
|
+
const result = db.prepare(query).textResult(...params);
|
|
25817
|
+
const rows = [];
|
|
25818
|
+
for (const row of result.rows.slice(offset)) {
|
|
25819
|
+
const key = this.rowLockKey(db, query, result.columns, row);
|
|
25820
|
+
if (this.cluster.tryLock(this, key, true)) {
|
|
25821
|
+
rows.push(row);
|
|
25822
|
+
if (rows.length >= limit) break;
|
|
25823
|
+
continue;
|
|
25824
|
+
}
|
|
25825
|
+
if (policy === "skip_locked") continue;
|
|
25826
|
+
if (policy === "nowait") {
|
|
25827
|
+
throw pgError2("internal", "could not obtain lock on row in relation", "55P03");
|
|
25828
|
+
}
|
|
25829
|
+
throw new LockWait(key);
|
|
25830
|
+
}
|
|
25831
|
+
return { ...result, rows, rowCount: rows.length };
|
|
25832
|
+
}
|
|
25833
|
+
rowLockKey(db, query, columns, row) {
|
|
25834
|
+
const rawName = FROM_TABLE.exec(query)?.[1] ?? "query";
|
|
25835
|
+
const parts = rawName.split(".").map((part) => part.startsWith('"') ? part.slice(1, -1).replaceAll('""', '"') : part.toLowerCase());
|
|
25836
|
+
const table = db.state.findTable(parts);
|
|
25837
|
+
const primary = table?.constraints.find((constraint) => constraint.kind === "primary_key");
|
|
25838
|
+
const indexes = primary?.columns.map((column) => columns.indexOf(column)) ?? [];
|
|
25839
|
+
const identity = indexes.length > 0 && indexes.every((index) => index >= 0) ? indexes.map((i) => row[i]) : row;
|
|
25840
|
+
return `row:${this.databaseName}:${parts.join(".")}:${JSON.stringify(identity)}`;
|
|
24684
25841
|
}
|
|
24685
25842
|
flushNotifies() {
|
|
24686
25843
|
for (const { channel, payload } of this.pendingNotifies.splice(0)) this.cluster.notify(this, channel, payload);
|
|
24687
25844
|
}
|
|
25845
|
+
executeClusterDdl(text) {
|
|
25846
|
+
if (this.inTransaction) {
|
|
25847
|
+
throw pgError2("internal", "CREATE/DROP/ALTER DATABASE cannot run inside a transaction block", "25001");
|
|
25848
|
+
}
|
|
25849
|
+
const create = CREATE_DATABASE.exec(text);
|
|
25850
|
+
if (create) {
|
|
25851
|
+
const name = identifier(create[1], create[2]);
|
|
25852
|
+
const template = create[3] === void 0 && create[4] === void 0 ? void 0 : identifier(create[3], create[4]);
|
|
25853
|
+
this.cluster.createDatabase(name, template);
|
|
25854
|
+
return command("CREATE DATABASE");
|
|
25855
|
+
}
|
|
25856
|
+
const drop = DROP_DATABASE.exec(text);
|
|
25857
|
+
if (drop) {
|
|
25858
|
+
const name = identifier(drop[2], drop[3]);
|
|
25859
|
+
if (drop[1] && !this.cluster.getDatabase(name)) return command("DROP DATABASE");
|
|
25860
|
+
this.cluster.dropDatabase(name, this);
|
|
25861
|
+
return command("DROP DATABASE");
|
|
25862
|
+
}
|
|
25863
|
+
const rename2 = RENAME_DATABASE.exec(text);
|
|
25864
|
+
if (rename2) {
|
|
25865
|
+
const from = identifier(rename2[1], rename2[2]);
|
|
25866
|
+
const to = identifier(rename2[3], rename2[4]);
|
|
25867
|
+
this.cluster.renameDatabase(from, to, this);
|
|
25868
|
+
return command("ALTER DATABASE");
|
|
25869
|
+
}
|
|
25870
|
+
throw pgError2("syntax", "unsupported CREATE/DROP/ALTER DATABASE option", "0A000");
|
|
25871
|
+
}
|
|
24688
25872
|
listen(channel) {
|
|
24689
25873
|
const set = this.cluster.listeners.get(channel) ?? /* @__PURE__ */ new Set();
|
|
24690
25874
|
set.add(this);
|
|
@@ -24705,6 +25889,24 @@ var Connection = class {
|
|
|
24705
25889
|
this.readyForQuery();
|
|
24706
25890
|
return;
|
|
24707
25891
|
}
|
|
25892
|
+
if (statements.length === 1) {
|
|
25893
|
+
const copy = this.copyStatement(statements[0]);
|
|
25894
|
+
if (copy?.direction === "from") {
|
|
25895
|
+
await this.beginCopyIn(copy);
|
|
25896
|
+
return;
|
|
25897
|
+
}
|
|
25898
|
+
if (copy?.direction === "to") {
|
|
25899
|
+
try {
|
|
25900
|
+
const outcome = await this.execute(statements[0]);
|
|
25901
|
+
this.sendCopyOut(outcome, this.copyColumnCount(copy));
|
|
25902
|
+
} catch (error) {
|
|
25903
|
+
this.write(backend.errorResponse(errorFields(error)));
|
|
25904
|
+
if (this.inTransaction) this.aborted = true;
|
|
25905
|
+
}
|
|
25906
|
+
this.readyForQuery();
|
|
25907
|
+
return;
|
|
25908
|
+
}
|
|
25909
|
+
}
|
|
24708
25910
|
const implicit = statements.length > 1 && !this.inTransaction && !statements.some((s) => TX_CONTROL.test(s));
|
|
24709
25911
|
let failed = false;
|
|
24710
25912
|
try {
|
|
@@ -24725,6 +25927,138 @@ var Connection = class {
|
|
|
24725
25927
|
if (failed && this.inTransaction) this.aborted = true;
|
|
24726
25928
|
this.readyForQuery();
|
|
24727
25929
|
}
|
|
25930
|
+
copyStatement(sql) {
|
|
25931
|
+
try {
|
|
25932
|
+
const statements = parse(sql);
|
|
25933
|
+
const stmt = statements.length === 1 ? statements[0] : void 0;
|
|
25934
|
+
return stmt?.type === "copy" ? stmt : null;
|
|
25935
|
+
} catch {
|
|
25936
|
+
return null;
|
|
25937
|
+
}
|
|
25938
|
+
}
|
|
25939
|
+
copyColumnCount(stmt) {
|
|
25940
|
+
if (stmt.columns) return stmt.columns.length;
|
|
25941
|
+
if (stmt.table) return this.database.state.findTable(stmt.table)?.columns.length ?? 0;
|
|
25942
|
+
return 0;
|
|
25943
|
+
}
|
|
25944
|
+
async handleCopyIn(type, body) {
|
|
25945
|
+
const copy = this.copyIn;
|
|
25946
|
+
if (type === "d") {
|
|
25947
|
+
try {
|
|
25948
|
+
copy.buffer += copy.decoder.decode(body.rest(), { stream: true });
|
|
25949
|
+
this.flushCopyRecords(copy, false);
|
|
25950
|
+
} catch (error) {
|
|
25951
|
+
this.abortCopyIn(copy, error, true);
|
|
25952
|
+
}
|
|
25953
|
+
return;
|
|
25954
|
+
}
|
|
25955
|
+
if (type === "f") {
|
|
25956
|
+
const detail = body.cstring();
|
|
25957
|
+
this.abortCopyIn(copy, pgError2("internal", `COPY from stdin failed: ${detail}`, "57014"), false);
|
|
25958
|
+
return;
|
|
25959
|
+
}
|
|
25960
|
+
if (type === "c") {
|
|
25961
|
+
this.finishCopyIn(copy);
|
|
25962
|
+
return;
|
|
25963
|
+
}
|
|
25964
|
+
if (type === "H") return;
|
|
25965
|
+
if (type === "X") {
|
|
25966
|
+
this.socket.end();
|
|
25967
|
+
this.dispose();
|
|
25968
|
+
return;
|
|
25969
|
+
}
|
|
25970
|
+
this.fatal("08P01", `unexpected frontend message type ${JSON.stringify(type)} during COPY`);
|
|
25971
|
+
}
|
|
25972
|
+
async beginCopyIn(stmt) {
|
|
25973
|
+
await this.cluster.acquireTurn(this);
|
|
25974
|
+
const workspace = this.transaction ? this.openWorkspace() : null;
|
|
25975
|
+
const database = workspace ?? this.database;
|
|
25976
|
+
database.transactions.begin();
|
|
25977
|
+
this.copyIn = {
|
|
25978
|
+
stmt,
|
|
25979
|
+
database,
|
|
25980
|
+
workspace: workspace !== null,
|
|
25981
|
+
buffer: "",
|
|
25982
|
+
decoder: new TextDecoder(),
|
|
25983
|
+
rowCount: 0,
|
|
25984
|
+
headerPending: stmt.options.header === true || String(stmt.options.header ?? "").toLowerCase() === "true",
|
|
25985
|
+
loggedData: ""
|
|
25986
|
+
};
|
|
25987
|
+
this.write(backend.copyInResponse(this.copyColumnCount(stmt)));
|
|
25988
|
+
}
|
|
25989
|
+
finishCopyIn(copy) {
|
|
25990
|
+
const tx = copy.database.transactions;
|
|
25991
|
+
try {
|
|
25992
|
+
copy.buffer += copy.decoder.decode();
|
|
25993
|
+
this.flushCopyRecords(copy, true);
|
|
25994
|
+
tx.commit();
|
|
25995
|
+
if (copy.workspace) {
|
|
25996
|
+
this.transaction?.statements.push({ kind: "copy", stmt: copy.stmt, data: copy.loggedData });
|
|
25997
|
+
copy.database.close();
|
|
25998
|
+
}
|
|
25999
|
+
this.copyIn = null;
|
|
26000
|
+
this.write(backend.commandComplete(`COPY ${copy.rowCount}`));
|
|
26001
|
+
this.cluster.releaseTurn(this);
|
|
26002
|
+
this.readyForQuery();
|
|
26003
|
+
} catch (error) {
|
|
26004
|
+
this.abortCopyIn(copy, error, false);
|
|
26005
|
+
}
|
|
26006
|
+
}
|
|
26007
|
+
flushCopyRecords(copy, final) {
|
|
26008
|
+
const format = String(copy.stmt.options.format ?? "text").toLowerCase();
|
|
26009
|
+
const quote = String(copy.stmt.options.quote ?? '"');
|
|
26010
|
+
const boundaries = [];
|
|
26011
|
+
let quoted = false;
|
|
26012
|
+
for (let i = 0; i < copy.buffer.length; i++) {
|
|
26013
|
+
const char = copy.buffer[i];
|
|
26014
|
+
if (format === "csv" && char === quote) {
|
|
26015
|
+
if (quoted && copy.buffer[i + 1] === quote) i++;
|
|
26016
|
+
else quoted = !quoted;
|
|
26017
|
+
} else if (char === "\n" && !quoted) {
|
|
26018
|
+
boundaries.push(i + 1);
|
|
26019
|
+
}
|
|
26020
|
+
}
|
|
26021
|
+
if (final && quoted) {
|
|
26022
|
+
throw new PostgresError("invalid_text_representation", "unterminated CSV quoted field", "22P04");
|
|
26023
|
+
}
|
|
26024
|
+
const end = boundaries.at(-1) ?? 0;
|
|
26025
|
+
const data = final ? copy.buffer : end > 0 ? copy.buffer.slice(0, end) : "";
|
|
26026
|
+
if (data === "") return;
|
|
26027
|
+
copy.buffer = final ? "" : copy.buffer.slice(end);
|
|
26028
|
+
const stmt = copy.headerPending ? copy.stmt : { ...copy.stmt, options: { ...copy.stmt.options, header: false } };
|
|
26029
|
+
const env = {
|
|
26030
|
+
ctx: new EngineCtx(copy.database.state),
|
|
26031
|
+
params: null,
|
|
26032
|
+
ctes: /* @__PURE__ */ new Map(),
|
|
26033
|
+
outer: null
|
|
26034
|
+
};
|
|
26035
|
+
const result = this.cluster.as(this, () => executeCopyFromData(env, stmt, data));
|
|
26036
|
+
copy.rowCount += result.rowCount;
|
|
26037
|
+
copy.loggedData += data;
|
|
26038
|
+
copy.headerPending = false;
|
|
26039
|
+
}
|
|
26040
|
+
abortCopyIn(copy, error, drain) {
|
|
26041
|
+
const tx = copy.database.transactions;
|
|
26042
|
+
if (tx.inTransaction) tx.rollback();
|
|
26043
|
+
if (copy.workspace) copy.database.close();
|
|
26044
|
+
if (this.inTransaction) this.aborted = true;
|
|
26045
|
+
this.cluster.releaseTurn(this);
|
|
26046
|
+
this.copyIn = null;
|
|
26047
|
+
this.copyDrain = drain;
|
|
26048
|
+
this.write(backend.errorResponse(errorFields(error)));
|
|
26049
|
+
if (!drain) this.readyForQuery();
|
|
26050
|
+
}
|
|
26051
|
+
sendCopyOut(outcome, columns) {
|
|
26052
|
+
if (outcome.empty) throw pgError2("internal", "COPY TO produced no result", "XX000");
|
|
26053
|
+
this.write(backend.copyOutResponse(columns));
|
|
26054
|
+
for (const row of outcome.result.rows) {
|
|
26055
|
+
const line = row[0] ?? "";
|
|
26056
|
+
this.write(backend.copyData(utf83.encode(`${line}
|
|
26057
|
+
`)));
|
|
26058
|
+
}
|
|
26059
|
+
this.write(backend.copyDone());
|
|
26060
|
+
this.write(backend.commandComplete(outcome.tag));
|
|
26061
|
+
}
|
|
24728
26062
|
sendOutcome(outcome, resultFormats, opts = { rowDescription: false }) {
|
|
24729
26063
|
const from = opts.from ?? 0;
|
|
24730
26064
|
const limit = opts.limit ?? 0;
|
|
@@ -24779,7 +26113,7 @@ var Connection = class {
|
|
|
24779
26113
|
for (let i = 0; i < count; i++) paramOids.push(body.i32());
|
|
24780
26114
|
const text = sql.trim();
|
|
24781
26115
|
if (text !== "" && !LISTEN.test(text) && !UNLISTEN.test(text) && !NOTIFY.test(text) && !TX_CONTROL.test(text)) {
|
|
24782
|
-
this.
|
|
26116
|
+
if (!CLUSTER_DDL.test(text)) this.database.prepare(text);
|
|
24783
26117
|
}
|
|
24784
26118
|
if (name !== "" && this.prepared.has(name)) {
|
|
24785
26119
|
throw pgError2("internal", `prepared statement "${name}" already exists`, "42P05");
|
|
@@ -24838,7 +26172,7 @@ var Connection = class {
|
|
|
24838
26172
|
*/
|
|
24839
26173
|
async shapeOf(prepared) {
|
|
24840
26174
|
if (!RETURNS_ROWS.test(prepared.sql)) return null;
|
|
24841
|
-
const db = this.
|
|
26175
|
+
const db = this.database;
|
|
24842
26176
|
await this.cluster.acquireTurn(this);
|
|
24843
26177
|
try {
|
|
24844
26178
|
const params = prepared.paramOids.map(() => null);
|
|
@@ -24952,40 +26286,49 @@ var normalize = (input) => {
|
|
|
24952
26286
|
};
|
|
24953
26287
|
};
|
|
24954
26288
|
var isSnapshot = (value) => typeof value === "object" && value !== null && "open" in value && typeof value.open === "function";
|
|
24955
|
-
var
|
|
26289
|
+
var serve2 = (input = {}) => {
|
|
24956
26290
|
const options2 = normalize(input);
|
|
24957
26291
|
const database = isSnapshot(options2.database) ? options2.database.open() : options2.database ?? new Database();
|
|
24958
|
-
const
|
|
26292
|
+
const initialName = options2.databaseName ?? "postgres";
|
|
26293
|
+
const cluster = new Cluster(database, initialName);
|
|
24959
26294
|
const faults = {};
|
|
24960
26295
|
const connections = /* @__PURE__ */ new Set();
|
|
24961
26296
|
const host = options2.host ?? "127.0.0.1";
|
|
24962
26297
|
const user = options2.user || "postgres";
|
|
24963
|
-
const server2 =
|
|
26298
|
+
const server2 = createServer2((socket) => {
|
|
24964
26299
|
socket.setNoDelay(true);
|
|
24965
26300
|
const connection = new Connection(socket, cluster, {
|
|
24966
26301
|
...options2.password !== void 0 ? { password: options2.password } : {},
|
|
24967
26302
|
serverVersion: options2.serverVersion ?? "18.3",
|
|
24968
26303
|
parameters: options2.parameters ?? {},
|
|
24969
26304
|
faults,
|
|
24970
|
-
...options2.databaseName !== void 0 ? { database: options2.databaseName } : {},
|
|
26305
|
+
...options2.databaseName !== void 0 && !options2.allowDatabaseSelection ? { database: options2.databaseName } : {},
|
|
24971
26306
|
...options2.onLog ? { onLog: options2.onLog } : {}
|
|
24972
26307
|
});
|
|
24973
26308
|
connections.add(connection);
|
|
24974
26309
|
socket.on("close", () => connections.delete(connection));
|
|
24975
26310
|
});
|
|
24976
|
-
return new Promise((
|
|
26311
|
+
return new Promise((resolve2, reject) => {
|
|
24977
26312
|
server2.once("error", reject);
|
|
24978
26313
|
server2.listen(options2.port ?? 0, host, () => {
|
|
24979
26314
|
server2.removeListener("error", reject);
|
|
24980
26315
|
const address = server2.address();
|
|
24981
26316
|
const port = typeof address === "object" && address !== null ? address.port : 0;
|
|
24982
|
-
const name =
|
|
26317
|
+
const name = initialName;
|
|
24983
26318
|
const auth = options2.password ? `${encodeURIComponent(user)}:${encodeURIComponent(options2.password)}@` : `${encodeURIComponent(user)}@`;
|
|
24984
|
-
|
|
26319
|
+
resolve2({
|
|
24985
26320
|
port,
|
|
24986
26321
|
host,
|
|
24987
26322
|
connectionString: `postgres://${auth}${host}:${port}/${encodeURIComponent(name)}`,
|
|
24988
|
-
database
|
|
26323
|
+
get database() {
|
|
26324
|
+
return cluster.db;
|
|
26325
|
+
},
|
|
26326
|
+
databaseNames() {
|
|
26327
|
+
return cluster.databaseNames();
|
|
26328
|
+
},
|
|
26329
|
+
getDatabase(databaseName) {
|
|
26330
|
+
return cluster.requireDatabase(databaseName);
|
|
26331
|
+
},
|
|
24989
26332
|
server: server2,
|
|
24990
26333
|
get connections() {
|
|
24991
26334
|
return connections.size;
|
|
@@ -24993,8 +26336,16 @@ var serve = (input = {}) => {
|
|
|
24993
26336
|
fault(next) {
|
|
24994
26337
|
Object.assign(faults, next);
|
|
24995
26338
|
},
|
|
24996
|
-
|
|
24997
|
-
|
|
26339
|
+
clearFaults() {
|
|
26340
|
+
for (const key of Object.keys(faults)) delete faults[key];
|
|
26341
|
+
},
|
|
26342
|
+
faultState() {
|
|
26343
|
+
return structuredClone(faults);
|
|
26344
|
+
},
|
|
26345
|
+
snapshotAll: () => cluster.snapshotAll(),
|
|
26346
|
+
restoreAll: (points) => cluster.restoreAll(points),
|
|
26347
|
+
snapshot(databaseName = initialName) {
|
|
26348
|
+
return cluster.requireDatabase(databaseName).snapshot();
|
|
24998
26349
|
},
|
|
24999
26350
|
close() {
|
|
25000
26351
|
for (const connection of connections) connection.destroy();
|
|
@@ -25008,9 +26359,251 @@ var serve = (input = {}) => {
|
|
|
25008
26359
|
});
|
|
25009
26360
|
};
|
|
25010
26361
|
|
|
26362
|
+
// src/wire/probe.ts
|
|
26363
|
+
import { createHash as createHash2, createHmac as createHmac2, pbkdf2Sync as pbkdf2Sync2, randomBytes as randomBytes2 } from "node:crypto";
|
|
26364
|
+
import { createConnection } from "node:net";
|
|
26365
|
+
function probe(connectionString) {
|
|
26366
|
+
const url = new URL(connectionString);
|
|
26367
|
+
return new Promise((resolve2) => {
|
|
26368
|
+
const socket = createConnection({ host: url.hostname, port: Number(url.port) });
|
|
26369
|
+
let buffer = Buffer.alloc(0);
|
|
26370
|
+
let result = false;
|
|
26371
|
+
let querying = false;
|
|
26372
|
+
let row = false;
|
|
26373
|
+
const nonce = randomBytes2(18).toString("base64");
|
|
26374
|
+
const first = `n=,r=${nonce}`;
|
|
26375
|
+
const finish = (ok) => {
|
|
26376
|
+
result = ok;
|
|
26377
|
+
socket.destroy();
|
|
26378
|
+
};
|
|
26379
|
+
const timeout = setTimeout(() => finish(false), 2e3);
|
|
26380
|
+
const send = (type, body) => {
|
|
26381
|
+
const header = Buffer.alloc(5);
|
|
26382
|
+
header.write(type);
|
|
26383
|
+
header.writeInt32BE(body.length + 4, 1);
|
|
26384
|
+
socket.write(Buffer.concat([header, body]));
|
|
26385
|
+
};
|
|
26386
|
+
socket.once("close", () => {
|
|
26387
|
+
clearTimeout(timeout);
|
|
26388
|
+
resolve2(result);
|
|
26389
|
+
});
|
|
26390
|
+
socket.on("error", () => finish(false));
|
|
26391
|
+
socket.once("connect", () => {
|
|
26392
|
+
const parameters = Buffer.from(
|
|
26393
|
+
`user\0${decodeURIComponent(url.username)}\0database\0${decodeURIComponent(url.pathname.slice(1))}\0\0`
|
|
26394
|
+
);
|
|
26395
|
+
const header = Buffer.alloc(8);
|
|
26396
|
+
header.writeInt32BE(parameters.length + 8);
|
|
26397
|
+
header.writeInt32BE(196608, 4);
|
|
26398
|
+
socket.write(Buffer.concat([header, parameters]));
|
|
26399
|
+
});
|
|
26400
|
+
socket.on("data", (data) => {
|
|
26401
|
+
buffer = Buffer.concat([buffer, data]);
|
|
26402
|
+
while (buffer.length >= 5) {
|
|
26403
|
+
const length = buffer.readInt32BE(1);
|
|
26404
|
+
if (length < 4 || length > 1e6) {
|
|
26405
|
+
finish(false);
|
|
26406
|
+
return;
|
|
26407
|
+
}
|
|
26408
|
+
if (buffer.length < length + 1) return;
|
|
26409
|
+
const type = String.fromCharCode(buffer[0]);
|
|
26410
|
+
const body = buffer.subarray(5, length + 1);
|
|
26411
|
+
buffer = buffer.subarray(length + 1);
|
|
26412
|
+
if (type === "E") {
|
|
26413
|
+
finish(false);
|
|
26414
|
+
return;
|
|
26415
|
+
}
|
|
26416
|
+
if (type === "R") {
|
|
26417
|
+
const auth = body.readInt32BE();
|
|
26418
|
+
if (auth === 10) {
|
|
26419
|
+
const initial = Buffer.from(`n,,${first}`);
|
|
26420
|
+
const size = Buffer.alloc(4);
|
|
26421
|
+
size.writeInt32BE(initial.length);
|
|
26422
|
+
send("p", Buffer.concat([Buffer.from("SCRAM-SHA-256\0"), size, initial]));
|
|
26423
|
+
} else if (auth === 11) {
|
|
26424
|
+
const serverFirst = body.subarray(4).toString();
|
|
26425
|
+
const fields = Object.fromEntries(serverFirst.split(",").map((field) => [field[0], field.slice(2)]));
|
|
26426
|
+
if (!fields.r?.startsWith(nonce) || !fields.s || fields.i !== "4096") {
|
|
26427
|
+
finish(false);
|
|
26428
|
+
return;
|
|
26429
|
+
}
|
|
26430
|
+
const salted = pbkdf2Sync2(
|
|
26431
|
+
decodeURIComponent(url.password),
|
|
26432
|
+
Buffer.from(fields.s, "base64"),
|
|
26433
|
+
4096,
|
|
26434
|
+
32,
|
|
26435
|
+
"sha256"
|
|
26436
|
+
);
|
|
26437
|
+
const key = createHmac2("sha256", salted).update("Client Key").digest();
|
|
26438
|
+
const stored = createHash2("sha256").update(key).digest();
|
|
26439
|
+
const final = `c=biws,r=${fields.r}`;
|
|
26440
|
+
const signature = createHmac2("sha256", stored).update(`${first},${serverFirst},${final}`).digest();
|
|
26441
|
+
const proof = Buffer.from(key.map((value, index) => value ^ signature[index]));
|
|
26442
|
+
send("p", Buffer.from(`${final},p=${proof.toString("base64")}`));
|
|
26443
|
+
} else if (auth !== 0 && auth !== 12) {
|
|
26444
|
+
finish(false);
|
|
26445
|
+
return;
|
|
26446
|
+
}
|
|
26447
|
+
}
|
|
26448
|
+
if (type === "D") row = body.readInt16BE() === 1 && body.subarray(6).toString() === "1";
|
|
26449
|
+
if (type === "Z") {
|
|
26450
|
+
if (querying) {
|
|
26451
|
+
finish(row);
|
|
26452
|
+
return;
|
|
26453
|
+
}
|
|
26454
|
+
querying = true;
|
|
26455
|
+
send("Q", Buffer.from("SELECT 1\0"));
|
|
26456
|
+
}
|
|
26457
|
+
}
|
|
26458
|
+
});
|
|
26459
|
+
});
|
|
26460
|
+
}
|
|
26461
|
+
|
|
26462
|
+
// src/wire/fleet.ts
|
|
26463
|
+
async function startProtocol(entry) {
|
|
26464
|
+
const namespaces = /* @__PURE__ */ new Map();
|
|
26465
|
+
const selectors = { default: entry.database ?? "postgres", ...entry.namespaces };
|
|
26466
|
+
try {
|
|
26467
|
+
for (const [name, databaseName] of Object.entries(selectors)) {
|
|
26468
|
+
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(name) || typeof databaseName !== "string")
|
|
26469
|
+
throw new Error("invalid Postgres namespace/database name");
|
|
26470
|
+
const clock = createClock();
|
|
26471
|
+
const database = new Database({
|
|
26472
|
+
now: () => new Date(clock.now()),
|
|
26473
|
+
...entry.seed !== void 0 ? { seed: Number(entry.seed) } : {}
|
|
26474
|
+
});
|
|
26475
|
+
const server2 = await serve2({
|
|
26476
|
+
database,
|
|
26477
|
+
databaseName,
|
|
26478
|
+
allowDatabaseSelection: true,
|
|
26479
|
+
host: entry.host ?? "127.0.0.1",
|
|
26480
|
+
port: name === "default" ? entry.port ?? 0 : 0,
|
|
26481
|
+
...entry.password !== void 0 ? { password: entry.password } : {},
|
|
26482
|
+
...entry.user !== void 0 ? { user: entry.user } : {}
|
|
26483
|
+
});
|
|
26484
|
+
namespaces.set(name, { server: server2, clock });
|
|
26485
|
+
}
|
|
26486
|
+
} catch (error) {
|
|
26487
|
+
await Promise.allSettled([...namespaces.values()].map(({ server: server2 }) => server2.close()));
|
|
26488
|
+
throw error;
|
|
26489
|
+
}
|
|
26490
|
+
const get = (name) => {
|
|
26491
|
+
const found = namespaces.get(name);
|
|
26492
|
+
if (!found) throw new Error(`Postgres namespace ${name} is not configured`);
|
|
26493
|
+
return found;
|
|
26494
|
+
};
|
|
26495
|
+
const publicUrl2 = (name) => {
|
|
26496
|
+
const url = new URL(get(name).server.connectionString);
|
|
26497
|
+
url.password = "";
|
|
26498
|
+
return url.href;
|
|
26499
|
+
};
|
|
26500
|
+
const locked = /* @__PURE__ */ new Set();
|
|
26501
|
+
const snapshots = /* @__PURE__ */ new WeakMap();
|
|
26502
|
+
const origins = new Map([...namespaces].map(([name, { server: server2 }]) => [name, server2.snapshotAll()]));
|
|
26503
|
+
return {
|
|
26504
|
+
protocol: "postgres",
|
|
26505
|
+
url: publicUrl2("default"),
|
|
26506
|
+
connection: get("default").server.connectionString,
|
|
26507
|
+
connections: Object.fromEntries([...namespaces].map(([name, { server: server2 }]) => [name, server2.connectionString])),
|
|
26508
|
+
namespaces: {
|
|
26509
|
+
mechanism: "endpoint",
|
|
26510
|
+
endpoints: Object.fromEntries([...namespaces.keys()].map((name) => [name, publicUrl2(name)]))
|
|
26511
|
+
},
|
|
26512
|
+
async ready() {
|
|
26513
|
+
const results = await Promise.all(
|
|
26514
|
+
[...namespaces.values()].map(
|
|
26515
|
+
({ server: server2 }) => server2.server.listening ? probe(server2.connectionString) : false
|
|
26516
|
+
)
|
|
26517
|
+
);
|
|
26518
|
+
return results.every(Boolean);
|
|
26519
|
+
},
|
|
26520
|
+
async close() {
|
|
26521
|
+
await Promise.all(
|
|
26522
|
+
[...namespaces.values()].map(async ({ server: server2 }) => {
|
|
26523
|
+
await server2.close();
|
|
26524
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).close();
|
|
26525
|
+
})
|
|
26526
|
+
);
|
|
26527
|
+
},
|
|
26528
|
+
lock(namespace) {
|
|
26529
|
+
const { server: server2 } = get(namespace);
|
|
26530
|
+
if (locked.has(namespace)) throw new Error("Postgres namespace already locked");
|
|
26531
|
+
locked.add(namespace);
|
|
26532
|
+
const reject = (socket) => socket.destroy();
|
|
26533
|
+
server2.server.prependListener("connection", reject);
|
|
26534
|
+
return () => {
|
|
26535
|
+
locked.delete(namespace);
|
|
26536
|
+
server2.server.off("connection", reject);
|
|
26537
|
+
};
|
|
26538
|
+
},
|
|
26539
|
+
diagnostics(namespace) {
|
|
26540
|
+
const { server: server2 } = get(namespace);
|
|
26541
|
+
return {
|
|
26542
|
+
activeRequests: server2.connections,
|
|
26543
|
+
activeConnections: server2.connections,
|
|
26544
|
+
blockedCommands: 0,
|
|
26545
|
+
pendingJobs: 0,
|
|
26546
|
+
pendingWebhooks: 0,
|
|
26547
|
+
unmatchedRequests: 0
|
|
26548
|
+
};
|
|
26549
|
+
},
|
|
26550
|
+
async checkpoint(namespace) {
|
|
26551
|
+
const { server: server2, clock } = get(namespace);
|
|
26552
|
+
const handle = {};
|
|
26553
|
+
snapshots.set(handle, {
|
|
26554
|
+
namespace,
|
|
26555
|
+
databases: server2.snapshotAll(),
|
|
26556
|
+
clock: clock.state(),
|
|
26557
|
+
faults: server2.faultState()
|
|
26558
|
+
});
|
|
26559
|
+
return handle;
|
|
26560
|
+
},
|
|
26561
|
+
async restore(namespace, handle) {
|
|
26562
|
+
const point = typeof handle === "object" && handle !== null ? snapshots.get(handle) : void 0;
|
|
26563
|
+
if (!point || point.namespace !== namespace) throw new Error("invalid Postgres checkpoint");
|
|
26564
|
+
const { server: server2, clock } = get(namespace);
|
|
26565
|
+
server2.restoreAll(point.databases);
|
|
26566
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
|
|
26567
|
+
server2.clearFaults();
|
|
26568
|
+
server2.fault(point.faults);
|
|
26569
|
+
clock.freeze();
|
|
26570
|
+
clock.set(point.clock.now);
|
|
26571
|
+
if (!point.clock.frozen) clock.unfreeze();
|
|
26572
|
+
},
|
|
26573
|
+
async reset(namespace) {
|
|
26574
|
+
const { server: server2, clock } = get(namespace);
|
|
26575
|
+
server2.restoreAll(origins.get(namespace));
|
|
26576
|
+
for (const name of server2.databaseNames()) server2.getDatabase(name).now = () => new Date(clock.now());
|
|
26577
|
+
server2.clearFaults();
|
|
26578
|
+
clock.reset();
|
|
26579
|
+
},
|
|
26580
|
+
clock: (namespace) => get(namespace).clock
|
|
26581
|
+
};
|
|
26582
|
+
}
|
|
26583
|
+
|
|
26584
|
+
// src/server.ts
|
|
26585
|
+
var serveTarget = {
|
|
26586
|
+
name: "postgres",
|
|
26587
|
+
protocol: "postgres",
|
|
26588
|
+
defaultPort: 0,
|
|
26589
|
+
start: startProtocol
|
|
26590
|
+
};
|
|
26591
|
+
|
|
25011
26592
|
// src/wire/cli.ts
|
|
25012
26593
|
var usage = `mockingbird-postgres serve [postgres://URI] [--port <n>] [--host <h>] [--password <p>] [--server-version <v>] [--log]`;
|
|
25013
26594
|
var args = process.argv.slice(2);
|
|
26595
|
+
if (args[0] === "serve" && args.includes("--config")) {
|
|
26596
|
+
process.exit(
|
|
26597
|
+
await runCli(
|
|
26598
|
+
{
|
|
26599
|
+
bin: "mockingbird-postgres",
|
|
26600
|
+
description: "Postgres and fleet server",
|
|
26601
|
+
commands: { serve: serveCommand(serveTarget) }
|
|
26602
|
+
},
|
|
26603
|
+
args
|
|
26604
|
+
)
|
|
26605
|
+
);
|
|
26606
|
+
}
|
|
25014
26607
|
if (args[0] !== "serve") {
|
|
25015
26608
|
console.error(usage);
|
|
25016
26609
|
process.exit(args[0] === "--help" || args[0] === "-h" ? 0 : 2);
|
|
@@ -25059,7 +26652,7 @@ if (log) {
|
|
|
25059
26652
|
`[pid ${entry.pid}] ${entry.status} ${entry.durationMs.toFixed(1)}ms ${entry.sql.replace(/\s+/g, " ").slice(0, 200)}`
|
|
25060
26653
|
);
|
|
25061
26654
|
}
|
|
25062
|
-
var server = await
|
|
26655
|
+
var server = await serve2(options);
|
|
25063
26656
|
console.log(`mockingbird-postgres listening on ${server.connectionString}`);
|
|
25064
26657
|
var stop = async () => {
|
|
25065
26658
|
await server.close();
|