neonctl 2.28.0 → 2.29.1

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.
Files changed (135) hide show
  1. package/README.md +71 -71
  2. package/dist/analytics.js +35 -33
  3. package/dist/api.js +34 -34
  4. package/dist/auth.js +50 -44
  5. package/dist/cli.js +2 -2
  6. package/dist/commands/auth.js +58 -52
  7. package/dist/commands/bootstrap.js +115 -157
  8. package/dist/commands/branches.js +154 -147
  9. package/dist/commands/bucket.js +124 -118
  10. package/dist/commands/checkout.js +49 -49
  11. package/dist/commands/config.js +212 -88
  12. package/dist/commands/connection_string.js +62 -62
  13. package/dist/commands/data_api.js +96 -96
  14. package/dist/commands/databases.js +23 -23
  15. package/dist/commands/deploy.js +12 -12
  16. package/dist/commands/dev.js +114 -114
  17. package/dist/commands/env.js +43 -43
  18. package/dist/commands/functions.js +97 -98
  19. package/dist/commands/index.js +26 -26
  20. package/dist/commands/init.js +23 -22
  21. package/dist/commands/ip_allow.js +29 -29
  22. package/dist/commands/link.js +223 -166
  23. package/dist/commands/neon_auth.js +381 -363
  24. package/dist/commands/operations.js +11 -11
  25. package/dist/commands/orgs.js +8 -8
  26. package/dist/commands/projects.js +101 -99
  27. package/dist/commands/psql.js +31 -31
  28. package/dist/commands/roles.js +21 -21
  29. package/dist/commands/schema_diff.js +23 -23
  30. package/dist/commands/set_context.js +17 -17
  31. package/dist/commands/status.js +17 -17
  32. package/dist/commands/user.js +5 -5
  33. package/dist/commands/vpc_endpoints.js +50 -50
  34. package/dist/config.js +7 -7
  35. package/dist/config_format.js +5 -5
  36. package/dist/context.js +23 -16
  37. package/dist/current_branch_fast_path.js +6 -6
  38. package/dist/dev/env.js +34 -34
  39. package/dist/dev/functions.js +4 -4
  40. package/dist/dev/inputs.js +6 -6
  41. package/dist/dev/runtime.js +25 -25
  42. package/dist/env.js +14 -14
  43. package/dist/env_file.js +13 -13
  44. package/dist/errors.js +19 -19
  45. package/dist/functions_api.js +10 -10
  46. package/dist/help.js +15 -15
  47. package/dist/index.js +94 -92
  48. package/dist/log.js +2 -2
  49. package/dist/pkg.js +5 -5
  50. package/dist/psql/cli.js +4 -2
  51. package/dist/psql/command/cmd_cond.js +61 -61
  52. package/dist/psql/command/cmd_connect.js +159 -154
  53. package/dist/psql/command/cmd_copy.js +107 -97
  54. package/dist/psql/command/cmd_describe.js +368 -363
  55. package/dist/psql/command/cmd_format.js +276 -263
  56. package/dist/psql/command/cmd_io.js +269 -263
  57. package/dist/psql/command/cmd_lo.js +74 -66
  58. package/dist/psql/command/cmd_meta.js +148 -148
  59. package/dist/psql/command/cmd_misc.js +17 -17
  60. package/dist/psql/command/cmd_pipeline.js +142 -135
  61. package/dist/psql/command/cmd_restrict.js +25 -25
  62. package/dist/psql/command/cmd_show.js +183 -168
  63. package/dist/psql/command/dispatch.js +26 -26
  64. package/dist/psql/command/shared.js +14 -14
  65. package/dist/psql/complete/filenames.js +16 -16
  66. package/dist/psql/complete/index.js +4 -4
  67. package/dist/psql/complete/matcher.js +33 -32
  68. package/dist/psql/complete/psqlVars.js +173 -173
  69. package/dist/psql/complete/queries.js +5 -3
  70. package/dist/psql/complete/rules.js +900 -863
  71. package/dist/psql/core/common.js +136 -133
  72. package/dist/psql/core/help.js +343 -343
  73. package/dist/psql/core/mainloop.js +160 -153
  74. package/dist/psql/core/prompt.js +126 -123
  75. package/dist/psql/core/settings.js +111 -111
  76. package/dist/psql/core/sqlHelp.js +150 -150
  77. package/dist/psql/core/startup.js +211 -205
  78. package/dist/psql/core/syncVars.js +14 -14
  79. package/dist/psql/core/variables.js +24 -24
  80. package/dist/psql/describe/formatters.js +302 -289
  81. package/dist/psql/describe/processNamePattern.js +28 -28
  82. package/dist/psql/describe/queries.js +656 -651
  83. package/dist/psql/index.js +436 -411
  84. package/dist/psql/io/history.js +36 -36
  85. package/dist/psql/io/input.js +15 -15
  86. package/dist/psql/io/lineEditor/buffer.js +27 -25
  87. package/dist/psql/io/lineEditor/complete.js +15 -15
  88. package/dist/psql/io/lineEditor/filename.js +22 -22
  89. package/dist/psql/io/lineEditor/index.js +65 -62
  90. package/dist/psql/io/lineEditor/keymap.js +325 -318
  91. package/dist/psql/io/lineEditor/vt100.js +60 -60
  92. package/dist/psql/io/pgpass.js +18 -18
  93. package/dist/psql/io/pgservice.js +14 -14
  94. package/dist/psql/io/psqlrc.js +46 -46
  95. package/dist/psql/print/aligned.js +175 -166
  96. package/dist/psql/print/asciidoc.js +51 -51
  97. package/dist/psql/print/crosstab.js +34 -31
  98. package/dist/psql/print/csv.js +25 -22
  99. package/dist/psql/print/html.js +54 -54
  100. package/dist/psql/print/json.js +12 -12
  101. package/dist/psql/print/latex.js +118 -118
  102. package/dist/psql/print/pager.js +28 -26
  103. package/dist/psql/print/troff.js +48 -48
  104. package/dist/psql/print/unaligned.js +15 -14
  105. package/dist/psql/print/units.js +17 -17
  106. package/dist/psql/scanner/slash.js +48 -46
  107. package/dist/psql/scanner/sql.js +88 -84
  108. package/dist/psql/scanner/stringutils.js +21 -17
  109. package/dist/psql/types/index.js +7 -7
  110. package/dist/psql/types/scanner.js +8 -8
  111. package/dist/psql/wire/connection.js +341 -327
  112. package/dist/psql/wire/copy.js +7 -7
  113. package/dist/psql/wire/pipeline.js +26 -24
  114. package/dist/psql/wire/protocol.js +102 -102
  115. package/dist/psql/wire/sasl.js +62 -62
  116. package/dist/psql/wire/tls.js +79 -73
  117. package/dist/storage_api.js +15 -15
  118. package/dist/test_utils/fixtures.js +34 -31
  119. package/dist/test_utils/oauth_server.js +5 -5
  120. package/dist/utils/api_enums.js +13 -13
  121. package/dist/utils/branch_notice.js +5 -5
  122. package/dist/utils/branch_picker.js +26 -26
  123. package/dist/utils/compute_units.js +4 -4
  124. package/dist/utils/enrichers.js +20 -15
  125. package/dist/utils/esbuild.js +28 -28
  126. package/dist/utils/formats.js +1 -1
  127. package/dist/utils/middlewares.js +3 -3
  128. package/dist/utils/package_manager.js +68 -0
  129. package/dist/utils/point_in_time.js +12 -12
  130. package/dist/utils/psql.js +30 -30
  131. package/dist/utils/string.js +2 -2
  132. package/dist/utils/ui.js +9 -9
  133. package/dist/utils/zip.js +1 -1
  134. package/dist/writer.js +17 -17
  135. package/package.json +6 -7
@@ -76,23 +76,23 @@
76
76
  * that and also stash the message on `settings.lastErrorResult` so the
77
77
  * mainloop's `writeError()` wrapper can pick it up.
78
78
  */
