neon 3.0.0 → 3.1.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 (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,766 +1,607 @@
1
- /**
2
- * `\sf [+] FUNCNAME` / `\sv [+] VIEWNAME` show-source commands and their
3
- * `\ef` / `\ev` edit-form siblings, ported from upstream `command.c`'s
4
- * `exec_command_sf_sv` and `exec_command_ef_ev`.
5
- *
6
- * Behaviour matches upstream byte-for-byte for the show forms:
7
- *
8
- * 1. Lookup the object's OID — function via `regproc` (or `regprocedure`
9
- * when the name carries an argument list `(int)`), view via `regclass`.
10
- * 2. Fetch the definition — `pg_get_functiondef(oid)` for functions; for
11
- * views we re-assemble the `CREATE OR REPLACE VIEW … AS …` head (with
12
- * schema-qualified name, reloptions, and optional `WITH CHECK OPTION`)
13
- * around the body returned by `pg_get_viewdef(oid, true)`.
14
- * 3. Stream the rendered text to `stdout`, optionally prefixed by line
15
- * numbers when the user passed `+` (e.g. `\sf+ foo`).
16
- *
17
- * Line-number formatting (mirrors upstream `print_with_linenumbers`):
18
- *
19
- * - For functions: lines before the body marker (`AS `, `BEGIN `, or
20
- * `RETURN `) are unnumbered and rendered as ` <line>\n` (8 spaces
21
- * of padding). Body lines render as `<lineno> <line>\n` where the
22
- * numeric field is left-justified in a 7-character slot, with one
23
- * literal space separator. The first body line becomes line 1.
24
- * - For views: every line is a "body" line — `lineno` starts at 1 and
25
- * increments for every output line, no header padding.
26
- *
27
- * Edit forms (`\ef` / `\ev`):
28
- * We do not implement editor invocation — that needs TTY interaction and
29
- * `$EDITOR` semantics outside the scope of this embedded psql. When the
30
- * user supplies a name we route through the same fetch+print path as the
31
- * show forms (`\ef foo` ≡ `\sf foo`); without a name we error with a hint
32
- * pointing back at `\sf` / `\sv`.
33
- *
34
- * Argument parsing matches upstream's `OT_WHOLE_LINE`: we slurp the rest of
35
- * the line and trim, so `\sf myschema.foo ` round-trips cleanly without
36
- * splitting on the dot or whitespace inside parens.
37
- */
38
1
  import { writeErr, writeOut } from "./shared.js";
39
- // ---------------------------------------------------------------------------
40
- // Helpers
41
- // ---------------------------------------------------------------------------
2
+ //#region src/psql/command/cmd_show.ts
42
3
  /**
43
- * Emit a `\<cmd>: <message>` error to stderr, stash the message on
44
- * `lastErrorResult` so `\errverbose` / the mainloop fallback see it, and
45
- * return an error result with `errorWritten: true` so the mainloop doesn't
46
- * double-print.
47
- */
4
+ * Emit a `\<cmd>: <message>` error to stderr, stash the message on
5
+ * `lastErrorResult` so `\errverbose` / the mainloop fallback see it, and
6
+ * return an error result with `errorWritten: true` so the mainloop doesn't
7
+ * double-print.
8
+ */
48
9
  const errResult = (ctx, message) => {
49
- ctx.settings.lastErrorResult = { message };
50
- writeErr(`\\${ctx.cmdName}: ${message}\n`);
51
- return { status: "error", errorWritten: true };
10
+ ctx.settings.lastErrorResult = { message };
11
+ writeErr(`\\${ctx.cmdName}: ${message}\n`);
12
+ return {
13
+ status: "error",
14
+ errorWritten: true
15
+ };
52
16
  };
53
17
  /**
54
- * Format a server error for stderr the way upstream's
55
- * `minimal_error_message` does: `<severity>: <primary message>\n`. Falls
56
- * back to "ERROR:" + Error.message when the error doesn't carry severity /
57
- * message fields (e.g. a wire-layer rejection).
58
- */
18
+ * Format a server error for stderr the way upstream's
19
+ * `minimal_error_message` does: `<severity>: <primary message>\n`. Falls
20
+ * back to "ERROR:" + Error.message when the error doesn't carry severity /
21
+ * message fields (e.g. a wire-layer rejection).
22
+ */
59
23
  const formatServerError = (err) => {
60
- if (err && typeof err === "object") {
61
- const e = err;
62
- const sev = e.severity ?? "ERROR";
63
- const msg = e.message ??
64
- (err instanceof Error ? err.message : safeToString(err));
65
- return `${sev}: ${msg}`;
66
- }
67
- if (err instanceof Error)
68
- return `ERROR: ${err.message}`;
69
- return `ERROR: ${safeToString(err)}`;
24
+ if (err && typeof err === "object") {
25
+ const e = err;
26
+ return `${e.severity ?? "ERROR"}: ${e.message ?? (err instanceof Error ? err.message : safeToString(err))}`;
27
+ }
28
+ if (err instanceof Error) return `ERROR: ${err.message}`;
29
+ return `ERROR: ${safeToString(err)}`;
70
30
  };
71
31
  /**
72
- * Coerce an unknown value to a string defensively. Plain non-`Error`
73
- * objects would render as `[object Object]` via the default `String()`
74
- * path; we sidestep that by JSON-encoding when possible (falls back to
75
- * the typeof when JSON throws — e.g. circular structures).
76
- */
32
+ * Coerce an unknown value to a string defensively. Plain non-`Error`
33
+ * objects would render as `[object Object]` via the default `String()`
34
+ * path; we sidestep that by JSON-encoding when possible (falls back to
35
+ * the typeof when JSON throws — e.g. circular structures).
36
+ */
77
37
  const safeToString = (v) => {
78
- if (v === null)
79
- return "null";
80
- if (v === undefined)
81
- return "undefined";
82
- if (typeof v === "string")
83
- return v;
84
- if (typeof v === "number" ||
85
- typeof v === "boolean" ||
86
- typeof v === "bigint") {
87
- return String(v);
88
- }
89
- try {
90
- return JSON.stringify(v) ?? typeof v;
91
- }
92
- catch {
93
- return typeof v;
94
- }
38
+ if (v === null) return "null";
39
+ if (v === void 0) return "undefined";
40
+ if (typeof v === "string") return v;
41
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
42
+ try {
43
+ return JSON.stringify(v) ?? typeof v;
44
+ } catch {
45
+ return typeof v;
46
+ }
95
47
  };
96
48
  /**
97
- * Print a server-side query failure the way upstream does — `<sev>: <msg>`
98
- * directly on stderr, without the `\<cmd>: ` prefix that local-only errors
99
- * use. Also stashes the message on `lastErrorResult` so `\errverbose`
100
- * survives.
101
- */
49
+ * Print a server-side query failure the way upstream does — `<sev>: <msg>`
50
+ * directly on stderr, without the `\<cmd>: ` prefix that local-only errors
51
+ * use. Also stashes the message on `lastErrorResult` so `\errverbose`
52
+ * survives.
53
+ */
102
54
  const queryErrResult = (ctx, err) => {
103
- const line = formatServerError(err);
104
- ctx.settings.lastErrorResult = {
105
- message: err &&
106
- typeof err === "object" &&
107
- err.message
108
- ? err.message
109
- : err instanceof Error
110
- ? err.message
111
- : safeToString(err),
112
- };
113
- writeErr(`${line}\n`);
114
- return { status: "error", errorWritten: true };
55
+ const line = formatServerError(err);
56
+ ctx.settings.lastErrorResult = { message: err && typeof err === "object" && err.message ? err.message : err instanceof Error ? err.message : safeToString(err) };
57
+ writeErr(`${line}\n`);
58
+ return {
59
+ status: "error",
60
+ errorWritten: true
61
+ };
115
62
  };
116
63
  const conn = (ctx) => ctx.settings.db;
117
64
  const noConn = (ctx) => errResult(ctx, "no connection to the server");
118
65
  /**
119
- * Read the object descriptor as a whole-line argument with surrounding
120
- * whitespace AND trailing semicolons stripped. Returns `null` when no
121
- * name was supplied (after the strip — i.e. `\sf ` or `\sf ;;` is
122
- * treated as empty).
123
- *
124
- * Upstream `psql_scan_slash_option(scan_state, OT_WHOLE_LINE, …)` keeps
125
- * the `;` in the returned string, but `exec_command_sf_sv` (and its `ef`/
126
- * `ev` siblings) trim trailing whitespace + `;` before passing the
127
- * descriptor to `lookup_object_oid`. Without this, `\sf ts_debug(text);`
128
- * sends `'ts_debug(text);'::regprocedure` to the server, which the
129
- * regprocedure input parser rejects with "expected a right parenthesis".
130
- */
66
+ * Read the object descriptor as a whole-line argument with surrounding
67
+ * whitespace AND trailing semicolons stripped. Returns `null` when no
68
+ * name was supplied (after the strip — i.e. `\sf ` or `\sf ;;` is
69
+ * treated as empty).
70
+ *
71
+ * Upstream `psql_scan_slash_option(scan_state, OT_WHOLE_LINE, …)` keeps
72
+ * the `;` in the returned string, but `exec_command_sf_sv` (and its `ef`/
73
+ * `ev` siblings) trim trailing whitespace + `;` before passing the
74
+ * descriptor to `lookup_object_oid`. Without this, `\sf ts_debug(text);`
75
+ * sends `'ts_debug(text);'::regprocedure` to the server, which the
76
+ * regprocedure input parser rejects with "expected a right parenthesis".
77
+ */
131
78
  const readObjDesc = (ctx) => {
132
- const raw = ctx.restOfLine();
133
- // Strip trailing whitespace and `;` (in any order, any count) so
134
- // `\sf foo(arg) ;; ` round-trips like vanilla psql.
135
- const trimmed = raw.replace(/[\s;]+$/, "").trimStart();
136
- return trimmed.length === 0 ? null : trimmed;
79
+ const trimmed = ctx.restOfLine().replace(/[\s;]+$/, "").trimStart();
80
+ return trimmed.length === 0 ? null : trimmed;
137
81
  };
138
82
  /**
139
- * Decode whether the command name ends in `+` (request line numbers). The
140
- * caller knows the base name (`sf`, `sv`, `ef`, `ev`); any extra letters
141
- * are looked at for a literal `+`.
142
- */
83
+ * Decode whether the command name ends in `+` (request line numbers). The
84
+ * caller knows the base name (`sf`, `sv`, `ef`, `ev`); any extra letters
85
+ * are looked at for a literal `+`.
86
+ */
143
87
  const decodeShowSuffix = (cmdName, base) => {
144
- const tail = cmdName.slice(base.length);
145
- return { plus: tail.includes("+") };
88
+ return { plus: cmdName.slice(base.length).includes("+") };
146
89
  };
147
90
  /**
148
- * Look up a function OID from `desc`. Mirrors upstream's
149
- * `lookup_object_oid(EditableFunction, ...)` exactly:
150
- *
151
- * - If `desc` contains `(`, cast through `regprocedure` (which resolves
152
- * by full argument signature, e.g. `foo(int)`).
153
- * - Otherwise cast through `regproc` (which matches by bare name and
154
- * errors on overloaded ambiguity).
155
- *
156
- * The descriptor is passed as a SQL string literal — we use the server's
157
- * `escapeLiteral` to mirror libpq's `appendStringLiteralConn`.
158
- */
91
+ * Look up a function OID from `desc`. Mirrors upstream's
92
+ * `lookup_object_oid(EditableFunction, ...)` exactly:
93
+ *
94
+ * - If `desc` contains `(`, cast through `regprocedure` (which resolves
95
+ * by full argument signature, e.g. `foo(int)`).
96
+ * - Otherwise cast through `regproc` (which matches by bare name and
97
+ * errors on overloaded ambiguity).
98
+ *
99
+ * The descriptor is passed as a SQL string literal — we use the server's
100
+ * `escapeLiteral` to mirror libpq's `appendStringLiteralConn`.
101
+ */
159
102
  const lookupFunctionOid = async (c, desc) => {
160
- const cast = desc.includes("(") ? "regprocedure" : "regproc";
161
- const sql = `SELECT ${c.escapeLiteral(desc)}::pg_catalog.${cast}::pg_catalog.oid`;
162
- try {
163
- const rs = await c.query(sql, []);
164
- if (rs.rows.length !== 1 || rs.rows[0][0] === null) {
165
- return {
166
- ok: false,
167
- err: new Error("object lookup returned no rows"),
168
- };
169
- }
170
- const raw = cellToString(rs.rows[0][0]);
171
- const oid = Number(raw);
172
- if (!Number.isFinite(oid)) {
173
- return {
174
- ok: false,
175
- err: new Error(`invalid oid in lookup result: ${raw}`),
176
- };
177
- }
178
- return { ok: true, oid };
179
- }
180
- catch (err) {
181
- return { ok: false, err };
182
- }
103
+ const cast = desc.includes("(") ? "regprocedure" : "regproc";
104
+ const sql = `SELECT ${c.escapeLiteral(desc)}::pg_catalog.${cast}::pg_catalog.oid`;
105
+ try {
106
+ const rs = await c.query(sql, []);
107
+ if (rs.rows.length !== 1 || rs.rows[0][0] === null) return {
108
+ ok: false,
109
+ err: /* @__PURE__ */ new Error("object lookup returned no rows")
110
+ };
111
+ const raw = cellToString(rs.rows[0][0]);
112
+ const oid = Number(raw);
113
+ if (!Number.isFinite(oid)) return {
114
+ ok: false,
115
+ err: /* @__PURE__ */ new Error(`invalid oid in lookup result: ${raw}`)
116
+ };
117
+ return {
118
+ ok: true,
119
+ oid
120
+ };
121
+ } catch (err) {
122
+ return {
123
+ ok: false,
124
+ err
125
+ };
126
+ }
183
127
  };
184
128
  /**
185
- * Look up a view OID from `desc` via `regclass`. Matches upstream's
186
- * `lookup_object_oid(EditableView, ...)`. Note that this does NOT verify
187
- * the relation is actually a view; the kind check happens in
188
- * `getViewCreateCmd` where upstream catches it via the relkind column.
189
- */
129
+ * Look up a view OID from `desc` via `regclass`. Matches upstream's
130
+ * `lookup_object_oid(EditableView, ...)`. Note that this does NOT verify
131
+ * the relation is actually a view; the kind check happens in
132
+ * `getViewCreateCmd` where upstream catches it via the relkind column.
133
+ */
190
134
  const lookupRelationOid = async (c, desc) => {
191
- const sql = `SELECT ${c.escapeLiteral(desc)}::pg_catalog.regclass::pg_catalog.oid`;
192
- try {
193
- const rs = await c.query(sql, []);
194
- if (rs.rows.length !== 1 || rs.rows[0][0] === null) {
195
- return {
196
- ok: false,
197
- err: new Error("object lookup returned no rows"),
198
- };
199
- }
200
- const raw = cellToString(rs.rows[0][0]);
201
- const oid = Number(raw);
202
- if (!Number.isFinite(oid)) {
203
- return {
204
- ok: false,
205
- err: new Error(`invalid oid in lookup result: ${raw}`),
206
- };
207
- }
208
- return { ok: true, oid };
209
- }
210
- catch (err) {
211
- return { ok: false, err };
212
- }
135
+ const sql = `SELECT ${c.escapeLiteral(desc)}::pg_catalog.regclass::pg_catalog.oid`;
136
+ try {
137
+ const rs = await c.query(sql, []);
138
+ if (rs.rows.length !== 1 || rs.rows[0][0] === null) return {
139
+ ok: false,
140
+ err: /* @__PURE__ */ new Error("object lookup returned no rows")
141
+ };
142
+ const raw = cellToString(rs.rows[0][0]);
143
+ const oid = Number(raw);
144
+ if (!Number.isFinite(oid)) return {
145
+ ok: false,
146
+ err: /* @__PURE__ */ new Error(`invalid oid in lookup result: ${raw}`)
147
+ };
148
+ return {
149
+ ok: true,
150
+ oid
151
+ };
152
+ } catch (err) {
153
+ return {
154
+ ok: false,
155
+ err
156
+ };
157
+ }
213
158
  };
214
159
  /**
215
- * Coerce a wire-layer cell to a string. Text-mode results arrive as
216
- * strings; null is treated as "" so missing-row paths fall through to
217
- * empty output instead of crashing.
218
- */
160
+ * Coerce a wire-layer cell to a string. Text-mode results arrive as
161
+ * strings; null is treated as "" so missing-row paths fall through to
162
+ * empty output instead of crashing.
163
+ */
219
164
  const cellToString = (v) => {
220
- if (v === null || v === undefined)
221
- return "";
222
- if (typeof v === "string")
223
- return v;
224
- if (Buffer.isBuffer(v))
225
- return v.toString("utf-8");
226
- if (typeof v === "number" ||
227
- typeof v === "boolean" ||
228
- typeof v === "bigint") {
229
- return String(v);
230
- }
231
- // Non-primitive fallback: encode JSON so we never emit a stray
232
- // `[object Object]`. The wire layer hands us strings or nulls in
233
- // practice, so this branch is defensive only.
234
- try {
235
- return JSON.stringify(v) ?? "";
236
- }
237
- catch {
238
- return "";
239
- }
165
+ if (v === null || v === void 0) return "";
166
+ if (typeof v === "string") return v;
167
+ if (Buffer.isBuffer(v)) return v.toString("utf-8");
168
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") return String(v);
169
+ try {
170
+ return JSON.stringify(v) ?? "";
171
+ } catch {
172
+ return "";
173
+ }
240
174
  };
241
175
  /**
242
- * Fetch the CREATE FUNCTION source for `oid` via
243
- * `pg_catalog.pg_get_functiondef(oid)`. Upstream guarantees the result is
244
- * newline-terminated; we re-assert that here so the caller can stream
245
- * straight to stdout (or hand to the line-number formatter).
246
- */
176
+ * Fetch the CREATE FUNCTION source for `oid` via
177
+ * `pg_catalog.pg_get_functiondef(oid)`. Upstream guarantees the result is
178
+ * newline-terminated; we re-assert that here so the caller can stream
179
+ * straight to stdout (or hand to the line-number formatter).
180
+ */
247
181
  const getFunctionCreateCmd = async (c, oid) => {
248
- const sql = `SELECT pg_catalog.pg_get_functiondef(${oid})`;
249
- try {
250
- const rs = await c.query(sql, []);
251
- if (rs.rows.length !== 1) {
252
- return {
253
- ok: false,
254
- err: new Error("function definition not found"),
255
- };
256
- }
257
- let def = cellToString(rs.rows[0][0]);
258
- if (def.length > 0 && !def.endsWith("\n"))
259
- def += "\n";
260
- return { ok: true, def };
261
- }
262
- catch (err) {
263
- return { ok: false, err };
264
- }
182
+ const sql = `SELECT pg_catalog.pg_get_functiondef(${oid})`;
183
+ try {
184
+ const rs = await c.query(sql, []);
185
+ if (rs.rows.length !== 1) return {
186
+ ok: false,
187
+ err: /* @__PURE__ */ new Error("function definition not found")
188
+ };
189
+ let def = cellToString(rs.rows[0][0]);
190
+ if (def.length > 0 && !def.endsWith("\n")) def += "\n";
191
+ return {
192
+ ok: true,
193
+ def
194
+ };
195
+ } catch (err) {
196
+ return {
197
+ ok: false,
198
+ err
199
+ };
200
+ }
265
201
  };
266
202
  /**
267
- * Quote an SQL identifier when needed. Mirrors libpq's `fmtId`: lowercase
268
- * ASCII identifiers starting with `[a-z_]` and continuing with
269
- * `[a-z0-9_$]` may go unquoted; anything else gets double-quoted with
270
- * embedded double-quotes doubled. Used to schema-qualify view names in
271
- * the synthesised `CREATE OR REPLACE VIEW …` head.
272
- */
203
+ * Quote an SQL identifier when needed. Mirrors libpq's `fmtId`: lowercase
204
+ * ASCII identifiers starting with `[a-z_]` and continuing with
205
+ * `[a-z0-9_$]` may go unquoted; anything else gets double-quoted with
206
+ * embedded double-quotes doubled. Used to schema-qualify view names in
207
+ * the synthesised `CREATE OR REPLACE VIEW …` head.
208
+ */
273
209
  const fmtId = (ident) => {
274
- if (/^[a-z_][a-z0-9_$]*$/.test(ident) && !RESERVED_WORDS.has(ident)) {
275
- return ident;
276
- }
277
- return `"${ident.replace(/"/g, '""')}"`;
210
+ if (/^[a-z_][a-z0-9_$]*$/.test(ident) && !RESERVED_WORDS.has(ident)) return ident;
211
+ return `"${ident.replace(/"/g, "\"\"")}"`;
278
212
  };
279
213
  /**
280
- * Minimal reserved-word set used by `fmtId`. Upstream's `fmtId` is much
281
- * more conservative — any keyword needs quoting regardless of category.
282
- * For our use case we only quote the keywords that show up as actual
283
- * relation names; that's vanishingly rare in practice (CREATE VIEW
284
- * "select" AS … is legal but no one does it). Keeping the set small
285
- * avoids a large keyword table; the worst case is an un-needed
286
- * double-quote pair, which is still valid SQL.
287
- */
288
- const RESERVED_WORDS = new Set([
289
- "all",
290
- "analyse",
291
- "analyze",
292
- "and",
293
- "any",
294
- "array",
295
- "as",
296
- "asc",
297
- "asymmetric",
298
- "both",
299
- "case",
300
- "cast",
301
- "check",
302
- "collate",
303
- "column",
304
- "constraint",
305
- "create",
306
- "current_catalog",
307
- "current_date",
308
- "current_role",
309
- "current_time",
310
- "current_timestamp",
311
- "current_user",
312
- "default",
313
- "deferrable",
314
- "desc",
315
- "distinct",
316
- "do",
317
- "else",
318
- "end",
319
- "except",
320
- "false",
321
- "fetch",
322
- "for",
323
- "foreign",
324
- "from",
325
- "grant",
326
- "group",
327
- "having",
328
- "in",
329
- "initially",
330
- "intersect",
331
- "into",
332
- "lateral",
333
- "leading",
334
- "limit",
335
- "localtime",
336
- "localtimestamp",
337
- "not",
338
- "null",
339
- "offset",
340
- "on",
341
- "only",
342
- "or",
343
- "order",
344
- "placing",
345
- "primary",
346
- "references",
347
- "returning",
348
- "select",
349
- "session_user",
350
- "some",
351
- "symmetric",
352
- "table",
353
- "then",
354
- "to",
355
- "trailing",
356
- "true",
357
- "union",
358
- "unique",
359
- "user",
360
- "using",
361
- "variadic",
362
- "when",
363
- "where",
364
- "window",
365
- "with",
214
+ * Minimal reserved-word set used by `fmtId`. Upstream's `fmtId` is much
215
+ * more conservative — any keyword needs quoting regardless of category.
216
+ * For our use case we only quote the keywords that show up as actual
217
+ * relation names; that's vanishingly rare in practice (CREATE VIEW
218
+ * "select" AS … is legal but no one does it). Keeping the set small
219
+ * avoids a large keyword table; the worst case is an un-needed
220
+ * double-quote pair, which is still valid SQL.
221
+ */
222
+ const RESERVED_WORDS = /* @__PURE__ */ new Set([
223
+ "all",
224
+ "analyse",
225
+ "analyze",
226
+ "and",
227
+ "any",
228
+ "array",
229
+ "as",
230
+ "asc",
231
+ "asymmetric",
232
+ "both",
233
+ "case",
234
+ "cast",
235
+ "check",
236
+ "collate",
237
+ "column",
238
+ "constraint",
239
+ "create",
240
+ "current_catalog",
241
+ "current_date",
242
+ "current_role",
243
+ "current_time",
244
+ "current_timestamp",
245
+ "current_user",
246
+ "default",
247
+ "deferrable",
248
+ "desc",
249
+ "distinct",
250
+ "do",
251
+ "else",
252
+ "end",
253
+ "except",
254
+ "false",
255
+ "fetch",
256
+ "for",
257
+ "foreign",
258
+ "from",
259
+ "grant",
260
+ "group",
261
+ "having",
262
+ "in",
263
+ "initially",
264
+ "intersect",
265
+ "into",
266
+ "lateral",
267
+ "leading",
268
+ "limit",
269
+ "localtime",
270
+ "localtimestamp",
271
+ "not",
272
+ "null",
273
+ "offset",
274
+ "on",
275
+ "only",
276
+ "or",
277
+ "order",
278
+ "placing",
279
+ "primary",
280
+ "references",
281
+ "returning",
282
+ "select",
283
+ "session_user",
284
+ "some",
285
+ "symmetric",
286
+ "table",
287
+ "then",
288
+ "to",
289
+ "trailing",
290
+ "true",
291
+ "union",
292
+ "unique",
293
+ "user",
294
+ "using",
295
+ "variadic",
296
+ "when",
297
+ "where",
298
+ "window",
299
+ "with"
366
300
  ]);
367
301
  /**
368
- * Re-build a `CREATE OR REPLACE VIEW <schema>.<name>[ WITH (opts)] AS
369
- * <body>[\n WITH <checkoption> CHECK OPTION]\n` definition the same way
370
- * upstream's `get_create_object_cmd(EditableView)` does. Returns either
371
- * the assembled text or a synthetic error when the relation isn't
372
- * actually a view.
373
- */
302
+ * Re-build a `CREATE OR REPLACE VIEW <schema>.<name>[ WITH (opts)] AS
303
+ * <body>[\n WITH <checkoption> CHECK OPTION]\n` definition the same way
304
+ * upstream's `get_create_object_cmd(EditableView)` does. Returns either
305
+ * the assembled text or a synthetic error when the relation isn't
306
+ * actually a view.
307
+ */
374
308
  const getViewCreateCmd = async (c, oid) => {
375
- const ver = c.serverVersion >= 90400 ? "modern" : "legacy";
376
- const sql = ver === "modern"
377
- ? `SELECT nspname, relname, relkind, ` +
378
- `pg_catalog.pg_get_viewdef(c.oid, true), ` +
379
- `pg_catalog.array_remove(pg_catalog.array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, ` +
380
- `CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text ` +
381
- `WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption ` +
382
- `FROM pg_catalog.pg_class c ` +
383
- `LEFT JOIN pg_catalog.pg_namespace n ` +
384
- `ON c.relnamespace = n.oid WHERE c.oid = ${oid}`
385
- : `SELECT nspname, relname, relkind, ` +
386
- `pg_catalog.pg_get_viewdef(c.oid, true), ` +
387
- `c.reloptions AS reloptions, ` +
388
- `NULL AS checkoption ` +
389
- `FROM pg_catalog.pg_class c ` +
390
- `LEFT JOIN pg_catalog.pg_namespace n ` +
391
- `ON c.relnamespace = n.oid WHERE c.oid = ${oid}`;
392
- let rs;
393
- try {
394
- rs = await c.query(sql, []);
395
- }
396
- catch (err) {
397
- return { ok: false, err };
398
- }
399
- if (rs.rows.length !== 1) {
400
- return { ok: false, err: new Error("view definition not found") };
401
- }
402
- const row = rs.rows[0];
403
- const nspname = cellToString(row[0]);
404
- const relname = cellToString(row[1]);
405
- const relkind = cellToString(row[2]);
406
- const viewdef = cellToString(row[3]);
407
- const reloptions = row[4]; // may be string ("{a=b,c=d}") or null
408
- const checkoption = cellToString(row[5]);
409
- if (relkind !== "v") {
410
- return {
411
- ok: false,
412
- err: new Error(`"${nspname}.${relname}" is not a view`),
413
- };
414
- }
415
- let out = "CREATE OR REPLACE VIEW ";
416
- out += `${fmtId(nspname)}.${fmtId(relname)}`;
417
- // reloptions: postgres returns it as a text-mode array literal like
418
- // `{foo=bar,baz=qux}`; we only need to detect non-empty (different
419
- // from the literal `{}`) and split entries on `,` outside quotes.
420
- const reloptStr = reloptions === null ? null : cellToString(reloptions);
421
- if (reloptStr !== null && reloptStr.length > 2) {
422
- out += "\n WITH (";
423
- out += renderReloptions(reloptStr);
424
- out += ")";
425
- }
426
- out += ` AS\n${viewdef}`;
427
- // Strip trailing semicolon from pg_get_viewdef.
428
- if (out.endsWith(";")) {
429
- out = out.slice(0, -1);
430
- }
431
- if (checkoption !== "") {
432
- out += `\n WITH ${checkoption} CHECK OPTION`;
433
- }
434
- if (!out.endsWith("\n"))
435
- out += "\n";
436
- return { ok: true, def: out };
309
+ const sql = (c.serverVersion >= 90400 ? "modern" : "legacy") === "modern" ? `SELECT nspname, relname, relkind, pg_catalog.pg_get_viewdef(c.oid, true), pg_catalog.array_remove(pg_catalog.array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.oid = ${oid}` : `SELECT nspname, relname, relkind, pg_catalog.pg_get_viewdef(c.oid, true), c.reloptions AS reloptions, NULL AS checkoption FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON c.relnamespace = n.oid WHERE c.oid = ${oid}`;
310
+ let rs;
311
+ try {
312
+ rs = await c.query(sql, []);
313
+ } catch (err) {
314
+ return {
315
+ ok: false,
316
+ err
317
+ };
318
+ }
319
+ if (rs.rows.length !== 1) return {
320
+ ok: false,
321
+ err: /* @__PURE__ */ new Error("view definition not found")
322
+ };
323
+ const row = rs.rows[0];
324
+ const nspname = cellToString(row[0]);
325
+ const relname = cellToString(row[1]);
326
+ const relkind = cellToString(row[2]);
327
+ const viewdef = cellToString(row[3]);
328
+ const reloptions = row[4];
329
+ const checkoption = cellToString(row[5]);
330
+ if (relkind !== "v") return {
331
+ ok: false,
332
+ err: /* @__PURE__ */ new Error(`"${nspname}.${relname}" is not a view`)
333
+ };
334
+ let out = "CREATE OR REPLACE VIEW ";
335
+ out += `${fmtId(nspname)}.${fmtId(relname)}`;
336
+ const reloptStr = reloptions === null ? null : cellToString(reloptions);
337
+ if (reloptStr !== null && reloptStr.length > 2) {
338
+ out += "\n WITH (";
339
+ out += renderReloptions(reloptStr);
340
+ out += ")";
341
+ }
342
+ out += ` AS\n${viewdef}`;
343
+ if (out.endsWith(";")) out = out.slice(0, -1);
344
+ if (checkoption !== "") out += `\n WITH ${checkoption} CHECK OPTION`;
345
+ if (!out.endsWith("\n")) out += "\n";
346
+ return {
347
+ ok: true,
348
+ def: out
349
+ };
437
350
  };
438
351
  /**
439
- * Render a Postgres text-mode array literal of `key=value` reloption
440
- * entries (e.g. `{security_barrier=true,security_invoker=false}`) into
441
- * the comma-separated `key=value, key2=value2` form upstream emits
442
- * inside the `WITH (…)` clause.
443
- *
444
- * Mirrors `appendReloptionsArray`'s output behaviour for the limited
445
- * surface relevant to views (no per-namespace options, no embedded
446
- * quotes). For any value that contains characters that would need
447
- * escaping in SQL — anything other than `[A-Za-z0-9_.\-]` — we render
448
- * it as a quoted string literal, matching upstream's `appendStringLiteral`
449
- * fallback.
450
- */
352
+ * Render a Postgres text-mode array literal of `key=value` reloption
353
+ * entries (e.g. `{security_barrier=true,security_invoker=false}`) into
354
+ * the comma-separated `key=value, key2=value2` form upstream emits
355
+ * inside the `WITH (…)` clause.
356
+ *
357
+ * Mirrors `appendReloptionsArray`'s output behaviour for the limited
358
+ * surface relevant to views (no per-namespace options, no embedded
359
+ * quotes). For any value that contains characters that would need
360
+ * escaping in SQL — anything other than `[A-Za-z0-9_.\-]` — we render
361
+ * it as a quoted string literal, matching upstream's `appendStringLiteral`
362
+ * fallback.
363
+ */
451
364
  const renderReloptions = (literal) => {
452
- // Strip surrounding `{}`.
453
- if (!literal.startsWith("{") || !literal.endsWith("}")) {
454
- return literal;
455
- }
456
- const inside = literal.slice(1, -1);
457
- if (inside.length === 0)
458
- return "";
459
- // Postgres array literals quote individual elements with `"…"` when
460
- // they contain commas or special chars. For reloptions on a view the
461
- // values are typically bare `key=value` strings, but we still need to
462
- // tolerate the quoted form.
463
- const entries = splitArrayElems(inside);
464
- const out = [];
465
- for (let entry of entries) {
466
- // unquote double-quoted entries
467
- if (entry.startsWith('"') && entry.endsWith('"')) {
468
- entry = entry
469
- .slice(1, -1)
470
- .replace(/\\"/g, '"')
471
- .replace(/\\\\/g, "\\");
472
- }
473
- const eq = entry.indexOf("=");
474
- if (eq < 0) {
475
- out.push(entry);
476
- continue;
477
- }
478
- const key = entry.slice(0, eq);
479
- const value = entry.slice(eq + 1);
480
- if (/^[A-Za-z0-9_.-]+$/.test(value)) {
481
- out.push(`${key}=${value}`);
482
- }
483
- else {
484
- // Quote the value as a SQL string literal.
485
- out.push(`${key}='${value.replace(/'/g, "''")}'`);
486
- }
487
- }
488
- return out.join(", ");
365
+ if (!literal.startsWith("{") || !literal.endsWith("}")) return literal;
366
+ const inside = literal.slice(1, -1);
367
+ if (inside.length === 0) return "";
368
+ const entries = splitArrayElems(inside);
369
+ const out = [];
370
+ for (let entry of entries) {
371
+ if (entry.startsWith("\"") && entry.endsWith("\"")) entry = entry.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
372
+ const eq = entry.indexOf("=");
373
+ if (eq < 0) {
374
+ out.push(entry);
375
+ continue;
376
+ }
377
+ const key = entry.slice(0, eq);
378
+ const value = entry.slice(eq + 1);
379
+ if (/^[A-Za-z0-9_.-]+$/.test(value)) out.push(`${key}=${value}`);
380
+ else out.push(`${key}='${value.replace(/'/g, "''")}'`);
381
+ }
382
+ return out.join(", ");
489
383
  };
490
384
  /** Split a Postgres text-mode array's inner content on top-level commas. */
491
385
  const splitArrayElems = (s) => {
492
- const out = [];
493
- let i = 0;
494
- let cur = "";
495
- let inQuote = false;
496
- while (i < s.length) {
497
- const ch = s[i];
498
- if (ch === "\\" && i + 1 < s.length) {
499
- cur += s[i] + s[i + 1];
500
- i += 2;
501
- continue;
502
- }
503
- if (ch === '"') {
504
- inQuote = !inQuote;
505
- cur += ch;
506
- i++;
507
- continue;
508
- }
509
- if (ch === "," && !inQuote) {
510
- out.push(cur);
511
- cur = "";
512
- i++;
513
- continue;
514
- }
515
- cur += ch;
516
- i++;
517
- }
518
- if (cur.length > 0 || s.endsWith(","))
519
- out.push(cur);
520
- return out;
386
+ const out = [];
387
+ let i = 0;
388
+ let cur = "";
389
+ let inQuote = false;
390
+ while (i < s.length) {
391
+ const ch = s[i];
392
+ if (ch === "\\" && i + 1 < s.length) {
393
+ cur += s[i] + s[i + 1];
394
+ i += 2;
395
+ continue;
396
+ }
397
+ if (ch === "\"") {
398
+ inQuote = !inQuote;
399
+ cur += ch;
400
+ i++;
401
+ continue;
402
+ }
403
+ if (ch === "," && !inQuote) {
404
+ out.push(cur);
405
+ cur = "";
406
+ i++;
407
+ continue;
408
+ }
409
+ cur += ch;
410
+ i++;
411
+ }
412
+ if (cur.length > 0 || s.endsWith(",")) out.push(cur);
413
+ return out;
521
414
  };
522
415
  /**
523
- * Print `buf` with line numbers in the upstream format:
524
- *
525
- * - For functions (`isFunc=true`): scan for the first line whose first
526
- * three / six / seven bytes are `AS `, `BEGIN `, or `RETURN ` and
527
- * treat that as the start of the body. Header lines (before the
528
- * marker) render as ` <line>\n`; body lines render as
529
- * `<lineno><6 spaces> <line>\n` (`%-7d %s\n`), with `lineno` starting
530
- * at 1 on the marker line.
531
- * - For views (`isFunc=false`): everything is body; `lineno` starts at
532
- * 1 and increments per output line.
533
- */
416
+ * Print `buf` with line numbers in the upstream format:
417
+ *
418
+ * - For functions (`isFunc=true`): scan for the first line whose first
419
+ * three / six / seven bytes are `AS `, `BEGIN `, or `RETURN ` and
420
+ * treat that as the start of the body. Header lines (before the
421
+ * marker) render as ` <line>\n`; body lines render as
422
+ * `<lineno><6 spaces> <line>\n` (`%-7d %s\n`), with `lineno` starting
423
+ * at 1 on the marker line.
424
+ * - For views (`isFunc=false`): everything is body; `lineno` starts at
425
+ * 1 and increments per output line.
426
+ */
534
427
  const writeWithLineNumbers = (buf, isFunc, out) => {
535
- let inHeader = isFunc;
536
- let lineno = 0;
537
- let i = 0;
538
- while (i < buf.length) {
539
- // Find end-of-line.
540
- const eol = buf.indexOf("\n", i);
541
- const line = eol === -1 ? buf.slice(i) : buf.slice(i, eol);
542
- if (inHeader &&
543
- (line.startsWith("AS ") ||
544
- line.startsWith("BEGIN ") ||
545
- line.startsWith("RETURN "))) {
546
- inHeader = false;
547
- }
548
- if (!inHeader)
549
- lineno++;
550
- if (inHeader) {
551
- out(` ${line}\n`);
552
- }
553
- else {
554
- // %-7d → left-justified, padded to 7. Then literal space, then line.
555
- const numStr = String(lineno);
556
- const pad = numStr.length >= 7 ? "" : " ".repeat(7 - numStr.length);
557
- out(`${numStr}${pad} ${line}\n`);
558
- }
559
- if (eol === -1)
560
- break;
561
- i = eol + 1;
562
- }
428
+ let inHeader = isFunc;
429
+ let lineno = 0;
430
+ let i = 0;
431
+ while (i < buf.length) {
432
+ const eol = buf.indexOf("\n", i);
433
+ const line = eol === -1 ? buf.slice(i) : buf.slice(i, eol);
434
+ if (inHeader && (line.startsWith("AS ") || line.startsWith("BEGIN ") || line.startsWith("RETURN "))) inHeader = false;
435
+ if (!inHeader) lineno++;
436
+ if (inHeader) out(` ${line}\n`);
437
+ else {
438
+ const numStr = String(lineno);
439
+ out(`${numStr}${numStr.length >= 7 ? "" : " ".repeat(7 - numStr.length)} ${line}\n`);
440
+ }
441
+ if (eol === -1) break;
442
+ i = eol + 1;
443
+ }
563
444
  };
564
445
  /** Stream the definition (with or without line numbers) to stdout. */
565
446
  const emitDefinition = (def, plus, isFunc) => {
566
- if (plus) {
567
- writeWithLineNumbers(def, isFunc, writeOut);
568
- }
569
- else {
570
- writeOut(def);
571
- }
447
+ if (plus) writeWithLineNumbers(def, isFunc, writeOut);
448
+ else writeOut(def);
572
449
  };
573
- // ---------------------------------------------------------------------------
574
- // Shared core: \sf / \ef (function) and \sv / \ev (view).
575
- // ---------------------------------------------------------------------------
576
450
  /**
577
- * Resolve a function/view definition and dump it to stdout. Used by
578
- * both the show forms (`\sf` / `\sv`) and the edit forms (`\ef` / `\ev`)
579
- * when the user supplies a name.
580
- */
451
+ * Resolve a function/view definition and dump it to stdout. Used by
452
+ * both the show forms (`\sf` / `\sv`) and the edit forms (`\ef` / `\ev`)
453
+ * when the user supplies a name.
454
+ */
581
455
  const runShowFunction = async (ctx, cmdName, base) => {
582
- const c = conn(ctx);
583
- if (!c)
584
- return noConn(ctx);
585
- const { plus } = decodeShowSuffix(cmdName, base);
586
- const desc = readObjDesc(ctx);
587
- if (desc === null) {
588
- return errResult(ctx, "function name is required");
589
- }
590
- const oidLookup = await lookupFunctionOid(c, desc);
591
- if (!oidLookup.ok)
592
- return queryErrResult(ctx, oidLookup.err);
593
- const defLookup = await getFunctionCreateCmd(c, oidLookup.oid);
594
- if (!defLookup.ok)
595
- return queryErrResult(ctx, defLookup.err);
596
- emitDefinition(defLookup.def, plus, /*isFunc=*/ true);
597
- return { status: "ok" };
456
+ const c = conn(ctx);
457
+ if (!c) return noConn(ctx);
458
+ const { plus } = decodeShowSuffix(cmdName, base);
459
+ const desc = readObjDesc(ctx);
460
+ if (desc === null) return errResult(ctx, "function name is required");
461
+ const oidLookup = await lookupFunctionOid(c, desc);
462
+ if (!oidLookup.ok) return queryErrResult(ctx, oidLookup.err);
463
+ const defLookup = await getFunctionCreateCmd(c, oidLookup.oid);
464
+ if (!defLookup.ok) return queryErrResult(ctx, defLookup.err);
465
+ emitDefinition(defLookup.def, plus, true);
466
+ return { status: "ok" };
598
467
  };
599
468
  const runShowView = async (ctx, cmdName, base) => {
600
- const c = conn(ctx);
601
- if (!c)
602
- return noConn(ctx);
603
- const { plus } = decodeShowSuffix(cmdName, base);
604
- const desc = readObjDesc(ctx);
605
- if (desc === null) {
606
- return errResult(ctx, "view name is required");
607
- }
608
- const oidLookup = await lookupRelationOid(c, desc);
609
- if (!oidLookup.ok)
610
- return queryErrResult(ctx, oidLookup.err);
611
- const defLookup = await getViewCreateCmd(c, oidLookup.oid);
612
- if (!defLookup.ok)
613
- return queryErrResult(ctx, defLookup.err);
614
- emitDefinition(defLookup.def, plus, /*isFunc=*/ false);
615
- return { status: "ok" };
469
+ const c = conn(ctx);
470
+ if (!c) return noConn(ctx);
471
+ const { plus } = decodeShowSuffix(cmdName, base);
472
+ const desc = readObjDesc(ctx);
473
+ if (desc === null) return errResult(ctx, "view name is required");
474
+ const oidLookup = await lookupRelationOid(c, desc);
475
+ if (!oidLookup.ok) return queryErrResult(ctx, oidLookup.err);
476
+ const defLookup = await getViewCreateCmd(c, oidLookup.oid);
477
+ if (!defLookup.ok) return queryErrResult(ctx, defLookup.err);
478
+ emitDefinition(defLookup.def, plus, false);
479
+ return { status: "ok" };
616
480
  };
617
- // ---------------------------------------------------------------------------
618
- // BackslashCmdSpec exports
619
- // ---------------------------------------------------------------------------
620
481
  /** `\sf [+] FUNCNAME` — show function source. */
621
- export const cmdShowFunction = {
622
- name: "sf",
623
- argMode: "whole-line",
624
- helpKey: "sf",
625
- run: (ctx) => runShowFunction(ctx, ctx.cmdName, "sf"),
482
+ const cmdShowFunction = {
483
+ name: "sf",
484
+ argMode: "whole-line",
485
+ helpKey: "sf",
486
+ run: (ctx) => runShowFunction(ctx, ctx.cmdName, "sf")
626
487
  };
627
488
  /** `\sf+ FUNCNAME` — show function source with line numbers. */
628
- export const cmdShowFunctionPlus = {
629
- name: "sf+",
630
- argMode: "whole-line",
631
- helpKey: "sf",
632
- run: (ctx) => runShowFunction(ctx, ctx.cmdName, "sf"),
489
+ const cmdShowFunctionPlus = {
490
+ name: "sf+",
491
+ argMode: "whole-line",
492
+ helpKey: "sf",
493
+ run: (ctx) => runShowFunction(ctx, ctx.cmdName, "sf")
633
494
  };
634
495
  /** `\sv [+] VIEWNAME` — show view source. */
635
- export const cmdShowView = {
636
- name: "sv",
637
- argMode: "whole-line",
638
- helpKey: "sv",
639
- run: (ctx) => runShowView(ctx, ctx.cmdName, "sv"),
496
+ const cmdShowView = {
497
+ name: "sv",
498
+ argMode: "whole-line",
499
+ helpKey: "sv",
500
+ run: (ctx) => runShowView(ctx, ctx.cmdName, "sv")
640
501
  };
641
502
  /** `\sv+ VIEWNAME` — show view source with line numbers. */
642
- export const cmdShowViewPlus = {
643
- name: "sv+",
644
- argMode: "whole-line",
645
- helpKey: "sv",
646
- run: (ctx) => runShowView(ctx, ctx.cmdName, "sv"),
503
+ const cmdShowViewPlus = {
504
+ name: "sv+",
505
+ argMode: "whole-line",
506
+ helpKey: "sv",
507
+ run: (ctx) => runShowView(ctx, ctx.cmdName, "sv")
647
508
  };
648
509
  /**
649
- * `\ef [+] [FUNCNAME [LINE]]` — upstream opens `$EDITOR` on the function's
650
- * source. We don't implement editor invocation; when a name is supplied we
651
- * route through the same fetch+print path as `\sf` (with a stripped line
652
- * number — the trailing LINE argument is ignored). Without a name we error
653
- * with a hint pointing at `\sf`.
654
- */
655
- export const cmdEditFunction = {
656
- name: "ef",
657
- argMode: "whole-line",
658
- helpKey: "ef",
659
- async run(ctx) {
660
- const c = conn(ctx);
661
- if (!c)
662
- return noConn(ctx);
663
- const { plus } = decodeShowSuffix(ctx.cmdName, "ef");
664
- const desc = readObjDesc(ctx);
665
- if (desc === null) {
666
- return errResult(ctx, "editing not supported in embedded psql; supply a name to display the source");
667
- }
668
- // Strip a possible trailing LINE number (upstream behaviour for \ef
669
- // FUNCNAME LINE — the editor opens at that line; we just discard it).
670
- const objDesc = stripTrailingLine(desc);
671
- const oidLookup = await lookupFunctionOid(c, objDesc);
672
- if (!oidLookup.ok)
673
- return queryErrResult(ctx, oidLookup.err);
674
- const defLookup = await getFunctionCreateCmd(c, oidLookup.oid);
675
- if (!defLookup.ok)
676
- return queryErrResult(ctx, defLookup.err);
677
- emitDefinition(defLookup.def, plus, /*isFunc=*/ true);
678
- return { status: "ok" };
679
- },
510
+ * `\ef [+] [FUNCNAME [LINE]]` — upstream opens `$EDITOR` on the function's
511
+ * source. We don't implement editor invocation; when a name is supplied we
512
+ * route through the same fetch+print path as `\sf` (with a stripped line
513
+ * number — the trailing LINE argument is ignored). Without a name we error
514
+ * with a hint pointing at `\sf`.
515
+ */
516
+ const cmdEditFunction = {
517
+ name: "ef",
518
+ argMode: "whole-line",
519
+ helpKey: "ef",
520
+ async run(ctx) {
521
+ const c = conn(ctx);
522
+ if (!c) return noConn(ctx);
523
+ const { plus } = decodeShowSuffix(ctx.cmdName, "ef");
524
+ const desc = readObjDesc(ctx);
525
+ if (desc === null) return errResult(ctx, "editing not supported in embedded psql; supply a name to display the source");
526
+ const objDesc = stripTrailingLine(desc);
527
+ const oidLookup = await lookupFunctionOid(c, objDesc);
528
+ if (!oidLookup.ok) return queryErrResult(ctx, oidLookup.err);
529
+ const defLookup = await getFunctionCreateCmd(c, oidLookup.oid);
530
+ if (!defLookup.ok) return queryErrResult(ctx, defLookup.err);
531
+ emitDefinition(defLookup.def, plus, true);
532
+ return { status: "ok" };
533
+ }
680
534
  };
681
535
  /**
682
- * `\ev [+] [VIEWNAME [LINE]]` — same contract as `\ef` but for views.
683
- */
684
- export const cmdEditView = {
685
- name: "ev",
686
- argMode: "whole-line",
687
- helpKey: "ev",
688
- async run(ctx) {
689
- const c = conn(ctx);
690
- if (!c)
691
- return noConn(ctx);
692
- const { plus } = decodeShowSuffix(ctx.cmdName, "ev");
693
- const desc = readObjDesc(ctx);
694
- if (desc === null) {
695
- return errResult(ctx, "editing not supported in embedded psql; supply a name to display the source");
696
- }
697
- const objDesc = stripTrailingLine(desc);
698
- const oidLookup = await lookupRelationOid(c, objDesc);
699
- if (!oidLookup.ok)
700
- return queryErrResult(ctx, oidLookup.err);
701
- const defLookup = await getViewCreateCmd(c, oidLookup.oid);
702
- if (!defLookup.ok)
703
- return queryErrResult(ctx, defLookup.err);
704
- emitDefinition(defLookup.def, plus, /*isFunc=*/ false);
705
- return { status: "ok" };
706
- },
536
+ * `\ev [+] [VIEWNAME [LINE]]` — same contract as `\ef` but for views.
537
+ */
538
+ const cmdEditView = {
539
+ name: "ev",
540
+ argMode: "whole-line",
541
+ helpKey: "ev",
542
+ async run(ctx) {
543
+ const c = conn(ctx);
544
+ if (!c) return noConn(ctx);
545
+ const { plus } = decodeShowSuffix(ctx.cmdName, "ev");
546
+ const desc = readObjDesc(ctx);
547
+ if (desc === null) return errResult(ctx, "editing not supported in embedded psql; supply a name to display the source");
548
+ const objDesc = stripTrailingLine(desc);
549
+ const oidLookup = await lookupRelationOid(c, objDesc);
550
+ if (!oidLookup.ok) return queryErrResult(ctx, oidLookup.err);
551
+ const defLookup = await getViewCreateCmd(c, oidLookup.oid);
552
+ if (!defLookup.ok) return queryErrResult(ctx, defLookup.err);
553
+ emitDefinition(defLookup.def, plus, false);
554
+ return { status: "ok" };
555
+ }
707
556
  };
708
557
  /**
709
- * Aliases so that `\ef+` and `\ev+` resolve to the corresponding command;
710
- * we register the plus variants explicitly because the registry is keyed
711
- * by full name.
712
- */
713
- export const cmdEditFunctionPlus = {
714
- ...cmdEditFunction,
715
- name: "ef+",
558
+ * Aliases so that `\ef+` and `\ev+` resolve to the corresponding command;
559
+ * we register the plus variants explicitly because the registry is keyed
560
+ * by full name.
561
+ */
562
+ const cmdEditFunctionPlus = {
563
+ ...cmdEditFunction,
564
+ name: "ef+"
716
565
  };
717
- export const cmdEditViewPlus = {
718
- ...cmdEditView,
719
- name: "ev+",
566
+ const cmdEditViewPlus = {
567
+ ...cmdEditView,
568
+ name: "ev+"
720
569
  };
721
570
  /**
722
- * Strip a trailing LINE number from an object descriptor, matching
723
- * upstream's `strip_lineno_from_objdesc`. We rebuild the simpler subset
724
- * here because the slash-arg scanner already handed us a single trimmed
725
- * whole-line string: if it ends with `<digits>` separated from the
726
- * preceding name by whitespace or `)`, strip the digits.
727
- *
728
- * Returns the descriptor with any trailing line number removed. Invalid
729
- * line numbers (zero) are not detected here — we treat them the same as
730
- * "no line number" because the LINE arg is meaningless to our
731
- * show-only impl.
732
- */
571
+ * Strip a trailing LINE number from an object descriptor, matching
572
+ * upstream's `strip_lineno_from_objdesc`. We rebuild the simpler subset
573
+ * here because the slash-arg scanner already handed us a single trimmed
574
+ * whole-line string: if it ends with `<digits>` separated from the
575
+ * preceding name by whitespace or `)`, strip the digits.
576
+ *
577
+ * Returns the descriptor with any trailing line number removed. Invalid
578
+ * line numbers (zero) are not detected here — we treat them the same as
579
+ * "no line number" because the LINE arg is meaningless to our
580
+ * show-only impl.
581
+ */
733
582
  const stripTrailingLine = (desc) => {
734
- let i = desc.length - 1;
735
- while (i > 0 && /\s/.test(desc[i]))
736
- i--;
737
- if (i <= 0 || !/[0-9]/.test(desc[i]))
738
- return desc;
739
- while (i > 0 && /[0-9]/.test(desc[i]))
740
- i--;
741
- // The char before the digit run must be whitespace or `)` and not the
742
- // very first char.
743
- if (i <= 0)
744
- return desc;
745
- const sep = desc[i];
746
- if (!(/\s/.test(sep) || sep === ")"))
747
- return desc;
748
- return desc.slice(0, i + 1).trimEnd();
583
+ let i = desc.length - 1;
584
+ while (i > 0 && /\s/.test(desc[i])) i--;
585
+ if (i <= 0 || !/[0-9]/.test(desc[i])) return desc;
586
+ while (i > 0 && /[0-9]/.test(desc[i])) i--;
587
+ if (i <= 0) return desc;
588
+ const sep = desc[i];
589
+ if (!(/\s/.test(sep) || sep === ")")) return desc;
590
+ return desc.slice(0, i + 1).trimEnd();
749
591
  };
750
- // ---------------------------------------------------------------------------
751
- // Registration
752
- // ---------------------------------------------------------------------------
753
592
  /**
754
- * Register `\sf`, `\sf+`, `\sv`, `\sv+`, `\ef`, `\ef+`, `\ev`, `\ev+` on
755
- * the supplied registry. Wired into `defaultRegistry()` from `dispatch.ts`.
756
- */
757
- export const registerShowCommands = (registry) => {
758
- registry.register(cmdShowFunction);
759
- registry.register(cmdShowFunctionPlus);
760
- registry.register(cmdShowView);
761
- registry.register(cmdShowViewPlus);
762
- registry.register(cmdEditFunction);
763
- registry.register(cmdEditFunctionPlus);
764
- registry.register(cmdEditView);
765
- registry.register(cmdEditViewPlus);
593
+ * Register `\sf`, `\sf+`, `\sv`, `\sv+`, `\ef`, `\ef+`, `\ev`, `\ev+` on
594
+ * the supplied registry. Wired into `defaultRegistry()` from `dispatch.ts`.
595
+ */
596
+ const registerShowCommands = (registry) => {
597
+ registry.register(cmdShowFunction);
598
+ registry.register(cmdShowFunctionPlus);
599
+ registry.register(cmdShowView);
600
+ registry.register(cmdShowViewPlus);
601
+ registry.register(cmdEditFunction);
602
+ registry.register(cmdEditFunctionPlus);
603
+ registry.register(cmdEditView);
604
+ registry.register(cmdEditViewPlus);
766
605
  };
606
+ //#endregion
607
+ export { cmdEditFunction, cmdEditFunctionPlus, cmdEditView, cmdEditViewPlus, cmdShowFunction, cmdShowFunctionPlus, cmdShowView, cmdShowViewPlus, registerShowCommands };