neonctl 2.27.1 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -3
- package/dist/analytics.js +52 -34
- package/dist/api.js +643 -13
- package/dist/auth.js +50 -44
- package/dist/cli.js +8 -1
- package/dist/commands/auth.js +64 -51
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +160 -150
- package/dist/commands/bucket.js +183 -146
- package/dist/commands/checkout.js +51 -51
- package/dist/commands/config.js +228 -82
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +100 -101
- package/dist/commands/databases.js +29 -26
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +101 -104
- package/dist/commands/index.js +27 -25
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +232 -182
- package/dist/commands/neon_auth.js +385 -370
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +103 -101
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +27 -24
- package/dist/commands/schema_diff.js +25 -26
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +40 -0
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +37 -14
- package/dist/current_branch_fast_path.js +55 -0
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +68 -5
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +110 -107
- package/dist/log.js +2 -2
- package/dist/parameters.gen.js +14 -14
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +22 -23
- package/dist/test_utils/fixtures.js +74 -41
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +33 -0
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +28 -16
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +10 -12
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* the line and trim, so `\sf myschema.foo ` round-trips cleanly without
|
|
36
36
|
* splitting on the dot or whitespace inside parens.
|
|
37
37
|
*/
|
|
38
|
-
import { writeErr, writeOut } from
|
|
38
|
+
import { writeErr, writeOut } from "./shared.js";
|
|
39
39
|
// ---------------------------------------------------------------------------
|
|
40
40
|
// Helpers
|
|
41
41
|
// ---------------------------------------------------------------------------
|
|
@@ -48,7 +48,7 @@ import { writeErr, writeOut } from './shared.js';
|
|
|
48
48
|
const errResult = (ctx, message) => {
|
|
49
49
|
ctx.settings.lastErrorResult = { message };
|
|
50
50
|
writeErr(`\\${ctx.cmdName}: ${message}\n`);
|
|
51
|
-
return { status:
|
|
51
|
+
return { status: "error", errorWritten: true };
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
54
54
|
* Format a server error for stderr the way upstream's
|
|
@@ -57,10 +57,11 @@ const errResult = (ctx, message) => {
|
|
|
57
57
|
* message fields (e.g. a wire-layer rejection).
|
|
58
58
|
*/
|
|
59
59
|
const formatServerError = (err) => {
|
|
60
|
-
if (err && typeof err ===
|
|
60
|
+
if (err && typeof err === "object") {
|
|
61
61
|
const e = err;
|
|
62
|
-
const sev = e.severity ??
|
|
63
|
-
const msg = e.message ??
|
|
62
|
+
const sev = e.severity ?? "ERROR";
|
|
63
|
+
const msg = e.message ??
|
|
64
|
+
(err instanceof Error ? err.message : safeToString(err));
|
|
64
65
|
return `${sev}: ${msg}`;
|
|
65
66
|
}
|
|
66
67
|
if (err instanceof Error)
|
|
@@ -75,14 +76,14 @@ const formatServerError = (err) => {
|
|
|
75
76
|
*/
|
|
76
77
|
const safeToString = (v) => {
|
|
77
78
|
if (v === null)
|
|
78
|
-
return
|
|
79
|
+
return "null";
|
|
79
80
|
if (v === undefined)
|
|
80
|
-
return
|
|
81
|
-
if (typeof v ===
|
|
81
|
+
return "undefined";
|
|
82
|
+
if (typeof v === "string")
|
|
82
83
|
return v;
|
|
83
|
-
if (typeof v ===
|
|
84
|
-
typeof v ===
|
|
85
|
-
typeof v ===
|
|
84
|
+
if (typeof v === "number" ||
|
|
85
|
+
typeof v === "boolean" ||
|
|
86
|
+
typeof v === "bigint") {
|
|
86
87
|
return String(v);
|
|
87
88
|
}
|
|
88
89
|
try {
|
|
@@ -101,17 +102,19 @@ const safeToString = (v) => {
|
|
|
101
102
|
const queryErrResult = (ctx, err) => {
|
|
102
103
|
const line = formatServerError(err);
|
|
103
104
|
ctx.settings.lastErrorResult = {
|
|
104
|
-
message: err &&
|
|
105
|
+
message: err &&
|
|
106
|
+
typeof err === "object" &&
|
|
107
|
+
err.message
|
|
105
108
|
? err.message
|
|
106
109
|
: err instanceof Error
|
|
107
110
|
? err.message
|
|
108
111
|
: safeToString(err),
|
|
109
112
|
};
|
|
110
113
|
writeErr(`${line}\n`);
|
|
111
|
-
return { status:
|
|
114
|
+
return { status: "error", errorWritten: true };
|
|
112
115
|
};
|
|
113
116
|
const conn = (ctx) => ctx.settings.db;
|
|
114
|
-
const noConn = (ctx) => errResult(ctx,
|
|
117
|
+
const noConn = (ctx) => errResult(ctx, "no connection to the server");
|
|
115
118
|
/**
|
|
116
119
|
* Read the object descriptor as a whole-line argument with surrounding
|
|
117
120
|
* whitespace AND trailing semicolons stripped. Returns `null` when no
|
|
@@ -129,7 +132,7 @@ const readObjDesc = (ctx) => {
|
|
|
129
132
|
const raw = ctx.restOfLine();
|
|
130
133
|
// Strip trailing whitespace and `;` (in any order, any count) so
|
|
131
134
|
// `\sf foo(arg) ;; ` round-trips like vanilla psql.
|
|
132
|
-
const trimmed = raw.replace(/[\s;]+$/,
|
|
135
|
+
const trimmed = raw.replace(/[\s;]+$/, "").trimStart();
|
|
133
136
|
return trimmed.length === 0 ? null : trimmed;
|
|
134
137
|
};
|
|
135
138
|
/**
|
|
@@ -139,7 +142,7 @@ const readObjDesc = (ctx) => {
|
|
|
139
142
|
*/
|
|
140
143
|
const decodeShowSuffix = (cmdName, base) => {
|
|
141
144
|
const tail = cmdName.slice(base.length);
|
|
142
|
-
return { plus: tail.includes(
|
|
145
|
+
return { plus: tail.includes("+") };
|
|
143
146
|
};
|
|
144
147
|
/**
|
|
145
148
|
* Look up a function OID from `desc`. Mirrors upstream's
|
|
@@ -154,12 +157,15 @@ const decodeShowSuffix = (cmdName, base) => {
|
|
|
154
157
|
* `escapeLiteral` to mirror libpq's `appendStringLiteralConn`.
|
|
155
158
|
*/
|
|
156
159
|
const lookupFunctionOid = async (c, desc) => {
|
|
157
|
-
const cast = desc.includes(
|
|
160
|
+
const cast = desc.includes("(") ? "regprocedure" : "regproc";
|
|
158
161
|
const sql = `SELECT ${c.escapeLiteral(desc)}::pg_catalog.${cast}::pg_catalog.oid`;
|
|
159
162
|
try {
|
|
160
163
|
const rs = await c.query(sql, []);
|
|
161
164
|
if (rs.rows.length !== 1 || rs.rows[0][0] === null) {
|
|
162
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
err: new Error("object lookup returned no rows"),
|
|
168
|
+
};
|
|
163
169
|
}
|
|
164
170
|
const raw = cellToString(rs.rows[0][0]);
|
|
165
171
|
const oid = Number(raw);
|
|
@@ -186,7 +192,10 @@ const lookupRelationOid = async (c, desc) => {
|
|
|
186
192
|
try {
|
|
187
193
|
const rs = await c.query(sql, []);
|
|
188
194
|
if (rs.rows.length !== 1 || rs.rows[0][0] === null) {
|
|
189
|
-
return {
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
err: new Error("object lookup returned no rows"),
|
|
198
|
+
};
|
|
190
199
|
}
|
|
191
200
|
const raw = cellToString(rs.rows[0][0]);
|
|
192
201
|
const oid = Number(raw);
|
|
@@ -209,24 +218,24 @@ const lookupRelationOid = async (c, desc) => {
|
|
|
209
218
|
*/
|
|
210
219
|
const cellToString = (v) => {
|
|
211
220
|
if (v === null || v === undefined)
|
|
212
|
-
return
|
|
213
|
-
if (typeof v ===
|
|
221
|
+
return "";
|
|
222
|
+
if (typeof v === "string")
|
|
214
223
|
return v;
|
|
215
224
|
if (Buffer.isBuffer(v))
|
|
216
|
-
return v.toString(
|
|
217
|
-
if (typeof v ===
|
|
218
|
-
typeof v ===
|
|
219
|
-
typeof v ===
|
|
225
|
+
return v.toString("utf-8");
|
|
226
|
+
if (typeof v === "number" ||
|
|
227
|
+
typeof v === "boolean" ||
|
|
228
|
+
typeof v === "bigint") {
|
|
220
229
|
return String(v);
|
|
221
230
|
}
|
|
222
231
|
// Non-primitive fallback: encode JSON so we never emit a stray
|
|
223
232
|
// `[object Object]`. The wire layer hands us strings or nulls in
|
|
224
233
|
// practice, so this branch is defensive only.
|
|
225
234
|
try {
|
|
226
|
-
return JSON.stringify(v) ??
|
|
235
|
+
return JSON.stringify(v) ?? "";
|
|
227
236
|
}
|
|
228
237
|
catch {
|
|
229
|
-
return
|
|
238
|
+
return "";
|
|
230
239
|
}
|
|
231
240
|
};
|
|
232
241
|
/**
|
|
@@ -240,11 +249,14 @@ const getFunctionCreateCmd = async (c, oid) => {
|
|
|
240
249
|
try {
|
|
241
250
|
const rs = await c.query(sql, []);
|
|
242
251
|
if (rs.rows.length !== 1) {
|
|
243
|
-
return {
|
|
252
|
+
return {
|
|
253
|
+
ok: false,
|
|
254
|
+
err: new Error("function definition not found"),
|
|
255
|
+
};
|
|
244
256
|
}
|
|
245
257
|
let def = cellToString(rs.rows[0][0]);
|
|
246
|
-
if (def.length > 0 && !def.endsWith(
|
|
247
|
-
def +=
|
|
258
|
+
if (def.length > 0 && !def.endsWith("\n"))
|
|
259
|
+
def += "\n";
|
|
248
260
|
return { ok: true, def };
|
|
249
261
|
}
|
|
250
262
|
catch (err) {
|
|
@@ -274,83 +286,83 @@ const fmtId = (ident) => {
|
|
|
274
286
|
* double-quote pair, which is still valid SQL.
|
|
275
287
|
*/
|
|
276
288
|
const RESERVED_WORDS = new Set([
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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",
|
|
354
366
|
]);
|
|
355
367
|
/**
|
|
356
368
|
* Re-build a `CREATE OR REPLACE VIEW <schema>.<name>[ WITH (opts)] AS
|
|
@@ -360,8 +372,8 @@ const RESERVED_WORDS = new Set([
|
|
|
360
372
|
* actually a view.
|
|
361
373
|
*/
|
|
362
374
|
const getViewCreateCmd = async (c, oid) => {
|
|
363
|
-
const ver = c.serverVersion >= 90400 ?
|
|
364
|
-
const sql = ver ===
|
|
375
|
+
const ver = c.serverVersion >= 90400 ? "modern" : "legacy";
|
|
376
|
+
const sql = ver === "modern"
|
|
365
377
|
? `SELECT nspname, relname, relkind, ` +
|
|
366
378
|
`pg_catalog.pg_get_viewdef(c.oid, true), ` +
|
|
367
379
|
`pg_catalog.array_remove(pg_catalog.array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, ` +
|
|
@@ -385,7 +397,7 @@ const getViewCreateCmd = async (c, oid) => {
|
|
|
385
397
|
return { ok: false, err };
|
|
386
398
|
}
|
|
387
399
|
if (rs.rows.length !== 1) {
|
|
388
|
-
return { ok: false, err: new Error(
|
|
400
|
+
return { ok: false, err: new Error("view definition not found") };
|
|
389
401
|
}
|
|
390
402
|
const row = rs.rows[0];
|
|
391
403
|
const nspname = cellToString(row[0]);
|
|
@@ -394,33 +406,33 @@ const getViewCreateCmd = async (c, oid) => {
|
|
|
394
406
|
const viewdef = cellToString(row[3]);
|
|
395
407
|
const reloptions = row[4]; // may be string ("{a=b,c=d}") or null
|
|
396
408
|
const checkoption = cellToString(row[5]);
|
|
397
|
-
if (relkind !==
|
|
409
|
+
if (relkind !== "v") {
|
|
398
410
|
return {
|
|
399
411
|
ok: false,
|
|
400
412
|
err: new Error(`"${nspname}.${relname}" is not a view`),
|
|
401
413
|
};
|
|
402
414
|
}
|
|
403
|
-
let out =
|
|
415
|
+
let out = "CREATE OR REPLACE VIEW ";
|
|
404
416
|
out += `${fmtId(nspname)}.${fmtId(relname)}`;
|
|
405
417
|
// reloptions: postgres returns it as a text-mode array literal like
|
|
406
418
|
// `{foo=bar,baz=qux}`; we only need to detect non-empty (different
|
|
407
419
|
// from the literal `{}`) and split entries on `,` outside quotes.
|
|
408
420
|
const reloptStr = reloptions === null ? null : cellToString(reloptions);
|
|
409
421
|
if (reloptStr !== null && reloptStr.length > 2) {
|
|
410
|
-
out +=
|
|
422
|
+
out += "\n WITH (";
|
|
411
423
|
out += renderReloptions(reloptStr);
|
|
412
|
-
out +=
|
|
424
|
+
out += ")";
|
|
413
425
|
}
|
|
414
426
|
out += ` AS\n${viewdef}`;
|
|
415
427
|
// Strip trailing semicolon from pg_get_viewdef.
|
|
416
|
-
if (out.endsWith(
|
|
428
|
+
if (out.endsWith(";")) {
|
|
417
429
|
out = out.slice(0, -1);
|
|
418
430
|
}
|
|
419
|
-
if (checkoption !==
|
|
431
|
+
if (checkoption !== "") {
|
|
420
432
|
out += `\n WITH ${checkoption} CHECK OPTION`;
|
|
421
433
|
}
|
|
422
|
-
if (!out.endsWith(
|
|
423
|
-
out +=
|
|
434
|
+
if (!out.endsWith("\n"))
|
|
435
|
+
out += "\n";
|
|
424
436
|
return { ok: true, def: out };
|
|
425
437
|
};
|
|
426
438
|
/**
|
|
@@ -438,12 +450,12 @@ const getViewCreateCmd = async (c, oid) => {
|
|
|
438
450
|
*/
|
|
439
451
|
const renderReloptions = (literal) => {
|
|
440
452
|
// Strip surrounding `{}`.
|
|
441
|
-
if (!literal.startsWith(
|
|
453
|
+
if (!literal.startsWith("{") || !literal.endsWith("}")) {
|
|
442
454
|
return literal;
|
|
443
455
|
}
|
|
444
456
|
const inside = literal.slice(1, -1);
|
|
445
457
|
if (inside.length === 0)
|
|
446
|
-
return
|
|
458
|
+
return "";
|
|
447
459
|
// Postgres array literals quote individual elements with `"…"` when
|
|
448
460
|
// they contain commas or special chars. For reloptions on a view the
|
|
449
461
|
// values are typically bare `key=value` strings, but we still need to
|
|
@@ -453,9 +465,12 @@ const renderReloptions = (literal) => {
|
|
|
453
465
|
for (let entry of entries) {
|
|
454
466
|
// unquote double-quoted entries
|
|
455
467
|
if (entry.startsWith('"') && entry.endsWith('"')) {
|
|
456
|
-
entry = entry
|
|
468
|
+
entry = entry
|
|
469
|
+
.slice(1, -1)
|
|
470
|
+
.replace(/\\"/g, '"')
|
|
471
|
+
.replace(/\\\\/g, "\\");
|
|
457
472
|
}
|
|
458
|
-
const eq = entry.indexOf(
|
|
473
|
+
const eq = entry.indexOf("=");
|
|
459
474
|
if (eq < 0) {
|
|
460
475
|
out.push(entry);
|
|
461
476
|
continue;
|
|
@@ -470,17 +485,17 @@ const renderReloptions = (literal) => {
|
|
|
470
485
|
out.push(`${key}='${value.replace(/'/g, "''")}'`);
|
|
471
486
|
}
|
|
472
487
|
}
|
|
473
|
-
return out.join(
|
|
488
|
+
return out.join(", ");
|
|
474
489
|
};
|
|
475
490
|
/** Split a Postgres text-mode array's inner content on top-level commas. */
|
|
476
491
|
const splitArrayElems = (s) => {
|
|
477
492
|
const out = [];
|
|
478
493
|
let i = 0;
|
|
479
|
-
let cur =
|
|
494
|
+
let cur = "";
|
|
480
495
|
let inQuote = false;
|
|
481
496
|
while (i < s.length) {
|
|
482
497
|
const ch = s[i];
|
|
483
|
-
if (ch ===
|
|
498
|
+
if (ch === "\\" && i + 1 < s.length) {
|
|
484
499
|
cur += s[i] + s[i + 1];
|
|
485
500
|
i += 2;
|
|
486
501
|
continue;
|
|
@@ -491,16 +506,16 @@ const splitArrayElems = (s) => {
|
|
|
491
506
|
i++;
|
|
492
507
|
continue;
|
|
493
508
|
}
|
|
494
|
-
if (ch ===
|
|
509
|
+
if (ch === "," && !inQuote) {
|
|
495
510
|
out.push(cur);
|
|
496
|
-
cur =
|
|
511
|
+
cur = "";
|
|
497
512
|
i++;
|
|
498
513
|
continue;
|
|
499
514
|
}
|
|
500
515
|
cur += ch;
|
|
501
516
|
i++;
|
|
502
517
|
}
|
|
503
|
-
if (cur.length > 0 || s.endsWith(
|
|
518
|
+
if (cur.length > 0 || s.endsWith(","))
|
|
504
519
|
out.push(cur);
|
|
505
520
|
return out;
|
|
506
521
|
};
|
|
@@ -522,12 +537,12 @@ const writeWithLineNumbers = (buf, isFunc, out) => {
|
|
|
522
537
|
let i = 0;
|
|
523
538
|
while (i < buf.length) {
|
|
524
539
|
// Find end-of-line.
|
|
525
|
-
const eol = buf.indexOf(
|
|
540
|
+
const eol = buf.indexOf("\n", i);
|
|
526
541
|
const line = eol === -1 ? buf.slice(i) : buf.slice(i, eol);
|
|
527
542
|
if (inHeader &&
|
|
528
|
-
(line.startsWith(
|
|
529
|
-
line.startsWith(
|
|
530
|
-
line.startsWith(
|
|
543
|
+
(line.startsWith("AS ") ||
|
|
544
|
+
line.startsWith("BEGIN ") ||
|
|
545
|
+
line.startsWith("RETURN "))) {
|
|
531
546
|
inHeader = false;
|
|
532
547
|
}
|
|
533
548
|
if (!inHeader)
|
|
@@ -538,7 +553,7 @@ const writeWithLineNumbers = (buf, isFunc, out) => {
|
|
|
538
553
|
else {
|
|
539
554
|
// %-7d → left-justified, padded to 7. Then literal space, then line.
|
|
540
555
|
const numStr = String(lineno);
|
|
541
|
-
const pad = numStr.length >= 7 ?
|
|
556
|
+
const pad = numStr.length >= 7 ? "" : " ".repeat(7 - numStr.length);
|
|
542
557
|
out(`${numStr}${pad} ${line}\n`);
|
|
543
558
|
}
|
|
544
559
|
if (eol === -1)
|
|
@@ -570,7 +585,7 @@ const runShowFunction = async (ctx, cmdName, base) => {
|
|
|
570
585
|
const { plus } = decodeShowSuffix(cmdName, base);
|
|
571
586
|
const desc = readObjDesc(ctx);
|
|
572
587
|
if (desc === null) {
|
|
573
|
-
return errResult(ctx,
|
|
588
|
+
return errResult(ctx, "function name is required");
|
|
574
589
|
}
|
|
575
590
|
const oidLookup = await lookupFunctionOid(c, desc);
|
|
576
591
|
if (!oidLookup.ok)
|
|
@@ -579,7 +594,7 @@ const runShowFunction = async (ctx, cmdName, base) => {
|
|
|
579
594
|
if (!defLookup.ok)
|
|
580
595
|
return queryErrResult(ctx, defLookup.err);
|
|
581
596
|
emitDefinition(defLookup.def, plus, /*isFunc=*/ true);
|
|
582
|
-
return { status:
|
|
597
|
+
return { status: "ok" };
|
|
583
598
|
};
|
|
584
599
|
const runShowView = async (ctx, cmdName, base) => {
|
|
585
600
|
const c = conn(ctx);
|
|
@@ -588,7 +603,7 @@ const runShowView = async (ctx, cmdName, base) => {
|
|
|
588
603
|
const { plus } = decodeShowSuffix(cmdName, base);
|
|
589
604
|
const desc = readObjDesc(ctx);
|
|
590
605
|
if (desc === null) {
|
|
591
|
-
return errResult(ctx,
|
|
606
|
+
return errResult(ctx, "view name is required");
|
|
592
607
|
}
|
|
593
608
|
const oidLookup = await lookupRelationOid(c, desc);
|
|
594
609
|
if (!oidLookup.ok)
|
|
@@ -597,38 +612,38 @@ const runShowView = async (ctx, cmdName, base) => {
|
|
|
597
612
|
if (!defLookup.ok)
|
|
598
613
|
return queryErrResult(ctx, defLookup.err);
|
|
599
614
|
emitDefinition(defLookup.def, plus, /*isFunc=*/ false);
|
|
600
|
-
return { status:
|
|
615
|
+
return { status: "ok" };
|
|
601
616
|
};
|
|
602
617
|
// ---------------------------------------------------------------------------
|
|
603
618
|
// BackslashCmdSpec exports
|
|
604
619
|
// ---------------------------------------------------------------------------
|
|
605
620
|
/** `\sf [+] FUNCNAME` — show function source. */
|
|
606
621
|
export const cmdShowFunction = {
|
|
607
|
-
name:
|
|
608
|
-
argMode:
|
|
609
|
-
helpKey:
|
|
610
|
-
run: (ctx) => runShowFunction(ctx, ctx.cmdName,
|
|
622
|
+
name: "sf",
|
|
623
|
+
argMode: "whole-line",
|
|
624
|
+
helpKey: "sf",
|
|
625
|
+
run: (ctx) => runShowFunction(ctx, ctx.cmdName, "sf"),
|
|
611
626
|
};
|
|
612
627
|
/** `\sf+ FUNCNAME` — show function source with line numbers. */
|
|
613
628
|
export const cmdShowFunctionPlus = {
|
|
614
|
-
name:
|
|
615
|
-
argMode:
|
|
616
|
-
helpKey:
|
|
617
|
-
run: (ctx) => runShowFunction(ctx, ctx.cmdName,
|
|
629
|
+
name: "sf+",
|
|
630
|
+
argMode: "whole-line",
|
|
631
|
+
helpKey: "sf",
|
|
632
|
+
run: (ctx) => runShowFunction(ctx, ctx.cmdName, "sf"),
|
|
618
633
|
};
|
|
619
634
|
/** `\sv [+] VIEWNAME` — show view source. */
|
|
620
635
|
export const cmdShowView = {
|
|
621
|
-
name:
|
|
622
|
-
argMode:
|
|
623
|
-
helpKey:
|
|
624
|
-
run: (ctx) => runShowView(ctx, ctx.cmdName,
|
|
636
|
+
name: "sv",
|
|
637
|
+
argMode: "whole-line",
|
|
638
|
+
helpKey: "sv",
|
|
639
|
+
run: (ctx) => runShowView(ctx, ctx.cmdName, "sv"),
|
|
625
640
|
};
|
|
626
641
|
/** `\sv+ VIEWNAME` — show view source with line numbers. */
|
|
627
642
|
export const cmdShowViewPlus = {
|
|
628
|
-
name:
|
|
629
|
-
argMode:
|
|
630
|
-
helpKey:
|
|
631
|
-
run: (ctx) => runShowView(ctx, ctx.cmdName,
|
|
643
|
+
name: "sv+",
|
|
644
|
+
argMode: "whole-line",
|
|
645
|
+
helpKey: "sv",
|
|
646
|
+
run: (ctx) => runShowView(ctx, ctx.cmdName, "sv"),
|
|
632
647
|
};
|
|
633
648
|
/**
|
|
634
649
|
* `\ef [+] [FUNCNAME [LINE]]` — upstream opens `$EDITOR` on the function's
|
|
@@ -638,17 +653,17 @@ export const cmdShowViewPlus = {
|
|
|
638
653
|
* with a hint pointing at `\sf`.
|
|
639
654
|
*/
|
|
640
655
|
export const cmdEditFunction = {
|
|
641
|
-
name:
|
|
642
|
-
argMode:
|
|
643
|
-
helpKey:
|
|
656
|
+
name: "ef",
|
|
657
|
+
argMode: "whole-line",
|
|
658
|
+
helpKey: "ef",
|
|
644
659
|
async run(ctx) {
|
|
645
660
|
const c = conn(ctx);
|
|
646
661
|
if (!c)
|
|
647
662
|
return noConn(ctx);
|
|
648
|
-
const { plus } = decodeShowSuffix(ctx.cmdName,
|
|
663
|
+
const { plus } = decodeShowSuffix(ctx.cmdName, "ef");
|
|
649
664
|
const desc = readObjDesc(ctx);
|
|
650
665
|
if (desc === null) {
|
|
651
|
-
return errResult(ctx,
|
|
666
|
+
return errResult(ctx, "editing not supported in embedded psql; supply a name to display the source");
|
|
652
667
|
}
|
|
653
668
|
// Strip a possible trailing LINE number (upstream behaviour for \ef
|
|
654
669
|
// FUNCNAME LINE — the editor opens at that line; we just discard it).
|
|
@@ -660,24 +675,24 @@ export const cmdEditFunction = {
|
|
|
660
675
|
if (!defLookup.ok)
|
|
661
676
|
return queryErrResult(ctx, defLookup.err);
|
|
662
677
|
emitDefinition(defLookup.def, plus, /*isFunc=*/ true);
|
|
663
|
-
return { status:
|
|
678
|
+
return { status: "ok" };
|
|
664
679
|
},
|
|
665
680
|
};
|
|
666
681
|
/**
|
|
667
682
|
* `\ev [+] [VIEWNAME [LINE]]` — same contract as `\ef` but for views.
|
|
668
683
|
*/
|
|
669
684
|
export const cmdEditView = {
|
|
670
|
-
name:
|
|
671
|
-
argMode:
|
|
672
|
-
helpKey:
|
|
685
|
+
name: "ev",
|
|
686
|
+
argMode: "whole-line",
|
|
687
|
+
helpKey: "ev",
|
|
673
688
|
async run(ctx) {
|
|
674
689
|
const c = conn(ctx);
|
|
675
690
|
if (!c)
|
|
676
691
|
return noConn(ctx);
|
|
677
|
-
const { plus } = decodeShowSuffix(ctx.cmdName,
|
|
692
|
+
const { plus } = decodeShowSuffix(ctx.cmdName, "ev");
|
|
678
693
|
const desc = readObjDesc(ctx);
|
|
679
694
|
if (desc === null) {
|
|
680
|
-
return errResult(ctx,
|
|
695
|
+
return errResult(ctx, "editing not supported in embedded psql; supply a name to display the source");
|
|
681
696
|
}
|
|
682
697
|
const objDesc = stripTrailingLine(desc);
|
|
683
698
|
const oidLookup = await lookupRelationOid(c, objDesc);
|
|
@@ -687,7 +702,7 @@ export const cmdEditView = {
|
|
|
687
702
|
if (!defLookup.ok)
|
|
688
703
|
return queryErrResult(ctx, defLookup.err);
|
|
689
704
|
emitDefinition(defLookup.def, plus, /*isFunc=*/ false);
|
|
690
|
-
return { status:
|
|
705
|
+
return { status: "ok" };
|
|
691
706
|
},
|
|
692
707
|
};
|
|
693
708
|
/**
|
|
@@ -697,11 +712,11 @@ export const cmdEditView = {
|
|
|
697
712
|
*/
|
|
698
713
|
export const cmdEditFunctionPlus = {
|
|
699
714
|
...cmdEditFunction,
|
|
700
|
-
name:
|
|
715
|
+
name: "ef+",
|
|
701
716
|
};
|
|
702
717
|
export const cmdEditViewPlus = {
|
|
703
718
|
...cmdEditView,
|
|
704
|
-
name:
|
|
719
|
+
name: "ev+",
|
|
705
720
|
};
|
|
706
721
|
/**
|
|
707
722
|
* Strip a trailing LINE number from an object descriptor, matching
|
|
@@ -728,7 +743,7 @@ const stripTrailingLine = (desc) => {
|
|
|
728
743
|
if (i <= 0)
|
|
729
744
|
return desc;
|
|
730
745
|
const sep = desc[i];
|
|
731
|
-
if (!(/\s/.test(sep) || sep ===
|
|
746
|
+
if (!(/\s/.test(sep) || sep === ")"))
|
|
732
747
|
return desc;
|
|
733
748
|
return desc.slice(0, i + 1).trimEnd();
|
|
734
749
|
};
|