79
- import { spawn } from 'node:child_process';
80
- import { promises as fsPromises, closeSync, createWriteStream, fsyncSync, openSync, } from 'node:fs';
81
- import * as path from 'node:path';
82
- import { platform } from 'node:os';
83
- import { alignedPrinter } from '../print/aligned.js';
84
- import { asciidocPrinter } from '../print/asciidoc.js';
85
- import { csvPrinter } from '../print/csv.js';
86
- import { htmlPrinter } from '../print/html.js';
87
- import { jsonPrinter } from '../print/json.js';
88
- import { latexLongtablePrinter, latexPrinter } from '../print/latex.js';
89
- import { troffMsPrinter } from '../print/troff.js';
90
- import { unalignedPrinter } from '../print/unaligned.js';
91
- import { writeErr, writeOut } from './shared.js';
92
- import { formatErrorReport, psqlErrorPrefix } from './cmd_meta.js';
93
- import { applyPset } from './cmd_format.js';
94
- import { consumeBindState, lookupPrepared, stagedNamedBindPresent, } from './cmd_pipeline.js';
95
- import { captureLastError, refreshErrorVars, stripLeadingCommentsAndWS, } from '../core/common.js';
79
+ import { spawn } from "node:child_process";
80
+ import { closeSync, createWriteStream, promises as fsPromises, fsyncSync, openSync, } from "node:fs";
81
+ import { platform } from "node:os";
82
+ import * as path from "node:path";
83
+ import { captureLastError, refreshErrorVars, stripLeadingCommentsAndWS, } from "../core/common.js";
84
+ import { alignedPrinter } from "../print/aligned.js";
85
+ import { asciidocPrinter } from "../print/asciidoc.js";
86
+ import { csvPrinter } from "../print/csv.js";
87
+ import { htmlPrinter } from "../print/html.js";
88
+ import { jsonPrinter } from "../print/json.js";
89
+ import { latexLongtablePrinter, latexPrinter } from "../print/latex.js";
90
+ import { troffMsPrinter } from "../print/troff.js";
91
+ import { unalignedPrinter } from "../print/unaligned.js";
92
+ import { applyPset } from "./cmd_format.js";
93
+ import { formatErrorReport, psqlErrorPrefix } from "./cmd_meta.js";
94
+ import { consumeBindState, lookupPrepared, stagedNamedBindPresent, } from "./cmd_pipeline.js";
95
+ import { writeErr, writeOut } from "./shared.js";
96
96
  // ---------------------------------------------------------------------------
97
97
  // Query-output (queryFout) stash.
98
98
  //
@@ -101,7 +101,7 @@ import { captureLastError, refreshErrorVars, stripLeadingCommentsAndWS, } from '
101
101
  // at WP-00, so we stash the stream on the settings object via a well-known
102
102
  // symbol — the same approach used for the CondStack in cmd_cond.ts.
103
103
  // ---------------------------------------------------------------------------
104
- const QUERY_FOUT_KEY = Symbol.for('neonctl.psql.queryFout');
104
+ const QUERY_FOUT_KEY = Symbol.for("neonctl.psql.queryFout");
105
105
  /**
106
106
  * Return the currently active queryFout stream (or `null` if none).
107
107
  * The mainloop is encouraged to call this in lieu of writing directly to
@@ -152,7 +152,7 @@ const errResult = (ctx, message) => {
152
152
  // it would also write a `psql: ERROR: <msg>` fallback, producing a stray
153
153
  // duplicate that breaks the `\errverbose` ordering check on tests like
154
154
  // `SELECT error\gdesc\n\errverbose`.
155
- return { status: 'error', errorWritten: true };
155
+ return { status: "error", errorWritten: true };
156
156
  };
157
157
  /**
158
158
  * Reject buffer-consuming commands when an extended pipeline is open. Upstream
@@ -182,7 +182,7 @@ const errResult = (ctx, message) => {
182
182
  * own). Mirror that with a settings-stashed accumulator keyed off the
183
183
  * current pipeline session; reset when the pipeline ends.
184
184
  */
