neonctl 2.28.0 → 2.29.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/README.md +2 -2
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +6 -7
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
* - Encoding / multibyte handling beyond UTF-8 (handled implicitly by JS).
|
|
30
30
|
* - `\watch` continuous-execution mode.
|
|
31
31
|
*/
|
|
32
|
-
import * as readline from
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
32
|
+
import * as readline from "node:readline";
|
|
33
|
+
import { attachCondStack, COND_COMMAND_NAMES, cmdElif, cmdElse, cmdEndif, cmdIf, } from "../command/cmd_cond.js";
|
|
34
|
+
import { consumeBindState, getPipelineState } from "../command/cmd_pipeline.js";
|
|
35
|
+
import { consumeNext as consumeQueuedInput } from "../command/inputQueue.js";
|
|
36
|
+
import { psqlCompleter } from "../complete/index.js";
|
|
37
|
+
import { appendHistory, defaultHistoryPath, loadHistory, resolveHistSize, truncateHistory, } from "../io/history.js";
|
|
38
|
+
import { LineEditor } from "../io/lineEditor/index.js";
|
|
39
|
+
import { formatDurationMs } from "../print/units.js";
|
|
40
|
+
import { scanSlashArgs } from "../scanner/slash.js";
|
|
41
|
+
import { scanSql } from "../scanner/sql.js";
|
|
42
|
+
import { initialScanState } from "../types/scanner.js";
|
|
43
|
+
import { captureLastError, pickOut, refreshErrorVars, renderResultSet, sendQuery, writeQueryError, } from "./common.js";
|
|
44
|
+
import { renderPromptByName } from "./prompt.js";
|
|
45
45
|
// ---------------------------------------------------------------------------
|
|
46
46
|
// Exit codes — mirror psql's `EXIT_*` constants.
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
@@ -54,10 +54,10 @@ export const EXIT_USER = 3;
|
|
|
54
54
|
// registry lookup, because they must run even inside an inactive branch.
|
|
55
55
|
// ---------------------------------------------------------------------------
|
|
56
56
|
const COND_COMMANDS = new Map([
|
|
57
|
-
[
|
|
58
|
-
[
|
|
59
|
-
[
|
|
60
|
-
[
|
|
57
|
+
["if", cmdIf],
|
|
58
|
+
["elif", cmdElif],
|
|
59
|
+
["else", cmdElse],
|
|
60
|
+
["endif", cmdEndif],
|
|
61
61
|
]);
|
|
62
62
|
const makeStreamLineReader = (input, out) => {
|
|
63
63
|
const rl = readline.createInterface({
|
|
@@ -79,7 +79,7 @@ const makeStreamLineReader = (input, out) => {
|
|
|
79
79
|
const lineQueue = [];
|
|
80
80
|
let waiter = null;
|
|
81
81
|
let ended = false;
|
|
82
|
-
rl.on(
|
|
82
|
+
rl.on("line", (line) => {
|
|
83
83
|
if (waiter) {
|
|
84
84
|
const w = waiter;
|
|
85
85
|
waiter = null;
|
|
@@ -89,7 +89,7 @@ const makeStreamLineReader = (input, out) => {
|
|
|
89
89
|
lineQueue.push(line);
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
|
-
rl.on(
|
|
92
|
+
rl.on("close", () => {
|
|
93
93
|
ended = true;
|
|
94
94
|
if (waiter) {
|
|
95
95
|
const w = waiter;
|
|
@@ -127,10 +127,10 @@ const makeStreamLineReader = (input, out) => {
|
|
|
127
127
|
*/
|
|
128
128
|
const parseBoolVar = (raw) => {
|
|
129
129
|
const v = raw.toLowerCase().trim();
|
|
130
|
-
if (v ===
|
|
130
|
+
if (v === "" || v === "on" || v === "true" || v === "yes" || v === "1") {
|
|
131
131
|
return true;
|
|
132
132
|
}
|
|
133
|
-
if (v ===
|
|
133
|
+
if (v === "off" || v === "false" || v === "no" || v === "0") {
|
|
134
134
|
return false;
|
|
135
135
|
}
|
|
136
136
|
return null;
|
|
@@ -138,14 +138,14 @@ const parseBoolVar = (raw) => {
|
|
|
138
138
|
/** Translate a psql VI_MODE var value into the LineEditor mode. */
|
|
139
139
|
const viModeOption = (raw) => {
|
|
140
140
|
if (raw === undefined)
|
|
141
|
-
return
|
|
142
|
-
return parseBoolVar(raw) === true ?
|
|
141
|
+
return "emacs";
|
|
142
|
+
return parseBoolVar(raw) === true ? "vi" : "emacs";
|
|
143
143
|
};
|
|
144
144
|
const makeEditorLineReader = async (ctx, opts = {}) => {
|
|
145
145
|
const env = process.env;
|
|
146
146
|
const histPath = defaultHistoryPath(env);
|
|
147
147
|
const histSize = resolveHistSize(env);
|
|
148
|
-
const histControl = ctx.settings.vars.get(
|
|
148
|
+
const histControl = ctx.settings.vars.get("HISTCONTROL") ??
|
|
149
149
|
ctx.settings.histControl;
|
|
150
150
|
let history = [];
|
|
151
151
|
try {
|
|
@@ -158,7 +158,7 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
|
|
|
158
158
|
// VI_MODE: upstream readline's `set editing-mode {emacs|vi}`. We read once
|
|
159
159
|
// here for the initial mode, and below we install a VarStore hook so a
|
|
160
160
|
// subsequent `\set VI_MODE on` switches the editor at the next prompt.
|
|
161
|
-
const initialMode = viModeOption(ctx.settings.vars.get(
|
|
161
|
+
const initialMode = viModeOption(ctx.settings.vars.get("VI_MODE"));
|
|
162
162
|
const editor = new LineEditor({
|
|
163
163
|
stdin: ctx.stdin,
|
|
164
164
|
stdout: ctx.stdout,
|
|
@@ -174,9 +174,9 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
|
|
|
174
174
|
// and on success forward to `editor.setMode` (which defers the switch to
|
|
175
175
|
// the next readLine boundary). Replay on registration is fine — the hook
|
|
176
176
|
// is idempotent for a no-op `null`/unchanged value.
|
|
177
|
-
ctx.settings.vars.addHook(
|
|
177
|
+
ctx.settings.vars.addHook("VI_MODE", (newValue) => {
|
|
178
178
|
if (newValue === null) {
|
|
179
|
-
editor.setMode(
|
|
179
|
+
editor.setMode("emacs");
|
|
180
180
|
return true;
|
|
181
181
|
}
|
|
182
182
|
const parsed = parseBoolVar(newValue);
|
|
@@ -184,7 +184,7 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
|
|
|
184
184
|
ctx.stderr.write(`\\set: VI_MODE: invalid value "${newValue}"; valid values: on, off\n`);
|
|
185
185
|
return false;
|
|
186
186
|
}
|
|
187
|
-
editor.setMode(parsed ?
|
|
187
|
+
editor.setMode(parsed ? "vi" : "emacs");
|
|
188
188
|
return true;
|
|
189
189
|
});
|
|
190
190
|
return {
|
|
@@ -195,7 +195,7 @@ const makeEditorLineReader = async (ctx, opts = {}) => {
|
|
|
195
195
|
return r;
|
|
196
196
|
},
|
|
197
197
|
pushHistory: (line) => {
|
|
198
|
-
const trimmed = line.replace(/\n+$/,
|
|
198
|
+
const trimmed = line.replace(/\n+$/, "");
|
|
199
199
|
if (trimmed.length === 0)
|
|
200
200
|
return;
|
|
201
201
|
editor.pushHistory(trimmed);
|
|
@@ -231,8 +231,8 @@ const isQuitKeyword = (line) => {
|
|
|
231
231
|
const trimmed = line.trim();
|
|
232
232
|
if (trimmed.length === 0)
|
|
233
233
|
return false;
|
|
234
|
-
const stripped = trimmed.replace(/;+\s*$/u,
|
|
235
|
-
return stripped ===
|
|
234
|
+
const stripped = trimmed.replace(/;+\s*$/u, "").trimEnd();
|
|
235
|
+
return stripped === "exit" || stripped === "quit";
|
|
236
236
|
};
|
|
237
237
|
/**
|
|
238
238
|
* Recognize the bare `help` keyword the same way upstream does: at the start
|
|
@@ -243,27 +243,27 @@ const isHelpKeyword = (line) => {
|
|
|
243
243
|
const trimmed = line.trim();
|
|
244
244
|
if (trimmed.length === 0)
|
|
245
245
|
return false;
|
|
246
|
-
const stripped = trimmed.replace(/;+\s*$/u,
|
|
247
|
-
return stripped ===
|
|
246
|
+
const stripped = trimmed.replace(/;+\s*$/u, "").trimEnd();
|
|
247
|
+
return stripped === "help";
|
|
248
248
|
};
|
|
249
|
-
const HELP_TEXT =
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
const HELP_TEXT = "You are using psql-ts, the embedded TypeScript psql in neonctl.\n" +
|
|
250
|
+
"Type: \\copyright for distribution terms\n" +
|
|
251
|
+
" \\h for help with SQL commands\n" +
|
|
252
|
+
" \\? for help with psql commands\n" +
|
|
253
|
+
" \\g or terminate with semicolon to execute query\n" +
|
|
254
|
+
" \\q to quit\n";
|
|
255
255
|
const makeLineReader = async (ctx, opts = {}) => {
|
|
256
|
-
const debug = process.env.NEONCTL_PSQL_DEBUG ===
|
|
256
|
+
const debug = process.env.NEONCTL_PSQL_DEBUG === "1";
|
|
257
257
|
if (ctx.settings.notty) {
|
|
258
258
|
if (debug) {
|
|
259
|
-
ctx.stderr.write(
|
|
259
|
+
ctx.stderr.write("[psql-debug] notty=true; using stream reader (no line editor / no Tab completion)\n");
|
|
260
260
|
}
|
|
261
261
|
return makeStreamLineReader(ctx.stdin, ctx.stdout);
|
|
262
262
|
}
|
|
263
263
|
try {
|
|
264
264
|
const r = await makeEditorLineReader(ctx, opts);
|
|
265
265
|
if (debug) {
|
|
266
|
-
ctx.stderr.write(
|
|
266
|
+
ctx.stderr.write("[psql-debug] LineEditor engaged (raw mode, Tab completion active)\n");
|
|
267
267
|
}
|
|
268
268
|
return r;
|
|
269
269
|
}
|
|
@@ -277,22 +277,22 @@ const makeLineReader = async (ctx, opts = {}) => {
|
|
|
277
277
|
const transactionState = (ctx) => {
|
|
278
278
|
const db = ctx.settings.db;
|
|
279
279
|
if (!db)
|
|
280
|
-
return
|
|
280
|
+
return "idle";
|
|
281
281
|
const status = db.txStatus;
|
|
282
282
|
switch (status) {
|
|
283
|
-
case
|
|
284
|
-
case
|
|
285
|
-
return
|
|
286
|
-
case
|
|
287
|
-
case
|
|
288
|
-
return
|
|
289
|
-
case
|
|
290
|
-
case
|
|
291
|
-
return
|
|
292
|
-
case
|
|
293
|
-
return
|
|
283
|
+
case "I":
|
|
284
|
+
case "idle":
|
|
285
|
+
return "idle";
|
|
286
|
+
case "T":
|
|
287
|
+
case "in-block":
|
|
288
|
+
return "in-block";
|
|
289
|
+
case "E":
|
|
290
|
+
case "failed":
|
|
291
|
+
return "failed";
|
|
292
|
+
case "unknown":
|
|
293
|
+
return "unknown";
|
|
294
294
|
default:
|
|
295
|
-
return
|
|
295
|
+
return "idle";
|
|
296
296
|
}
|
|
297
297
|
};
|
|
298
298
|
// ---------------------------------------------------------------------------
|
|
@@ -321,7 +321,7 @@ const makeBackslashContext = (ctx, cmdName, rawArgs, queryBuf) => {
|
|
|
321
321
|
cmdName,
|
|
322
322
|
queryBuf,
|
|
323
323
|
rawArgs,
|
|
324
|
-
nextArg(mode =
|
|
324
|
+
nextArg(mode = "normal") {
|
|
325
325
|
const args = argsFor(mode);
|
|
326
326
|
const idx = cursors.get(mode) ?? 0;
|
|
327
327
|
if (idx >= args.length)
|
|
@@ -351,7 +351,7 @@ const buildPromptContext = (ctx, promptStatus, lineNumber) => ({
|
|
|
351
351
|
promptStatus,
|
|
352
352
|
lineNumber,
|
|
353
353
|
inTransaction: transactionState(ctx),
|
|
354
|
-
pipelineState:
|
|
354
|
+
pipelineState: "off",
|
|
355
355
|
});
|
|
356
356
|
// ---------------------------------------------------------------------------
|
|
357
357
|
// Conditional-command dispatch. Returns true if the command was a cond
|
|
@@ -370,7 +370,7 @@ const dispatchCondCommand = async (ctx, cmdName, rawArgs, queryBuf) => {
|
|
|
370
370
|
// these: commands using cmd_io's `errResult` (and inline writers) set it
|
|
371
371
|
// to `true`; cond commands (which only stash `lastErrorResult.message`)
|
|
372
372
|
// leave it unset so the mainloop surfaces the message.
|
|
373
|
-
if (result.status ===
|
|
373
|
+
if (result.status === "error" &&
|
|
374
374
|
!result.errorWritten &&
|
|
375
375
|
ctx.settings.lastErrorResult?.message) {
|
|
376
376
|
writeError(ctx, ctx.settings.lastErrorResult.message);
|
|
@@ -389,7 +389,7 @@ const dispatchRegisteredCommand = async (ctx, cmdName, rawArgs, queryBuf) => {
|
|
|
389
389
|
// layer doesn't add a second one. (Other dispatch paths set
|
|
390
390
|
// `lastErrorResult.message`; this one does not, so the duplicate
|
|
391
391
|
// guard below would skip anyway — flag it explicitly for symmetry.)
|
|
392
|
-
return { status:
|
|
392
|
+
return { status: "error", errorWritten: true };
|
|
393
393
|
}
|
|
394
394
|
const bctx = makeBackslashContext(ctx, cmdName, rawArgs, queryBuf);
|
|
395
395
|
attachCondStack(bctx, ctx.cond);
|
|
@@ -400,7 +400,7 @@ const dispatchRegisteredCommand = async (ctx, cmdName, rawArgs, queryBuf) => {
|
|
|
400
400
|
// would emit a stray `psql: ERROR: <msg>` line between the LINE/`^`
|
|
401
401
|
// block and the `\errverbose` re-render, breaking the strict ordering
|
|
402
402
|
// check in the conformance regex.
|
|
403
|
-
if (result.status ===
|
|
403
|
+
if (result.status === "error" &&
|
|
404
404
|
!result.errorWritten &&
|
|
405
405
|
ctx.settings.lastErrorResult?.message) {
|
|
406
406
|
writeError(ctx, ctx.settings.lastErrorResult.message);
|
|
@@ -426,9 +426,9 @@ const refreshConnectionVars = (ctx) => {
|
|
|
426
426
|
const db = ctx.settings.db;
|
|
427
427
|
if (!db)
|
|
428
428
|
return;
|
|
429
|
-
const enc = db.parameterStatus(
|
|
430
|
-
if (enc !== undefined && ctx.settings.vars.get(
|
|
431
|
-
ctx.settings.vars.set(
|
|
429
|
+
const enc = db.parameterStatus("client_encoding");
|
|
430
|
+
if (enc !== undefined && ctx.settings.vars.get("ENCODING") !== enc) {
|
|
431
|
+
ctx.settings.vars.set("ENCODING", enc);
|
|
432
432
|
}
|
|
433
433
|
};
|
|
434
434
|
const dispatchSendQuery = async (ctx, sql) => {
|
|
@@ -457,15 +457,15 @@ const dispatchSendQuery = async (ctx, sql) => {
|
|
|
457
457
|
const trimmed = sql.trimStart();
|
|
458
458
|
if (/^COPY\b/i.test(trimmed) &&
|
|
459
459
|
/\b(FROM\s+STDIN|TO\s+STDOUT)\b/i.test(trimmed)) {
|
|
460
|
-
ctx.stderr.write(
|
|
460
|
+
ctx.stderr.write("psql: error: COPY in a pipeline is not supported, aborting connection\n");
|
|
461
461
|
// Hard-abort the underlying socket so isClosed() flips true and the
|
|
462
462
|
// mainloop's post-dispatch `checkConnectionLost` ends the loop.
|
|
463
463
|
try {
|
|
464
464
|
const db = ctx.settings.db;
|
|
465
|
-
if (typeof db.abortForCopyInPipeline ===
|
|
465
|
+
if (typeof db.abortForCopyInPipeline === "function") {
|
|
466
466
|
db.abortForCopyInPipeline();
|
|
467
467
|
}
|
|
468
|
-
else if (typeof db.close ===
|
|
468
|
+
else if (typeof db.close === "function") {
|
|
469
469
|
await db.close();
|
|
470
470
|
}
|
|
471
471
|
}
|
|
@@ -478,23 +478,23 @@ const dispatchSendQuery = async (ctx, sql) => {
|
|
|
478
478
|
// Pipeline-mode `;`-queries: empty parameter list, anonymous prepared
|
|
479
479
|
// statement, anonymous portal. The result will surface later through
|
|
480
480
|
// `\endpipeline` / `\getresults`.
|
|
481
|
-
await ps.session.parse(
|
|
482
|
-
await ps.session.bind(
|
|
481
|
+
await ps.session.parse("", sql, []);
|
|
482
|
+
await ps.session.bind("", bind?.values ?? []);
|
|
483
483
|
const exec = (async () => {
|
|
484
|
-
await ps.session.execute(
|
|
484
|
+
await ps.session.execute("", 0);
|
|
485
485
|
return undefined;
|
|
486
486
|
})();
|
|
487
487
|
ps.pending.push(exec);
|
|
488
488
|
// The enqueue succeeded; the actual result will flush at
|
|
489
489
|
// `\endpipeline` time. Mark the diagnostic vars as success-now so
|
|
490
490
|
// intervening `\echo :ERROR` sees "false" between pipeline appends.
|
|
491
|
-
refreshErrorVars(ctx.settings, { kind:
|
|
491
|
+
refreshErrorVars(ctx.settings, { kind: "success", rowCount: null });
|
|
492
492
|
return true;
|
|
493
493
|
}
|
|
494
494
|
catch (err) {
|
|
495
495
|
const message = captureLastError(ctx.settings, err, sql);
|
|
496
496
|
writeQueryError(ctx, message);
|
|
497
|
-
refreshErrorVars(ctx.settings, { kind:
|
|
497
|
+
refreshErrorVars(ctx.settings, { kind: "error" });
|
|
498
498
|
return false;
|
|
499
499
|
}
|
|
500
500
|
}
|
|
@@ -523,10 +523,10 @@ const dispatchSendQuery = async (ctx, sql) => {
|
|
|
523
523
|
finally {
|
|
524
524
|
refreshConnectionVars(ctx);
|
|
525
525
|
refreshErrorVars(ctx.settings, hadError
|
|
526
|
-
? { kind:
|
|
527
|
-
: { kind:
|
|
526
|
+
? { kind: "error" }
|
|
527
|
+
: { kind: "success", rowCount: lastRowCount });
|
|
528
528
|
if (ctx.settings.timing) {
|
|
529
|
-
ctx.stdout.write(
|
|
529
|
+
ctx.stdout.write("\n" + formatDurationMs(Date.now() - started) + "\n");
|
|
530
530
|
}
|
|
531
531
|
}
|
|
532
532
|
}
|
|
@@ -581,18 +581,18 @@ const installNotificationHandler = (ctx, reader) => {
|
|
|
581
581
|
* returned string directly.
|
|
582
582
|
*/
|
|
583
583
|
const formatNotice = (notice, verbosity, showContext) => {
|
|
584
|
-
const severity = notice.severity ||
|
|
585
|
-
const message = notice.message ||
|
|
584
|
+
const severity = notice.severity || "NOTICE";
|
|
585
|
+
const message = notice.message || "";
|
|
586
586
|
const lines = [];
|
|
587
|
-
if (verbosity ===
|
|
588
|
-
const sqlstate = notice.code ??
|
|
587
|
+
if (verbosity === "verbose" || verbosity === "sqlstate") {
|
|
588
|
+
const sqlstate = notice.code ?? "XX000";
|
|
589
589
|
lines.push(`${severity}: ${sqlstate}: ${message}`);
|
|
590
590
|
}
|
|
591
591
|
else {
|
|
592
592
|
lines.push(`${severity}: ${message}`);
|
|
593
593
|
}
|
|
594
|
-
if (verbosity ===
|
|
595
|
-
return lines.join(
|
|
594
|
+
if (verbosity === "terse" || verbosity === "sqlstate") {
|
|
595
|
+
return lines.join("\n") + "\n";
|
|
596
596
|
}
|
|
597
597
|
if (notice.detail)
|
|
598
598
|
lines.push(`DETAIL: ${notice.detail}`);
|
|
@@ -603,19 +603,19 @@ const formatNotice = (notice, verbosity, showContext) => {
|
|
|
603
603
|
// - `default` shows CONTEXT only when SHOW_CONTEXT is `always` for
|
|
604
604
|
// non-error severities (NOTICE / WARNING / INFO / LOG / DEBUG), or
|
|
605
605
|
// when SHOW_CONTEXT is `errors`/`always` for ERROR-level entries.
|
|
606
|
-
const isError = severity ===
|
|
607
|
-
const includeContext = verbosity ===
|
|
608
|
-
showContext ===
|
|
609
|
-
(showContext ===
|
|
606
|
+
const isError = severity === "ERROR" || severity === "FATAL" || severity === "PANIC";
|
|
607
|
+
const includeContext = verbosity === "verbose" ||
|
|
608
|
+
showContext === "always" ||
|
|
609
|
+
(showContext === "errors" && isError);
|
|
610
610
|
if (includeContext && notice.where) {
|
|
611
611
|
lines.push(`CONTEXT: ${notice.where}`);
|
|
612
612
|
}
|
|
613
|
-
if (verbosity ===
|
|
614
|
-
const location = (notice.routine ??
|
|
615
|
-
(notice.file ? `, ${notice.file}:${notice.line ??
|
|
613
|
+
if (verbosity === "verbose" && (notice.routine || notice.file)) {
|
|
614
|
+
const location = (notice.routine ?? "") +
|
|
615
|
+
(notice.file ? `, ${notice.file}:${notice.line ?? ""}` : "");
|
|
616
616
|
lines.push(`LOCATION: ${location}`);
|
|
617
617
|
}
|
|
618
|
-
return lines.join(
|
|
618
|
+
return lines.join("\n") + "\n";
|
|
619
619
|
};
|
|
620
620
|
/**
|
|
621
621
|
* Subscribe to NoticeResponse on the active connection, rendering each to
|
|
@@ -637,7 +637,7 @@ const installNoticeHandler = (ctx, reader) => {
|
|
|
637
637
|
// the NOTICE lands AT the result boundary, not before. Emitting here too
|
|
638
638
|
// would duplicate every notice — once when the wire layer parses it,
|
|
639
639
|
// once when the drain walks `rs.notices`.
|
|
640
|
-
if (ctx.settings.sendMode ===
|
|
640
|
+
if (ctx.settings.sendMode === "extended-pipeline")
|
|
641
641
|
return;
|
|
642
642
|
const text = formatNotice(notice, ctx.settings.verbosity, ctx.settings.showContext);
|
|
643
643
|
// Notices go to stderr (libpq default). The LineEditor's prompt-redraw
|
|
@@ -662,8 +662,8 @@ const installSigint = (ctx, state) => {
|
|
|
662
662
|
}
|
|
663
663
|
state.resetBuf();
|
|
664
664
|
};
|
|
665
|
-
process.on(
|
|
666
|
-
return () => process.off(
|
|
665
|
+
process.on("SIGINT", handler);
|
|
666
|
+
return () => process.off("SIGINT", handler);
|
|
667
667
|
};
|
|
668
668
|
// ---------------------------------------------------------------------------
|
|
669
669
|
// The main entry point.
|
|
@@ -674,7 +674,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
674
674
|
// on every Tab. The mainloop reassigns this variable across statements
|
|
675
675
|
// (resetBuf, after dispatch); the closure stays valid because it captures
|
|
676
676
|
// the binding, not a snapshot.
|
|
677
|
-
let queryBuf =
|
|
677
|
+
let queryBuf = "";
|
|
678
678
|
const reader = await makeLineReader(ctx, { getQueryBuf: () => queryBuf });
|
|
679
679
|
let scanState = initialScanState();
|
|
680
680
|
let stmtLineNumber = 1;
|
|
@@ -689,7 +689,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
689
689
|
// in lock-step with cond.push / cond.pop / cond.setSavedQueryBufLen.
|
|
690
690
|
const condScanStateStack = [];
|
|
691
691
|
const resetBuf = () => {
|
|
692
|
-
queryBuf =
|
|
692
|
+
queryBuf = "";
|
|
693
693
|
scanState = initialScanState();
|
|
694
694
|
stmtLineNumber = 1;
|
|
695
695
|
};
|
|
@@ -699,7 +699,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
699
699
|
// we halt the loop instead so we don't spam ERROR lines for every one.
|
|
700
700
|
const checkConnectionLost = () => {
|
|
701
701
|
if (ctx.settings.db?.isClosed()) {
|
|
702
|
-
ctx.stderr.write(
|
|
702
|
+
ctx.stderr.write("psql: error: connection to server was lost\n");
|
|
703
703
|
successResult = EXIT_BADCONN;
|
|
704
704
|
exitRequested = true;
|
|
705
705
|
return true;
|
|
@@ -726,12 +726,12 @@ export const runMainLoop = async (ctx) => {
|
|
|
726
726
|
// with stdout. For TTY input the LineEditor renders the prompt itself.
|
|
727
727
|
const computePrompt = (status) => {
|
|
728
728
|
if (ctx.settings.notty)
|
|
729
|
-
return
|
|
730
|
-
const name = queryBuf.trim().length === 0 || status ===
|
|
731
|
-
?
|
|
732
|
-
: status ===
|
|
733
|
-
?
|
|
734
|
-
:
|
|
729
|
+
return "";
|
|
730
|
+
const name = queryBuf.trim().length === 0 || status === "ready"
|
|
731
|
+
? "PROMPT1"
|
|
732
|
+
: status === "copy"
|
|
733
|
+
? "PROMPT3"
|
|
734
|
+
: "PROMPT2";
|
|
735
735
|
const promptCtx = buildPromptContext(ctx, status, stmtLineNumber);
|
|
736
736
|
return renderPromptByName(name, promptCtx);
|
|
737
737
|
};
|
|
@@ -749,8 +749,8 @@ export const runMainLoop = async (ctx) => {
|
|
|
749
749
|
const spec = ctx.registry.lookup(cmdName);
|
|
750
750
|
if (!spec)
|
|
751
751
|
return undefined;
|
|
752
|
-
if (spec.argMode ===
|
|
753
|
-
return
|
|
752
|
+
if (spec.argMode === "whole-line")
|
|
753
|
+
return "whole-line";
|
|
754
754
|
// Backslash registry currently only distinguishes whole-line vs the
|
|
755
755
|
// default `lex` mode. Filepipe is signalled per-call via
|
|
756
756
|
// `nextArg('filepipe')` inside cmd implementations rather than the
|
|
@@ -758,15 +758,15 @@ export const runMainLoop = async (ctx) => {
|
|
|
758
758
|
// upstream declares as `OT_FILEPIPE` (`\w` and `\o`). Without this,
|
|
759
759
|
// `\w |/no/such/file \else` would split off `\else` as a separate
|
|
760
760
|
// command instead of capturing it as the file's whole-line arg.
|
|
761
|
-
if (cmdName ===
|
|
762
|
-
return
|
|
761
|
+
if (cmdName === "w" || cmdName === "o")
|
|
762
|
+
return "filepipe";
|
|
763
763
|
return undefined;
|
|
764
764
|
};
|
|
765
765
|
/**
|
|
766
766
|
* Strip block / line comments cheaply before scanning so a COPY-shaped
|
|
767
767
|
* comment doesn't trigger pre-buffering or sink wiring.
|
|
768
768
|
*/
|
|
769
|
-
const stripSqlComments = (sql) => sql.replace(/\/\*[\s\S]*?\*\//g,
|
|
769
|
+
const stripSqlComments = (sql) => sql.replace(/\/\*[\s\S]*?\*\//g, "").replace(/--[^\n]*/g, "");
|
|
770
770
|
/**
|
|
771
771
|
* Count the number of `COPY ... FROM STDIN` segments in `sql`. Upstream
|
|
772
772
|
* `handleCopyIn` in copy.c is invoked for each one that hits the wire as
|
|
@@ -808,10 +808,10 @@ export const runMainLoop = async (ctx) => {
|
|
|
808
808
|
const readCopyDataBlock = async () => {
|
|
809
809
|
const lines = [];
|
|
810
810
|
for (;;) {
|
|
811
|
-
const line = await reader.readLine(
|
|
811
|
+
const line = await reader.readLine("");
|
|
812
812
|
if (line === null)
|
|
813
813
|
break;
|
|
814
|
-
if (line.replace(/\s+$/,
|
|
814
|
+
if (line.replace(/\s+$/, "") === "\\.")
|
|
815
815
|
break;
|
|
816
816
|
lines.push(line);
|
|
817
817
|
// Upstream `handleCopyIn` in copy.c reads COPY data lines straight
|
|
@@ -824,8 +824,8 @@ export const runMainLoop = async (ctx) => {
|
|
|
824
824
|
}
|
|
825
825
|
// Each line plus a trailing newline — matches the byte stream COPY
|
|
826
826
|
// expects on its input side.
|
|
827
|
-
const text = lines.length === 0 ?
|
|
828
|
-
return Buffer.from(text,
|
|
827
|
+
const text = lines.length === 0 ? "" : lines.join("\n") + "\n";
|
|
828
|
+
return Buffer.from(text, "utf8");
|
|
829
829
|
};
|
|
830
830
|
/**
|
|
831
831
|
* Process the assembled queryBuf+line through scanSql, dispatching the
|
|
@@ -842,11 +842,11 @@ export const runMainLoop = async (ctx) => {
|
|
|
842
842
|
singleline: ctx.settings.singleline,
|
|
843
843
|
});
|
|
844
844
|
scanState = result.nextState;
|
|
845
|
-
if (result.kind ===
|
|
845
|
+
if (result.kind === "semicolon") {
|
|
846
846
|
// Use the substituted `result.sql` so `:NAME` references already
|
|
847
847
|
// resolved at scan time make it into the executed SQL.
|
|
848
848
|
const sqlText = queryBuf + result.sql;
|
|
849
|
-
queryBuf =
|
|
849
|
+
queryBuf = "";
|
|
850
850
|
working = working.slice(result.consumed);
|
|
851
851
|
scanState = initialScanState();
|
|
852
852
|
stmtLineNumber = 1;
|
|
@@ -861,14 +861,16 @@ export const runMainLoop = async (ctx) => {
|
|
|
861
861
|
// layer before dispatch. Mirrors upstream `handleCopyIn` in
|
|
862
862
|
// copy.c — except we pump the bytes up-front instead of via a
|
|
863
863
|
// callback into the REPL when CopyInResponse arrives.
|
|
864
|
-
const copyCount = ctx.settings.db
|
|
864
|
+
const copyCount = ctx.settings.db
|
|
865
|
+
? countCopyFromStdin(sqlText)
|
|
866
|
+
: 0;
|
|
865
867
|
const wantsCopyOut = ctx.settings.db !== undefined && hasCopyToStdout(sqlText);
|
|
866
868
|
if (copyCount > 0 && ctx.settings.db) {
|
|
867
869
|
// The Connection type doesn't expose `queueCopyInData` (kept
|
|
868
870
|
// off the frozen interface), but the concrete PgConnection
|
|
869
871
|
// does. We duck-type the method to avoid coupling here.
|
|
870
872
|
const conn = ctx.settings.db;
|
|
871
|
-
if (typeof conn.queueCopyInData ===
|
|
873
|
+
if (typeof conn.queueCopyInData === "function") {
|
|
872
874
|
// Drop any leftover buffers from a previous (failed) batch so
|
|
873
875
|
// we don't accidentally re-use stale data.
|
|
874
876
|
conn.clearCopyInDataQueue?.();
|
|
@@ -917,7 +919,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
917
919
|
}
|
|
918
920
|
continue;
|
|
919
921
|
}
|
|
920
|
-
if (result.kind ===
|
|
922
|
+
if (result.kind === "backslash") {
|
|
921
923
|
// Fold buffered SQL accumulated before the backslash into queryBuf.
|
|
922
924
|
// `result.sql` carries the (possibly empty) text that preceded the
|
|
923
925
|
// backslash in this scan pass — empty when the backslash was at the
|
|
@@ -955,7 +957,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
955
957
|
// small immutable objects.
|
|
956
958
|
const scanStateBefore = { ...scanState };
|
|
957
959
|
const r = await dispatchCondCommand(ctx, cmdName, result.rest, queryBuf);
|
|
958
|
-
if (r.handled && r.result?.status ===
|
|
960
|
+
if (r.handled && r.result?.status === "exit") {
|
|
959
961
|
exitRequested = true;
|
|
960
962
|
return;
|
|
961
963
|
}
|
|
@@ -986,24 +988,26 @@ export const runMainLoop = async (ctx) => {
|
|
|
986
988
|
// inactive restoration (so the new branch
|
|
987
989
|
// starts from a clean checkpoint).
|
|
988
990
|
// `\endif` → pop the top entry.
|
|
989
|
-
if (cmdName ===
|
|
991
|
+
if (cmdName === "if") {
|
|
990
992
|
condScanStateStack.push(scanStateBefore);
|
|
991
993
|
}
|
|
992
|
-
else if (cmdName ===
|
|
994
|
+
else if (cmdName === "elif" || cmdName === "else") {
|
|
993
995
|
// Errors leave the top untouched (cond.setState not called on
|
|
994
996
|
// the no-matching/double-else paths). Only re-anchor when the
|
|
995
997
|
// command succeeded — `status: 'ok'` covers both the active
|
|
996
998
|
// and truncated paths.
|
|
997
|
-
if (condScanStateStack.length > 0 &&
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
999
|
+
if (condScanStateStack.length > 0 &&
|
|
1000
|
+
r.result?.status === "ok") {
|
|
1001
|
+
condScanStateStack[condScanStateStack.length - 1] =
|
|
1002
|
+
{
|
|
1003
|
+
...scanState,
|
|
1004
|
+
};
|
|
1001
1005
|
}
|
|
1002
1006
|
}
|
|
1003
|
-
else if (cmdName ===
|
|
1007
|
+
else if (cmdName === "endif") {
|
|
1004
1008
|
// Pop only on success — `\endif` with no matching `\if`
|
|
1005
1009
|
// returns an error and doesn't actually pop the cond frame.
|
|
1006
|
-
if (r.result?.status ===
|
|
1010
|
+
if (r.result?.status === "ok") {
|
|
1007
1011
|
condScanStateStack.pop();
|
|
1008
1012
|
}
|
|
1009
1013
|
}
|
|
@@ -1014,7 +1018,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
1014
1018
|
// the terminal `lastWasError → EXIT_USER` escalation. Only
|
|
1015
1019
|
// ON_ERROR_STOP can escalate cond failures.
|
|
1016
1020
|
if (r.handled &&
|
|
1017
|
-
r.result?.status ===
|
|
1021
|
+
r.result?.status === "error" &&
|
|
1018
1022
|
ctx.settings.onErrorStop) {
|
|
1019
1023
|
successResult = EXIT_USER;
|
|
1020
1024
|
exitRequested = true;
|
|
@@ -1050,12 +1054,12 @@ export const runMainLoop = async (ctx) => {
|
|
|
1050
1054
|
continue;
|
|
1051
1055
|
}
|
|
1052
1056
|
const bres = await dispatchRegisteredCommand(ctx, cmdName, result.rest, queryBuf);
|
|
1053
|
-
if (bres?.status ===
|
|
1057
|
+
if (bres?.status === "exit") {
|
|
1054
1058
|
exitRequested = true;
|
|
1055
1059
|
return;
|
|
1056
1060
|
}
|
|
1057
|
-
if (bres?.status ===
|
|
1058
|
-
queryBuf = bres.newBuf ??
|
|
1061
|
+
if (bres?.status === "reset-buf") {
|
|
1062
|
+
queryBuf = bres.newBuf ?? "";
|
|
1059
1063
|
scanState = initialScanState();
|
|
1060
1064
|
stmtLineNumber = 1;
|
|
1061
1065
|
// The SQL scanner intentionally stops the backslash boundary on
|
|
@@ -1083,10 +1087,11 @@ export const runMainLoop = async (ctx) => {
|
|
|
1083
1087
|
// through-newline behaviour for the buffer-reset case — without
|
|
1084
1088
|
// changing the scanner's semantics for the inline-slash + multi-
|
|
1085
1089
|
// line shape that depends on the `\n` surviving.
|
|
1086
|
-
if (working.startsWith(
|
|
1090
|
+
if (working.startsWith("\r\n")) {
|
|
1087
1091
|
working = working.slice(2);
|
|
1088
1092
|
}
|
|
1089
|
-
else if (working.startsWith(
|
|
1093
|
+
else if (working.startsWith("\n") ||
|
|
1094
|
+
working.startsWith("\r")) {
|
|
1090
1095
|
working = working.slice(1);
|
|
1091
1096
|
}
|
|
1092
1097
|
}
|
|
@@ -1101,11 +1106,12 @@ export const runMainLoop = async (ctx) => {
|
|
|
1101
1106
|
// would assemble the SELECT's queryBuf as `\n` + `SELECT...` —
|
|
1102
1107
|
// shifting the server's `LINE N` count by one and contaminating
|
|
1103
1108
|
// the `STATEMENT: ...` echo emitted on error.
|
|
1104
|
-
if (bres?.status !==
|
|
1105
|
-
if (working.startsWith(
|
|
1109
|
+
if (bres?.status !== "reset-buf" && slashOnlyLine) {
|
|
1110
|
+
if (working.startsWith("\r\n")) {
|
|
1106
1111
|
working = working.slice(2);
|
|
1107
1112
|
}
|
|
1108
|
-
else if (working.startsWith(
|
|
1113
|
+
else if (working.startsWith("\n") ||
|
|
1114
|
+
working.startsWith("\r")) {
|
|
1109
1115
|
working = working.slice(1);
|
|
1110
1116
|
}
|
|
1111
1117
|
}
|
|
@@ -1124,8 +1130,8 @@ export const runMainLoop = async (ctx) => {
|
|
|
1124
1130
|
// newline. Without this, `\bind_named NAME 1 2 \gset pref02_ \echo X`
|
|
1125
1131
|
// would still execute `\echo X` after the pipeline-mode `\gset`
|
|
1126
1132
|
// rejection — vanilla suppresses it.
|
|
1127
|
-
if (bres?.status ===
|
|
1128
|
-
queryBuf =
|
|
1133
|
+
if (bres?.status === "error") {
|
|
1134
|
+
queryBuf = "";
|
|
1129
1135
|
scanState = initialScanState();
|
|
1130
1136
|
stmtLineNumber = 1;
|
|
1131
1137
|
// Discard any trailing content on the SAME physical line — but NOT
|
|
@@ -1139,21 +1145,22 @@ export const runMainLoop = async (ctx) => {
|
|
|
1139
1145
|
// discard). Without this branch a stack of `\gdesc\n\gdesc\n…`
|
|
1140
1146
|
// lines collapses to a single dispatched `\gdesc` because the
|
|
1141
1147
|
// first discard ate the second line.
|
|
1142
|
-
if (working.startsWith(
|
|
1148
|
+
if (working.startsWith("\r\n")) {
|
|
1143
1149
|
working = working.slice(2);
|
|
1144
1150
|
}
|
|
1145
|
-
else if (working.startsWith(
|
|
1151
|
+
else if (working.startsWith("\n") ||
|
|
1152
|
+
working.startsWith("\r")) {
|
|
1146
1153
|
working = working.slice(1);
|
|
1147
1154
|
}
|
|
1148
1155
|
else {
|
|
1149
|
-
const nlIdx = working.indexOf(
|
|
1150
|
-
working = nlIdx === -1 ?
|
|
1156
|
+
const nlIdx = working.indexOf("\n");
|
|
1157
|
+
working = nlIdx === -1 ? "" : working.slice(nlIdx + 1);
|
|
1151
1158
|
}
|
|
1152
1159
|
}
|
|
1153
1160
|
// Backslash commands like \connect can also tear down the connection.
|
|
1154
1161
|
if (checkConnectionLost())
|
|
1155
1162
|
return;
|
|
1156
|
-
if (bres?.status ===
|
|
1163
|
+
if (bres?.status === "error" && ctx.settings.onErrorStop) {
|
|
1157
1164
|
successResult = EXIT_USER;
|
|
1158
1165
|
exitRequested = true;
|
|
1159
1166
|
return;
|
|
@@ -1168,7 +1175,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
1168
1175
|
// current buffer; the line-reader feeds whole lines so this is
|
|
1169
1176
|
// effectively unreachable in interactive use.)
|
|
1170
1177
|
queryBuf += result.sql;
|
|
1171
|
-
working =
|
|
1178
|
+
working = "";
|
|
1172
1179
|
return;
|
|
1173
1180
|
}
|
|
1174
1181
|
};
|
|
@@ -1191,15 +1198,15 @@ export const runMainLoop = async (ctx) => {
|
|
|
1191
1198
|
// residue (e.g. a trailing `\n` left over after a `;` boundary) counts
|
|
1192
1199
|
// as empty so the next prompt is PROMPT1 not PROMPT2.
|
|
1193
1200
|
const status = queryBuf.trim().length === 0
|
|
1194
|
-
?
|
|
1195
|
-
: scanState.promptStatus ===
|
|
1196
|
-
?
|
|
1201
|
+
? "ready"
|
|
1202
|
+
: scanState.promptStatus === "ready"
|
|
1203
|
+
? "continue"
|
|
1197
1204
|
: scanState.promptStatus;
|
|
1198
1205
|
const prompt = computePrompt(status);
|
|
1199
1206
|
// 1. Pending input from \i: process as a single chunk and loop again.
|
|
1200
1207
|
const queued = consumeQueuedInput();
|
|
1201
1208
|
if (queued !== null) {
|
|
1202
|
-
await processChunk(queued.endsWith(
|
|
1209
|
+
await processChunk(queued.endsWith("\n") ? queued : queued + "\n");
|
|
1203
1210
|
continue;
|
|
1204
1211
|
}
|
|
1205
1212
|
// 2. Read the next line from stdin / line editor.
|
|
@@ -1210,7 +1217,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
1210
1217
|
catch (err) {
|
|
1211
1218
|
// SignalError (Ctrl-C on an interactive line) — drop the partial
|
|
1212
1219
|
// buffer and re-prompt, matching upstream psql.
|
|
1213
|
-
if (err.name ===
|
|
1220
|
+
if (err.name === "SignalError") {
|
|
1214
1221
|
resetBuf();
|
|
1215
1222
|
continue;
|
|
1216
1223
|
}
|
|
@@ -1251,8 +1258,8 @@ export const runMainLoop = async (ctx) => {
|
|
|
1251
1258
|
// the scanner is mid-quote and the line is part of the assembled
|
|
1252
1259
|
// statement. ECHO=queries echoes only completed queries — handled
|
|
1253
1260
|
// separately by the exec path.
|
|
1254
|
-
if (ctx.settings.echo ===
|
|
1255
|
-
ctx.stdout.write(line +
|
|
1261
|
+
if (ctx.settings.echo === "all") {
|
|
1262
|
+
ctx.stdout.write(line + "\n");
|
|
1256
1263
|
}
|
|
1257
1264
|
// 2a. `exit`/`quit` keyword handling.
|
|
1258
1265
|
//
|
|
@@ -1273,7 +1280,7 @@ export const runMainLoop = async (ctx) => {
|
|
|
1273
1280
|
exitRequested = true;
|
|
1274
1281
|
break;
|
|
1275
1282
|
}
|
|
1276
|
-
ctx.stdout.write(
|
|
1283
|
+
ctx.stdout.write("Use \\q to quit.\n");
|
|
1277
1284
|
continue;
|
|
1278
1285
|
}
|
|
1279
1286
|
// 2b. `help` keyword handling, same shape.
|
|
@@ -1286,14 +1293,14 @@ export const runMainLoop = async (ctx) => {
|
|
|
1286
1293
|
ctx.stdout.write(HELP_TEXT);
|
|
1287
1294
|
}
|
|
1288
1295
|
else {
|
|
1289
|
-
ctx.stdout.write(
|
|
1296
|
+
ctx.stdout.write("Use \\? for help.\n");
|
|
1290
1297
|
}
|
|
1291
1298
|
continue;
|
|
1292
1299
|
}
|
|
1293
1300
|
// 3. Push to history once we have a complete submitted line (only
|
|
1294
1301
|
// when there's something non-blank to record).
|
|
1295
1302
|
reader.pushHistory(line);
|
|
1296
|
-
await processChunk(line +
|
|
1303
|
+
await processChunk(line + "\n");
|
|
1297
1304
|
}
|
|
1298
1305
|
// EOF: if there's a residual non-empty buffer in non-interactive mode,
|
|
1299
1306
|
// dispatch it (mirroring upstream's tail-of-MainLoop block). For
|
|
@@ -1310,18 +1317,18 @@ export const runMainLoop = async (ctx) => {
|
|
|
1310
1317
|
const ok = await dispatchSendQuery(ctx, queryBuf);
|
|
1311
1318
|
sigintState.inQuery = false;
|
|
1312
1319
|
if (ctx.settings.db?.isClosed()) {
|
|
1313
|
-
ctx.stderr.write(
|
|
1320
|
+
ctx.stderr.write("psql: error: connection to server was lost\n");
|
|
1314
1321
|
successResult = EXIT_BADCONN;
|
|
1315
1322
|
}
|
|
1316
1323
|
else if (!ok && ctx.settings.onErrorStop) {
|
|
1317
1324
|
successResult = EXIT_USER;
|
|
1318
1325
|
}
|
|
1319
1326
|
}
|
|
1320
|
-
queryBuf =
|
|
1327
|
+
queryBuf = "";
|
|
1321
1328
|
}
|
|
1322
1329
|
// Warn about unbalanced \if blocks (psql's tail-of-MainLoop check).
|
|
1323
1330
|
if (!exitRequested && ctx.cond.depth() > 0) {
|
|
1324
|
-
writeError(ctx,
|
|
1331
|
+
writeError(ctx, "reached EOF without finding closing \\endif(s)");
|
|
1325
1332
|
if (ctx.settings.onErrorStop && ctx.settings.notty) {
|
|
1326
1333
|
successResult = EXIT_USER;
|
|
1327
1334
|
}
|