@mason-data/cli 0.0.0-stage → 0.6.2
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/LICENSE +202 -0
- package/README.md +209 -2
- package/dist/src/api/error.js +18 -0
- package/dist/src/api/postgres/catalog-worker.js +92 -0
- package/dist/src/api/postgres/catalog.js +442 -0
- package/dist/src/api/postgres/encode.js +541 -0
- package/dist/src/api/postgres/explain.js +143 -0
- package/dist/src/api/postgres/listener.js +60 -0
- package/dist/src/api/postgres/parser-worker.js +58 -0
- package/dist/src/api/postgres/parser.js +121 -0
- package/dist/src/api/postgres/plain.js +33 -0
- package/dist/src/api/postgres/protocol.js +404 -0
- package/dist/src/api/postgres/schema.js +54 -0
- package/dist/src/api/postgres/server.js +1670 -0
- package/dist/src/api/postgres/translate.js +248 -0
- package/dist/src/api/request-log.js +50 -0
- package/dist/src/cdc/pg-wire.js +482 -0
- package/dist/src/cdc/pgoutput.js +107 -0
- package/dist/src/cdc/poll.js +67 -0
- package/dist/src/cdc/source.js +65 -0
- package/dist/src/cli/background.js +174 -0
- package/dist/src/cli/command.js +172 -0
- package/dist/src/cli/commands.js +1145 -0
- package/dist/src/cli/control.js +36 -0
- package/dist/src/cli/dev.js +576 -0
- package/dist/src/cli/doctor.js +257 -0
- package/dist/src/cli/guides.js +134 -0
- package/dist/src/cli/init.js +326 -0
- package/dist/src/cli/machine.js +157 -0
- package/dist/src/cli/main.js +83 -0
- package/dist/src/cli/pg-tls.js +100 -0
- package/dist/src/cli/pg-url.js +52 -0
- package/dist/src/cli/pin.js +36 -0
- package/dist/src/cli/ports.js +118 -0
- package/dist/src/cli/preflight.js +338 -0
- package/dist/src/cli/project.js +127 -0
- package/dist/src/cli/readiness.js +76 -0
- package/dist/src/cli/registry.js +217 -0
- package/dist/src/cli/replication-checks.js +120 -0
- package/dist/src/cli/replication-mode.js +148 -0
- package/dist/src/cli/run-server.js +388 -0
- package/dist/src/cli/runs.js +120 -0
- package/dist/src/cli/security.js +86 -0
- package/dist/src/cli/self-signed-cert.js +64 -0
- package/dist/src/cli/serve.js +296 -0
- package/dist/src/cli/sources.js +562 -0
- package/dist/src/cli/state.js +58 -0
- package/dist/src/cli/style.js +187 -0
- package/dist/src/cli/supervise.js +248 -0
- package/dist/src/cli/sync.js +94 -0
- package/dist/src/cli/ui.js +107 -0
- package/dist/src/cli/user.js +229 -0
- package/dist/src/cli/version.js +23 -0
- package/dist/src/duck/analyze.js +113 -0
- package/dist/src/duck/cancel.js +49 -0
- package/dist/src/duck/executor-worker.js +263 -0
- package/dist/src/duck/executor.js +526 -0
- package/dist/src/duck/explain.js +119 -0
- package/dist/src/duck/functions.js +764 -0
- package/dist/src/duck/gaps.js +89 -0
- package/dist/src/duck/jsonb.js +186 -0
- package/dist/src/duck/kept-route.js +219 -0
- package/dist/src/duck/kept.js +797 -0
- package/dist/src/duck/late.js +221 -0
- package/dist/src/duck/libc-order.js +190 -0
- package/dist/src/duck/pg-eval-worker.js +34 -0
- package/dist/src/duck/pg-eval.js +59 -0
- package/dist/src/duck/pgtree.js +338 -0
- package/dist/src/duck/plan.js +246 -0
- package/dist/src/duck/publication.js +207 -0
- package/dist/src/duck/requests.js +209 -0
- package/dist/src/duck/resolved.js +248 -0
- package/dist/src/duck/rewrite.js +4001 -0
- package/dist/src/duck/store.js +831 -0
- package/dist/src/duck/stream.js +518 -0
- package/dist/src/duck/sync.js +2864 -0
- package/dist/src/duck/to-char.js +482 -0
- package/dist/src/duck/token.js +31 -0
- package/dist/src/duck/types.js +427 -0
- package/dist/src/duck/users.js +231 -0
- package/dist/src/duck/verified.js +995 -0
- package/dist/src/duck/views.js +150 -0
- package/dist/src/lib/database-url.js +74 -0
- package/dist/src/lib/did-you-mean.js +43 -0
- package/dist/src/lib/dotenv.js +72 -0
- package/dist/src/lib/exit-when-written.js +20 -0
- package/dist/src/lib/float-equal.js +69 -0
- package/dist/src/lib/http-json.js +22 -0
- package/dist/src/lib/log.js +161 -0
- package/dist/src/lib/pg-numeric.js +489 -0
- package/dist/src/lib/port.js +22 -0
- package/dist/src/lib/project-file.js +49 -0
- package/dist/src/lib/project-files.js +43 -0
- package/dist/src/lib/project-version.js +76 -0
- package/dist/src/lib/runnable.js +14 -0
- package/dist/src/lib/shell-word.js +4 -0
- package/dist/src/lib/timezone.js +236 -0
- package/dist/src/lib/zone-names.js +114 -0
- package/dist/src/web/app.css +65 -0
- package/dist/src/web/app.js +336 -0
- package/dist/src/web/index.html +39 -0
- package/package.json +22 -4
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE EXECUTOR WORKER'S SIDE (src/duck/executor.ts says why it exists): the one DuckDB instance of the copy's file,
|
|
3
|
+
* every connection of it, and the Mason functions, registered from THIS thread so that every call DuckDB's executor
|
|
4
|
+
* makes lands here and never on the listener's thread. It answers the requests the main thread posts — a connection's
|
|
5
|
+
* statements (`conn.*`), a wire session's (`runner.*`, `answer.*`, run by src/duck/store.ts SessionRunner as before)
|
|
6
|
+
* — each with its id, and says its log lines as messages.
|
|
7
|
+
*
|
|
8
|
+
* THIS thread never waits for the executor (src/duck/store.ts "HOW A STATEMENT IS CANCELLED"): a result or a
|
|
9
|
+
* connection is closed only once DuckDB has handed it back. A request is answered asynchronously, so the turns between
|
|
10
|
+
* a statement's function calls serve the others; a statement's cancel does not need one (the cancel table,
|
|
11
|
+
* src/duck/cancel.ts).
|
|
12
|
+
*/
|
|
13
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
14
|
+
import { openDuck, SessionRunner } from "./store.js";
|
|
15
|
+
import { FUNCTION_CALLS, registerLibcOrder, useCancelTable } from "./functions.js";
|
|
16
|
+
import { stopPgEval } from "./pg-eval.js";
|
|
17
|
+
import { StatementError } from "../api/postgres/server.js";
|
|
18
|
+
import { STOPPING, wireError } from "./executor.js";
|
|
19
|
+
const { cancels } = workerData;
|
|
20
|
+
useCancelTable(cancels);
|
|
21
|
+
const port = parentPort;
|
|
22
|
+
let instance = null;
|
|
23
|
+
/** the connections by number: 0 is the writer (the store's), the rest `connect`'s */
|
|
24
|
+
const conns = new Map();
|
|
25
|
+
let nextConn = 1;
|
|
26
|
+
const runners = new Map();
|
|
27
|
+
let nextRunner = 1;
|
|
28
|
+
/** each session's close, from its start until it ends (`runner.close`, or the shutdown's): the shutdown waits for every
|
|
29
|
+
* one before it closes the instance — one it did not wait for closed the connections and the instance under a drain's
|
|
30
|
+
* pending fetch, and the process aborted (a review: exit 134, an uncaught Napi::Error) */
|
|
31
|
+
const closing = new Map();
|
|
32
|
+
/** every connection call still running, by connection: the shutdown closes a connection only once its calls settled */
|
|
33
|
+
const connCalls = new Map();
|
|
34
|
+
/** set by `shutdown`: a session's new statement is refused 57P01, a connection's new call refused */
|
|
35
|
+
let stopping = false;
|
|
36
|
+
const statements = new Map();
|
|
37
|
+
const key = (runner, seq) => `${runner}:${seq}`;
|
|
38
|
+
const log = (line) => { port.postMessage({ log: line }); };
|
|
39
|
+
/** an abort's reason as the main thread's signal has it: the statement timeout's error, else none (the client's cancel) */
|
|
40
|
+
const abortWith = (c, reason) => {
|
|
41
|
+
if (!c.signal.aborted) {
|
|
42
|
+
if (reason.fields !== undefined)
|
|
43
|
+
c.abort(new StatementError(reason.fields, ""));
|
|
44
|
+
else
|
|
45
|
+
c.abort();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
/** a connection's call, held in `connCalls` until it settles */
|
|
49
|
+
function onConn(n, f) {
|
|
50
|
+
if (stopping)
|
|
51
|
+
return Promise.reject(new Error("the copy is closing: no statement starts on it any more"));
|
|
52
|
+
const p = f(need(conns, n, "connection"));
|
|
53
|
+
const calls = connCalls.get(n) ?? new Set();
|
|
54
|
+
connCalls.set(n, calls);
|
|
55
|
+
calls.add(p);
|
|
56
|
+
void p.then(() => calls.delete(p), () => calls.delete(p));
|
|
57
|
+
return p;
|
|
58
|
+
}
|
|
59
|
+
/** a session's close, once: started by its `runner.close` or by the shutdown, whichever comes first */
|
|
60
|
+
function closeRunner(n) {
|
|
61
|
+
const already = closing.get(n);
|
|
62
|
+
if (already !== undefined)
|
|
63
|
+
return already;
|
|
64
|
+
const runner = need(runners, n, "session");
|
|
65
|
+
runners.delete(n);
|
|
66
|
+
const p = runner.close();
|
|
67
|
+
closing.set(n, p);
|
|
68
|
+
void p.then(() => closing.delete(n), () => closing.delete(n));
|
|
69
|
+
return p;
|
|
70
|
+
}
|
|
71
|
+
const need = (m, n, what) => {
|
|
72
|
+
const v = m.get(n);
|
|
73
|
+
if (v === undefined)
|
|
74
|
+
throw new Error(`no ${what} ${n} on the copy's thread (Mason bug: report it)`);
|
|
75
|
+
return v;
|
|
76
|
+
};
|
|
77
|
+
async function answer(m) {
|
|
78
|
+
// a session's statement asked once the stop began: told so, never started under the closing instance
|
|
79
|
+
if (stopping && ["runner.open", "runner.run", "runner.explain", "answer.fetch"].includes(m.op))
|
|
80
|
+
throw new StatementError(STOPPING, "");
|
|
81
|
+
switch (m.op) {
|
|
82
|
+
case "open": {
|
|
83
|
+
const opened = await openDuck(String(m.file), m.settings);
|
|
84
|
+
instance = opened.instance;
|
|
85
|
+
conns.set(0, opened.writer);
|
|
86
|
+
return { value: null };
|
|
87
|
+
}
|
|
88
|
+
case "connect": {
|
|
89
|
+
if (instance === null)
|
|
90
|
+
throw new Error("the copy is not open (Mason bug: report it)");
|
|
91
|
+
const n = nextConn++;
|
|
92
|
+
conns.set(n, await instance.connect());
|
|
93
|
+
return { value: n };
|
|
94
|
+
}
|
|
95
|
+
case "conn.run": {
|
|
96
|
+
const r = await onConn(Number(m.conn), (c) => c.run(String(m.sql), m.values));
|
|
97
|
+
return { value: { rowsChanged: r.rowsChanged } };
|
|
98
|
+
}
|
|
99
|
+
case "conn.read": {
|
|
100
|
+
const r = await onConn(Number(m.conn), (c) => c.runAndReadAll(String(m.sql), m.values));
|
|
101
|
+
return { value: { js: r.getRowsJS(), json: r.getRowsJson(), rows: r.getRows(), names: r.columnNames(),
|
|
102
|
+
types: Array.from({ length: r.columnCount }, (_, i) => r.columnType(i).toJson()) } };
|
|
103
|
+
}
|
|
104
|
+
case "conn.prepare": {
|
|
105
|
+
const p = await onConn(Number(m.conn), (c) => c.prepare(String(m.sql)));
|
|
106
|
+
try {
|
|
107
|
+
return { value: { names: Array.from({ length: p.columnCount }, (_, i) => p.columnName(i)), types: Array.from({ length: p.columnCount }, (_, i) => p.columnType(i).toJson()) } };
|
|
108
|
+
}
|
|
109
|
+
finally {
|
|
110
|
+
p.destroySync();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
case "conn.interrupt":
|
|
114
|
+
conns.get(Number(m.conn))?.interrupt();
|
|
115
|
+
return undefined;
|
|
116
|
+
case "conn.close": {
|
|
117
|
+
const c = conns.get(Number(m.conn));
|
|
118
|
+
conns.delete(Number(m.conn));
|
|
119
|
+
try {
|
|
120
|
+
c?.closeSync();
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
log(`closing a connection of the copy failed: ${e.message}`);
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
case "libc":
|
|
128
|
+
registerLibcOrder(need(conns, 0, "connection"), m.order);
|
|
129
|
+
return { value: null };
|
|
130
|
+
case "calls": return { value: Object.fromEntries(FUNCTION_CALLS) };
|
|
131
|
+
case "runner.open": {
|
|
132
|
+
if (instance === null)
|
|
133
|
+
throw new Error("the copy is not open (Mason bug: report it)");
|
|
134
|
+
const n = nextRunner++;
|
|
135
|
+
runners.set(n, await SessionRunner.open(instance, log, Number(m.slot), cancels));
|
|
136
|
+
return { value: n };
|
|
137
|
+
}
|
|
138
|
+
case "runner.run": {
|
|
139
|
+
const runner = need(runners, Number(m.runner), "session");
|
|
140
|
+
const k = key(Number(m.runner), Number(m.seq));
|
|
141
|
+
const s = { controller: new AbortController(), answer: null };
|
|
142
|
+
if (m.aborted !== null)
|
|
143
|
+
abortWith(s.controller, m.aborted);
|
|
144
|
+
statements.set(k, s);
|
|
145
|
+
runner.next = Number(m.seq);
|
|
146
|
+
try {
|
|
147
|
+
const a = await runner.run(m.sql, m.params, m.session, s.controller.signal, m.fields, m.exact);
|
|
148
|
+
s.answer = a;
|
|
149
|
+
return { value: a.columns.map((c) => ({ name: c.name, oid: c.oid, size: c.size })) };
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
statements.delete(k);
|
|
153
|
+
throw e;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
case "runner.abort": {
|
|
157
|
+
const s = statements.get(key(Number(m.runner), Number(m.seq)));
|
|
158
|
+
if (s !== undefined && m.reason !== null)
|
|
159
|
+
abortWith(s.controller, m.reason);
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
case "answer.fetch": {
|
|
163
|
+
const k = key(Number(m.runner), Number(m.seq));
|
|
164
|
+
const s = statements.get(k);
|
|
165
|
+
if (s?.answer == null)
|
|
166
|
+
return { value: null };
|
|
167
|
+
let rows;
|
|
168
|
+
try {
|
|
169
|
+
rows = await s.answer.fetch(m.formats, { timeZone: String(m.timeZone) });
|
|
170
|
+
}
|
|
171
|
+
catch (e) {
|
|
172
|
+
statements.delete(k);
|
|
173
|
+
throw e;
|
|
174
|
+
}
|
|
175
|
+
if (rows === null) {
|
|
176
|
+
statements.delete(k);
|
|
177
|
+
return { value: null };
|
|
178
|
+
}
|
|
179
|
+
// one buffer a chunk, moved (never copied) to the listener's thread, and where each row ends in it
|
|
180
|
+
let size = 0;
|
|
181
|
+
for (const r of rows)
|
|
182
|
+
size += r.length;
|
|
183
|
+
const bytes = new ArrayBuffer(size);
|
|
184
|
+
const into = new Uint8Array(bytes);
|
|
185
|
+
const ends = new Array(rows.length);
|
|
186
|
+
let at = 0;
|
|
187
|
+
rows.forEach((r, i) => { into.set(r, at); at += r.length; ends[i] = at; });
|
|
188
|
+
return { value: { bytes, ends }, transfer: [bytes] };
|
|
189
|
+
}
|
|
190
|
+
case "answer.close": {
|
|
191
|
+
const k = key(Number(m.runner), Number(m.seq));
|
|
192
|
+
statements.get(k)?.answer?.close();
|
|
193
|
+
statements.delete(k);
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
case "runner.explain": {
|
|
197
|
+
const runner = need(runners, Number(m.runner), "session");
|
|
198
|
+
const k = key(Number(m.runner), Number(m.seq));
|
|
199
|
+
const s = { controller: new AbortController(), answer: null };
|
|
200
|
+
if (m.aborted !== null)
|
|
201
|
+
abortWith(s.controller, m.aborted);
|
|
202
|
+
statements.set(k, s);
|
|
203
|
+
runner.next = Number(m.seq);
|
|
204
|
+
try {
|
|
205
|
+
return { value: await runner.explain(m.sql, m.params, m.session, s.controller.signal, m.how) };
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
statements.delete(k);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
case "runner.close": {
|
|
212
|
+
await closeRunner(Number(m.runner));
|
|
213
|
+
return { value: null };
|
|
214
|
+
}
|
|
215
|
+
case "shutdown": {
|
|
216
|
+
stopping = true;
|
|
217
|
+
// THE STOP (mason stop, a shutdown): every statement still in flight is stopped as a cancel stops it — through its
|
|
218
|
+
// session when it calls a Mason function, interrupted when it calls none — with the stop as its reason, so its
|
|
219
|
+
// session is told 57P01 (STOPPING), never "ok" and never a hang; then every session's connection is closed once
|
|
220
|
+
// nothing runs on it (the listener's own closes, still in flight, included), and only then the connections and
|
|
221
|
+
// the instance. Nothing is closed under a pending call of DuckDB's: that aborted the process
|
|
222
|
+
for (const s of statements.values())
|
|
223
|
+
abortWith(s.controller, { fields: STOPPING });
|
|
224
|
+
for (const n of [...runners.keys()])
|
|
225
|
+
void closeRunner(n);
|
|
226
|
+
await Promise.allSettled([...closing.values()]);
|
|
227
|
+
// a connection's call left running (the sync and the request log stop theirs first): interrupted, and waited
|
|
228
|
+
// for — never closed under (a task of it inside a call waits for this thread)
|
|
229
|
+
for (const [n, calls] of connCalls)
|
|
230
|
+
if (calls.size > 0)
|
|
231
|
+
conns.get(n)?.interrupt();
|
|
232
|
+
for (const calls of connCalls.values())
|
|
233
|
+
await Promise.allSettled([...calls]);
|
|
234
|
+
for (const [n, c] of [...conns].reverse()) {
|
|
235
|
+
conns.delete(n);
|
|
236
|
+
c.closeSync();
|
|
237
|
+
}
|
|
238
|
+
instance?.closeSync();
|
|
239
|
+
instance = null;
|
|
240
|
+
await stopPgEval();
|
|
241
|
+
return { value: null };
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
throw new Error(`the copy's thread was asked ${JSON.stringify(m.op)}, which it does not know (Mason bug: report it)`);
|
|
245
|
+
}
|
|
246
|
+
port.on("message", (m) => {
|
|
247
|
+
// the executor's watch (src/duck/executor.ts SILENT_MS): answered at once, by a thread whose loop turns
|
|
248
|
+
if (m.op === "ping") {
|
|
249
|
+
port.postMessage({ pong: true });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
answer(m).then((r) => {
|
|
253
|
+
if (m.id === undefined)
|
|
254
|
+
return;
|
|
255
|
+
port.postMessage({ id: m.id, ok: true, value: r?.value ?? null }, r?.transfer ?? []);
|
|
256
|
+
}, (e) => {
|
|
257
|
+
if (m.id === undefined) {
|
|
258
|
+
log(`${m.op} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
port.postMessage({ id: m.id, ok: false, error: wireError(e) });
|
|
262
|
+
});
|
|
263
|
+
});
|