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.
- package/README.md +70 -5
- package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
- package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
- package/dist/_chunks/credentials-MYdHdKah.js +188 -0
- package/dist/_chunks/env-NbA61JR3.js +585 -0
- package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
- package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
- package/dist/_chunks/profiles-Ir29rqns.js +217 -0
- package/dist/_chunks/psql-DWH-kc69.js +2169 -0
- package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
- package/dist/analytics.js +163 -207
- package/dist/api.js +815 -758
- package/dist/auth.js +121 -141
- package/dist/auth_context.js +39 -53
- package/dist/cli.js +4 -7
- package/dist/commands/api.js +220 -250
- package/dist/commands/api_keys.js +251 -314
- package/dist/commands/auth.js +283 -328
- package/dist/commands/bootstrap.js +372 -437
- package/dist/commands/branches.js +304 -455
- package/dist/commands/bucket.js +374 -514
- package/dist/commands/checkout.js +213 -298
- package/dist/commands/config.js +573 -690
- package/dist/commands/connection_string.js +137 -165
- package/dist/commands/data_api.js +238 -260
- package/dist/commands/databases.js +67 -76
- package/dist/commands/deploy.js +31 -25
- package/dist/commands/dev.js +639 -719
- package/dist/commands/diff.js +156 -200
- package/dist/commands/env.js +255 -305
- package/dist/commands/functions.js +275 -355
- package/dist/commands/index.js +70 -65
- package/dist/commands/init.js +84 -119
- package/dist/commands/inspect.js +55 -55
- package/dist/commands/ip_allow.js +88 -120
- package/dist/commands/link.js +874 -1019
- package/dist/commands/logs.js +291 -0
- package/dist/commands/neon_auth.js +725 -933
- package/dist/commands/operations.js +34 -25
- package/dist/commands/orgs.js +28 -18
- package/dist/commands/profile.js +615 -846
- package/dist/commands/projects.js +313 -373
- package/dist/commands/psql.js +60 -58
- package/dist/commands/roles.js +55 -58
- package/dist/commands/schema_diff.js +87 -131
- package/dist/commands/set_context.js +34 -26
- package/dist/commands/snapshots.js +288 -413
- package/dist/commands/status.js +41 -37
- package/dist/commands/user.js +21 -10
- package/dist/commands/vpc_endpoints.js +85 -113
- package/dist/config.js +7 -6
- package/dist/config_format.js +50 -66
- package/dist/config_template.js +128 -157
- package/dist/context.js +183 -235
- package/dist/current_branch_fast_path.js +40 -49
- package/dist/dev/env.js +2 -446
- package/dist/dev/functions.js +54 -68
- package/dist/dev/inputs.js +46 -58
- package/dist/dev/runtime.js +135 -164
- package/dist/dev/websocket.js +766 -959
- package/dist/env.js +27 -33
- package/dist/env_file.js +118 -132
- package/dist/env_services.js +2 -51
- package/dist/errors.js +57 -68
- package/dist/functions_api.js +45 -43
- package/dist/help.js +189 -140
- package/dist/index.js +182 -257
- package/dist/init/agents.js +137 -118
- package/dist/init/auth.js +58 -68
- package/dist/init/bootstrap.js +325 -396
- package/dist/init/build_config.js +4 -2
- package/dist/init/detect_agent.js +56 -101
- package/dist/init/editors.js +35 -52
- package/dist/init/enrich_output.js +51 -66
- package/dist/init/extension.js +134 -171
- package/dist/init/inspect.js +179 -266
- package/dist/init/interactive.js +510 -622
- package/dist/init/neonctl.js +117 -168
- package/dist/init/orchestrate.js +157 -173
- package/dist/init/phases/auth.js +188 -202
- package/dist/init/phases/cleanup.js +23 -23
- package/dist/init/phases/db.js +251 -277
- package/dist/init/phases/getting_started.js +213 -223
- package/dist/init/phases/mcp.js +174 -224
- package/dist/init/phases/migrations.js +247 -248
- package/dist/init/phases/neon_auth.js +114 -133
- package/dist/init/phases/setup.js +546 -703
- package/dist/init/phases/skills.js +75 -86
- package/dist/init/phases/status.js +72 -67
- package/dist/init/resolve_context.js +102 -99
- package/dist/init/route_command.js +91 -98
- package/dist/init/skills.js +174 -218
- package/dist/init/vsix.js +77 -99
- package/dist/log.js +17 -16
- package/dist/neon_services.js +104 -129
- package/dist/parameters.gen.js +481 -471
- package/dist/pkg.js +17 -19
- package/dist/profile_keys.js +44 -47
- package/dist/psql/cli.js +44 -47
- package/dist/psql/command/cmd_cond.js +231 -406
- package/dist/psql/command/cmd_connect.js +557 -764
- package/dist/psql/command/cmd_copy.js +728 -984
- package/dist/psql/command/cmd_describe.js +1499 -1688
- package/dist/psql/command/cmd_format.js +733 -905
- package/dist/psql/command/cmd_io.js +2 -2193
- package/dist/psql/command/cmd_lo.js +297 -359
- package/dist/psql/command/cmd_meta.js +727 -878
- package/dist/psql/command/cmd_misc.js +138 -172
- package/dist/psql/command/cmd_pipeline.js +2 -1148
- package/dist/psql/command/cmd_restrict.js +119 -155
- package/dist/psql/command/cmd_show.js +529 -688
- package/dist/psql/command/dispatch.js +260 -325
- package/dist/psql/command/inputQueue.js +35 -33
- package/dist/psql/command/shared.js +49 -63
- package/dist/psql/complete/filenames.js +90 -133
- package/dist/psql/complete/index.js +59 -97
- package/dist/psql/complete/matcher.js +236 -300
- package/dist/psql/complete/psqlVars.js +218 -223
- package/dist/psql/complete/queries.js +159 -177
- package/dist/psql/complete/rules.js +1493 -2299
- package/dist/psql/core/common.js +2 -1253
- package/dist/psql/core/help.js +456 -546
- package/dist/psql/core/mainloop.js +692 -1303
- package/dist/psql/core/prompt.js +391 -408
- package/dist/psql/core/settings.js +429 -644
- package/dist/psql/core/sqlHelp.js +480 -554
- package/dist/psql/core/startup.js +2 -846
- package/dist/psql/core/syncVars.js +67 -110
- package/dist/psql/core/variables.js +156 -278
- package/dist/psql/describe/formatters.js +884 -1285
- package/dist/psql/describe/processNamePattern.js +173 -260
- package/dist/psql/describe/queries.js +1368 -2403
- package/dist/psql/describe/versionGate.js +32 -41
- package/dist/psql/index.js +2 -2030
- package/dist/psql/io/history.js +232 -271
- package/dist/psql/io/input.js +103 -108
- package/dist/psql/io/lineEditor/buffer.js +238 -319
- package/dist/psql/io/lineEditor/complete.js +135 -213
- package/dist/psql/io/lineEditor/filename.js +139 -148
- package/dist/psql/io/lineEditor/index.js +653 -870
- package/dist/psql/io/lineEditor/keymap.js +544 -702
- package/dist/psql/io/lineEditor/vt100.js +294 -341
- package/dist/psql/io/pgpass.js +158 -187
- package/dist/psql/io/pgservice.js +146 -183
- package/dist/psql/io/psqlrc.js +328 -403
- package/dist/psql/print/aligned.js +1020 -1683
- package/dist/psql/print/asciidoc.js +180 -214
- package/dist/psql/print/crosstab.js +281 -442
- package/dist/psql/print/csv.js +48 -70
- package/dist/psql/print/html.js +195 -226
- package/dist/psql/print/json.js +75 -88
- package/dist/psql/print/latex.js +291 -364
- package/dist/psql/print/pager.js +171 -242
- package/dist/psql/print/troff.js +194 -226
- package/dist/psql/print/unaligned.js +69 -95
- package/dist/psql/print/units.js +167 -169
- package/dist/psql/scanner/slash.js +428 -483
- package/dist/psql/scanner/sql.js +445 -889
- package/dist/psql/scanner/stringutils.js +309 -379
- package/dist/psql/types/index.js +8 -7
- package/dist/psql/types/scanner.js +25 -22
- package/dist/psql/wire/connection.js +2042 -2803
- package/dist/psql/wire/copy.js +84 -100
- package/dist/psql/wire/notify.js +39 -59
- package/dist/psql/wire/pipeline.js +305 -518
- package/dist/psql/wire/protocol.js +349 -417
- package/dist/psql/wire/sasl.js +180 -265
- package/dist/psql/wire/tls.js +400 -561
- package/dist/storage_api.js +115 -129
- package/dist/test_utils/fixtures.js +94 -113
- package/dist/test_utils/oauth_server.js +10 -7
- package/dist/test_utils/project_dir.js +33 -0
- package/dist/utils/ai_gateway_notice.js +131 -162
- package/dist/utils/api_enums.js +21 -28
- package/dist/utils/auth.js +10 -4
- package/dist/utils/branch_notice.js +20 -19
- package/dist/utils/branch_picker.js +83 -89
- package/dist/utils/cli_name.js +15 -12
- package/dist/utils/compute_units.js +20 -27
- package/dist/utils/config_diff.js +127 -158
- package/dist/utils/enrichers.js +95 -148
- package/dist/utils/esbuild.js +130 -189
- package/dist/utils/flags.js +35 -47
- package/dist/utils/formats.js +8 -15
- package/dist/utils/git_diff.js +69 -80
- package/dist/utils/inspect_db.js +101 -143
- package/dist/utils/inspect_queries.js +179 -142
- package/dist/utils/middlewares.js +39 -45
- package/dist/utils/openapi.js +87 -99
- package/dist/utils/package_manager.js +312 -110
- package/dist/utils/point_in_time.js +49 -53
- package/dist/utils/psql.js +89 -106
- package/dist/utils/service_picker.js +55 -58
- package/dist/utils/string.js +5 -5
- package/dist/utils/ui.js +38 -55
- package/dist/utils/write_sync.js +26 -35
- package/dist/utils/zip.js +4 -3
- package/dist/writer.js +67 -87
- package/package.json +11 -6
- package/dist/_shared/auth_selection.js +0 -86
- package/dist/_shared/credentials.js +0 -209
- package/dist/_shared/env-core/env.js +0 -558
- package/dist/_shared/env-core/reuse-secrets.js +0 -223
- package/dist/_shared/paths.js +0 -148
- package/dist/_shared/profiles.js +0 -276
- package/dist/_shared/secure_file.js +0 -43
package/dist/psql/scanner/sql.js
CHANGED
|
@@ -1,914 +1,470 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* psql SQL scanner — statement boundary detection.
|
|
3
|
-
*
|
|
4
|
-
* Hand-port of PostgreSQL's `src/fe_utils/psqlscan.l`. The upstream is a
|
|
5
|
-
* flex-generated state machine concerned with one thing: finding the end of a
|
|
6
|
-
* SQL statement — a semicolon that is *not* inside a quote, comment, paren or
|
|
7
|
-
* dollar-quoted block. Backslash commands at the top level are also detected.
|
|
8
|
-
*
|
|
9
|
-
* We deliberately do **not** mechanically translate the flex rules; that would
|
|
10
|
-
* produce dense, untestable code. Instead we collapse the upstream exclusive
|
|
11
|
-
* states into a single integer-tagged state machine inside {@link scanSql} and
|
|
12
|
-
* cover behaviour with a >40-case differential corpus in `sql.test.ts`.
|
|
13
|
-
*
|
|
14
|
-
* Upstream exclusive states and our mapping:
|
|
15
|
-
*
|
|
16
|
-
* - `<xc>` extended C-style comment (with nested-depth tracking) → `Mode.BlockComment`
|
|
17
|
-
* - `<xq>` standard single-quoted string → `Mode.SingleQuote`
|
|
18
|
-
* - `<xe>` extended single-quoted string (`E'…'`) → `Mode.SingleQuote` + `escape=true`
|
|
19
|
-
* - `<xqs>` quote-stop (lookahead for continuation across newlines) → folded into the
|
|
20
|
-
* `SingleQuote` exit logic via {@link tryQuoteContinue}: after the closing
|
|
21
|
-
* `'` of a single-quoted string we look ahead through whitespace; if we
|
|
22
|
-
* find a newline followed by another `'`, we re-enter the SingleQuote
|
|
23
|
-
* state so the two pieces concatenate per SQL standard.
|
|
24
|
-
* - `<xd>` double-quoted identifier → `Mode.DoubleQuote`
|
|
25
|
-
* - `<xdolq>` `$tag$…$tag$` dollar-quoted string → `Mode.DollarQuote`
|
|
26
|
-
* - `<xb>`, `<xh>`, `<xui>`, `<xus>` (bit / hex / unicode-quoted identifiers and strings)
|
|
27
|
-
* are folded into the standard single-/double-quoted paths because for
|
|
28
|
-
* statement-boundary purposes only the surrounding quote characters matter —
|
|
29
|
-
* no escapes inside them affect whether the closing quote is found.
|
|
30
|
-
*
|
|
31
|
-
* What's deliberately out of scope:
|
|
32
|
-
*
|
|
33
|
-
* - `COPY … FROM STDIN` data-line handling. Upstream's `<xcopy>` state is
|
|
34
|
-
* **mainloop-owned, not scanner-owned**: once libpq returns `PGRES_COPY_IN`
|
|
35
|
-
* the mainloop bypasses the SQL scanner entirely and forwards raw lines to
|
|
36
|
-
* `PQputCopyData` until it sees `\.` on a line by itself. Our mainloop has
|
|
37
|
-
* that wiring stubbed as WP-16 (see `src/psql/core/mainloop.ts`, comment
|
|
38
|
-
* near the top). The scanner state machine therefore has *nothing to do*
|
|
39
|
-
* here — when the mainloop is in copy mode it never calls `scanSql` until
|
|
40
|
-
* after `\.`. The contract is: `ScanState.promptStatus = 'copy'` is set by
|
|
41
|
-
* the mainloop (not by the scanner) while copy mode is active; the scanner
|
|
42
|
-
* only consumes it for PROMPT3 selection. See {@link computePromptStatus}.
|
|
43
|
-
* No scanner API change is required for COPY support to land — only the
|
|
44
|
-
* mainloop bypass logic.
|
|
45
|
-
* - Variable substitution `:var`, `:'var'`, `:"var"`. Upstream expands these
|
|
46
|
-
* inline via callbacks; we do the same when {@link scanSql} is given a
|
|
47
|
-
* `varLookup`. Substitution fires at top-level only — never inside SQL
|
|
48
|
-
* string literals, double-quoted identifiers, dollar-quoted blocks, or
|
|
49
|
-
* comments (matches upstream's `<INITIAL>` flex rule scope). The token
|
|
50
|
-
* `::` (PostgreSQL cast operator) is preserved verbatim. When no
|
|
51
|
-
* `varLookup` is supplied (legacy call site or `\set NEW :OLD` chains
|
|
52
|
-
* that should keep the literal), no substitution happens.
|
|
53
|
-
* - Tab-completion helpers (`psqlscan_test_*`). Not needed for the REPL.
|
|
54
|
-
* - U&'…' and U&"…" Unicode-escape forms — folded into the standard quoted paths;
|
|
55
|
-
* the `u&` prefix is treated as two identifier characters and the following quote
|
|
56
|
-
* starts the regular quoted run. Boundary detection is unaffected.
|
|
57
|
-
*
|
|
58
|
-
* CREATE FUNCTION / PROCEDURE bodies (fully handled — noted here because the
|
|
59
|
-
* surrounding flex machinery can make it look like an open question):
|
|
60
|
-
*
|
|
61
|
-
* - All three body shapes split correctly, so an embedded `;` never terminates
|
|
62
|
-
* the surrounding CREATE:
|
|
63
|
-
* • dollar-quoted bodies (`AS $$…$$`) — the modern idiom — via `<xdolq>`;
|
|
64
|
-
* • plain single-quoted bodies (`AS '…'`) — the pre-SQL-standard legacy
|
|
65
|
-
* idiom — via `<xq>` (the quote-state machine swallows the body, `;`s
|
|
66
|
-
* and all, with no special-casing required);
|
|
67
|
-
* • unquoted SQL-standard bodies (`BEGIN ATOMIC … END`, PG14+) — via the
|
|
68
|
-
* `begin_depth` tracking implemented in {@link maybeTrackBeginEnd}, which
|
|
69
|
-
* mirrors upstream's `begin_depth` counter.
|
|
70
|
-
* The first two are covered by the quote machine and the third by
|
|
71
|
-
* `begin_depth`; together they span every body form a real query writes —
|
|
72
|
-
* there is no remaining "legacy shape" gap here. Pinned by the
|
|
73
|
-
* "Plain-string … CREATE FUNCTION bodies" and "BEGIN ATOMIC" corpus cases
|
|
74
|
-
* in `sql.test.ts`.
|
|
75
|
-
*
|
|
76
|
-
* Incremental API:
|
|
77
|
-
*
|
|
78
|
-
* Callers thread {@link ScanState} between calls. On each call we return the first
|
|
79
|
-
* boundary in `input`. For statement terminators we hand back the SQL up to and
|
|
80
|
-
* including the `;`. For backslash commands at the top of the buffer we return
|
|
81
|
-
* the command name and the rest of the line (without consuming further input).
|
|
82
|
-
* When the chunk ends mid-statement we return `'incomplete'` (still inside a
|
|
83
|
-
* quote/comment/paren/dollar) or `'eof'` (clean break at end of buffer) with the
|
|
84
|
-
* residue and current state, and the caller is expected to read more input and
|
|
85
|
-
* call again.
|
|
86
|
-
*/
|
|
87
|
-
import { initialScanState } from "../types/scanner.js";
|
|
88
1
|
import { tryConsumeVarSubstitution } from "./stringutils.js";
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
// Upstream uses POSIX char classes inside flex. We mirror them as regex tests on
|
|
93
|
-
// the JS string. Bytes >= 0x80 are accepted in identifier positions to match
|
|
94
|
-
// upstream's `\200-\377` class — useful for dollar-quote tags that contain
|
|
95
|
-
// non-ASCII identifier characters in 8-bit encodings.
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
2
|
+
import { initialScanState } from "../types/scanner.js";
|
|
3
|
+
//#region src/psql/scanner/sql.ts
|
|
97
4
|
const IDENT_START_RE = /[A-Za-z_\u0080-\uffff]/;
|
|
98
5
|
const IDENT_CONT_RE = /[A-Za-z0-9_\u0080-\uffff]/;
|
|
99
|
-
const isIdentStart = (c) => c !==
|
|
100
|
-
const isIdentCont = (c) => c !==
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// State cloning.
|
|
103
|
-
//
|
|
104
|
-
// `initialScanState()` returns a fresh ScanState. We treat ScanState as
|
|
105
|
-
// immutable from the caller's perspective — every result returns a fresh
|
|
106
|
-
// nextState — but mutate a local working copy inside scanSql() for speed.
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
6
|
+
const isIdentStart = (c) => c !== void 0 && IDENT_START_RE.test(c);
|
|
7
|
+
const isIdentCont = (c) => c !== void 0 && IDENT_CONT_RE.test(c);
|
|
108
8
|
const cloneState = (s) => ({
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
9
|
+
promptStatus: s.promptStatus,
|
|
10
|
+
parenDepth: s.parenDepth,
|
|
11
|
+
dollarTag: s.dollarTag,
|
|
12
|
+
inLineComment: s.inLineComment,
|
|
13
|
+
inBlockComment: s.inBlockComment,
|
|
14
|
+
inSingleQuote: s.inSingleQuote,
|
|
15
|
+
inDoubleQuote: s.inDoubleQuote,
|
|
16
|
+
inEscapeString: s.inEscapeString,
|
|
17
|
+
beginDepth: s.beginDepth,
|
|
18
|
+
identifierLetters: [
|
|
19
|
+
s.identifierLetters[0],
|
|
20
|
+
s.identifierLetters[1],
|
|
21
|
+
s.identifierLetters[2],
|
|
22
|
+
s.identifierLetters[3]
|
|
23
|
+
],
|
|
24
|
+
identifierCount: s.identifierCount
|
|
125
25
|
});
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// when the prefix looks like `CREATE [OR REPLACE] {FUNCTION|PROCEDURE}` —
|
|
133
|
-
// i.e. exactly one of `c f`, `c p`, `c o r f`, `c o r p`. Inside that
|
|
134
|
-
// window, `BEGIN` increments depth, `CASE` increments depth ONLY when
|
|
135
|
-
// already inside a BEGIN (so that bare `SELECT CASE ... END` outside a
|
|
136
|
-
// function body doesn't unbalance the counter), and `END` decrements depth.
|
|
137
|
-
// While `begin_depth > 0`, a top-level `;` is not a statement terminator —
|
|
138
|
-
// see the gated `LEXRES_SEMI` return in `scanSql`.
|
|
139
|
-
//
|
|
140
|
-
// We mirror this behaviour with {@link maybeTrackBeginEnd}, which is fed
|
|
141
|
-
// every lexed identifier at top level (outside quotes, comments, paren
|
|
142
|
-
// blocks, dollar-quoted bodies). Identifier letters are stored lower-cased.
|
|
143
|
-
// ---------------------------------------------------------------------------
|
|
144
|
-
const KEYWORD_PREFIX_LETTERS = new Set([
|
|
145
|
-
"c", // create
|
|
146
|
-
"f", // function
|
|
147
|
-
"p", // procedure
|
|
148
|
-
"o", // or
|
|
149
|
-
"r", // replace
|
|
26
|
+
const KEYWORD_PREFIX_LETTERS = /* @__PURE__ */ new Set([
|
|
27
|
+
"c",
|
|
28
|
+
"f",
|
|
29
|
+
"p",
|
|
30
|
+
"o",
|
|
31
|
+
"r"
|
|
150
32
|
]);
|
|
151
33
|
const PREFIX_MATCHES_CREATE_FN_OR_PROC = (letters) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// CREATE PROCEDURE
|
|
158
|
-
if (letters[1] === "p")
|
|
159
|
-
return true;
|
|
160
|
-
// CREATE OR REPLACE FUNCTION / PROCEDURE
|
|
161
|
-
if (letters[1] === "o" &&
|
|
162
|
-
letters[2] === "r" &&
|
|
163
|
-
(letters[3] === "f" || letters[3] === "p")) {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
return false;
|
|
34
|
+
if (letters[0] !== "c") return false;
|
|
35
|
+
if (letters[1] === "f") return true;
|
|
36
|
+
if (letters[1] === "p") return true;
|
|
37
|
+
if (letters[1] === "o" && letters[2] === "r" && (letters[3] === "f" || letters[3] === "p")) return true;
|
|
38
|
+
return false;
|
|
167
39
|
};
|
|
168
40
|
const maybeTrackBeginEnd = (st, word) => {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
st.identifierCount++;
|
|
188
|
-
if (!PREFIX_MATCHES_CREATE_FN_OR_PROC(st.identifierLetters))
|
|
189
|
-
return;
|
|
190
|
-
if (lower === "begin") {
|
|
191
|
-
st.beginDepth++;
|
|
192
|
-
}
|
|
193
|
-
else if (lower === "case") {
|
|
194
|
-
// Upstream comment: "CASE also ends with END. We only need to track
|
|
195
|
-
// this if we are already inside a BEGIN." Guard so `SELECT CASE WHEN
|
|
196
|
-
// ... END` outside a function body doesn't double-bump the counter.
|
|
197
|
-
if (st.beginDepth >= 1)
|
|
198
|
-
st.beginDepth++;
|
|
199
|
-
}
|
|
200
|
-
else if (lower === "end") {
|
|
201
|
-
if (st.beginDepth > 0)
|
|
202
|
-
st.beginDepth--;
|
|
203
|
-
}
|
|
41
|
+
if (word.length === 0) return;
|
|
42
|
+
if (st.parenDepth !== 0) return;
|
|
43
|
+
const lower = word.toLowerCase();
|
|
44
|
+
if (st.identifierCount === 0) st.identifierLetters = [
|
|
45
|
+
"",
|
|
46
|
+
"",
|
|
47
|
+
"",
|
|
48
|
+
""
|
|
49
|
+
];
|
|
50
|
+
if (st.identifierCount < st.identifierLetters.length && KEYWORD_PREFIX_LETTERS.has(lower[0])) st.identifierLetters[st.identifierCount] = lower[0];
|
|
51
|
+
st.identifierCount++;
|
|
52
|
+
if (!PREFIX_MATCHES_CREATE_FN_OR_PROC(st.identifierLetters)) return;
|
|
53
|
+
if (lower === "begin") st.beginDepth++;
|
|
54
|
+
else if (lower === "case") {
|
|
55
|
+
if (st.beginDepth >= 1) st.beginDepth++;
|
|
56
|
+
} else if (lower === "end") {
|
|
57
|
+
if (st.beginDepth > 0) st.beginDepth--;
|
|
58
|
+
}
|
|
204
59
|
};
|
|
205
60
|
const matchDollarDelim = (s, i) => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
61
|
+
if (s[i] !== "$") return null;
|
|
62
|
+
if (s[i + 1] === "$") return {
|
|
63
|
+
tag: "",
|
|
64
|
+
end: i + 2
|
|
65
|
+
};
|
|
66
|
+
if (!isIdentStart(s[i + 1])) return null;
|
|
67
|
+
let j = i + 2;
|
|
68
|
+
while (j < s.length && isIdentCont(s[j])) j++;
|
|
69
|
+
if (s[j] !== "$") return null;
|
|
70
|
+
return {
|
|
71
|
+
tag: s.slice(i + 1, j),
|
|
72
|
+
end: j + 1
|
|
73
|
+
};
|
|
219
74
|
};
|
|
220
|
-
// ---------------------------------------------------------------------------
|
|
221
|
-
// Determine whether a `'` at position `i` starts an extended string (E'…').
|
|
222
|
-
//
|
|
223
|
-
// Upstream `{xestart}` is `[eE]{quote}`. We require the `E` to be at a token
|
|
224
|
-
// boundary so that an identifier ending in `E` (like `THREE'foo'`) doesn't
|
|
225
|
-
// mistakenly enable backslash escapes. Upstream's flex resolves this by
|
|
226
|
-
// preferring the longer identifier match; we approximate with a "previous char
|
|
227
|
-
// is not an identifier continuation" check.
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
75
|
const isExtendedStringStart = (input, quotePos) => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Must be a standalone E — not part of a longer identifier.
|
|
236
|
-
if (quotePos >= 2 && isIdentCont(input[quotePos - 2]))
|
|
237
|
-
return false;
|
|
238
|
-
return true;
|
|
76
|
+
if (quotePos === 0) return false;
|
|
77
|
+
const prev = input[quotePos - 1];
|
|
78
|
+
if (prev !== "E" && prev !== "e") return false;
|
|
79
|
+
if (quotePos >= 2 && isIdentCont(input[quotePos - 2])) return false;
|
|
80
|
+
return true;
|
|
239
81
|
};
|
|
240
|
-
// ---------------------------------------------------------------------------
|
|
241
|
-
// Skip helpers used while inside an extended single-quoted string. We need to
|
|
242
|
-
// recognise `\\`, `\'`, etc. so that an escaped quote does **not** close the
|
|
243
|
-
// string. Returns the count of characters consumed by the escape sequence (>=2
|
|
244
|
-
// when an escape was recognised, or 1 to advance past a non-escape backslash —
|
|
245
|
-
// matching upstream's `<xe>.` fallback).
|
|
246
|
-
// ---------------------------------------------------------------------------
|
|
247
82
|
const consumeXeEscape = (input, i) => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
k++;
|
|
288
|
-
count++;
|
|
289
|
-
}
|
|
290
|
-
return k - i;
|
|
291
|
-
}
|
|
292
|
-
// Any other char (including `'`, `\`, `n`, `t`, etc.) — consume both chars.
|
|
293
|
-
return 2;
|
|
83
|
+
const n = input[i + 1];
|
|
84
|
+
if (n === void 0) return 1;
|
|
85
|
+
if (n >= "0" && n <= "7") {
|
|
86
|
+
let k = i + 2;
|
|
87
|
+
let count = 1;
|
|
88
|
+
while (k < input.length && count < 3 && input[k] >= "0" && input[k] <= "7") {
|
|
89
|
+
k++;
|
|
90
|
+
count++;
|
|
91
|
+
}
|
|
92
|
+
return k - i;
|
|
93
|
+
}
|
|
94
|
+
if (n === "x") {
|
|
95
|
+
let k = i + 2;
|
|
96
|
+
let count = 0;
|
|
97
|
+
while (k < input.length && count < 2 && /[0-9a-fA-F]/.test(input[k])) {
|
|
98
|
+
k++;
|
|
99
|
+
count++;
|
|
100
|
+
}
|
|
101
|
+
return k - i;
|
|
102
|
+
}
|
|
103
|
+
if (n === "u") {
|
|
104
|
+
let k = i + 2;
|
|
105
|
+
let count = 0;
|
|
106
|
+
while (k < input.length && count < 4 && /[0-9a-fA-F]/.test(input[k])) {
|
|
107
|
+
k++;
|
|
108
|
+
count++;
|
|
109
|
+
}
|
|
110
|
+
return k - i;
|
|
111
|
+
}
|
|
112
|
+
if (n === "U") {
|
|
113
|
+
let k = i + 2;
|
|
114
|
+
let count = 0;
|
|
115
|
+
while (k < input.length && count < 8 && /[0-9a-fA-F]/.test(input[k])) {
|
|
116
|
+
k++;
|
|
117
|
+
count++;
|
|
118
|
+
}
|
|
119
|
+
return k - i;
|
|
120
|
+
}
|
|
121
|
+
return 2;
|
|
294
122
|
};
|
|
295
|
-
// ---------------------------------------------------------------------------
|
|
296
|
-
// `<xqs>` quote-continuation lookahead. SQL standard: two single-quoted
|
|
297
|
-
// strings separated only by whitespace that **contains at least one newline**
|
|
298
|
-
// concatenate into a single logical literal (`'abc'\n'def'` == `'abcdef'`).
|
|
299
|
-
// Whitespace without a newline is **not** a continuation — the strings stay
|
|
300
|
-
// separate at the lexer level (and would be a syntax error in most contexts,
|
|
301
|
-
// which is the parser's problem, not ours).
|
|
302
|
-
//
|
|
303
|
-
// Returns the index of the new opening `'` if a continuation is found, or
|
|
304
|
-
// `null` otherwise. `i` is the position just past the closing `'` of the
|
|
305
|
-
// previous string. We do not consume `--` line comments or `/* */` block
|
|
306
|
-
// comments inside the gap; upstream's flex rules treat the gap as plain
|
|
307
|
-
// whitespace per the lexical spec. We also avoid descending into block
|
|
308
|
-
// comments because that would require recursive comment-depth tracking on
|
|
309
|
-
// the lookahead path.
|
|
310
|
-
// ---------------------------------------------------------------------------
|
|
311
123
|
const tryQuoteContinue = (input, i) => {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return null;
|
|
331
|
-
return k;
|
|
124
|
+
let k = i;
|
|
125
|
+
let sawNewline = false;
|
|
126
|
+
while (k < input.length) {
|
|
127
|
+
const c = input[k];
|
|
128
|
+
if (c === "\n" || c === "\r") {
|
|
129
|
+
sawNewline = true;
|
|
130
|
+
k++;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (c === " " || c === " " || c === "\f" || c === "\v") {
|
|
134
|
+
k++;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
if (!sawNewline) return null;
|
|
140
|
+
if (input[k] !== "'") return null;
|
|
141
|
+
return k;
|
|
332
142
|
};
|
|
333
|
-
// ---------------------------------------------------------------------------
|
|
334
|
-
// Recognise a `--` line comment at position `i`. Returns the index just past
|
|
335
|
-
// the terminating newline (or end of input). Boundary semantics: the entire
|
|
336
|
-
// span is part of the surrounding SQL.
|
|
337
|
-
// ---------------------------------------------------------------------------
|
|
338
143
|
const skipLineComment = (input, i) => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
k++;
|
|
343
|
-
return k;
|
|
144
|
+
let k = i + 2;
|
|
145
|
+
while (k < input.length && input[k] !== "\n" && input[k] !== "\r") k++;
|
|
146
|
+
return k;
|
|
344
147
|
};
|
|
345
|
-
// ---------------------------------------------------------------------------
|
|
346
|
-
// Compute the PROMPT2 status for an incomplete chunk. Matches upstream's
|
|
347
|
-
// `promptStatus_t`: the *reason* we need more input drives the `%R` rendering
|
|
348
|
-
// under PROMPT2 (single-quote → `'`, double-quote → `"`, dollar-quote → `$`,
|
|
349
|
-
// block comment → `*`, paren → `(`, otherwise → `-`).
|
|
350
|
-
//
|
|
351
|
-
// Precedence mirrors upstream `psql_scan_get_prompt`: block comment first
|
|
352
|
-
// (because `/*` can wrap anything else), then quoted-state checks, then
|
|
353
|
-
// paren depth. Plain "buffer's not empty but no special state" falls through
|
|
354
|
-
// to `'continue'`.
|
|
355
|
-
// ---------------------------------------------------------------------------
|
|
356
148
|
const computePromptStatus = (state) => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (state.dollarTag !== null)
|
|
364
|
-
return "continue-dollar";
|
|
365
|
-
if (state.parenDepth > 0)
|
|
366
|
-
return "paren";
|
|
367
|
-
return "continue";
|
|
149
|
+
if (state.inBlockComment > 0) return "comment";
|
|
150
|
+
if (state.inSingleQuote) return "continue-quote";
|
|
151
|
+
if (state.inDoubleQuote) return "continue-dquote";
|
|
152
|
+
if (state.dollarTag !== null) return "continue-dollar";
|
|
153
|
+
if (state.parenDepth > 0) return "paren";
|
|
154
|
+
return "continue";
|
|
368
155
|
};
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
// multi-word commands: `\lo_import`, `\lo_export`, `\lo_list`,
|
|
636
|
-
// `\lo_unlink`, `\bind_named`, `\close_prepared`.
|
|
637
|
-
while (cmdEnd < input.length &&
|
|
638
|
-
/[A-Za-z0-9_+]/.test(input[cmdEnd])) {
|
|
639
|
-
cmdEnd++;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
else if (first !== undefined && /[?!|]/.test(first)) {
|
|
643
|
-
cmdEnd = i + 1;
|
|
644
|
-
}
|
|
645
|
-
else {
|
|
646
|
-
// Empty or strange char: treat as a zero-length command and let the
|
|
647
|
-
// dispatcher report "unknown command".
|
|
648
|
-
cmdEnd = i;
|
|
649
|
-
}
|
|
650
|
-
const cmd = input.slice(i, cmdEnd);
|
|
651
|
-
// Determine the command's argument-mode hint (if any). Upstream's
|
|
652
|
-
// psqlscanslash.l flips between `<xslasharg>` (default; breaks on `\`)
|
|
653
|
-
// and `<xslashwholeline>` (no `\` break) based on `option_type`. We
|
|
654
|
-
// recreate that decision here so commands declared with `argMode:
|
|
655
|
-
// 'whole-line'` (`\!`, `\sf`, `\sv`, `\copy`, `\help`, etc.) capture
|
|
656
|
-
// embedded `\else` / `\endif` etc. as plain argument text instead of
|
|
657
|
-
// having the scanner treat them as a new command boundary. Filepipe
|
|
658
|
-
// commands enter whole-line mode only when the first non-whitespace
|
|
659
|
-
// character is `|` — matching upstream's `<xslashargstart>` rule.
|
|
660
|
-
const argMode = slashCmdMode?.(cmd);
|
|
661
|
-
let consumeWholeLine = argMode === "whole-line";
|
|
662
|
-
if (argMode === "filepipe") {
|
|
663
|
-
// Skip leading whitespace inside the arg; if the next char is `|`,
|
|
664
|
-
// upstream switches into `<xslashwholeline>` for the rest of the line.
|
|
665
|
-
let p = cmdEnd;
|
|
666
|
-
while (p < input.length &&
|
|
667
|
-
(input[p] === " " || input[p] === "\t")) {
|
|
668
|
-
p++;
|
|
669
|
-
}
|
|
670
|
-
if (input[p] === "|")
|
|
671
|
-
consumeWholeLine = true;
|
|
672
|
-
}
|
|
673
|
-
// Rest of the line — up to a newline OR (for normal-mode commands)
|
|
674
|
-
// the next unquoted backslash command on the same line. Mirrors
|
|
675
|
-
// upstream `psqlscanslash.l`'s <xslasharg> exit-on-`\` rule: a second
|
|
676
|
-
// backslash on the same line STARTS a new slash command and
|
|
677
|
-
// terminates the previous one's arg list. We track minimal quote
|
|
678
|
-
// state (single, double, back) so backslashes inside arg quotes
|
|
679
|
-
// don't trigger the boundary.
|
|
680
|
-
//
|
|
681
|
-
// Upstream special: `\\` (two consecutive backslashes) is the
|
|
682
|
-
// "flush-and-continue" separator. It terminates the current command's
|
|
683
|
-
// args AND is itself consumed silently — the next iteration's input
|
|
684
|
-
// starts immediately after the `\\` pair. Without this, our scanner
|
|
685
|
-
// would surface the second `\` as a fresh empty-name backslash command
|
|
686
|
-
// (and the dispatcher would log "invalid command \"), spuriously
|
|
687
|
-
// doubling diagnostics for shapes like `\gset pref \\ \echo foo`.
|
|
688
|
-
// See psqlscanslash.l <xslasharg> rule for the upstream equivalent.
|
|
689
|
-
//
|
|
690
|
-
// Whole-line mode (`\!`, `\sf`, `\sv`, `\help`, `\copy`, and filepipe
|
|
691
|
-
// commands with a leading `|`) skips the `\` boundary entirely:
|
|
692
|
-
// upstream's `<xslashwholeline>` state only matches `{space}+` and
|
|
693
|
-
// `{other}`, with no rule for `\` — so `\` characters end up in the
|
|
694
|
-
// ECHO sink as plain argument text.
|
|
695
|
-
let restEnd = cmdEnd;
|
|
696
|
-
let inSingle = false;
|
|
697
|
-
let inDouble = false;
|
|
698
|
-
let inBack = false;
|
|
699
|
-
// Set true when we exit the loop via the `\\` separator branch. In that
|
|
700
|
-
// case `consumed` skips both backslashes so the next scan doesn't see
|
|
701
|
-
// the trailing `\` as a fresh empty-name backslash command.
|
|
702
|
-
let sawDoubleBackslashSeparator = false;
|
|
703
|
-
while (restEnd < input.length &&
|
|
704
|
-
input[restEnd] !== "\n" &&
|
|
705
|
-
input[restEnd] !== "\r") {
|
|
706
|
-
const ch = input[restEnd];
|
|
707
|
-
if (consumeWholeLine) {
|
|
708
|
-
// Whole-line / filepipe-pipe path: never break on `\`. Just walk
|
|
709
|
-
// to end of line so the entire tail lands in `rest`.
|
|
710
|
-
restEnd++;
|
|
711
|
-
continue;
|
|
712
|
-
}
|
|
713
|
-
if (inSingle) {
|
|
714
|
-
// C-style `\'` escape inside single quotes.
|
|
715
|
-
if (ch === "\\" && input[restEnd + 1] !== undefined) {
|
|
716
|
-
restEnd += 2;
|
|
717
|
-
continue;
|
|
718
|
-
}
|
|
719
|
-
if (ch === "'")
|
|
720
|
-
inSingle = false;
|
|
721
|
-
}
|
|
722
|
-
else if (inDouble) {
|
|
723
|
-
if (ch === '"')
|
|
724
|
-
inDouble = false;
|
|
725
|
-
}
|
|
726
|
-
else if (inBack) {
|
|
727
|
-
if (ch === "`")
|
|
728
|
-
inBack = false;
|
|
729
|
-
}
|
|
730
|
-
else {
|
|
731
|
-
if (ch === "\\") {
|
|
732
|
-
// `\\` = "flush and continue" separator: end this command's
|
|
733
|
-
// args HERE, but consume both backslashes so the next scan
|
|
734
|
-
// resumes immediately after the pair. A lone `\` is a regular
|
|
735
|
-
// next-slash-cmd boundary; we stop at it without consuming so
|
|
736
|
-
// the next iteration picks it up.
|
|
737
|
-
if (input[restEnd + 1] === "\\") {
|
|
738
|
-
sawDoubleBackslashSeparator = true;
|
|
739
|
-
}
|
|
740
|
-
break;
|
|
741
|
-
}
|
|
742
|
-
if (ch === "'")
|
|
743
|
-
inSingle = true;
|
|
744
|
-
else if (ch === '"')
|
|
745
|
-
inDouble = true;
|
|
746
|
-
else if (ch === "`")
|
|
747
|
-
inBack = true;
|
|
748
|
-
}
|
|
749
|
-
restEnd++;
|
|
750
|
-
}
|
|
751
|
-
const rest = input.slice(cmdEnd, restEnd);
|
|
752
|
-
const consumed = sawDoubleBackslashSeparator
|
|
753
|
-
? restEnd + 2
|
|
754
|
-
: restEnd;
|
|
755
|
-
// Note: we *don't* consume the newline; it's left for the next chunk
|
|
756
|
-
// so caller can see PROMPT1 reset cleanly. Upstream's
|
|
757
|
-
// `psql_scan_slash_command_end()` does eat the trailing newline, but
|
|
758
|
-
// doing so HERE would lose the inter-line `\n` separator when a
|
|
759
|
-
// non-buffer-consuming slash command (e.g. `\echo`) sits between two
|
|
760
|
-
// lines of a continuing multi-line query. We compensate elsewhere:
|
|
761
|
-
// the mainloop's `reset-buf` branch strips a residual leading `\n`
|
|
762
|
-
// from the working chunk so the next statement's queryBuf doesn't
|
|
763
|
-
// pick it up via the `eof` accumulation path.
|
|
764
|
-
return {
|
|
765
|
-
kind: "backslash",
|
|
766
|
-
cmd,
|
|
767
|
-
rest,
|
|
768
|
-
sql,
|
|
769
|
-
consumed,
|
|
770
|
-
nextState: cloneState(st),
|
|
771
|
-
};
|
|
772
|
-
}
|
|
773
|
-
// Variable substitution (`:NAME`, `:'NAME'`, `:"NAME"`). Only fires at
|
|
774
|
-
// top level — not inside strings / dollar-quoted blocks / identifiers /
|
|
775
|
-
// comments, all of which are handled above this point.
|
|
776
|
-
//
|
|
777
|
-
// `::` (PostgreSQL cast operator): emit BOTH colons as a single unit so
|
|
778
|
-
// the second `:` doesn't get re-examined on the next iteration and
|
|
779
|
-
// wrongly interpreted as the start of a `:NAME` substitution. This is
|
|
780
|
-
// load-bearing for `'foo'::int`-style casts and matches upstream
|
|
781
|
-
// `psqlscan.l`, which absorbs the `::` via its `{op_chars}+` operator
|
|
782
|
-
// rule before the `:{variable}` rule can fire. We do not need to gate
|
|
783
|
-
// this on `varLookup` — without substitution the result is byte-
|
|
784
|
-
// identical to the catch-all path.
|
|
785
|
-
if (c === ":" && input[i + 1] === ":") {
|
|
786
|
-
sql += "::";
|
|
787
|
-
i += 2;
|
|
788
|
-
continue;
|
|
789
|
-
}
|
|
790
|
-
if (c === ":") {
|
|
791
|
-
const sub = tryConsumeVarSubstitution(input, i, varLookup);
|
|
792
|
-
if (sub !== null) {
|
|
793
|
-
sql += sub.text;
|
|
794
|
-
i = sub.end;
|
|
795
|
-
continue;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
// Identifier — lexed as a whole token so we can run upstream's
|
|
799
|
-
// `psqlscan.l` `{identifier}` rule that gates `BEGIN`/`CASE`/`END`
|
|
800
|
-
// depth tracking on a leading `CREATE [OR REPLACE] {FUNCTION|PROCEDURE}`
|
|
801
|
-
// signature. We consume `[A-Za-z_][A-Za-z0-9_]*` greedily; ASCII letters
|
|
802
|
-
// are sufficient for the keywords we recognise (upstream's `identifier`
|
|
803
|
-
// class is wider but the BEGIN-tracking logic only cares about lowercased
|
|
804
|
-
// letter prefixes of these specific keywords).
|
|
805
|
-
if (/[A-Za-z_]/.test(c)) {
|
|
806
|
-
let j = i + 1;
|
|
807
|
-
while (j < input.length && /[A-Za-z0-9_]/.test(input[j]))
|
|
808
|
-
j++;
|
|
809
|
-
const word = input.slice(i, j);
|
|
810
|
-
maybeTrackBeginEnd(st, word);
|
|
811
|
-
sql += word;
|
|
812
|
-
i = j;
|
|
813
|
-
continue;
|
|
814
|
-
}
|
|
815
|
-
// Anything else: just emit. This is the catch-all matching upstream's
|
|
816
|
-
// `{self}`, `{operator}`, `{numeric}`, `{other}` rules — none of which
|
|
817
|
-
// can change scanner state at the top level.
|
|
818
|
-
sql += c;
|
|
819
|
-
i++;
|
|
820
|
-
}
|
|
821
|
-
// --- End of input. ---
|
|
822
|
-
//
|
|
823
|
-
// Decide between 'incomplete' (still inside something) and 'eof' (clean
|
|
824
|
-
// break, but no semicolon was found in this chunk).
|
|
825
|
-
const hasOpenContext = st.inBlockComment > 0 ||
|
|
826
|
-
st.inSingleQuote ||
|
|
827
|
-
st.inDoubleQuote ||
|
|
828
|
-
st.dollarTag !== null ||
|
|
829
|
-
st.parenDepth > 0;
|
|
830
|
-
if (hasOpenContext) {
|
|
831
|
-
const promptStatus = computePromptStatus(st);
|
|
832
|
-
st.promptStatus = promptStatus;
|
|
833
|
-
return {
|
|
834
|
-
kind: "incomplete",
|
|
835
|
-
sql,
|
|
836
|
-
consumed: i,
|
|
837
|
-
nextState: cloneState(st),
|
|
838
|
-
promptStatus,
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
// COPY-data handling is a mainloop concern, not a scanner concern.
|
|
842
|
-
// After a `COPY ... FROM STDIN` statement libpq returns PGRES_COPY_IN; the
|
|
843
|
-
// mainloop bypasses the scanner and forwards raw lines until `\.`. See the
|
|
844
|
-
// file header for the contract.
|
|
845
|
-
return {
|
|
846
|
-
kind: "eof",
|
|
847
|
-
sql,
|
|
848
|
-
consumed: i,
|
|
849
|
-
nextState: cloneState(st),
|
|
850
|
-
};
|
|
156
|
+
const scanSql = (input, state, varLookup, slashCmdMode, options) => {
|
|
157
|
+
const singleline = options?.singleline ?? false;
|
|
158
|
+
const st = cloneState(state ?? initialScanState());
|
|
159
|
+
let sql = "";
|
|
160
|
+
let i = 0;
|
|
161
|
+
const emit = (from, to) => {
|
|
162
|
+
sql += input.slice(from, to);
|
|
163
|
+
i = to;
|
|
164
|
+
};
|
|
165
|
+
while (i < input.length) {
|
|
166
|
+
const c = input[i];
|
|
167
|
+
if (st.inBlockComment > 0) {
|
|
168
|
+
if (c === "/" && input[i + 1] === "*") {
|
|
169
|
+
st.inBlockComment++;
|
|
170
|
+
sql += "/*";
|
|
171
|
+
i += 2;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (c === "*" && input[i + 1] === "/") {
|
|
175
|
+
st.inBlockComment--;
|
|
176
|
+
sql += "*/";
|
|
177
|
+
i += 2;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
sql += c;
|
|
181
|
+
i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (st.inSingleQuote) {
|
|
185
|
+
if (st.inEscapeString && c === "\\") {
|
|
186
|
+
const n = consumeXeEscape(input, i);
|
|
187
|
+
emit(i, i + n);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (c === "'") {
|
|
191
|
+
if (input[i + 1] === "'") {
|
|
192
|
+
sql += "''";
|
|
193
|
+
i += 2;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
sql += "'";
|
|
197
|
+
i++;
|
|
198
|
+
const cont = tryQuoteContinue(input, i);
|
|
199
|
+
if (cont !== null) {
|
|
200
|
+
sql += input.slice(i, cont + 1);
|
|
201
|
+
i = cont + 1;
|
|
202
|
+
st.inEscapeString = isExtendedStringStart(input, cont);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
st.inSingleQuote = false;
|
|
206
|
+
st.inEscapeString = false;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
sql += c;
|
|
210
|
+
i++;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (st.inDoubleQuote) {
|
|
214
|
+
if (c === "\"") {
|
|
215
|
+
if (input[i + 1] === "\"") {
|
|
216
|
+
sql += "\"\"";
|
|
217
|
+
i += 2;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
sql += "\"";
|
|
221
|
+
i++;
|
|
222
|
+
st.inDoubleQuote = false;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
sql += c;
|
|
226
|
+
i++;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (st.dollarTag !== null) {
|
|
230
|
+
if (c === "$") {
|
|
231
|
+
const m = matchDollarDelim(input, i);
|
|
232
|
+
if (m !== null && m.tag === st.dollarTag) {
|
|
233
|
+
sql += input.slice(i, m.end);
|
|
234
|
+
i = m.end;
|
|
235
|
+
st.dollarTag = null;
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
sql += "$";
|
|
239
|
+
i++;
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
sql += c;
|
|
243
|
+
i++;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (singleline && (c === "\n" || c === "\r") && st.beginDepth === 0 && sql.trim().length > 0) {
|
|
247
|
+
sql += c;
|
|
248
|
+
i++;
|
|
249
|
+
return {
|
|
250
|
+
kind: "semicolon",
|
|
251
|
+
sql,
|
|
252
|
+
consumed: i,
|
|
253
|
+
nextState: initialScanState()
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (c === "/" && input[i + 1] === "*") {
|
|
257
|
+
st.inBlockComment = 1;
|
|
258
|
+
sql += "/*";
|
|
259
|
+
i += 2;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (c === "-" && input[i + 1] === "-") {
|
|
263
|
+
const end = skipLineComment(input, i);
|
|
264
|
+
sql += input.slice(i, end);
|
|
265
|
+
i = end;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (c === "\"") {
|
|
269
|
+
sql += "\"";
|
|
270
|
+
i++;
|
|
271
|
+
st.inDoubleQuote = true;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (c === "'") {
|
|
275
|
+
sql += "'";
|
|
276
|
+
i++;
|
|
277
|
+
st.inSingleQuote = true;
|
|
278
|
+
st.inEscapeString = isExtendedStringStart(input, i - 1);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (c === "$") {
|
|
282
|
+
const m = matchDollarDelim(input, i);
|
|
283
|
+
if (m !== null) {
|
|
284
|
+
sql += input.slice(i, m.end);
|
|
285
|
+
i = m.end;
|
|
286
|
+
st.dollarTag = m.tag;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
sql += "$";
|
|
290
|
+
i++;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (c === "(") {
|
|
294
|
+
sql += "(";
|
|
295
|
+
i++;
|
|
296
|
+
st.parenDepth++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (c === ")") {
|
|
300
|
+
sql += ")";
|
|
301
|
+
i++;
|
|
302
|
+
if (st.parenDepth > 0) st.parenDepth--;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (c === ";" && st.parenDepth === 0 && st.beginDepth === 0) {
|
|
306
|
+
sql += ";";
|
|
307
|
+
i++;
|
|
308
|
+
const next = initialScanState();
|
|
309
|
+
return {
|
|
310
|
+
kind: "semicolon",
|
|
311
|
+
sql,
|
|
312
|
+
consumed: i,
|
|
313
|
+
nextState: next
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (c === "\\") {
|
|
317
|
+
const nxt = input[i + 1];
|
|
318
|
+
if (nxt === ";" || nxt === ":") {
|
|
319
|
+
sql += nxt;
|
|
320
|
+
i += 2;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
i++;
|
|
324
|
+
let cmdEnd = i;
|
|
325
|
+
const first = input[i];
|
|
326
|
+
if (first !== void 0 && /[A-Za-z]/.test(first)) while (cmdEnd < input.length && /[A-Za-z0-9_+]/.test(input[cmdEnd])) cmdEnd++;
|
|
327
|
+
else if (first !== void 0 && /[?!|]/.test(first)) cmdEnd = i + 1;
|
|
328
|
+
else cmdEnd = i;
|
|
329
|
+
const cmd = input.slice(i, cmdEnd);
|
|
330
|
+
const argMode = slashCmdMode?.(cmd);
|
|
331
|
+
let consumeWholeLine = argMode === "whole-line";
|
|
332
|
+
if (argMode === "filepipe") {
|
|
333
|
+
let p = cmdEnd;
|
|
334
|
+
while (p < input.length && (input[p] === " " || input[p] === " ")) p++;
|
|
335
|
+
if (input[p] === "|") consumeWholeLine = true;
|
|
336
|
+
}
|
|
337
|
+
let restEnd = cmdEnd;
|
|
338
|
+
let inSingle = false;
|
|
339
|
+
let inDouble = false;
|
|
340
|
+
let inBack = false;
|
|
341
|
+
let sawDoubleBackslashSeparator = false;
|
|
342
|
+
while (restEnd < input.length && input[restEnd] !== "\n" && input[restEnd] !== "\r") {
|
|
343
|
+
const ch = input[restEnd];
|
|
344
|
+
if (consumeWholeLine) {
|
|
345
|
+
restEnd++;
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (inSingle) {
|
|
349
|
+
if (ch === "\\" && input[restEnd + 1] !== void 0) {
|
|
350
|
+
restEnd += 2;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
if (ch === "'") inSingle = false;
|
|
354
|
+
} else if (inDouble) {
|
|
355
|
+
if (ch === "\"") inDouble = false;
|
|
356
|
+
} else if (inBack) {
|
|
357
|
+
if (ch === "`") inBack = false;
|
|
358
|
+
} else {
|
|
359
|
+
if (ch === "\\") {
|
|
360
|
+
if (input[restEnd + 1] === "\\") sawDoubleBackslashSeparator = true;
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
if (ch === "'") inSingle = true;
|
|
364
|
+
else if (ch === "\"") inDouble = true;
|
|
365
|
+
else if (ch === "`") inBack = true;
|
|
366
|
+
}
|
|
367
|
+
restEnd++;
|
|
368
|
+
}
|
|
369
|
+
const rest = input.slice(cmdEnd, restEnd);
|
|
370
|
+
const consumed = sawDoubleBackslashSeparator ? restEnd + 2 : restEnd;
|
|
371
|
+
return {
|
|
372
|
+
kind: "backslash",
|
|
373
|
+
cmd,
|
|
374
|
+
rest,
|
|
375
|
+
sql,
|
|
376
|
+
consumed,
|
|
377
|
+
nextState: cloneState(st)
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
if (c === ":" && input[i + 1] === ":") {
|
|
381
|
+
sql += "::";
|
|
382
|
+
i += 2;
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (c === ":") {
|
|
386
|
+
const sub = tryConsumeVarSubstitution(input, i, varLookup);
|
|
387
|
+
if (sub !== null) {
|
|
388
|
+
sql += sub.text;
|
|
389
|
+
i = sub.end;
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (/[A-Za-z_]/.test(c)) {
|
|
394
|
+
let j = i + 1;
|
|
395
|
+
while (j < input.length && /[A-Za-z0-9_]/.test(input[j])) j++;
|
|
396
|
+
const word = input.slice(i, j);
|
|
397
|
+
maybeTrackBeginEnd(st, word);
|
|
398
|
+
sql += word;
|
|
399
|
+
i = j;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
sql += c;
|
|
403
|
+
i++;
|
|
404
|
+
}
|
|
405
|
+
if (st.inBlockComment > 0 || st.inSingleQuote || st.inDoubleQuote || st.dollarTag !== null || st.parenDepth > 0) {
|
|
406
|
+
const promptStatus = computePromptStatus(st);
|
|
407
|
+
st.promptStatus = promptStatus;
|
|
408
|
+
return {
|
|
409
|
+
kind: "incomplete",
|
|
410
|
+
sql,
|
|
411
|
+
consumed: i,
|
|
412
|
+
nextState: cloneState(st),
|
|
413
|
+
promptStatus
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
kind: "eof",
|
|
418
|
+
sql,
|
|
419
|
+
consumed: i,
|
|
420
|
+
nextState: cloneState(st)
|
|
421
|
+
};
|
|
851
422
|
};
|
|
852
423
|
/**
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
remaining = remaining.slice(r.consumed);
|
|
898
|
-
state = r.nextState;
|
|
899
|
-
continue;
|
|
900
|
-
}
|
|
901
|
-
if (r.kind === "eof" || r.kind === "incomplete") {
|
|
902
|
-
// No more boundaries in this input. Append residue if non-empty.
|
|
903
|
-
// Same as the semicolon branch: emit `r.sql` (with substitutions
|
|
904
|
-
// applied) when `varLookup` is set, otherwise pass the raw slice
|
|
905
|
-
// through for byte-identical round-tripping.
|
|
906
|
-
if (remaining.length > 0) {
|
|
907
|
-
out.push(varLookup ? r.sql : remaining);
|
|
908
|
-
}
|
|
909
|
-
remaining = "";
|
|
910
|
-
break;
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
return out;
|
|
424
|
+
* Split a complete script into statement boundaries.
|
|
425
|
+
*
|
|
426
|
+
* Convenience wrapper for non-streaming inputs (e.g. `-c "SELECT 1; SELECT 2;"`,
|
|
427
|
+
* or whole-file `-f` runs). Returns one string per terminated statement, plus
|
|
428
|
+
* a trailing un-terminated residue iff non-empty (matching psql's behaviour:
|
|
429
|
+
* input that ends without `;` is still dispatched on EOF for `-c`/`-f`).
|
|
430
|
+
*
|
|
431
|
+
* Backslash commands appear in the result as `\cmd rest…` strings so the caller
|
|
432
|
+
* can dispatch them uniformly; this matches `psql -f script.sql` which mixes
|
|
433
|
+
* SQL and backslash commands in a single stream.
|
|
434
|
+
*
|
|
435
|
+
* The optional `varLookup` is forwarded to {@link scanSql} so callers that
|
|
436
|
+
* want variable expansion in `-c`/`-f` input get it. NOTE: the consumed-slice
|
|
437
|
+
* round-trip property (sum of returned strings === `input`) is preserved
|
|
438
|
+
* **only** when `varLookup` is omitted; substitution legitimately changes
|
|
439
|
+
* the byte content of each returned statement.
|
|
440
|
+
*/
|
|
441
|
+
const splitStatements = (input, varLookup, slashCmdMode, options) => {
|
|
442
|
+
const out = [];
|
|
443
|
+
let remaining = input;
|
|
444
|
+
let state = initialScanState();
|
|
445
|
+
let safety = 0;
|
|
446
|
+
while (remaining.length > 0) {
|
|
447
|
+
if (++safety > input.length + 10) break;
|
|
448
|
+
const r = scanSql(remaining, state, varLookup, slashCmdMode, options);
|
|
449
|
+
if (r.kind === "semicolon") {
|
|
450
|
+
out.push(varLookup ? r.sql : remaining.slice(0, r.consumed));
|
|
451
|
+
remaining = remaining.slice(r.consumed);
|
|
452
|
+
state = r.nextState;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
if (r.kind === "backslash") {
|
|
456
|
+
out.push(remaining.slice(0, r.consumed));
|
|
457
|
+
remaining = remaining.slice(r.consumed);
|
|
458
|
+
state = r.nextState;
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
if (r.kind === "eof" || r.kind === "incomplete") {
|
|
462
|
+
if (remaining.length > 0) out.push(varLookup ? r.sql : remaining);
|
|
463
|
+
remaining = "";
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return out;
|
|
914
468
|
};
|
|
469
|
+
//#endregion
|
|
470
|
+
export { scanSql, splitStatements };
|