185
- const PIPELINE_GATE_ERRORS_KEY = Symbol.for('neonctl.psql.pipelineGateErrors');
185
+ const PIPELINE_GATE_ERRORS_KEY = Symbol.for("neonctl.psql.pipelineGateErrors");
186
186
  const getGateErrors = (settings) => {
187
187
  const s = settings;
188
188
  let cur = s[PIPELINE_GATE_ERRORS_KEY];
@@ -203,10 +203,10 @@ export const clearPipelineGateErrors = (settings) => {
203
203
  s[PIPELINE_GATE_ERRORS_KEY] = undefined;
204
204
  };
205
205
  const pipelineGate = (ctx) => {
206
- if (ctx.settings.sendMode !== 'extended-pipeline')
206
+ if (ctx.settings.sendMode !== "extended-pipeline")
207
207
  return null;
208
- const message = ctx.cmdName === 'gdesc'
209
- ? 'synchronous command execution functions are not allowed in pipeline mode'
208
+ const message = ctx.cmdName === "gdesc"
209
+ ? "synchronous command execution functions are not allowed in pipeline mode"
210
210
  : `\\${ctx.cmdName} not allowed in pipeline mode`;
211
211
  ctx.settings.lastErrorResult = { message };
212
212
  const prefix = psqlErrorPrefix(ctx.settings);
@@ -218,7 +218,7 @@ const pipelineGate = (ctx) => {
218
218
  // the session-scoped error log; other gated commands (`\g`, `\gx`,
219
219
  // `\gset`, `\gexec`, `\watch`) emit a single line per invocation
220
220
  // and do NOT participate in the accumulator.
221
- if (ctx.cmdName === 'gdesc') {
221
+ if (ctx.cmdName === "gdesc") {
222
222
  const log = getGateErrors(ctx.settings);
223
223
  log.push(message);
224
224
  for (const m of log) {
@@ -228,7 +228,7 @@ const pipelineGate = (ctx) => {
228
228
  else {
229
229
  writeErr(`${prefix}${message}\n`);
230
230
  }
231
- return { status: 'error', errorWritten: true };
231
+ return { status: "error", errorWritten: true };
232
232
  };
233
233
  /**
234
234
  * Set of psql variables upstream marks as "specially treated" — i.e. names
@@ -242,28 +242,28 @@ const pipelineGate = (ctx) => {
242
242
  * for us.
243
243
  */
244
244
  const UPSTREAM_SPECIAL_VAR_NAMES = new Set([
245
- 'AUTOCOMMIT',
246
- 'COMP_KEYWORD_CASE',
247
- 'ECHO',
248
- 'ECHO_HIDDEN',
249
- 'FETCH_COUNT',
250
- 'HIDE_TABLEAM',
251
- 'HIDE_TOAST_COMPRESSION',
252
- 'HISTCONTROL',
253
- 'HISTFILE',
254
- 'HISTSIZE',
255
- 'IGNOREEOF',
256
- 'ON_ERROR_ROLLBACK',
257
- 'ON_ERROR_STOP',
258
- 'PROMPT1',
259
- 'PROMPT2',
260
- 'PROMPT3',
261
- 'QUIET',
262
- 'SHOW_ALL_RESULTS',
263
- 'SHOW_CONTEXT',
264
- 'SINGLELINE',
265
- 'SINGLESTEP',
266
- 'VERBOSITY',
245
+ "AUTOCOMMIT",
246
+ "COMP_KEYWORD_CASE",
247
+ "ECHO",
248
+ "ECHO_HIDDEN",
249
+ "FETCH_COUNT",
250
+ "HIDE_TABLEAM",
251
+ "HIDE_TOAST_COMPRESSION",
252
+ "HISTCONTROL",
253
+ "HISTFILE",
254
+ "HISTSIZE",
255
+ "IGNOREEOF",
256
+ "ON_ERROR_ROLLBACK",
257
+ "ON_ERROR_STOP",
258
+ "PROMPT1",
259
+ "PROMPT2",
260
+ "PROMPT3",
261
+ "QUIET",
262
+ "SHOW_ALL_RESULTS",
263
+ "SHOW_CONTEXT",
264
+ "SINGLELINE",
265
+ "SINGLESTEP",
266
+ "VERBOSITY",
267
267
  ]);
268
268
  /**
269
269
  * True when `name` is a psql variable that `\gset` must skip with an
@@ -273,7 +273,8 @@ const UPSTREAM_SPECIAL_VAR_NAMES = new Set([
273
273
  * IGNOREEOF that aren't hooked in our settings.ts still match upstream's
274
274
  * `\gset` behaviour exactly).
275
275
  */
276
- const isSpeciallyTreatedVar = (settings, name) => settings.vars.hasSubstituteHook(name) || UPSTREAM_SPECIAL_VAR_NAMES.has(name);
276
+ const isSpeciallyTreatedVar = (settings, name) => settings.vars.hasSubstituteHook(name) ||
277
+ UPSTREAM_SPECIAL_VAR_NAMES.has(name);
277
278
  // `stripLeadingCommentsAndWS` lives in core/common.ts so the wire path
278
279
  // (sendQuery / executeAndPrint) and the slash-command paths share one
279
280
  // implementation. Re-imported from there at the top of the file.
@@ -287,7 +288,7 @@ const isSpeciallyTreatedVar = (settings, name) => settings.vars.hasSubstituteHoo
287
288
  * intentionally conservative, and the worst-case outcome is "route bytes
288
289
  * that never arrive to the file" — harmless).
289
290
  */
290
- const stripSqlCommentsForCopyScan = (sql) => sql.replace(/\/\*[\s\S]*?\*\//gu, '').replace(/--[^\n]*/gu, '');
291
+ const stripSqlCommentsForCopyScan = (sql) => sql.replace(/\/\*[\s\S]*?\*\//gu, "").replace(/--[^\n]*/gu, "");
291
292
  /**
292
293
  * True when `sql` contains at least one `COPY ... TO STDOUT` segment.
293
294
  * Used by `runGCore` to install a CopyData sink while `\g` / `\gx` /
@@ -323,7 +324,7 @@ const formatServerError = (ctx, err, sql) => {
323
324
  const lines = formatErrorReport(e, ctx.settings.verbosity, ctx.settings.showContext);
324
325
  const prefix = psqlErrorPrefix(ctx.settings);
325
326
  const prefixed = [prefix + lines[0], ...lines.slice(1)];
326
- writeErr(prefixed.join('\n') + '\n');
327
+ writeErr(prefixed.join("\n") + "\n");
327
328
  }
328
329
  else {
329
330
  // Defensive fallback — captureLastError always sets lastErrorResult,
@@ -335,8 +336,8 @@ const formatServerError = (ctx, err, sql) => {
335
336
  // following `\echo :LAST_ERROR_MESSAGE` and `\errverbose` see the new
336
337
  // outcome. Matches upstream's `SetErrorVariables` call after every
337
338
  // failed dispatch.
338
- refreshErrorVars(ctx.settings, { kind: 'error' });
339
- return { status: 'error', errorWritten: true };
339
+ refreshErrorVars(ctx.settings, { kind: "error" });
340
+ return { status: "error", errorWritten: true };
340
341
  };
341
342
  /**
342
343
  * Open a writable destination for `\o` / `\w` / `\g FILE` / `\g |cmd`.
@@ -348,15 +349,15 @@ const formatServerError = (ctx, err, sql) => {
348
349
  * file path; the file is truncated.
349
350
  */
350
351
  const openWriter = (target) => {
351
- if (target.startsWith('|')) {
352
+ if (target.startsWith("|")) {
352
353
  const cmd = target.slice(1);
353
- const child = spawn('sh', ['-c', cmd], {
354
- stdio: ['pipe', 'inherit', 'inherit'],
354
+ const child = spawn("sh", ["-c", cmd], {
355
+ stdio: ["pipe", "inherit", "inherit"],
355
356
  });
356
357
  // Swallow EPIPE on the stdin pipe — the child may exit before we
357
358
  // finish writing, and Node would otherwise raise an unhandled error.
358
- child.stdin.on('error', (err) => {
359
- if (err.code !== 'EPIPE') {
359
+ child.stdin.on("error", (err) => {
360
+ if (err.code !== "EPIPE") {
360
361
  // Re-raise non-EPIPE errors as a crash so they show up; tests
361
362
  // run with the default unhandledRejection handler and will see
362
363
  // these via the failing assertion.
@@ -374,10 +375,10 @@ const openWriter = (target) => {
374
375
  settled = true;
375
376
  resolve({ exitCode: code, signal });
376
377
  };
377
- child.once('close', (code, signal) => {
378
+ child.once("close", (code, signal) => {
378
379
  finish(code, signal);
379
380
  });
380
- child.once('error', () => {
381
+ child.once("error", () => {
381
382
  // spawn failure or stdio glitch — treat as a non-zero exit so
382
383
  // \w sees a failure. \g intentionally ignores this, mirroring
383
384
  // upstream `CloseGOutput` which only sets SHELL_ERROR /
@@ -406,9 +407,9 @@ const openWriter = (target) => {
406
407
  // a follow-on server-side `COPY FROM` (Docker bind-mount on macOS) sees
407
408
  // the fully flushed file even when the next command immediately follows
408
409
  // the `\g`.
409
- const fd = openSync(target, 'w');
410
+ const fd = openSync(target, "w");
410
411
  const stream = createWriteStream(target, {
411
- encoding: 'utf8',
412
+ encoding: "utf8",
412
413
  fd,
413
414
  autoClose: false,
414
415
  });
@@ -418,7 +419,7 @@ const openWriter = (target) => {
418
419
  // re-raises it as an uncaught exception and kills the whole neonctl process.
419
420
  // Capture it; close() surfaces it to the caller.
420
421
  let writeError = null;
421
- stream.on('error', (err) => {
422
+ stream.on("error", (err) => {
422
423
  writeError = writeError ?? err;
423
424
  });
424
425
  return {
@@ -473,7 +474,7 @@ const openWriter = (target) => {
473
474
  // `COPY FROM '/bind/mount/file'` can read a partial view even
474
475
  // though the file is fully synced on the host. Linux + Windows
475
476
  // bind mounts are coherent, so this branch is macOS-only.
476
- if (platform() === 'darwin') {
477
+ if (platform() === "darwin") {
477
478
  setTimeout(() => {
478
479
  resolve({});
479
480
  }, 25);
@@ -494,38 +495,38 @@ const openWriter = (target) => {
494
495
  */
495
496
  const errnoToStrerror = (err) => {
496
497
  switch (err.code) {
497
- case 'ENOENT':
498
- return 'No such file or directory';
499
- case 'EACCES':
500
- return 'Permission denied';
501
- case 'EISDIR':
502
- return 'Is a directory';
503
- case 'ENOTDIR':
504
- return 'Not a directory';
505
- case 'EEXIST':
506
- return 'File exists';
507
- case 'EROFS':
508
- return 'Read-only file system';
509
- case 'ELOOP':
510
- return 'Too many levels of symbolic links';
511
- case 'ENAMETOOLONG':
512
- return 'File name too long';
513
- case 'ENOSPC':
514
- return 'No space left on device';
515
- case 'EMFILE':
516
- return 'Too many open files';
517
- case 'ENFILE':
518
- return 'Too many open files in system';
519
- case 'EIO':
520
- return 'Input/output error';
521
- case 'EFBIG':
522
- return 'File too large';
523
- case 'EDQUOT':
524
- return 'Disk quota exceeded';
525
- case 'EPERM':
526
- return 'Operation not permitted';
527
- case 'EINVAL':
528
- return 'Invalid argument';
498
+ case "ENOENT":
499
+ return "No such file or directory";
500
+ case "EACCES":
501
+ return "Permission denied";
502
+ case "EISDIR":
503
+ return "Is a directory";
504
+ case "ENOTDIR":
505
+ return "Not a directory";
506
+ case "EEXIST":
507
+ return "File exists";
508
+ case "EROFS":
509
+ return "Read-only file system";
510
+ case "ELOOP":
511
+ return "Too many levels of symbolic links";
512
+ case "ENAMETOOLONG":
513
+ return "File name too long";
514
+ case "ENOSPC":
515
+ return "No space left on device";
516
+ case "EMFILE":
517
+ return "Too many open files";
518
+ case "ENFILE":
519
+ return "Too many open files in system";
520
+ case "EIO":
521
+ return "Input/output error";
522
+ case "EFBIG":
523
+ return "File too large";
524
+ case "EDQUOT":
525
+ return "Disk quota exceeded";
526
+ case "EPERM":
527
+ return "Operation not permitted";
528
+ case "EINVAL":
529
+ return "Invalid argument";
529
530
  default: {
530
531
  // Strip Node's `ENOENT: no such file or directory, open '/x'`
531
532
  // prefix when present so the fallback at least looks like the
@@ -557,7 +558,7 @@ const reportFileOpenFailure = (ctx, target, err) => {
557
558
  ctx.settings.lastErrorResult = { message: line };
558
559
  const prefix = psqlErrorPrefix(ctx.settings);
559
560
  writeErr(`${prefix}${line}\n`);
560
- return { status: 'error', errorWritten: true };
561
+ return { status: "error", errorWritten: true };
561
562
  };
562
563
  /**
563
564
  * True when `err` was thrown by our synchronous `openSync` in
@@ -566,10 +567,10 @@ const reportFileOpenFailure = (ctx, target, err) => {
566
567
  * `\<cmd>: <msg>` path.
567
568
  */
568
569
  const isFileOpenFailure = (err) => {
569
- if (!err || typeof err !== 'object')
570
+ if (!err || typeof err !== "object")
570
571
  return false;
571
572
  const e = err;
572
- return typeof e.code === 'string' && e.code.startsWith('E');
573
+ return typeof e.code === "string" && e.code.startsWith("E");
573
574
  };
574
575
  /**
575
576
  * Format a child process exit code + signal into upstream psql's
@@ -595,9 +596,9 @@ const formatChildWaitResult = (exitCode, signal) => {
595
596
  if (exitCode === 0)
596
597
  return null;
597
598
  if (exitCode === 127)
598
- return 'command not found';
599
+ return "command not found";
599
600
  if (exitCode === 126)
600
- return 'command was not executable';
601
+ return "command was not executable";
601
602
  return `child process exited with exit code ${String(exitCode)}`;
602
603
  };
603
604
  /**
@@ -608,10 +609,10 @@ const formatChildWaitResult = (exitCode, signal) => {
608
609
  * available (e.g. `EmptyQueryResponse` carries `command = ''`).
609
610
  */
610
611
  const formatCommandTagText = (rs) => {
611
- const command = (rs.command || '').trim();
612
+ const command = (rs.command || "").trim();
612
613
  if (command.length === 0)
613
- return '';
614
- if (command === 'INSERT') {
614
+ return "";
615
+ if (command === "INSERT") {
615
616
  // INSERT is the only tag with the legacy oid in front of rowCount.
616
617
  return `INSERT ${String(rs.oid ?? 0)} ${String(rs.rowCount ?? 0)}`;
617
618
  }
@@ -656,7 +657,9 @@ const renderResult = async (settings, rs, out) => {
656
657
  // For COPY-out results, the tag is suppressed regardless — the bytes
657
658
  // already flowed; upstream's `handleCopyOut` doesn't emit `COPY N`
658
659
  // on the queryFout.
659
- if (!settings.popt.topt.tuplesOnly && !settings.quiet && !rs.copyOutBytes) {
660
+ if (!settings.popt.topt.tuplesOnly &&
661
+ !settings.quiet &&
662
+ !rs.copyOutBytes) {
660
663
  const tag = formatCommandTagText(rs);
661
664
  if (tag.length > 0)
662
665
  out.write(`${tag}\n`);
@@ -675,24 +678,24 @@ const renderResult = async (settings, rs, out) => {
675
678
  */
676
679
  const pickActivePrinter = (settings) => {
677
680
  switch (settings.popt.topt.format) {
678
- case 'aligned':
679
- case 'wrapped':
681
+ case "aligned":
682
+ case "wrapped":
680
683
  return alignedPrinter;
681
- case 'unaligned':
684
+ case "unaligned":
682
685
  return unalignedPrinter;
683
- case 'csv':
686
+ case "csv":
684
687
  return csvPrinter;
685
- case 'json':
688
+ case "json":
686
689
  return jsonPrinter;
687
- case 'html':
690
+ case "html":
688
691
  return htmlPrinter;
689
- case 'asciidoc':
692
+ case "asciidoc":
690
693
  return asciidocPrinter;
691
- case 'latex':
694
+ case "latex":
692
695
  return latexPrinter;
693
- case 'latex-longtable':
696
+ case "latex-longtable":
694
697
  return latexLongtablePrinter;
695
- case 'troff-ms':
698
+ case "troff-ms":
696
699
  return troffMsPrinter;
697
700
  default:
698
701
  return alignedPrinter;
@@ -713,9 +716,9 @@ const pickOut = (settings, oneShot) => {
713
716
  // \i FILE / \include FILE
714
717
  // ---------------------------------------------------------------------------
715
718
  const runInclude = async (ctx, relative) => {
716
- const arg = ctx.nextArg('normal');
719
+ const arg = ctx.nextArg("normal");
717
720
  if (arg === null || arg.length === 0) {
718
- return errResult(ctx, 'missing required argument');
721
+ return errResult(ctx, "missing required argument");
719
722
  }
720
723
  // Resolve path: \ir resolves relative to the current input file's
721
724
  // directory (if any); \i resolves relative to cwd unless absolute.
@@ -731,7 +734,7 @@ const runInclude = async (ctx, relative) => {
731
734
  }
732
735
  let contents;
733
736
  try {
734
- contents = await fsPromises.readFile(resolved, 'utf8');
737
+ contents = await fsPromises.readFile(resolved, "utf8");
735
738
  }
736
739
  catch (err) {
737
740
  const msg = err instanceof Error ? err.message : String(err);
@@ -744,11 +747,11 @@ const runInclude = async (ctx, relative) => {
744
747
  // -f/-c and (b) double-run interactively (the mainloop drains the queue
745
748
  // AND we run execSimple). See.
746
749
  if (!ctx.settings.db) {
747
- return errResult(ctx, 'no connection to the server');
750
+ return errResult(ctx, "no connection to the server");
748
751
  }
749
752
  const trimmed = contents.trim();
750
753
  if (trimmed.length === 0) {
751
- return { status: 'ok' };
754
+ return { status: "ok" };
752
755
  }
753
756
  // Track the prior inputfile so `\ir` chains relative to the included
754
757
  // file's directory.
@@ -760,7 +763,7 @@ const runInclude = async (ctx, relative) => {
760
763
  for (const rs of results) {
761
764
  await renderResult(ctx.settings, rs, out);
762
765
  }
763
- return { status: 'ok' };
766
+ return { status: "ok" };
764
767
  }
765
768
  catch (err) {
766
769
  const msg = err instanceof Error ? err.message : String(err);
@@ -771,36 +774,36 @@ const runInclude = async (ctx, relative) => {
771
774
  }
772
775
  };
773
776
  export const cmdInclude = {
774
- name: 'i',
775
- aliases: ['include'],
776
- helpKey: 'i',
777
+ name: "i",
778
+ aliases: ["include"],
779
+ helpKey: "i",
777
780
  run: (ctx) => runInclude(ctx, false),
778
781
  };
779
782
  export const cmdIncludeRel = {
780
- name: 'ir',
781
- aliases: ['include_relative'],
782
- helpKey: 'ir',
783
+ name: "ir",
784
+ aliases: ["include_relative"],
785
+ helpKey: "ir",
783
786
  run: (ctx) => runInclude(ctx, true),
784
787
  };
785
788
  // ---------------------------------------------------------------------------
786
789
  // \o [FILE|cmd] / \out
787
790
  // ---------------------------------------------------------------------------
788
791
  export const cmdOut = {
789
- name: 'o',
790
- aliases: ['out'],
791
- helpKey: 'o',
792
+ name: "o",
793
+ aliases: ["out"],
794
+ helpKey: "o",
792
795
  async run(ctx) {
793
- const arg = ctx.nextArg('filepipe');
796
+ const arg = ctx.nextArg("filepipe");
794
797
  // Drain any previous target first so writes flush before we rebind.
795
798
  await closeQueryFout(ctx.settings);
796
799
  if (arg === null || arg.length === 0) {
797
800
  // Restore default (stdout).
798
- return { status: 'ok' };
801
+ return { status: "ok" };
799
802
  }
800
803
  try {
801
804
  const entry = openWriter(arg);
802
805
  setQueryFout(ctx.settings, entry);
803
- return { status: 'ok' };
806
+ return { status: "ok" };
804
807
  }
805
808
  catch (err) {
806
809
  // File targets fail synchronously in `openWriter` via `openSync`;
@@ -808,7 +811,7 @@ export const cmdOut = {
808
811
  // no `\o:` prefix) and continue with the loop so a follow-up
809
812
  // `SELECT` still executes. Pipe spawn failures (which lack an
810
813
  // errno code) fall through to the generic `\o: <msg>` path.
811
- if (!arg.startsWith('|') && isFileOpenFailure(err)) {
814
+ if (!arg.startsWith("|") && isFileOpenFailure(err)) {
812
815
  return reportFileOpenFailure(ctx, arg, err);
813
816
  }
814
817
  const msg = err instanceof Error ? err.message : String(err);
@@ -820,13 +823,13 @@ export const cmdOut = {
820
823
  // \w FILE / \write FILE
821
824
  // ---------------------------------------------------------------------------
822
825
  export const cmdWrite = {
823
- name: 'w',
824
- aliases: ['write'],
825
- helpKey: 'w',
826
+ name: "w",
827
+ aliases: ["write"],
828
+ helpKey: "w",
826
829
  async run(ctx) {
827
- const arg = ctx.nextArg('filepipe');
830
+ const arg = ctx.nextArg("filepipe");
828
831
  if (arg === null || arg.length === 0) {
829
- return errResult(ctx, 'missing required argument');
832
+ return errResult(ctx, "missing required argument");
830
833
  }
831
834
  let entry;
832
835
  try {
@@ -837,7 +840,7 @@ export const cmdWrite = {
837
840
  // path errors out as a bare `<path>: <strerror>` line and the
838
841
  // shim keeps reading commands. Pipe spawn failures still use
839
842
  // the generic `\w: <msg>` envelope.
840
- if (!arg.startsWith('|') && isFileOpenFailure(err)) {
843
+ if (!arg.startsWith("|") && isFileOpenFailure(err)) {
841
844
  return reportFileOpenFailure(ctx, arg, err);
842
845
  }
843
846
  const msg = err instanceof Error ? err.message : String(err);
@@ -861,7 +864,8 @@ export const cmdWrite = {
861
864
  // status is what we want to report, NOT the write error — so we
862
865
  // swallow EPIPE on pipes and fall through to entry.close() which
863
866
  // awaits the child and emits the upstream-shape wait_result_to_str.
864
- const isEpipe = err instanceof Error && err.code === 'EPIPE';
867
+ const isEpipe = err instanceof Error &&
868
+ err.code === "EPIPE";
865
869
  if (!entry.isPipe || !isEpipe) {
866
870
  try {
867
871
  await entry.close();
@@ -895,10 +899,10 @@ export const cmdWrite = {
895
899
  ctx.settings.lastErrorResult = { message: line };
896
900
  const prefix = psqlErrorPrefix(ctx.settings);
897
901
  writeErr(`${prefix}${line}\n`);
898
- return { status: 'error', errorWritten: true };
902
+ return { status: "error", errorWritten: true };
899
903
  }
900
904
  }
901
- return { status: 'ok' };
905
+ return { status: "ok" };
902
906
  }
903
907
  catch (err) {
904
908
  const msg = err instanceof Error ? err.message : String(err);
@@ -930,13 +934,13 @@ const parseGPsetOptions = (body) => {
930
934
  break;
931
935
  // Read option name up to `=`.
932
936
  const optStart = i;
933
- while (i < body.length && body[i] !== '=' && !/\s/.test(body[i]))
937
+ while (i < body.length && body[i] !== "=" && !/\s/.test(body[i]))
934
938
  i++;
935
939
  const option = body.slice(optStart, i);
936
940
  if (option.length === 0)
937
941
  break;
938
- let value = '';
939
- if (body[i] === '=') {
942
+ let value = "";
943
+ if (body[i] === "=") {
940
944
  i++; // skip '='
941
945
  // Value: single-quoted or unquoted.
942
946
  if (body[i] === "'") {
@@ -946,16 +950,16 @@ const parseGPsetOptions = (body) => {
946
950
  // C-style escapes (\n, \t, \\, \'). Mirror enough of the
947
951
  // upstream `xslashquote` handling to round-trip the regress
948
952
  // corpus.
949
- if (body[i] === '\\' && i + 1 < body.length) {
953
+ if (body[i] === "\\" && i + 1 < body.length) {
950
954
  const next = body[i + 1];
951
- if (next === 'n')
952
- value += '\n';
953
- else if (next === 't')
954
- value += '\t';
955
- else if (next === 'r')
956
- value += '\r';
957
- else if (next === '\\')
958
- value += '\\';
955
+ if (next === "n")
956
+ value += "\n";
957
+ else if (next === "t")
958
+ value += "\t";
959
+ else if (next === "r")
960
+ value += "\r";
961
+ else if (next === "\\")
962
+ value += "\\";
959
963
  else if (next === "'")
960
964
  value += "'";
961
965
  else
@@ -1001,10 +1005,10 @@ const runGCore = async (ctx, forceExpanded) => {
1001
1005
  // arg block ourselves; otherwise fall back to normal filepipe arg
1002
1006
  // extraction.
1003
1007
  const rawTrimmed = ctx.rawArgs.trimStart();
1004
- if (rawTrimmed.startsWith('(')) {
1005
- const close = rawTrimmed.indexOf(')');
1008
+ if (rawTrimmed.startsWith("(")) {
1009
+ const close = rawTrimmed.indexOf(")");
1006
1010
  if (close === -1) {
1007
- return errResult(ctx, 'missing right parenthesis in \\g options');
1011
+ return errResult(ctx, "missing right parenthesis in \\g options");
1008
1012
  }
1009
1013
  // Strip parens; parse `key=value` pairs (values may be single-
1010
1014
  // quoted). The conformance corpus exercises `format=`,
@@ -1017,7 +1021,7 @@ const runGCore = async (ctx, forceExpanded) => {
1017
1021
  target = afterParen.length > 0 ? afterParen : null;
1018
1022
  }
1019
1023
  else {
1020
- target = ctx.nextArg('filepipe');
1024
+ target = ctx.nextArg("filepipe");
1021
1025
  }
1022
1026
  // `\g` / `\gx` with an empty buffer re-runs the most recently submitted
1023
1027
  // query — upstream tracks this in `pset.last_query` and `PSQLexec` reads
@@ -1035,10 +1039,10 @@ const runGCore = async (ctx, forceExpanded) => {
1035
1039
  if (sql.length === 0 && !hasPendingNamedBind) {
1036
1040
  // No buffered SQL, no prior query, no staged bind — silent no-op
1037
1041
  // like upstream.
1038
- return { status: 'reset-buf', newBuf: '' };
1042
+ return { status: "reset-buf", newBuf: "" };
1039
1043
  }
1040
1044
  if (!ctx.settings.db) {
1041
- return errResult(ctx, 'no connection to the server');
1045
+ return errResult(ctx, "no connection to the server");
1042
1046
  }
1043
1047
  // Open the one-shot writer if a target was supplied; close it on the way
1044
1048
  // out so the file/pipe is flushed before we return.
@@ -1055,7 +1059,7 @@ const runGCore = async (ctx, forceExpanded) => {
1055
1059
  // upstream's bare `<path>: <strerror>` shape and continue so the
1056
1060
  // next command in the script still executes. Pipe spawn
1057
1061
  // failures retain the generic `\g: <msg>` envelope.
1058
- if (!target.startsWith('|') && isFileOpenFailure(err)) {
1062
+ if (!target.startsWith("|") && isFileOpenFailure(err)) {
1059
1063
  return reportFileOpenFailure(ctx, target, err);
1060
1064
  }
1061
1065
  const msg = err instanceof Error ? err.message : String(err);
@@ -1069,7 +1073,7 @@ const runGCore = async (ctx, forceExpanded) => {
1069
1073
  // mutation would persist `expanded = 'on'` across queries.
1070
1074
  const toptSnapshot = { ...topt };
1071
1075
  if (forceExpanded)
1072
- topt.expanded = 'on';
1076
+ topt.expanded = "on";
1073
1077
  // Apply per-query pset overrides silently. Upstream applies the
1074
1078
  // temporary options without emitting the status lines that
1075
1079
  // interactive `\pset` would.
@@ -1104,7 +1108,7 @@ const runGCore = async (ctx, forceExpanded) => {
1104
1108
  // Synthesise a thrown-Error-like object so formatServerError can
1105
1109
  // render the same `ERROR: <msg>` shape vanilla emits for the
1106
1110
  // server's `prepared statement "X" does not exist` error.
1107
- execError = Object.assign(new Error(`prepared statement "${bindState.name}" does not exist`), { severity: 'ERROR', code: '26000' });
1111
+ execError = Object.assign(new Error(`prepared statement "${bindState.name}" does not exist`), { severity: "ERROR", code: "26000" });
1108
1112
  }
1109
1113
  else {
1110
1114
  // Bind + Execute MUST go in one extended-protocol batch: the
@@ -1210,16 +1214,16 @@ const runGCore = async (ctx, forceExpanded) => {
1210
1214
  if (pipeError !== null) {
1211
1215
  return errResult(ctx, pipeError);
1212
1216
  }
1213
- return { status: 'reset-buf', newBuf: '' };
1217
+ return { status: "reset-buf", newBuf: "" };
1214
1218
  };
1215
1219
  export const cmdG = {
1216
- name: 'g',
1217
- helpKey: 'g',
1220
+ name: "g",
1221
+ helpKey: "g",
1218
1222
  run: (ctx) => runGCore(ctx, false),
1219
1223
  };
1220
1224
  export const cmdGx = {
1221
- name: 'gx',
1222
- helpKey: 'gx',
1225
+ name: "gx",
1226
+ helpKey: "gx",
1223
1227
  run: (ctx) => runGCore(ctx, true),
1224
1228
  };
1225
1229
  // ---------------------------------------------------------------------------
@@ -1260,9 +1264,9 @@ export const cmdGx = {
1260
1264
  * explicitly to match.
1261
1265
  */
1262
1266
  export const cmdPrint = {
1263
- name: 'p',
1264
- aliases: ['print'],
1265
- helpKey: 'p',
1267
+ name: "p",
1268
+ aliases: ["print"],
1269
+ helpKey: "p",
1266
1270
  run: (ctx) => {
1267
1271
  // `queryBuf.trim()` for the emptiness check — not the printed text.
1268
1272
  // Upstream's `query_buf->len > 0` is a byte-length check that, in
@@ -1282,9 +1286,9 @@ export const cmdPrint = {
1282
1286
  writeOut(`${ctx.settings.lastQuery}\n`);
1283
1287
  }
1284
1288
  else if (!ctx.settings.quiet) {
1285
- writeOut('Query buffer is empty.\n');
1289
+ writeOut("Query buffer is empty.\n");
1286
1290
  }
1287
- return Promise.resolve({ status: 'ok' });
1291
+ return Promise.resolve({ status: "ok" });
1288
1292
  },
1289
1293
  };
1290
1294
  // ---------------------------------------------------------------------------
@@ -1292,14 +1296,14 @@ export const cmdPrint = {
1292
1296
  // ---------------------------------------------------------------------------
1293
1297
  const formatCell = (value) => {
1294
1298
  if (value === null || value === undefined)
1295
- return '';
1296
- if (typeof value === 'string')
1299
+ return "";
1300
+ if (typeof value === "string")
1297
1301
  return value;
1298
1302
  if (Buffer.isBuffer(value))
1299
- return value.toString('utf8');
1300
- if (typeof value === 'number' ||
1301
- typeof value === 'boolean' ||
1302
- typeof value === 'bigint') {
1303
+ return value.toString("utf8");
1304
+ if (typeof value === "number" ||
1305
+ typeof value === "boolean" ||
1306
+ typeof value === "bigint") {
1303
1307
  return String(value);
1304
1308
  }
1305
1309
  // Plain objects / arrays from JSON columns: JSON-stringify so the test
@@ -1308,12 +1312,12 @@ const formatCell = (value) => {
1308
1312
  return JSON.stringify(value);
1309
1313
  }
1310
1314
  catch {
1311
- return '';
1315
+ return "";
1312
1316
  }
1313
1317
  };
1314
1318
  export const cmdGset = {
1315
- name: 'gset',
1316
- helpKey: 'gset',
1319
+ name: "gset",
1320
+ helpKey: "gset",
1317
1321
  async run(ctx) {
1318
1322
  const gated = pipelineGate(ctx);
1319
1323
  if (gated !== null)
@@ -1321,7 +1325,7 @@ export const cmdGset = {
1321
1325
  // Strip leading whitespace + comments — see runGCore for the rationale.
1322
1326
  const trimmedBuf = stripLeadingCommentsAndWS(ctx.queryBuf);
1323
1327
  const bufSql = trimmedBuf.trim();
1324
- const prefix = ctx.nextArg('normal') ?? '';
1328
+ const prefix = ctx.nextArg("normal") ?? "";
1325
1329
  // Empty buffer behaviour mirrors upstream `exec_command_gset`'s
1326
1330
  // `PSQL_CMD_SEND` return: the dispatch loop sends the active
1327
1331
  // `pset.last_query` (or nothing). Upstream does NOT emit an error
@@ -1330,10 +1334,10 @@ export const cmdGset = {
1330
1334
  // dispatch.
1331
1335
  const sql = bufSql.length > 0 ? bufSql : ctx.settings.lastQuery.trim();
1332
1336
  if (sql.length === 0) {
1333
- return { status: 'reset-buf', newBuf: '' };
1337
+ return { status: "reset-buf", newBuf: "" };
1334
1338
  }
1335
1339
  if (!ctx.settings.db) {
1336
- return errResult(ctx, 'no connection to the server');
1340
+ return errResult(ctx, "no connection to the server");
1337
1341
  }
1338
1342
  // Track for a subsequent `\g` re-run with empty buffer. Upstream
1339
1343
  // `exec_command_gset` updates `pset.last_query` to the dispatched SQL
@@ -1379,29 +1383,29 @@ export const cmdGset = {
1379
1383
  // tuples-producing, skip the variable-assignment step entirely.
1380
1384
  const lastRs = results[results.length - 1];
1381
1385
  if (!lastRs || lastRs.fields.length === 0) {
1382
- return { status: 'reset-buf', newBuf: '' };
1386
+ return { status: "reset-buf", newBuf: "" };
1383
1387
  }
1384
1388
  const rs = lastRs;
1385
1389
  if (rs.rows.length === 0) {
1386
1390
  // Bare `no rows returned for \gset` (no `\gset:` prefix) — matches
1387
1391
  // upstream psql's `pg_log_error("no rows returned for \\gset")`.
1388
1392
  ctx.settings.lastErrorResult = {
1389
- message: 'no rows returned for \\gset',
1393
+ message: "no rows returned for \\gset",
1390
1394
  };
1391
1395
  const errPrefix = psqlErrorPrefix(ctx.settings);
1392
1396
  writeErr(`${errPrefix}no rows returned for \\gset\n`);
1393
- return { status: 'error', errorWritten: true };
1397
+ return { status: "error", errorWritten: true };
1394
1398
  }
1395
1399
  if (rs.rows.length > 1) {
1396
1400
  // Match upstream psql's exact wording from `exec_command_gset` —
1397
1401
  // bare `more than one row returned for \gset` (no `\gset:` prefix).
1398
1402
  // Verified against vanilla psql; vendored psql.out emits it bare.
1399
1403
  ctx.settings.lastErrorResult = {
1400
- message: 'more than one row returned for \\gset',
1404
+ message: "more than one row returned for \\gset",
1401
1405
  };
1402
1406
  const errPrefix = psqlErrorPrefix(ctx.settings);
1403
1407
  writeErr(`${errPrefix}more than one row returned for \\gset\n`);
1404
- return { status: 'error', errorWritten: true };
1408
+ return { status: "error", errorWritten: true };
1405
1409
  }
1406
1410
  const row = rs.rows[0];
1407
1411
  for (let i = 0; i < rs.fields.length; i++) {
@@ -1450,10 +1454,10 @@ export const cmdGset = {
1450
1454
  };
1451
1455
  const errPrefix = psqlErrorPrefix(ctx.settings);
1452
1456
  writeErr(`${errPrefix}invalid variable name: "${fieldName}"\n`);
1453
- return { status: 'error', errorWritten: true };
1457
+ return { status: "error", errorWritten: true };
1454
1458
  }
1455
1459
  }
1456
- return { status: 'reset-buf', newBuf: '' };
1460
+ return { status: "reset-buf", newBuf: "" };
1457
1461
  },
1458
1462
  };
1459
1463
  // ---------------------------------------------------------------------------
@@ -1496,7 +1500,7 @@ const buildGdescFormatQuery = (fields) => {
1496
1500
  const typmod = String(f.dataTypeModifier | 0);
1497
1501
  return `(${String(i)}, '${safeName}', ${oid}::oid, ${typmod}::int4)`;
1498
1502
  })
1499
- .join(', ');
1503
+ .join(", ");
1500
1504
  // ORDER BY _idx preserves the describe order regardless of how the server
1501
1505
  // happens to evaluate the VALUES list. Aliases match upstream column
1502
1506
  // titles exactly so the printer header is identical.
@@ -1511,7 +1515,7 @@ const buildGdescFormatQuery = (fields) => {
1511
1515
  */
1512
1516
  const GDESC_SYNTHETIC_FIELDS = [
1513
1517
  {
1514
- name: 'Column',
1518
+ name: "Column",
1515
1519
  tableID: 0,
1516
1520
  columnID: 0,
1517
1521
  dataTypeID: 25, // text
@@ -1520,7 +1524,7 @@ const GDESC_SYNTHETIC_FIELDS = [
1520
1524
  format: 0,
1521
1525
  },
1522
1526
  {
1523
- name: 'Type',
1527
+ name: "Type",
1524
1528
  tableID: 0,
1525
1529
  columnID: 0,
1526
1530
  dataTypeID: 25, // text
@@ -1530,7 +1534,7 @@ const GDESC_SYNTHETIC_FIELDS = [
1530
1534
  },
1531
1535
  ];
1532
1536
  const buildSyntheticGdescResultSet = (rows) => ({
1533
- command: 'SELECT',
1537
+ command: "SELECT",
1534
1538
  rowCount: rows.length,
1535
1539
  oid: null,
1536
1540
  fields: GDESC_SYNTHETIC_FIELDS,
@@ -1538,8 +1542,8 @@ const buildSyntheticGdescResultSet = (rows) => ({
1538
1542
  notices: [],
1539
1543
  });
1540
1544
  export const cmdGdesc = {
1541
- name: 'gdesc',
1542
- helpKey: 'gdesc',
1545
+ name: "gdesc",
1546
+ helpKey: "gdesc",
1543
1547
  async run(ctx) {
1544
1548
  const gated = pipelineGate(ctx);
1545
1549
  if (gated !== null)
@@ -1553,13 +1557,13 @@ export const cmdGdesc = {
1553
1557
  // `PrintQueryStatus`'s "The command has no result, or the result
1554
1558
  // has no columns." line. Stdout, exit 0 — not an error. Verified
1555
1559
  // against vanilla psql 18.
1556
- process.stdout.write('The command has no result, or the result has no columns.\n');
1560
+ process.stdout.write("The command has no result, or the result has no columns.\n");
1557
1561
  // Match upstream's post-PSQL_CMD_SEND state vars: success, 0 rows.
1558
- refreshErrorVars(ctx.settings, { kind: 'success', rowCount: 0 });
1559
- return { status: 'reset-buf', newBuf: '' };
1562
+ refreshErrorVars(ctx.settings, { kind: "success", rowCount: 0 });
1563
+ return { status: "reset-buf", newBuf: "" };
1560
1564
  }
1561
1565
  if (!ctx.settings.db) {
1562
- return errResult(ctx, 'no connection to the server');
1566
+ return errResult(ctx, "no connection to the server");
1563
1567
  }
1564
1568
  // Track for a subsequent `\g` re-run with empty buffer. Upstream
1565
1569
  // `exec_command_gdesc` updates `pset.last_query` to the dispatched SQL
@@ -1573,7 +1577,7 @@ export const cmdGdesc = {
1573
1577
  ctx.settings.lastQuery = sql;
1574
1578
  let fields;
1575
1579
  try {
1576
- const stmt = await ctx.settings.db.prepare('', sql);
1580
+ const stmt = await ctx.settings.db.prepare("", sql);
1577
1581
  fields = await stmt.describe();
1578
1582
  // Close the unnamed prepared statement so we don't leak it. Failure
1579
1583
  // to close (e.g. server already in error state) is non-fatal.
@@ -1600,10 +1604,10 @@ export const cmdGdesc = {
1600
1604
  // Verified against vanilla psql 18: `SELECT \gdesc` and
1601
1605
  // `CREATE TABLE bububu(a int) \gdesc` both produce that text.
1602
1606
  if (fields.length === 0) {
1603
- process.stdout.write('The command has no result, or the result has no columns.\n');
1607
+ process.stdout.write("The command has no result, or the result has no columns.\n");
1604
1608
  // Match upstream's post-PSQL_CMD_SEND state vars: success, 0 rows.
1605
- refreshErrorVars(ctx.settings, { kind: 'success', rowCount: 0 });
1606
- return { status: 'reset-buf', newBuf: '' };
1609
+ refreshErrorVars(ctx.settings, { kind: "success", rowCount: 0 });
1610
+ return { status: "reset-buf", newBuf: "" };
1607
1611
  }
1608
1612
  // Resolve canonical type names via a follow-up round trip when we have
1609
1613
  // at least one field. On failure (or when the server returns nothing —
@@ -1619,7 +1623,8 @@ export const cmdGdesc = {
1619
1623
  try {
1620
1624
  const sets = await ctx.settings.db.execSimple(formatQuery);
1621
1625
  const last = sets[sets.length - 1];
1622
- rows = last && last.rows.length > 0 ? last.rows : fallbackRows();
1626
+ rows =
1627
+ last && last.rows.length > 0 ? last.rows : fallbackRows();
1623
1628
  }
1624
1629
  catch {
1625
1630
  rows = fallbackRows();
@@ -1641,18 +1646,18 @@ export const cmdGdesc = {
1641
1646
  // `SetResultVariables` sees the synthetic 2-column tuple result and
1642
1647
  // assigns ROW_COUNT to the field count we just rendered.
1643
1648
  refreshErrorVars(ctx.settings, {
1644
- kind: 'success',
1649
+ kind: "success",
1645
1650
  rowCount: rs.rowCount,
1646
1651
  });
1647
- return { status: 'reset-buf', newBuf: '' };
1652
+ return { status: "reset-buf", newBuf: "" };
1648
1653
  },
1649
1654
  };
1650
1655
  // ---------------------------------------------------------------------------
1651
1656
  // \gexec — treat each cell of the result as SQL to execute.
1652
1657
  // ---------------------------------------------------------------------------
1653
1658
  export const cmdGexec = {
1654
- name: 'gexec',
1655
- helpKey: 'gexec',
1659
+ name: "gexec",
1660
+ helpKey: "gexec",
1656
1661
  async run(ctx) {
1657
1662
  const gated = pipelineGate(ctx);
1658
1663
  if (gated !== null)
@@ -1665,10 +1670,10 @@ export const cmdGexec = {
1665
1670
  // query — exit 0, no message. Verified against vanilla psql 18.
1666
1671
  const sql = bufSql.length > 0 ? bufSql : ctx.settings.lastQuery.trim();
1667
1672
  if (sql.length === 0) {
1668
- return { status: 'reset-buf', newBuf: '' };
1673
+ return { status: "reset-buf", newBuf: "" };
1669
1674
  }
1670
1675
  if (!ctx.settings.db) {
1671
- return errResult(ctx, 'no connection to the server');
1676
+ return errResult(ctx, "no connection to the server");
1672
1677
  }
1673
1678
  // Track the outer (meta) query for a subsequent `\g` re-run with an empty
1674
1679
  // buffer. Upstream `exec_command_gexec` runs through PSQL_CMD_SEND, which
@@ -1686,7 +1691,7 @@ export const cmdGexec = {
1686
1691
  }
1687
1692
  const tupled = firstPass.filter((r) => r.fields.length > 0);
1688
1693
  if (tupled.length === 0) {
1689
- return { status: 'reset-buf', newBuf: '' };
1694
+ return { status: "reset-buf", newBuf: "" };
1690
1695
  }
1691
1696
  const out = pickOut(ctx.settings, null);
1692
1697
  // Echo each generated SQL when ECHO is `all` or `queries`. Vanilla
@@ -1696,7 +1701,7 @@ export const cmdGexec = {
1696
1701
  // appears BEFORE the result body so the conformance harness sees
1697
1702
  // the same interleaving vanilla produces.
1698
1703
  const echo = ctx.settings.echo;
1699
- const shouldEcho = echo === 'all' || echo === 'queries';
1704
+ const shouldEcho = echo === "all" || echo === "queries";
1700
1705
  // Per-row errors are tolerated: upstream `\gexec` calls
1701
1706
  // `SendQuery` in a loop and ignores its return value (the only
1702
1707
  // escape is the global ON_ERROR_STOP variable, which the
@@ -1713,7 +1718,7 @@ export const cmdGexec = {
1713
1718
  if (statement.length === 0)
1714
1719
  continue;
1715
1720
  if (shouldEcho) {
1716
- out.write(statement + '\n');
1721
+ out.write(statement + "\n");
1717
1722
  }
1718
1723
  try {
1719
1724
  const nested = await ctx.settings.db.execSimple(statement);
@@ -1735,7 +1740,7 @@ export const cmdGexec = {
1735
1740
  // global `pset.on_error_stop` flag via `SendQuery`'s
1736
1741
  // return; we mirror by checking the setting directly.
1737
1742
  if (ctx.settings.onErrorStop) {
1738
- return { status: 'error', errorWritten: true };
1743
+ return { status: "error", errorWritten: true };
1739
1744
  }
1740
1745
  }
1741
1746
  }
@@ -1745,7 +1750,7 @@ export const cmdGexec = {
1745
1750
  // outer `\gexec` buffer. Per-row error rendering already happened;
1746
1751
  // returning `error` here would re-trigger the writeError path.
1747
1752
  void sawError;
1748
- return { status: 'reset-buf', newBuf: '' };
1753
+ return { status: "reset-buf", newBuf: "" };
1749
1754
  },
1750
1755
  };
1751
1756
  // ---------------------------------------------------------------------------
@@ -1771,12 +1776,12 @@ export const cmdGexec = {
1771
1776
  // ---------------------------------------------------------------------------
1772
1777
  const sleepCancellable = (ms, signal) => new Promise((resolve) => {
1773
1778
  const timer = setTimeout(() => {
1774
- signal.removeEventListener('abort', onAbort);
1779
+ signal.removeEventListener("abort", onAbort);
1775
1780
  resolve();
1776
1781
  }, ms);
1777
1782
  const onAbort = () => {
1778
1783
  clearTimeout(timer);
1779
- signal.removeEventListener('abort', onAbort);
1784
+ signal.removeEventListener("abort", onAbort);
1780
1785
  resolve();
1781
1786
  };
1782
1787
  if (signal.aborted) {
@@ -1784,7 +1789,7 @@ const sleepCancellable = (ms, signal) => new Promise((resolve) => {
1784
1789
  resolve();
1785
1790
  return;
1786
1791
  }
1787
- signal.addEventListener('abort', onAbort);
1792
+ signal.addEventListener("abort", onAbort);
1788
1793
  });
1789
1794
  /**
1790
1795
  * Strictly parse a non-negative finite float.
@@ -1834,7 +1839,7 @@ const parseStrictNonNegativeInt = (raw) => {
1834
1839
  * it when the user unsets the `WATCH_INTERVAL` variable — upstream's
1835
1840
  * `watch_interval_substitute_hook` reseeds the value to `2` on null.
1836
1841
  */
1837
- export const DEFAULT_WATCH_INTERVAL = '2';
1842
+ export const DEFAULT_WATCH_INTERVAL = "2";
1838
1843
  /**
1839
1844
  * Render `\watch`'s per-iteration timestamp in upstream psql's
1840
1845
  * `ctime`-style layout: `Day Mon DD HH:MM:SS YYYY` (e.g. `Mon May 25
@@ -1844,20 +1849,20 @@ export const DEFAULT_WATCH_INTERVAL = '2';
1844
1849
  *
1845
1850
  * Exported only for unit-testing the format ladder.
1846
1851
  */
1847
- const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
1852
+ const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
1848
1853
  const MONTHS = [
1849
- 'Jan',
1850
- 'Feb',
1851
- 'Mar',
1852
- 'Apr',
1853
- 'May',
1854
- 'Jun',
1855
- 'Jul',
1856
- 'Aug',
1857
- 'Sep',
1858
- 'Oct',
1859
- 'Nov',
1860
- 'Dec',
1854
+ "Jan",
1855
+ "Feb",
1856
+ "Mar",
1857
+ "Apr",
1858
+ "May",
1859
+ "Jun",
1860
+ "Jul",
1861
+ "Aug",
1862
+ "Sep",
1863
+ "Oct",
1864
+ "Nov",
1865
+ "Dec",
1861
1866
  ];
1862
1867
  const pad2 = (n) => (n < 10 ? `0${String(n)}` : String(n));
1863
1868
  export const formatWatchTimestamp = (now) => {
@@ -1890,7 +1895,7 @@ const resolveWatchIntervalDefault = (settings) => {
1890
1895
  // If a future code path leaves it undefined we fall back to the same
1891
1896
  // documented default — upstream's `ParseVariableDouble` substitutes
1892
1897
  // `DEFAULT_WATCH_INTERVAL` when the var slot is empty.
1893
- const raw = settings.vars.get('WATCH_INTERVAL') ?? DEFAULT_WATCH_INTERVAL;
1898
+ const raw = settings.vars.get("WATCH_INTERVAL") ?? DEFAULT_WATCH_INTERVAL;
1894
1899
  const parsed = parseStrictNonNegativeFloat(raw);
1895
1900
  if (parsed === null || parsed > WATCH_INTERVAL_MAX_SECONDS) {
1896
1901
  return {
@@ -1925,14 +1930,14 @@ const resolveWatchIntervalDefault = (settings) => {
1925
1930
  * output target.
1926
1931
  */
1927
1932
  const openWatchPager = () => {
1928
- const cmd = process.env.PSQL_WATCH_PAGER ?? '';
1933
+ const cmd = process.env.PSQL_WATCH_PAGER ?? "";
1929
1934
  if (cmd.trim().length === 0)
1930
1935
  return null;
1931
- const child = spawn('sh', ['-c', cmd], {
1932
- stdio: ['pipe', 'inherit', 'inherit'],
1936
+ const child = spawn("sh", ["-c", cmd], {
1937
+ stdio: ["pipe", "inherit", "inherit"],
1933
1938
  });
1934
- child.stdin.on('error', (err) => {
1935
- if (err.code !== 'EPIPE') {
1939
+ child.stdin.on("error", (err) => {
1940
+ if (err.code !== "EPIPE") {
1936
1941
  throw err;
1937
1942
  }
1938
1943
  });
@@ -1954,15 +1959,15 @@ const openWatchPager = () => {
1954
1959
  settled = true;
1955
1960
  resolve();
1956
1961
  };
1957
- child.once('close', finish);
1958
- child.once('error', finish);
1962
+ child.once("close", finish);
1963
+ child.once("error", finish);
1959
1964
  if (!child.stdin.destroyed) {
1960
1965
  try {
1961
1966
  child.stdin.end();
1962
1967
  }
1963
1968
  catch (err) {
1964
1969
  const e = err;
1965
- if (e.code !== 'EPIPE')
1970
+ if (e.code !== "EPIPE")
1966
1971
  finish();
1967
1972
  }
1968
1973
  }
@@ -1970,8 +1975,8 @@ const openWatchPager = () => {
1970
1975
  };
1971
1976
  };
1972
1977
  export const cmdWatch = {
1973
- name: 'watch',
1974
- helpKey: 'watch',
1978
+ name: "watch",
1979
+ helpKey: "watch",
1975
1980
  async run(ctx) {
1976
1981
  const gated = pipelineGate(ctx);
1977
1982
  if (gated !== null)
@@ -1980,10 +1985,10 @@ export const cmdWatch = {
1980
1985
  const trimmedBuf = stripLeadingCommentsAndWS(ctx.queryBuf);
1981
1986
  const sql = trimmedBuf.trim();
1982
1987
  if (sql.length === 0) {
1983
- return errResult(ctx, 'no query buffer');
1988
+ return errResult(ctx, "no query buffer");
1984
1989
  }
1985
1990
  if (!ctx.settings.db) {
1986
- return errResult(ctx, 'no connection to the server');
1991
+ return errResult(ctx, "no connection to the server");
1987
1992
  }
1988
1993
  // Track which options have been seen so we can reject duplicates with
1989
1994
  // the upstream-formatted "<thing> is specified more than once" message.
@@ -1997,7 +2002,7 @@ export const cmdWatch = {
1997
2002
  // Drain all args. Each is either a `key=value` token or a bare
1998
2003
  // positional (only allowed as the very first arg, and only once).
1999
2004
  while (true) {
2000
- const arg = ctx.nextArg('normal');
2005
+ const arg = ctx.nextArg("normal");
2001
2006
  if (arg === null)
2002
2007
  break;
2003
2008
  if (arg.length === 0)
@@ -2005,25 +2010,26 @@ export const cmdWatch = {
2005
2010
  // Identify named flags by looking for `=`. Upstream tolerates an
2006
2011
  // empty value (treats it as the option not being provided), but we
2007
2012
  // mirror its stricter behaviour for the values we care about.
2008
- const eqIdx = arg.indexOf('=');
2013
+ const eqIdx = arg.indexOf("=");
2009
2014
  if (eqIdx > 0) {
2010
2015
  const key = arg.slice(0, eqIdx);
2011
2016
  const value = arg.slice(eqIdx + 1);
2012
- if (key === 'i') {
2017
+ if (key === "i") {
2013
2018
  if (intervalSet) {
2014
- return errResult(ctx, 'interval value is specified more than once');
2019
+ return errResult(ctx, "interval value is specified more than once");
2015
2020
  }
2016
2021
  const parsed = parseStrictNonNegativeFloat(value);
2017
- if (parsed === null || parsed > WATCH_INTERVAL_MAX_SECONDS) {
2022
+ if (parsed === null ||
2023
+ parsed > WATCH_INTERVAL_MAX_SECONDS) {
2018
2024
  return errResult(ctx, `incorrect interval value "${value}"`);
2019
2025
  }
2020
2026
  interval = parsed;
2021
2027
  intervalSet = true;
2022
2028
  continue;
2023
2029
  }
2024
- if (key === 'c') {
2030
+ if (key === "c") {
2025
2031
  if (iterSet) {
2026
- return errResult(ctx, 'iteration count is specified more than once');
2032
+ return errResult(ctx, "iteration count is specified more than once");
2027
2033
  }
2028
2034
  const parsed = parseStrictNonNegativeInt(value);
2029
2035
  // Upstream parses the count with `option_parse_int(..., 1, INT_MAX)`
@@ -2038,9 +2044,9 @@ export const cmdWatch = {
2038
2044
  iterSet = true;
2039
2045
  continue;
2040
2046
  }
2041
- if (key === 'm' || key === 'min_rows') {
2047
+ if (key === "m" || key === "min_rows") {
2042
2048
  if (minRowsSet) {
2043
- return errResult(ctx, 'minimum row count specified more than once');
2049
+ return errResult(ctx, "minimum row count specified more than once");
2044
2050
  }
2045
2051
  const parsed = parseStrictNonNegativeInt(value);
2046
2052
  if (parsed === null) {
@@ -2058,7 +2064,7 @@ export const cmdWatch = {
2058
2064
  // only collides with `i=` under the same upstream "specified more
2059
2065
  // than once" rubric.
2060
2066
  if (positionalSeen || intervalSet) {
2061
- return errResult(ctx, 'interval value is specified more than once');
2067
+ return errResult(ctx, "interval value is specified more than once");
2062
2068
  }
2063
2069
  const parsed = parseStrictNonNegativeFloat(arg);
2064
2070
  if (parsed === null || parsed > WATCH_INTERVAL_MAX_SECONDS) {
@@ -2071,7 +2077,7 @@ export const cmdWatch = {
2071
2077
  // If no explicit interval was supplied, fall back to WATCH_INTERVAL.
2072
2078
  if (interval === null) {
2073
2079
  const resolved = resolveWatchIntervalDefault(ctx.settings);
2074
- if ('error' in resolved) {
2080
+ if ("error" in resolved) {
2075
2081
  return errResult(ctx, resolved.error);
2076
2082
  }
2077
2083
  interval = resolved.value;
@@ -2085,7 +2091,7 @@ export const cmdWatch = {
2085
2091
  };
2086
2092
  const installedSigint = WATCH_TEST_CONTROLLER.ref === null;
2087
2093
  if (installedSigint) {
2088
- process.once('SIGINT', sigintHandler);
2094
+ process.once("SIGINT", sigintHandler);
2089
2095
  }
2090
2096
  // Open the pager once for the whole loop (upstream `do_watch` wraps the
2091
2097
  // entire session, not each iteration, so the user can scroll the
@@ -2152,13 +2158,13 @@ export const cmdWatch = {
2152
2158
  // possible `^C` echo. Mirror that here so the conformance output
2153
2159
  // shape (`...\n(N rows)\n\n\n`) matches vanilla psql.
2154
2160
  if (!pager) {
2155
- out.write('\n');
2161
+ out.write("\n");
2156
2162
  }
2157
- return { status: 'reset-buf', newBuf: '' };
2163
+ return { status: "reset-buf", newBuf: "" };
2158
2164
  }
2159
2165
  finally {
2160
2166
  if (installedSigint) {
2161
- process.removeListener('SIGINT', sigintHandler);
2167
+ process.removeListener("SIGINT", sigintHandler);
2162
2168
  }
2163
2169
  // Drain the pager so its child has a chance to exit before \watch
2164
2170
  // returns. Failures are swallowed: a broken pager shouldn't mask the