neonctl 2.22.2 → 2.23.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 +84 -0
- package/analytics.js +5 -2
- package/commands/branches.js +9 -1
- package/commands/connection_string.js +9 -1
- package/commands/functions.js +277 -0
- package/commands/index.js +4 -0
- package/commands/neon_auth.js +1013 -0
- package/commands/projects.js +9 -1
- package/commands/psql.js +6 -1
- package/functions_api.js +44 -0
- package/package.json +15 -5
- package/psql/cli.js +51 -0
- package/psql/command/cmd_cond.js +437 -0
- package/psql/command/cmd_connect.js +815 -0
- package/psql/command/cmd_copy.js +1025 -0
- package/psql/command/cmd_describe.js +1810 -0
- package/psql/command/cmd_format.js +909 -0
- package/psql/command/cmd_io.js +2187 -0
- package/psql/command/cmd_lo.js +385 -0
- package/psql/command/cmd_meta.js +970 -0
- package/psql/command/cmd_misc.js +187 -0
- package/psql/command/cmd_pipeline.js +1141 -0
- package/psql/command/cmd_restrict.js +171 -0
- package/psql/command/cmd_show.js +751 -0
- package/psql/command/dispatch.js +343 -0
- package/psql/command/inputQueue.js +42 -0
- package/psql/command/shared.js +71 -0
- package/psql/complete/filenames.js +139 -0
- package/psql/complete/index.js +104 -0
- package/psql/complete/matcher.js +314 -0
- package/psql/complete/psqlVars.js +247 -0
- package/psql/complete/queries.js +491 -0
- package/psql/complete/rules.js +2387 -0
- package/psql/core/common.js +1250 -0
- package/psql/core/help.js +576 -0
- package/psql/core/mainloop.js +1353 -0
- package/psql/core/prompt.js +437 -0
- package/psql/core/settings.js +684 -0
- package/psql/core/sqlHelp.js +1066 -0
- package/psql/core/startup.js +840 -0
- package/psql/core/syncVars.js +116 -0
- package/psql/core/variables.js +287 -0
- package/psql/describe/formatters.js +1277 -0
- package/psql/describe/processNamePattern.js +270 -0
- package/psql/describe/queries.js +2373 -0
- package/psql/describe/versionGate.js +43 -0
- package/psql/index.js +2005 -0
- package/psql/io/history.js +299 -0
- package/psql/io/input.js +120 -0
- package/psql/io/lineEditor/buffer.js +323 -0
- package/psql/io/lineEditor/complete.js +227 -0
- package/psql/io/lineEditor/filename.js +159 -0
- package/psql/io/lineEditor/index.js +891 -0
- package/psql/io/lineEditor/keymap.js +738 -0
- package/psql/io/lineEditor/vt100.js +363 -0
- package/psql/io/pgpass.js +202 -0
- package/psql/io/pgservice.js +194 -0
- package/psql/io/psqlrc.js +422 -0
- package/psql/print/aligned.js +1756 -0
- package/psql/print/asciidoc.js +248 -0
- package/psql/print/crosstab.js +460 -0
- package/psql/print/csv.js +92 -0
- package/psql/print/html.js +258 -0
- package/psql/print/json.js +96 -0
- package/psql/print/latex.js +396 -0
- package/psql/print/pager.js +265 -0
- package/psql/print/troff.js +258 -0
- package/psql/print/unaligned.js +118 -0
- package/psql/print/units.js +135 -0
- package/psql/scanner/slash.js +513 -0
- package/psql/scanner/sql.js +910 -0
- package/psql/scanner/stringutils.js +390 -0
- package/psql/types/backslash.js +1 -0
- package/psql/types/connection.js +1 -0
- package/psql/types/index.js +7 -0
- package/psql/types/printer.js +1 -0
- package/psql/types/repl.js +1 -0
- package/psql/types/scanner.js +24 -0
- package/psql/types/settings.js +1 -0
- package/psql/types/variables.js +1 -0
- package/psql/wire/connection.js +2844 -0
- package/psql/wire/copy.js +108 -0
- package/psql/wire/notify.js +59 -0
- package/psql/wire/pipeline.js +519 -0
- package/psql/wire/protocol.js +466 -0
- package/psql/wire/sasl.js +296 -0
- package/psql/wire/tls.js +596 -0
- package/test_utils/fixtures.js +1 -0
- package/utils/esbuild.js +147 -0
- package/utils/psql.js +107 -11
- package/utils/zip.js +4 -0
- package/writer.js +1 -1
- package/commands/auth.test.js +0 -211
- package/commands/branches.test.js +0 -460
- package/commands/checkout.test.js +0 -170
- package/commands/connection_string.test.js +0 -196
- package/commands/data_api.test.js +0 -169
- package/commands/databases.test.js +0 -39
- package/commands/help.test.js +0 -9
- package/commands/init.test.js +0 -56
- package/commands/ip_allow.test.js +0 -59
- package/commands/link.test.js +0 -381
- package/commands/operations.test.js +0 -7
- package/commands/orgs.test.js +0 -7
- package/commands/projects.test.js +0 -144
- package/commands/psql.test.js +0 -49
- package/commands/roles.test.js +0 -37
- package/commands/set_context.test.js +0 -159
- package/commands/vpc_endpoints.test.js +0 -69
- package/context.test.js +0 -119
- package/env.test.js +0 -55
- package/utils/formats.test.js +0 -32
- package/writer.test.js +0 -104
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* psql string utilities.
|
|
3
|
+
*
|
|
4
|
+
* TypeScript port of PostgreSQL's `src/bin/psql/stringutils.c`. Three pure
|
|
5
|
+
* helpers used by the slash-command scanner and by code that constructs
|
|
6
|
+
* round-trippable argument strings (notably tab-completion output).
|
|
7
|
+
*
|
|
8
|
+
* - {@link strtokx} — upstream `strtokx()`, a tokenizer with
|
|
9
|
+
* configurable whitespace, delimiter, quote and
|
|
10
|
+
* escape sets. Implemented as a pure function that
|
|
11
|
+
* returns `{ token, rest }` rather than the upstream
|
|
12
|
+
* re-entrant-via-static-variables style.
|
|
13
|
+
* - {@link quoteIfNeeded} — upstream `quote_if_needed()`. Returns the value
|
|
14
|
+
* unchanged when it contains no characters that
|
|
15
|
+
* require quoting; otherwise wraps in `quote`,
|
|
16
|
+
* doubling any embedded `quote` characters.
|
|
17
|
+
* - {@link dequote} — small companion to `quote_if_needed()`. Strips a
|
|
18
|
+
* single surrounding `quote` and undoubles any
|
|
19
|
+
* embedded occurrences (the inverse of the wrap
|
|
20
|
+
* done by `quoteIfNeeded`).
|
|
21
|
+
* - {@link tryConsumeVarSubstitution} — `:NAME`/`:'NAME'`/`:"NAME"`
|
|
22
|
+
* substitution helper used by the SQL scanner.
|
|
23
|
+
* (The slash-arg scanner has its own local copy of
|
|
24
|
+
* the same logic; future work can collapse them.)
|
|
25
|
+
*
|
|
26
|
+
* Deviations from upstream that are intentional:
|
|
27
|
+
*
|
|
28
|
+
* - All inputs are JS strings, processed as UTF-16 code units. Upstream uses
|
|
29
|
+
* `PQmblenBounded()` to advance one multibyte character at a time; for the
|
|
30
|
+
* purposes of `strtokx`/`quote_if_needed`/`strip_quotes` the only thing
|
|
31
|
+
* that matters is matching ASCII delimiter / quote / escape bytes, which
|
|
32
|
+
* are guaranteed not to be the middle byte of a multibyte sequence in any
|
|
33
|
+
* PostgreSQL-supported encoding. We therefore safely walk the string one
|
|
34
|
+
* code unit at a time.
|
|
35
|
+
* - The `encoding` argument is accepted for API parity but is currently
|
|
36
|
+
* unused. The slash scanner passes it through; documenting it lets us add
|
|
37
|
+
* encoding-aware handling later without a signature break.
|
|
38
|
+
* - `strtokx` returns `{ token, rest }`. The caller iterates by passing
|
|
39
|
+
* `rest` back in; this is friendlier to TS than threading a hidden static.
|
|
40
|
+
*/
|
|
41
|
+
/**
|
|
42
|
+
* Tokenizer used by the psql slash-command scanner.
|
|
43
|
+
*
|
|
44
|
+
* Behaviour, matching upstream `strtokx()`:
|
|
45
|
+
* 1. Skip any characters in `whitespace`.
|
|
46
|
+
* 2. If the cursor sits on a character in `delim`, return that single
|
|
47
|
+
* character as the token (the delimiter itself is a token).
|
|
48
|
+
* 3. If the cursor sits on a quote character, scan until the matching quote.
|
|
49
|
+
* Doubled quotes are kept verbatim in the returned token (the caller can
|
|
50
|
+
* post-process by passing the result through {@link dequote}). The
|
|
51
|
+
* `escape` character, when set, lets the next character be taken
|
|
52
|
+
* literally — including a quote that would otherwise close the token.
|
|
53
|
+
* 4. Otherwise scan until the next whitespace, delim, or quote character and
|
|
54
|
+
* return everything consumed.
|
|
55
|
+
*
|
|
56
|
+
* @param input remaining input string
|
|
57
|
+
* @param whitespace characters treated as whitespace (any sequence is
|
|
58
|
+
* a single separator and is consumed without
|
|
59
|
+
* emitting a token)
|
|
60
|
+
* @param delim characters returned as standalone single-char
|
|
61
|
+
* tokens (use `""` to disable)
|
|
62
|
+
* @param quote characters that open a quoted token (use `""` to
|
|
63
|
+
* disable)
|
|
64
|
+
* @param escape character that lets the next char be taken
|
|
65
|
+
* literally inside a quoted token (use `""` to
|
|
66
|
+
* disable)
|
|
67
|
+
* @param eAcceptInUnquoted optional set of "E-string" prefixes — letters that
|
|
68
|
+
* when followed by a single quote start a quoted
|
|
69
|
+
* token with backslash escaping enabled. Pass `"Ee"`
|
|
70
|
+
* to mirror upstream's `e_strings = true`. `null`
|
|
71
|
+
* disables the behaviour.
|
|
72
|
+
* @param atEol when `true`, a trailing delim character is left in
|
|
73
|
+
* the remainder for the next call. When `false`,
|
|
74
|
+
* trailing whitespace and any single trailing delim
|
|
75
|
+
* are consumed before returning.
|
|
76
|
+
* @param encoding accepted for API parity; unused.
|
|
77
|
+
*
|
|
78
|
+
* @returns `{ token, rest }` where `token` is `null` at end of input.
|
|
79
|
+
*/
|
|
80
|
+
export const strtokx = (input, whitespace, delim, quote, escape, eAcceptInUnquoted, atEol, encoding) => {
|
|
81
|
+
void encoding; // documented as unused
|
|
82
|
+
let i = 0;
|
|
83
|
+
const n = input.length;
|
|
84
|
+
// 1. Skip leading whitespace.
|
|
85
|
+
while (i < n && whitespace.includes(input[i]))
|
|
86
|
+
i++;
|
|
87
|
+
if (i >= n) {
|
|
88
|
+
return { token: null, rest: '' };
|
|
89
|
+
}
|
|
90
|
+
// 2. Single-character delim token.
|
|
91
|
+
if (delim.length > 0 && delim.includes(input[i])) {
|
|
92
|
+
const token = input[i];
|
|
93
|
+
i++;
|
|
94
|
+
if (!atEol) {
|
|
95
|
+
// Consume one immediately-following separator (whitespace) so the next
|
|
96
|
+
// call lands cleanly on the next real token. Upstream achieves the
|
|
97
|
+
// same effect by inserting a null after the delim and advancing
|
|
98
|
+
// `string` past it.
|
|
99
|
+
while (i < n && whitespace.includes(input[i]))
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
return { token, rest: input.slice(i) };
|
|
103
|
+
}
|
|
104
|
+
// 3. Quoted token.
|
|
105
|
+
let p = i;
|
|
106
|
+
let effectiveQuote = quote;
|
|
107
|
+
let effectiveEscape = escape;
|
|
108
|
+
// E-string prefix handling — upstream's `if (e_strings && (*p == 'E' ||
|
|
109
|
+
// *p == 'e') && p[1] == '\'') { quote = "'"; escape = '\\'; p++; }`.
|
|
110
|
+
if (eAcceptInUnquoted &&
|
|
111
|
+
p + 1 < n &&
|
|
112
|
+
eAcceptInUnquoted.includes(input[p]) &&
|
|
113
|
+
input[p + 1] === "'") {
|
|
114
|
+
effectiveQuote = "'";
|
|
115
|
+
effectiveEscape = '\\';
|
|
116
|
+
p++;
|
|
117
|
+
}
|
|
118
|
+
if (effectiveQuote.length > 0 && effectiveQuote.includes(input[p])) {
|
|
119
|
+
const thisQuote = input[p];
|
|
120
|
+
const start = p;
|
|
121
|
+
p++; // step over opening quote
|
|
122
|
+
while (p < n) {
|
|
123
|
+
const c = input[p];
|
|
124
|
+
if (effectiveEscape.length > 0 && c === effectiveEscape && p + 1 < n) {
|
|
125
|
+
// escape + anything (except end-of-input) is a literal data char
|
|
126
|
+
p += 2;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (c === thisQuote && input[p + 1] === thisQuote) {
|
|
130
|
+
// doubled quote — keep both in the returned token; the caller can
|
|
131
|
+
// dequote() if they want a clean value.
|
|
132
|
+
p += 2;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (c === thisQuote) {
|
|
136
|
+
p++; // step over closing quote
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
p++;
|
|
140
|
+
}
|
|
141
|
+
const token = input.slice(start, p);
|
|
142
|
+
if (!atEol) {
|
|
143
|
+
while (p < n && whitespace.includes(input[p]))
|
|
144
|
+
p++;
|
|
145
|
+
}
|
|
146
|
+
return { token, rest: input.slice(p) };
|
|
147
|
+
}
|
|
148
|
+
// 4. Bareword: scan to next whitespace, delim, or quote.
|
|
149
|
+
const start = p;
|
|
150
|
+
while (p < n) {
|
|
151
|
+
const c = input[p];
|
|
152
|
+
if (whitespace.includes(c))
|
|
153
|
+
break;
|
|
154
|
+
if (delim.length > 0 && delim.includes(c))
|
|
155
|
+
break;
|
|
156
|
+
if (quote.length > 0 && quote.includes(c))
|
|
157
|
+
break;
|
|
158
|
+
p++;
|
|
159
|
+
}
|
|
160
|
+
const token = input.slice(start, p);
|
|
161
|
+
// Always skip trailing whitespace so the next call lands on the next
|
|
162
|
+
// non-blank character. When `atEol` is `false` we additionally consume a
|
|
163
|
+
// single trailing delim — the caller has told us delims are line-internal
|
|
164
|
+
// separators rather than significant tokens.
|
|
165
|
+
while (p < n && whitespace.includes(input[p]))
|
|
166
|
+
p++;
|
|
167
|
+
if (!atEol && p < n && delim.length > 0 && delim.includes(input[p])) {
|
|
168
|
+
p++;
|
|
169
|
+
while (p < n && whitespace.includes(input[p]))
|
|
170
|
+
p++;
|
|
171
|
+
}
|
|
172
|
+
return { token, rest: input.slice(p) };
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Wrap `value` in `quote` if it contains any character in `escapeChars`,
|
|
176
|
+
* `quote` itself, or is otherwise ambiguous; embedded occurrences of `quote`
|
|
177
|
+
* are doubled to escape them. If no quoting is needed the original `value` is
|
|
178
|
+
* returned verbatim (so this is a no-op for already-clean tokens).
|
|
179
|
+
*
|
|
180
|
+
* @param value string to (possibly) quote
|
|
181
|
+
* @param escapeChars characters whose presence in `value` triggers quoting
|
|
182
|
+
* (typically the same character set passed as `whitespace`
|
|
183
|
+
* / `delim` to {@link strtokx})
|
|
184
|
+
* @param quote quote character to wrap with (e.g. `'` or `"`)
|
|
185
|
+
*/
|
|
186
|
+
export const quoteIfNeeded = (value, escapeChars, quote) => {
|
|
187
|
+
if (quote.length !== 1) {
|
|
188
|
+
throw new Error('quoteIfNeeded: quote must be exactly one character');
|
|
189
|
+
}
|
|
190
|
+
let needsQuotes = false;
|
|
191
|
+
let escaped = '';
|
|
192
|
+
for (const c of value) {
|
|
193
|
+
if (c === quote) {
|
|
194
|
+
needsQuotes = true;
|
|
195
|
+
escaped += quote + quote;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
if (escapeChars.includes(c))
|
|
199
|
+
needsQuotes = true;
|
|
200
|
+
escaped += c;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (!needsQuotes)
|
|
204
|
+
return value;
|
|
205
|
+
return quote + escaped + quote;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Inverse of {@link quoteIfNeeded}. If `value` is wrapped in `quote`, strip
|
|
209
|
+
* the outer quotes and undouble any embedded `quote` occurrences. If `value`
|
|
210
|
+
* is not wrapped in `quote`, it is returned unchanged.
|
|
211
|
+
*
|
|
212
|
+
* @param value any string (quoted or bare)
|
|
213
|
+
* @param quote quote character used to wrap (e.g. `'` or `"`)
|
|
214
|
+
*/
|
|
215
|
+
export const dequote = (value, quote) => {
|
|
216
|
+
if (quote.length !== 1) {
|
|
217
|
+
throw new Error('dequote: quote must be exactly one character');
|
|
218
|
+
}
|
|
219
|
+
if (value.length < 2 || !value.startsWith(quote) || !value.endsWith(quote)) {
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
const inner = value.slice(1, -1);
|
|
223
|
+
// Undouble embedded quote chars.
|
|
224
|
+
let out = '';
|
|
225
|
+
let i = 0;
|
|
226
|
+
while (i < inner.length) {
|
|
227
|
+
if (inner[i] === quote && inner[i + 1] === quote) {
|
|
228
|
+
out += quote;
|
|
229
|
+
i += 2;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
out += inner[i];
|
|
233
|
+
i++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
};
|
|
238
|
+
/**
|
|
239
|
+
* SQL-literal-quote a value for the `:'varname'` substitution form.
|
|
240
|
+
*
|
|
241
|
+
* Mirrors libpq's `PQescapeLiteral` for the common case: wrap in `'…'`,
|
|
242
|
+
* double any embedded `'`, and backslash-escape any embedded `\`. Upstream
|
|
243
|
+
* additionally emits an `E` prefix when the value contains backslashes; we
|
|
244
|
+
* preserve that behaviour for compatibility with code that round-trips
|
|
245
|
+
* through the SQL parser.
|
|
246
|
+
*/
|
|
247
|
+
export const quoteSqlLiteral = (value) => {
|
|
248
|
+
let needsEscape = false;
|
|
249
|
+
let inner = '';
|
|
250
|
+
for (const c of value) {
|
|
251
|
+
if (c === "'")
|
|
252
|
+
inner += "''";
|
|
253
|
+
else if (c === '\\') {
|
|
254
|
+
inner += '\\\\';
|
|
255
|
+
needsEscape = true;
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
inner += c;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return needsEscape ? `E'${inner}'` : `'${inner}'`;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* SQL-identifier-quote a value for the `:"varname"` substitution form.
|
|
265
|
+
* Wraps the value in `"…"` and doubles any embedded `"`.
|
|
266
|
+
*/
|
|
267
|
+
export const quoteSqlIdent = (value) => {
|
|
268
|
+
let inner = '';
|
|
269
|
+
for (const c of value) {
|
|
270
|
+
inner += c === '"' ? '""' : c;
|
|
271
|
+
}
|
|
272
|
+
return `"${inner}"`;
|
|
273
|
+
};
|
|
274
|
+
// Variable-name character class. Upstream's `variable_char` flex rule matches
|
|
275
|
+
// `[A-Za-z0-9_\x80-\xff]+`. We do not allow a leading digit (matches
|
|
276
|
+
// `VarStore`'s `[A-Za-z_][A-Za-z0-9_]*` validation rule) — a token like `:1`
|
|
277
|
+
// is not a substitution. The high-byte range `\x80-\xff` is spelled with
|
|
278
|
+
// explicit `\x..` escapes so the `_` and the range stay separated; an earlier
|
|
279
|
+
// `[A-Za-z0-9_-ÿ]` form silently included `{`, `|`, `}`, `~` via the
|
|
280
|
+
// dash-range bridge between `_` (0x5F) and `ÿ` (0xFF), which broke the
|
|
281
|
+
// `:{?NAME}` brace-form lookup whose closing `}` would be eaten as part of
|
|
282
|
+
// the name.
|
|
283
|
+
const VAR_NAME_CONT_RE = /[A-Za-z0-9_\x80-\xff]/;
|
|
284
|
+
const VAR_NAME_START_RE = /[A-Za-z_\x80-\xff]/;
|
|
285
|
+
const isVarNameStart = (c) => c !== undefined && VAR_NAME_START_RE.test(c);
|
|
286
|
+
const isVarNameCont = (c) => c !== undefined && VAR_NAME_CONT_RE.test(c);
|
|
287
|
+
/**
|
|
288
|
+
* Attempt to consume one of the `:NAME`, `:'NAME'`, `:"NAME"` variable
|
|
289
|
+
* substitution forms at position `i` in `s`. Returns the new index plus the
|
|
290
|
+
* substituted text, or `null` if no recognised form is present (i.e. the
|
|
291
|
+
* caller should emit `s[i]` verbatim and advance one char).
|
|
292
|
+
*
|
|
293
|
+
* When `varLookup` is `undefined`, substitution is disabled outright (the
|
|
294
|
+
* function returns `null` for every call).
|
|
295
|
+
*
|
|
296
|
+
* Unknown variables: upstream still ECHOes the raw `:NAME` token rather than
|
|
297
|
+
* substituting an empty string, so a misspelled reference stays visible to
|
|
298
|
+
* the user. We mirror that for all three forms.
|
|
299
|
+
*
|
|
300
|
+
* Edge cases handled here so callers don't repeat them:
|
|
301
|
+
*
|
|
302
|
+
* - `::` (PostgreSQL cast operator): when `s[i+1] === ':'`, we treat the
|
|
303
|
+
* sequence as not-a-substitution and return `null`. The caller is then
|
|
304
|
+
* responsible for advancing past both colons (the SQL scanner does that
|
|
305
|
+
* in a dedicated branch before calling us).
|
|
306
|
+
* - `:` followed by a non-identifier char: returns `null`. The caller emits
|
|
307
|
+
* the literal `:` and continues.
|
|
308
|
+
* - `:'` or `:"` with no matching closing quote or empty name: returns
|
|
309
|
+
* `null`. We require at least one identifier character and a properly
|
|
310
|
+
* closed quote, matching upstream's flex rules.
|
|
311
|
+
*/
|
|
312
|
+
export const tryConsumeVarSubstitution = (s, i, varLookup) => {
|
|
313
|
+
if (varLookup === undefined)
|
|
314
|
+
return null;
|
|
315
|
+
if (s[i] !== ':')
|
|
316
|
+
return null;
|
|
317
|
+
const next = s[i + 1];
|
|
318
|
+
if (next === undefined)
|
|
319
|
+
return null;
|
|
320
|
+
// `::` cast operator — never a substitution.
|
|
321
|
+
if (next === ':')
|
|
322
|
+
return null;
|
|
323
|
+
// :{?NAME} — defined-variable test. Emits literal `TRUE` if the named
|
|
324
|
+
// variable is set, `FALSE` otherwise. Mirrors upstream's
|
|
325
|
+
// `psqlscan_test_variable` (flex rule `:\{\?{variable_char}+\}` in
|
|
326
|
+
// `psqlscan.l`). Unlike the other forms we recognise here, an unset variable
|
|
327
|
+
// is NOT echoed back as a literal — the whole point of `:{?NAME}` is to
|
|
328
|
+
// produce a boolean regardless of definedness. A malformed brace expression
|
|
329
|
+
// (missing closing `}`, empty NAME, or non-variable_char content) returns
|
|
330
|
+
// `null` so the caller emits the literal `:` and continues, matching the
|
|
331
|
+
// upstream flex fallback rule `:\{\?{variable_char}*`.
|
|
332
|
+
if (next === '{' && s[i + 2] === '?') {
|
|
333
|
+
let j = i + 3;
|
|
334
|
+
while (j < s.length && isVarNameCont(s[j]))
|
|
335
|
+
j++;
|
|
336
|
+
if (j > i + 3 && s[j] === '}') {
|
|
337
|
+
const name = s.slice(i + 3, j);
|
|
338
|
+
const value = varLookup(name);
|
|
339
|
+
return { end: j + 1, text: value !== undefined ? 'TRUE' : 'FALSE' };
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
// :"NAME" — SQL identifier quote
|
|
344
|
+
if (next === '"') {
|
|
345
|
+
let j = i + 2;
|
|
346
|
+
while (j < s.length && isVarNameCont(s[j]))
|
|
347
|
+
j++;
|
|
348
|
+
if (j > i + 2 && s[j] === '"') {
|
|
349
|
+
const name = s.slice(i + 2, j);
|
|
350
|
+
const value = varLookup(name);
|
|
351
|
+
if (value === undefined) {
|
|
352
|
+
// Echo the literal `:"NAME"` for visibility.
|
|
353
|
+
return { end: j + 1, text: s.slice(i, j + 1) };
|
|
354
|
+
}
|
|
355
|
+
return { end: j + 1, text: quoteSqlIdent(value) };
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
// :'NAME' — SQL literal quote
|
|
360
|
+
if (next === "'") {
|
|
361
|
+
let j = i + 2;
|
|
362
|
+
while (j < s.length && isVarNameCont(s[j]))
|
|
363
|
+
j++;
|
|
364
|
+
if (j > i + 2 && s[j] === "'") {
|
|
365
|
+
const name = s.slice(i + 2, j);
|
|
366
|
+
const value = varLookup(name);
|
|
367
|
+
if (value === undefined) {
|
|
368
|
+
return { end: j + 1, text: s.slice(i, j + 1) };
|
|
369
|
+
}
|
|
370
|
+
return { end: j + 1, text: quoteSqlLiteral(value) };
|
|
371
|
+
}
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
// :NAME — plain substitution. We require the first char to be an
|
|
375
|
+
// identifier-start char (no leading digit) to avoid eating `:1` etc.
|
|
376
|
+
if (isVarNameStart(next)) {
|
|
377
|
+
let j = i + 1;
|
|
378
|
+
while (j < s.length && isVarNameCont(s[j]))
|
|
379
|
+
j++;
|
|
380
|
+
const name = s.slice(i + 1, j);
|
|
381
|
+
const value = varLookup(name);
|
|
382
|
+
if (value === undefined) {
|
|
383
|
+
// Unset → emit literally so it stays visible. Upstream ECHOes the
|
|
384
|
+
// entire `:name` text in this case.
|
|
385
|
+
return { end: j, text: s.slice(i, j) };
|
|
386
|
+
}
|
|
387
|
+
return { end: j, text: value };
|
|
388
|
+
}
|
|
389
|
+
return null;
|
|
390
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if the scanner status represents an unterminated statement
|
|
3
|
+
* waiting for more input. Useful for callers that only care whether to draw
|
|
4
|
+
* PROMPT2 vs PROMPT1 and don't care about the specific reason.
|
|
5
|
+
*/
|
|
6
|
+
export const isContinueStatus = (s) => s === 'continue' ||
|
|
7
|
+
s === 'continue-quote' ||
|
|
8
|
+
s === 'continue-dquote' ||
|
|
9
|
+
s === 'continue-dollar' ||
|
|
10
|
+
s === 'paren' ||
|
|
11
|
+
s === 'comment';
|
|
12
|
+
export const initialScanState = () => ({
|
|
13
|
+
promptStatus: 'ready',
|
|
14
|
+
parenDepth: 0,
|
|
15
|
+
dollarTag: null,
|
|
16
|
+
inLineComment: false,
|
|
17
|
+
inBlockComment: 0,
|
|
18
|
+
inSingleQuote: false,
|
|
19
|
+
inDoubleQuote: false,
|
|
20
|
+
inEscapeString: false,
|
|
21
|
+
beginDepth: 0,
|
|
22
|
+
identifierLetters: ['', '', '', ''],
|
|
23
|
+
identifierCount: 0,
|
|
24
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|