neon 3.0.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +70 -5
  2. package/dist/_chunks/auth_selection-DGgq6ifc.js +83 -0
  3. package/dist/_chunks/cmd_pipeline-CUbBO9U_.js +2818 -0
  4. package/dist/_chunks/credentials-MYdHdKah.js +188 -0
  5. package/dist/_chunks/env-NbA61JR3.js +585 -0
  6. package/dist/_chunks/env_services-Tz9G4JeT.js +531 -0
  7. package/dist/_chunks/paths-DMq0Lt7a.js +151 -0
  8. package/dist/_chunks/profiles-Ir29rqns.js +217 -0
  9. package/dist/_chunks/psql-DWH-kc69.js +2169 -0
  10. package/dist/_chunks/rolldown-runtime-D7D4PA-g.js +13 -0
  11. package/dist/_chunks/secure_file-BucZj4yQ.js +39 -0
  12. package/dist/analytics.js +163 -207
  13. package/dist/api.js +815 -758
  14. package/dist/auth.js +121 -141
  15. package/dist/auth_context.js +39 -53
  16. package/dist/cli.js +4 -7
  17. package/dist/commands/api.js +220 -250
  18. package/dist/commands/api_keys.js +251 -314
  19. package/dist/commands/auth.js +283 -328
  20. package/dist/commands/bootstrap.js +372 -437
  21. package/dist/commands/branches.js +304 -455
  22. package/dist/commands/bucket.js +374 -514
  23. package/dist/commands/checkout.js +213 -298
  24. package/dist/commands/config.js +573 -690
  25. package/dist/commands/connection_string.js +137 -165
  26. package/dist/commands/data_api.js +238 -260
  27. package/dist/commands/databases.js +67 -76
  28. package/dist/commands/deploy.js +31 -25
  29. package/dist/commands/dev.js +639 -719
  30. package/dist/commands/diff.js +156 -200
  31. package/dist/commands/env.js +255 -305
  32. package/dist/commands/functions.js +275 -355
  33. package/dist/commands/index.js +70 -65
  34. package/dist/commands/init.js +84 -119
  35. package/dist/commands/inspect.js +55 -55
  36. package/dist/commands/ip_allow.js +88 -120
  37. package/dist/commands/link.js +874 -1019
  38. package/dist/commands/logs.js +291 -0
  39. package/dist/commands/neon_auth.js +725 -933
  40. package/dist/commands/operations.js +34 -25
  41. package/dist/commands/orgs.js +28 -18
  42. package/dist/commands/profile.js +615 -846
  43. package/dist/commands/projects.js +313 -373
  44. package/dist/commands/psql.js +60 -58
  45. package/dist/commands/roles.js +55 -58
  46. package/dist/commands/schema_diff.js +87 -131
  47. package/dist/commands/set_context.js +34 -26
  48. package/dist/commands/snapshots.js +288 -413
  49. package/dist/commands/status.js +41 -37
  50. package/dist/commands/user.js +21 -10
  51. package/dist/commands/vpc_endpoints.js +85 -113
  52. package/dist/config.js +7 -6
  53. package/dist/config_format.js +50 -66
  54. package/dist/config_template.js +128 -157
  55. package/dist/context.js +183 -235
  56. package/dist/current_branch_fast_path.js +40 -49
  57. package/dist/dev/env.js +2 -446
  58. package/dist/dev/functions.js +54 -68
  59. package/dist/dev/inputs.js +46 -58
  60. package/dist/dev/runtime.js +135 -164
  61. package/dist/dev/websocket.js +766 -959
  62. package/dist/env.js +27 -33
  63. package/dist/env_file.js +118 -132
  64. package/dist/env_services.js +2 -51
  65. package/dist/errors.js +57 -68
  66. package/dist/functions_api.js +45 -43
  67. package/dist/help.js +189 -140
  68. package/dist/index.js +182 -257
  69. package/dist/init/agents.js +137 -118
  70. package/dist/init/auth.js +58 -68
  71. package/dist/init/bootstrap.js +325 -396
  72. package/dist/init/build_config.js +4 -2
  73. package/dist/init/detect_agent.js +56 -101
  74. package/dist/init/editors.js +35 -52
  75. package/dist/init/enrich_output.js +51 -66
  76. package/dist/init/extension.js +134 -171
  77. package/dist/init/inspect.js +179 -266
  78. package/dist/init/interactive.js +510 -622
  79. package/dist/init/neonctl.js +117 -168
  80. package/dist/init/orchestrate.js +157 -173
  81. package/dist/init/phases/auth.js +188 -202
  82. package/dist/init/phases/cleanup.js +23 -23
  83. package/dist/init/phases/db.js +251 -277
  84. package/dist/init/phases/getting_started.js +213 -223
  85. package/dist/init/phases/mcp.js +174 -224
  86. package/dist/init/phases/migrations.js +247 -248
  87. package/dist/init/phases/neon_auth.js +114 -133
  88. package/dist/init/phases/setup.js +546 -703
  89. package/dist/init/phases/skills.js +75 -86
  90. package/dist/init/phases/status.js +72 -67
  91. package/dist/init/resolve_context.js +102 -99
  92. package/dist/init/route_command.js +91 -98
  93. package/dist/init/skills.js +174 -218
  94. package/dist/init/vsix.js +77 -99
  95. package/dist/log.js +17 -16
  96. package/dist/neon_services.js +104 -129
  97. package/dist/parameters.gen.js +481 -471
  98. package/dist/pkg.js +17 -19
  99. package/dist/profile_keys.js +44 -47
  100. package/dist/psql/cli.js +44 -47
  101. package/dist/psql/command/cmd_cond.js +231 -406
  102. package/dist/psql/command/cmd_connect.js +557 -764
  103. package/dist/psql/command/cmd_copy.js +728 -984
  104. package/dist/psql/command/cmd_describe.js +1499 -1688
  105. package/dist/psql/command/cmd_format.js +733 -905
  106. package/dist/psql/command/cmd_io.js +2 -2193
  107. package/dist/psql/command/cmd_lo.js +297 -359
  108. package/dist/psql/command/cmd_meta.js +727 -878
  109. package/dist/psql/command/cmd_misc.js +138 -172
  110. package/dist/psql/command/cmd_pipeline.js +2 -1148
  111. package/dist/psql/command/cmd_restrict.js +119 -155
  112. package/dist/psql/command/cmd_show.js +529 -688
  113. package/dist/psql/command/dispatch.js +260 -325
  114. package/dist/psql/command/inputQueue.js +35 -33
  115. package/dist/psql/command/shared.js +49 -63
  116. package/dist/psql/complete/filenames.js +90 -133
  117. package/dist/psql/complete/index.js +59 -97
  118. package/dist/psql/complete/matcher.js +236 -300
  119. package/dist/psql/complete/psqlVars.js +218 -223
  120. package/dist/psql/complete/queries.js +159 -177
  121. package/dist/psql/complete/rules.js +1493 -2299
  122. package/dist/psql/core/common.js +2 -1253
  123. package/dist/psql/core/help.js +456 -546
  124. package/dist/psql/core/mainloop.js +692 -1303
  125. package/dist/psql/core/prompt.js +391 -408
  126. package/dist/psql/core/settings.js +429 -644
  127. package/dist/psql/core/sqlHelp.js +480 -554
  128. package/dist/psql/core/startup.js +2 -846
  129. package/dist/psql/core/syncVars.js +67 -110
  130. package/dist/psql/core/variables.js +156 -278
  131. package/dist/psql/describe/formatters.js +884 -1285
  132. package/dist/psql/describe/processNamePattern.js +173 -260
  133. package/dist/psql/describe/queries.js +1368 -2403
  134. package/dist/psql/describe/versionGate.js +32 -41
  135. package/dist/psql/index.js +2 -2030
  136. package/dist/psql/io/history.js +232 -271
  137. package/dist/psql/io/input.js +103 -108
  138. package/dist/psql/io/lineEditor/buffer.js +238 -319
  139. package/dist/psql/io/lineEditor/complete.js +135 -213
  140. package/dist/psql/io/lineEditor/filename.js +139 -148
  141. package/dist/psql/io/lineEditor/index.js +653 -870
  142. package/dist/psql/io/lineEditor/keymap.js +544 -702
  143. package/dist/psql/io/lineEditor/vt100.js +294 -341
  144. package/dist/psql/io/pgpass.js +158 -187
  145. package/dist/psql/io/pgservice.js +146 -183
  146. package/dist/psql/io/psqlrc.js +328 -403
  147. package/dist/psql/print/aligned.js +1020 -1683
  148. package/dist/psql/print/asciidoc.js +180 -214
  149. package/dist/psql/print/crosstab.js +281 -442
  150. package/dist/psql/print/csv.js +48 -70
  151. package/dist/psql/print/html.js +195 -226
  152. package/dist/psql/print/json.js +75 -88
  153. package/dist/psql/print/latex.js +291 -364
  154. package/dist/psql/print/pager.js +171 -242
  155. package/dist/psql/print/troff.js +194 -226
  156. package/dist/psql/print/unaligned.js +69 -95
  157. package/dist/psql/print/units.js +167 -169
  158. package/dist/psql/scanner/slash.js +428 -483
  159. package/dist/psql/scanner/sql.js +445 -889
  160. package/dist/psql/scanner/stringutils.js +309 -379
  161. package/dist/psql/types/index.js +8 -7
  162. package/dist/psql/types/scanner.js +25 -22
  163. package/dist/psql/wire/connection.js +2042 -2803
  164. package/dist/psql/wire/copy.js +84 -100
  165. package/dist/psql/wire/notify.js +39 -59
  166. package/dist/psql/wire/pipeline.js +305 -518
  167. package/dist/psql/wire/protocol.js +349 -417
  168. package/dist/psql/wire/sasl.js +180 -265
  169. package/dist/psql/wire/tls.js +400 -561
  170. package/dist/storage_api.js +115 -129
  171. package/dist/test_utils/fixtures.js +94 -113
  172. package/dist/test_utils/oauth_server.js +10 -7
  173. package/dist/test_utils/project_dir.js +33 -0
  174. package/dist/utils/ai_gateway_notice.js +131 -162
  175. package/dist/utils/api_enums.js +21 -28
  176. package/dist/utils/auth.js +10 -4
  177. package/dist/utils/branch_notice.js +20 -19
  178. package/dist/utils/branch_picker.js +83 -89
  179. package/dist/utils/cli_name.js +15 -12
  180. package/dist/utils/compute_units.js +20 -27
  181. package/dist/utils/config_diff.js +127 -158
  182. package/dist/utils/enrichers.js +95 -148
  183. package/dist/utils/esbuild.js +130 -189
  184. package/dist/utils/flags.js +35 -47
  185. package/dist/utils/formats.js +8 -15
  186. package/dist/utils/git_diff.js +69 -80
  187. package/dist/utils/inspect_db.js +101 -143
  188. package/dist/utils/inspect_queries.js +179 -142
  189. package/dist/utils/middlewares.js +39 -45
  190. package/dist/utils/openapi.js +87 -99
  191. package/dist/utils/package_manager.js +312 -110
  192. package/dist/utils/point_in_time.js +49 -53
  193. package/dist/utils/psql.js +89 -106
  194. package/dist/utils/service_picker.js +55 -58
  195. package/dist/utils/string.js +5 -5
  196. package/dist/utils/ui.js +38 -55
  197. package/dist/utils/write_sync.js +26 -35
  198. package/dist/utils/zip.js +4 -3
  199. package/dist/writer.js +67 -87
  200. package/package.json +11 -6
  201. package/dist/_shared/auth_selection.js +0 -86
  202. package/dist/_shared/credentials.js +0 -209
  203. package/dist/_shared/env-core/env.js +0 -558
  204. package/dist/_shared/env-core/reuse-secrets.js +0 -223
  205. package/dist/_shared/paths.js +0 -148
  206. package/dist/_shared/profiles.js +0 -276
  207. package/dist/_shared/secure_file.js +0 -43
@@ -1,43 +1,10 @@
1
- /**
2
- * psql Settings.
3
- *
4
- * TypeScript port of the `pset` initialization performed by PostgreSQL's
5
- * `src/bin/psql/startup.c` and the defaults declared in `src/bin/psql/settings.h`.
6
- *
7
- * Two responsibilities:
8
- *
9
- * 1. `defaultSettings(varStore)` — build a fully-initialized `PsqlSettings`
10
- * with the same defaults psql itself uses (prompts, verbosity, send mode,
11
- * completion case, print options, etc.). The Connection (`settings.db`)
12
- * stays `null` here; it is populated later by the startup/connect WP.
13
- *
14
- * 2. `applyEnvOverrides(settings, env?)` — bridge process environment to psql
15
- * state. Some variables psql looks up "live" (e.g. PSQL_HISTORY when
16
- * readline is initialized in `src/bin/psql/input.c`, PSQL_EDITOR/EDITOR/
17
- * VISUAL when `\e` is invoked in `command.c`, PAGER/PSQL_PAGER from
18
- * `fe_utils/print.c`). We capture them eagerly into the var store under
19
- * stable names so later WPs can read them without re-reading
20
- * `process.env`. The COLUMNS env var maps to `popt.topt.envColumns` to
21
- * mirror upstream's `pset.popt.topt.env_columns` assignment.
22
- *
23
- * Deviations from upstream:
24
- *
25
- * - psql exposes `notty` based on isatty(stdin) && isatty(stdout). We have
26
- * no stream context at construction time, so the default is `false`;
27
- * higher-level WPs (startup) override this.
28
- * - The PROMPT1/PROMPT2/PROMPT3 psql variables and `settings.prompt{1,2,3}`
29
- * are seeded to the same defaults and kept in sync via `addHook` so
30
- * `\set PROMPT1 …` reflects in `settings.prompt1`. Upstream achieves this
31
- * with `prompt1_hook` etc. in `startup.c`.
32
- * - We do not eagerly read PSQLRC (the rc loader lives in a later WP).
33
- * Instead we surface PSQLRC verbatim under the `PSQLRC` psql variable.
34
- */
35
- export const DEFAULT_PROMPT1 = "%/%R%x%# ";
36
- export const DEFAULT_PROMPT2 = "%/%R%x%# ";
37
- export const DEFAULT_PROMPT3 = ">> ";
38
- export const DEFAULT_CSV_FIELD_SEP = ",";
39
- export const DEFAULT_FIELD_SEP = "|";
40
- export const DEFAULT_RECORD_SEP = "\n";
1
+ //#region src/psql/core/settings.ts
2
+ const DEFAULT_PROMPT1 = "%/%R%x%# ";
3
+ const DEFAULT_PROMPT2 = "%/%R%x%# ";
4
+ const DEFAULT_PROMPT3 = ">> ";
5
+ const DEFAULT_CSV_FIELD_SEP = ",";
6
+ const DEFAULT_FIELD_SEP = "|";
7
+ const DEFAULT_RECORD_SEP = "\n";
41
8
  const DEFAULT_VERBOSITY = "default";
42
9
  const DEFAULT_SHOW_CONTEXT = "errors";
43
10
  const DEFAULT_ECHO = "none";
@@ -49,638 +16,456 @@ const DEFAULT_HIST_CONTROL = "none";
49
16
  /** Upstream `DEFAULT_WATCH_INTERVAL` from settings.h (psql 18). */
50
17
  const DEFAULT_WATCH_INTERVAL = "2";
51
18
  /** Upstream `DEFAULT_WATCH_INTERVAL_MAX` (1e6 seconds). */
52
- const WATCH_INTERVAL_MAX = 1000000;
19
+ const WATCH_INTERVAL_MAX = 1e6;
53
20
  /**
54
- * Build the psql `PrintQueryOpts` with the same defaults as upstream's
55
- * pset.popt initialization (see `startup.c` after the `pset.db = NULL`
56
- * block). border=1, format=aligned, pager="on" (upstream
57
- * `pset.popt.topt.pager = 1` — the printer paginates only on a TTY and
58
- * only when output exceeds the screen height, so the default is harmless
59
- * for non-TTY runs), start/stop_table=true, default_footer=true.
60
- */
21
+ * Build the psql `PrintQueryOpts` with the same defaults as upstream's
22
+ * pset.popt initialization (see `startup.c` after the `pset.db = NULL`
23
+ * block). border=1, format=aligned, pager="on" (upstream
24
+ * `pset.popt.topt.pager = 1` — the printer paginates only on a TTY and
25
+ * only when output exceeds the screen height, so the default is harmless
26
+ * for non-TTY runs), start/stop_table=true, default_footer=true.
27
+ */
61
28
  const buildDefaultPrintOpts = () => ({
62
- topt: {
63
- format: "aligned",
64
- expanded: "off",
65
- border: 1,
66
- pager: "on",
67
- pagerMinLines: 0,
68
- tuplesOnly: false,
69
- startTable: true,
70
- stopTable: true,
71
- defaultFooter: true,
72
- prior: 0,
73
- encoding: "UTF8",
74
- envColumns: 0,
75
- columns: 0,
76
- unicodeBorderLineStyle: "ascii",
77
- unicodeColumnLineStyle: "ascii",
78
- unicodeHeaderLineStyle: "ascii",
79
- // Upstream `popt.topt.unicode_{border,column,header}_linestyle` default
80
- // to `UNICODE_LINESTYLE_SINGLE` (see `initialize_global_options` in
81
- // `print.c`). Independent of `unicode*LineStyle` (ascii|unicode).
82
- unicodeBorderStyle: "single",
83
- unicodeColumnStyle: "single",
84
- unicodeHeaderStyle: "single",
85
- fieldSep: DEFAULT_FIELD_SEP,
86
- recordSep: DEFAULT_RECORD_SEP,
87
- numericLocale: false,
88
- tableAttr: null,
89
- title: null,
90
- footers: null,
91
- translateHeader: false,
92
- translateColumns: null,
93
- nullPrint: "",
94
- truePrint: "t",
95
- falsePrint: "f",
96
- csvFieldSep: DEFAULT_CSV_FIELD_SEP,
97
- // Upstream `pset.popt.topt.expanded_header_width_type =
98
- // PRINT_XHEADER_FULL` — default rendered as the literal "full" in the
99
- // bulk `\pset` view.
100
- xheaderWidth: "full",
101
- },
102
- nullPrint: "",
103
- title: null,
104
- footers: null,
105
- translateHeader: false,
106
- translateColumns: null,
107
- nTranslateColumns: 0,
29
+ topt: {
30
+ format: "aligned",
31
+ expanded: "off",
32
+ border: 1,
33
+ pager: "on",
34
+ pagerMinLines: 0,
35
+ tuplesOnly: false,
36
+ startTable: true,
37
+ stopTable: true,
38
+ defaultFooter: true,
39
+ prior: 0,
40
+ encoding: "UTF8",
41
+ envColumns: 0,
42
+ columns: 0,
43
+ unicodeBorderLineStyle: "ascii",
44
+ unicodeColumnLineStyle: "ascii",
45
+ unicodeHeaderLineStyle: "ascii",
46
+ unicodeBorderStyle: "single",
47
+ unicodeColumnStyle: "single",
48
+ unicodeHeaderStyle: "single",
49
+ fieldSep: "|",
50
+ recordSep: "\n",
51
+ numericLocale: false,
52
+ tableAttr: null,
53
+ title: null,
54
+ footers: null,
55
+ translateHeader: false,
56
+ translateColumns: null,
57
+ nullPrint: "",
58
+ truePrint: "t",
59
+ falsePrint: "f",
60
+ csvFieldSep: ",",
61
+ xheaderWidth: "full"
62
+ },
63
+ nullPrint: "",
64
+ title: null,
65
+ footers: null,
66
+ translateHeader: false,
67
+ translateColumns: null,
68
+ nTranslateColumns: 0
108
69
  });
109
70
  /**
110
- * Build a fresh `PsqlSettings` with psql defaults, backed by the provided
111
- * variable store. The store is mutated to:
112
- *
113
- * - seed `PROMPT1`/`PROMPT2`/`PROMPT3` to their default strings, and
114
- * - wire substitute-style hooks so that subsequent `\set PROMPT1 …` calls
115
- * update the corresponding `settings.prompt{1,2,3}` field.
116
- *
117
- * Connection (`settings.db`) is left `null`; it is the responsibility of
118
- * the startup/connect WP to populate it after a successful libpq-equivalent
119
- * connection.
120
- */
121
- export const defaultSettings = (varStore) => {
122
- const settings = {
123
- db: null,
124
- vars: varStore,
125
- popt: buildDefaultPrintOpts(),
126
- mainfile: null,
127
- inputfile: null,
128
- curCmdSource: "stdin",
129
- restrictedKey: null,
130
- prompt1: DEFAULT_PROMPT1,
131
- prompt2: DEFAULT_PROMPT2,
132
- prompt3: DEFAULT_PROMPT3,
133
- notty: false,
134
- quiet: false,
135
- singleline: false,
136
- singlestep: false,
137
- onErrorStop: false,
138
- fetchCount: 0,
139
- verbosity: DEFAULT_VERBOSITY,
140
- showContext: DEFAULT_SHOW_CONTEXT,
141
- echo: DEFAULT_ECHO,
142
- echoHidden: DEFAULT_ECHO_HIDDEN,
143
- onErrorRollback: DEFAULT_ON_ERROR_ROLLBACK,
144
- compCase: DEFAULT_COMP_CASE,
145
- sendMode: DEFAULT_SEND_MODE,
146
- histControl: DEFAULT_HIST_CONTROL,
147
- hideCompression: false,
148
- hideTableam: false,
149
- logfile: null,
150
- timing: false,
151
- lastErrorResult: null,
152
- lastQuery: "",
153
- };
154
- // Seed the PROMPT psql variables and wire them to the settings fields so
155
- // `\set PROMPT1 …` is reflected in `settings.prompt1`. Upstream does this
156
- // via assign hooks (prompt1_hook / prompt2_hook / prompt3_hook).
157
- varStore.addHook("PROMPT1", (newValue) => {
158
- settings.prompt1 = newValue ?? "";
159
- return true;
160
- });
161
- varStore.addHook("PROMPT2", (newValue) => {
162
- settings.prompt2 = newValue ?? "";
163
- return true;
164
- });
165
- varStore.addHook("PROMPT3", (newValue) => {
166
- settings.prompt3 = newValue ?? "";
167
- return true;
168
- });
169
- varStore.set("PROMPT1", DEFAULT_PROMPT1);
170
- varStore.set("PROMPT2", DEFAULT_PROMPT2);
171
- varStore.set("PROMPT3", DEFAULT_PROMPT3);
172
- // Mirror upstream's small set of always-present session vars.
173
- varStore.set("LAST_ERROR_MESSAGE", "");
174
- varStore.set("LAST_ERROR_SQLSTATE", "00000");
175
- // PIPELINE_SYNC_COUNT / PIPELINE_COMMAND_COUNT / PIPELINE_RESULT_COUNT —
176
- // upstream `pset.piped_syncs` / `pset.piped_commands` / `pset.piped_results`
177
- // are mirrored into psql variables of these names (used by `\echo
178
- // :PIPELINE_*` throughout `regress/psql_pipeline.sql`). The three counters
179
- // are plain string vars seeded to "0" at startup; `\startpipeline` /
180
- // `\endpipeline` reset them to "0", and the pipeline driver bumps them
181
- // per upstream's SendQuery / discardAbortedPipelineResults logic.
182
- // Upstream installs no substitute/assign hooks on these — `\unset
183
- // PIPELINE_SYNC_COUNT` actually drops the variable (verified empirically:
184
- // `\echo :PIPELINE_SYNC_COUNT` then prints the literal `:NAME`). We
185
- // mirror that here: plain seed, no hook.
186
- varStore.set("PIPELINE_SYNC_COUNT", "0");
187
- varStore.set("PIPELINE_COMMAND_COUNT", "0");
188
- varStore.set("PIPELINE_RESULT_COUNT", "0");
189
- // SHOW_ALL_RESULTS defaults to 'on' — when off ('0' / 'off') the REPL only
190
- // prints the final result set of a multi-statement `\;`-separated batch
191
- // (upstream `pset.show_all_results`, set in startup.c).
192
- varStore.set("SHOW_ALL_RESULTS", "on");
193
- // ENCODING tracks the server's client_encoding ParameterStatus. We seed
194
- // with the connection-default UTF8 here; mainloop refreshes it after the
195
- // connection is bound (and again whenever `SET client_encoding` flips the
196
- // server value). Mirrors `pset.encoding` / `SetVariable("ENCODING", ...)`
197
- // in upstream startup.c / common.c.
198
- varStore.set("ENCODING", "UTF8");
199
- // WATCH_INTERVAL — upstream `watch_interval_substitute_hook` (null →
200
- // DEFAULT_WATCH_INTERVAL "2") + `watch_interval_hook` which calls
201
- // `ParseVariableDouble("WATCH_INTERVAL", ..., 0, DEFAULT_WATCH_INTERVAL_MAX)`.
202
- // Empty value reaches ParseVariableDouble which short-circuits with
203
- // "invalid input syntax for variable \"WATCH_INTERVAL\"" without
204
- // substituting; the previous value is preserved by `trySet`.
205
- varStore.addHook("WATCH_INTERVAL", (newValue) => {
206
- if (newValue === null) {
207
- // `\unset WATCH_INTERVAL` re-seeds the default. The substitute also
208
- // runs once on `addHook` registration so `\echo :WATCH_INTERVAL`
209
- // prints "2" before the user touches it (no explicit seed needed).
210
- return { substitute: DEFAULT_WATCH_INTERVAL };
211
- }
212
- if (newValue === "") {
213
- return `invalid input syntax for variable "WATCH_INTERVAL"`;
214
- }
215
- const range = validateWatchInterval(newValue);
216
- if (range !== null)
217
- return range;
218
- return true;
219
- });
220
- // ON_ERROR_STOP assign hook. Upstream `bool_substitute_hook` /
221
- // `on_error_stop_assign_hook` (startup.c) keep `pset.on_error_stop` in
222
- // lockstep with the variable so both `--on-error-stop` (which flips the
223
- // flag directly) and `--set ON_ERROR_STOP=1` (which only writes the
224
- // variable) take effect. Empty value → "on" (substitute), non-boolean →
225
- // reject with upstream's wording.
226
- varStore.addHook("ON_ERROR_STOP", makeBoolHook("ON_ERROR_STOP", (parsed) => {
227
- settings.onErrorStop = parsed;
228
- }));
229
- // AUTOCOMMIT assign hook — upstream `bool_substitute_hook` +
230
- // `autocommit_assign_hook`. Empty value → "on", non-boolean → reject
231
- // with `unrecognized value "<value>" for "AUTOCOMMIT": Boolean expected`.
232
- varStore.addHook("AUTOCOMMIT", makeBoolHook("AUTOCOMMIT"));
233
- // Seed AUTOCOMMIT to "on" (upstream default; pset.autocommit = true).
234
- varStore.set("AUTOCOMMIT", "on");
235
- // FETCH_COUNT — upstream `fetch_count_substitute_hook` (null → "0") +
236
- // `fetch_count_assign_hook` which delegates to `ParseVariableNum`. Empty
237
- // `\set FETCH_COUNT` reaches ParseVariableNum which fails with
238
- // `invalid value "" for "FETCH_COUNT": integer expected`, preserving
239
- // the prior value. The substitute fires on addHook registration so
240
- // `\echo :FETCH_COUNT` prints "0" before the user touches it.
241
- varStore.addHook("FETCH_COUNT", (newValue) => {
242
- if (newValue === null) {
243
- settings.fetchCount = 0;
244
- return { substitute: "0" };
245
- }
246
- const n = parseIntOrNull(newValue);
247
- if (n === null) {
248
- return `invalid value "${newValue}" for "FETCH_COUNT": integer expected`;
249
- }
250
- settings.fetchCount = Math.max(0, n);
251
- return true;
252
- });
253
- // ON_ERROR_ROLLBACK assign hook — upstream `on_error_rollback_substitute_hook`
254
- // (null → "off", empty → "on") + `on_error_rollback_assign_hook`. Tri-state:
255
- // on, off, interactive. Non-matching values get the multi-line diagnostic
256
- // `unrecognized value "<value>" for "ON_ERROR_ROLLBACK"\nAvailable values
257
- // are: on, off, interactive.`. The substitute on addHook seeds "off" so
258
- // `\echo :ON_ERROR_ROLLBACK` works without explicit init.
259
- varStore.addHook("ON_ERROR_ROLLBACK", makeOnErrorRollbackHook((parsed) => {
260
- settings.onErrorRollback = parsed;
261
- }));
262
- // VERBOSITY — upstream `verbosity_substitute_hook` (empty → "default")
263
- // + `verbosity_assign_hook`. Accepts default | verbose | terse | sqlstate.
264
- varStore.addHook("VERBOSITY", makeEnumHook("VERBOSITY", ["default", "verbose", "terse", "sqlstate"], "default", (parsed) => {
265
- settings.verbosity = parsed;
266
- }));
267
- // SHOW_CONTEXT — upstream `show_context_substitute_hook` (empty → "errors")
268
- // + `show_context_assign_hook`. Accepts never | errors | always.
269
- varStore.addHook("SHOW_CONTEXT", makeEnumHook("SHOW_CONTEXT", ["never", "errors", "always"], "errors", (parsed) => {
270
- settings.showContext = parsed;
271
- }));
272
- // ECHO — upstream `echo_substitute_hook` (empty → "none") +
273
- // `echo_assign_hook`. Accepts none | errors | queries | all.
274
- varStore.addHook("ECHO", makeEnumHook("ECHO", ["none", "errors", "queries", "all"], "none", (parsed) => {
275
- settings.echo = parsed;
276
- }));
277
- // ECHO_HIDDEN — upstream `bool_substitute_hook` +
278
- // `echo_hidden_assign_hook`. Tri-state: on / off / noexec. Empty → "on".
279
- varStore.addHook("ECHO_HIDDEN", makeEchoHiddenHook((parsed) => {
280
- settings.echoHidden = parsed;
281
- }));
282
- // COMP_KEYWORD_CASE assign hook. Upstream `assign_var_comp_keyword_case_hook`
283
- // (in startup.c) reflects the spelling into `pset.comp_case`; the completer
284
- // then consults that on every Tab to decide whether to upper/lower/preserve
285
- // candidate casing. Accepts the four canonical spellings; the upstream
286
- // diagnostic wording is `unrecognized value "<value>" for
287
- // "COMP_KEYWORD_CASE"\nAvailable values are: lower, upper, preserve-lower,
288
- // preserve-upper.`.
289
- varStore.addHook("COMP_KEYWORD_CASE", makeEnumHook("COMP_KEYWORD_CASE", ["lower", "upper", "preserve-lower", "preserve-upper"], DEFAULT_COMP_CASE, (parsed) => {
290
- settings.compCase = parsed;
291
- }));
292
- // HISTCONTROL — upstream `histcontrol_substitute_hook` (empty → "none") +
293
- // `histcontrol_assign_hook`. Accepts ignorespace | ignoredups | ignoreboth
294
- // | none.
295
- varStore.addHook("HISTCONTROL", makeEnumHook("HISTCONTROL", ["none", "ignorespace", "ignoredups", "ignoreboth"], "none", (parsed) => {
296
- settings.histControl = parsed;
297
- }));
298
- // SHOW_ALL_RESULTS — upstream `bool_substitute_hook` +
299
- // `show_all_results_assign_hook`. Strict boolean; empty → "on".
300
- varStore.addHook("SHOW_ALL_RESULTS", makeBoolHook("SHOW_ALL_RESULTS"));
301
- // QUIET — upstream `bool_substitute_hook` + `quiet_hook`. Mirrors into
302
- // `pset.quiet`. Default ("off") matches the unset substitute, so addHook
303
- // registration seeds it via the substitute.
304
- varStore.addHook("QUIET", makeBoolHook("QUIET", (parsed) => {
305
- settings.quiet = parsed;
306
- }));
307
- // SINGLELINE — upstream `bool_substitute_hook` + `singleline_hook`.
308
- // Mirrors into `pset.singleline`.
309
- varStore.addHook("SINGLELINE", makeBoolHook("SINGLELINE", (parsed) => {
310
- settings.singleline = parsed;
311
- }));
312
- // SINGLESTEP — upstream `bool_substitute_hook` + `singlestep_hook`.
313
- // Mirrors into `pset.singlestep`.
314
- varStore.addHook("SINGLESTEP", makeBoolHook("SINGLESTEP", (parsed) => {
315
- settings.singlestep = parsed;
316
- }));
317
- // HIDE_TOAST_COMPRESSION — upstream `bool_substitute_hook` +
318
- // `hide_compression_hook`. Mirrors into `pset.hide_compression` (our
319
- // `settings.hideCompression`). Note: the upstream variable name is
320
- // HIDE_TOAST_COMPRESSION, not HIDE_COMPRESSION.
321
- varStore.addHook("HIDE_TOAST_COMPRESSION", makeBoolHook("HIDE_TOAST_COMPRESSION", (parsed) => {
322
- settings.hideCompression = parsed;
323
- }));
324
- // HIDE_TABLEAM — upstream `bool_substitute_hook` + `hide_tableam_hook`.
325
- // Mirrors into `pset.hide_tableam` (our `settings.hideTableam`).
326
- varStore.addHook("HIDE_TABLEAM", makeBoolHook("HIDE_TABLEAM", (parsed) => {
327
- settings.hideTableam = parsed;
328
- }));
329
- // HISTSIZE — upstream `histsize_substitute_hook` (null → "500") +
330
- // `histsize_hook` which calls `ParseVariableNum`. Empty `\set HISTSIZE`
331
- // reaches ParseVariableNum and fails with the "integer expected" message,
332
- // preserving the prior value. We don't drive a derived settings field
333
- // (readline isn't implemented), but we still install the hook so the
334
- // variable surface matches vanilla: `\echo :HISTSIZE` prints "500" on
335
- // startup, accepts integer values, rejects junk.
336
- varStore.addHook("HISTSIZE", (newValue) => {
337
- if (newValue === null) {
338
- return { substitute: "500" };
339
- }
340
- const n = parseIntOrNull(newValue);
341
- if (n === null) {
342
- return `invalid value "${newValue}" for "HISTSIZE": integer expected`;
343
- }
344
- return true;
345
- });
346
- // IGNOREEOF — upstream `ignoreeof_substitute_hook` + `ignoreeof_hook`.
347
- // The substitute hook is unusual: it silently rewrites non-numeric values
348
- // to "10" (bash's documented default) instead of rejecting them, and maps
349
- // null → "0" (feature disabled). The assign hook never fails in practice
350
- // because the substitute already normalized the value — it's
351
- // `ParseVariableNum(newval, "IGNOREEOF", &pset.ignoreeof)` over a value
352
- // guaranteed to parse. We have no derived `settings.ignoreeof` field (the
353
- // EOF-counting logic lives in the REPL, which isn't implemented), so the
354
- // hook just maintains the variable surface: `\echo :IGNOREEOF` prints "0"
355
- // on startup, accepts integers (incl. hex / octal — `ParseVariableNum`
356
- // uses `strtol(..., 0)`), silently rewrites junk to "10", and on `\unset`
357
- // resubstitutes "0". This is the key behavior the regress/psql `\gset
358
- // IGNORE` test relies on (it expects `:IGNOREEOF` to render "0" even
359
- // though `\gset` rejected the assignment as specially-treated).
360
- varStore.addHook("IGNOREEOF", (newValue) => {
361
- if (newValue === null) {
362
- return { substitute: "0" };
363
- }
364
- if (parseStrtolBase0OrNull(newValue) === null) {
365
- return { substitute: "10" };
366
- }
367
- return true;
368
- });
369
- return settings;
71
+ * Build a fresh `PsqlSettings` with psql defaults, backed by the provided
72
+ * variable store. The store is mutated to:
73
+ *
74
+ * - seed `PROMPT1`/`PROMPT2`/`PROMPT3` to their default strings, and
75
+ * - wire substitute-style hooks so that subsequent `\set PROMPT1 …` calls
76
+ * update the corresponding `settings.prompt{1,2,3}` field.
77
+ *
78
+ * Connection (`settings.db`) is left `null`; it is the responsibility of
79
+ * the startup/connect WP to populate it after a successful libpq-equivalent
80
+ * connection.
81
+ */
82
+ const defaultSettings = (varStore) => {
83
+ const settings = {
84
+ db: null,
85
+ vars: varStore,
86
+ popt: buildDefaultPrintOpts(),
87
+ mainfile: null,
88
+ inputfile: null,
89
+ curCmdSource: "stdin",
90
+ restrictedKey: null,
91
+ prompt1: DEFAULT_PROMPT1,
92
+ prompt2: DEFAULT_PROMPT2,
93
+ prompt3: ">> ",
94
+ notty: false,
95
+ quiet: false,
96
+ singleline: false,
97
+ singlestep: false,
98
+ onErrorStop: false,
99
+ fetchCount: 0,
100
+ verbosity: DEFAULT_VERBOSITY,
101
+ showContext: DEFAULT_SHOW_CONTEXT,
102
+ echo: DEFAULT_ECHO,
103
+ echoHidden: DEFAULT_ECHO_HIDDEN,
104
+ onErrorRollback: DEFAULT_ON_ERROR_ROLLBACK,
105
+ compCase: DEFAULT_COMP_CASE,
106
+ sendMode: DEFAULT_SEND_MODE,
107
+ histControl: DEFAULT_HIST_CONTROL,
108
+ hideCompression: false,
109
+ hideTableam: false,
110
+ logfile: null,
111
+ timing: false,
112
+ lastErrorResult: null,
113
+ lastQuery: ""
114
+ };
115
+ varStore.addHook("PROMPT1", (newValue) => {
116
+ settings.prompt1 = newValue ?? "";
117
+ return true;
118
+ });
119
+ varStore.addHook("PROMPT2", (newValue) => {
120
+ settings.prompt2 = newValue ?? "";
121
+ return true;
122
+ });
123
+ varStore.addHook("PROMPT3", (newValue) => {
124
+ settings.prompt3 = newValue ?? "";
125
+ return true;
126
+ });
127
+ varStore.set("PROMPT1", DEFAULT_PROMPT1);
128
+ varStore.set("PROMPT2", DEFAULT_PROMPT2);
129
+ varStore.set("PROMPT3", ">> ");
130
+ varStore.set("LAST_ERROR_MESSAGE", "");
131
+ varStore.set("LAST_ERROR_SQLSTATE", "00000");
132
+ varStore.set("PIPELINE_SYNC_COUNT", "0");
133
+ varStore.set("PIPELINE_COMMAND_COUNT", "0");
134
+ varStore.set("PIPELINE_RESULT_COUNT", "0");
135
+ varStore.set("SHOW_ALL_RESULTS", "on");
136
+ varStore.set("ENCODING", "UTF8");
137
+ varStore.addHook("WATCH_INTERVAL", (newValue) => {
138
+ if (newValue === null) return { substitute: DEFAULT_WATCH_INTERVAL };
139
+ if (newValue === "") return `invalid input syntax for variable "WATCH_INTERVAL"`;
140
+ const range = validateWatchInterval(newValue);
141
+ if (range !== null) return range;
142
+ return true;
143
+ });
144
+ varStore.addHook("ON_ERROR_STOP", makeBoolHook("ON_ERROR_STOP", (parsed) => {
145
+ settings.onErrorStop = parsed;
146
+ }));
147
+ varStore.addHook("AUTOCOMMIT", makeBoolHook("AUTOCOMMIT"));
148
+ varStore.set("AUTOCOMMIT", "on");
149
+ varStore.addHook("FETCH_COUNT", (newValue) => {
150
+ if (newValue === null) {
151
+ settings.fetchCount = 0;
152
+ return { substitute: "0" };
153
+ }
154
+ const n = parseIntOrNull(newValue);
155
+ if (n === null) return `invalid value "${newValue}" for "FETCH_COUNT": integer expected`;
156
+ settings.fetchCount = Math.max(0, n);
157
+ return true;
158
+ });
159
+ varStore.addHook("ON_ERROR_ROLLBACK", makeOnErrorRollbackHook((parsed) => {
160
+ settings.onErrorRollback = parsed;
161
+ }));
162
+ varStore.addHook("VERBOSITY", makeEnumHook("VERBOSITY", [
163
+ "default",
164
+ "verbose",
165
+ "terse",
166
+ "sqlstate"
167
+ ], "default", (parsed) => {
168
+ settings.verbosity = parsed;
169
+ }));
170
+ varStore.addHook("SHOW_CONTEXT", makeEnumHook("SHOW_CONTEXT", [
171
+ "never",
172
+ "errors",
173
+ "always"
174
+ ], "errors", (parsed) => {
175
+ settings.showContext = parsed;
176
+ }));
177
+ varStore.addHook("ECHO", makeEnumHook("ECHO", [
178
+ "none",
179
+ "errors",
180
+ "queries",
181
+ "all"
182
+ ], "none", (parsed) => {
183
+ settings.echo = parsed;
184
+ }));
185
+ varStore.addHook("ECHO_HIDDEN", makeEchoHiddenHook((parsed) => {
186
+ settings.echoHidden = parsed;
187
+ }));
188
+ varStore.addHook("COMP_KEYWORD_CASE", makeEnumHook("COMP_KEYWORD_CASE", [
189
+ "lower",
190
+ "upper",
191
+ "preserve-lower",
192
+ "preserve-upper"
193
+ ], DEFAULT_COMP_CASE, (parsed) => {
194
+ settings.compCase = parsed;
195
+ }));
196
+ varStore.addHook("HISTCONTROL", makeEnumHook("HISTCONTROL", [
197
+ "none",
198
+ "ignorespace",
199
+ "ignoredups",
200
+ "ignoreboth"
201
+ ], "none", (parsed) => {
202
+ settings.histControl = parsed;
203
+ }));
204
+ varStore.addHook("SHOW_ALL_RESULTS", makeBoolHook("SHOW_ALL_RESULTS"));
205
+ varStore.addHook("QUIET", makeBoolHook("QUIET", (parsed) => {
206
+ settings.quiet = parsed;
207
+ }));
208
+ varStore.addHook("SINGLELINE", makeBoolHook("SINGLELINE", (parsed) => {
209
+ settings.singleline = parsed;
210
+ }));
211
+ varStore.addHook("SINGLESTEP", makeBoolHook("SINGLESTEP", (parsed) => {
212
+ settings.singlestep = parsed;
213
+ }));
214
+ varStore.addHook("HIDE_TOAST_COMPRESSION", makeBoolHook("HIDE_TOAST_COMPRESSION", (parsed) => {
215
+ settings.hideCompression = parsed;
216
+ }));
217
+ varStore.addHook("HIDE_TABLEAM", makeBoolHook("HIDE_TABLEAM", (parsed) => {
218
+ settings.hideTableam = parsed;
219
+ }));
220
+ varStore.addHook("HISTSIZE", (newValue) => {
221
+ if (newValue === null) return { substitute: "500" };
222
+ if (parseIntOrNull(newValue) === null) return `invalid value "${newValue}" for "HISTSIZE": integer expected`;
223
+ return true;
224
+ });
225
+ varStore.addHook("IGNOREEOF", (newValue) => {
226
+ if (newValue === null) return { substitute: "0" };
227
+ if (parseStrtolBase0OrNull(newValue) === null) return { substitute: "10" };
228
+ return true;
229
+ });
230
+ return settings;
370
231
  };
371
232
  /**
372
- * Build a strict-boolean hook with upstream's `bool_substitute_hook` +
373
- * `bool_assign_hook` semantics:
374
- *
375
- * - empty / null → substitute "on"
376
- * - on/off/true/false/yes/no/1/0 (case-insensitive) → accepted
377
- * - anything else → reject with `unrecognized value "<value>" for
378
- * "<name>": Boolean expected`
379
- *
380
- * The optional `apply` callback receives the parsed boolean so callers can
381
- * keep a derived `PsqlSettings` field in sync (e.g. `settings.onErrorStop`).
382
- */
233
+ * Build a strict-boolean hook with upstream's `bool_substitute_hook` +
234
+ * `bool_assign_hook` semantics:
235
+ *
236
+ * - empty / null → substitute "on"
237
+ * - on/off/true/false/yes/no/1/0 (case-insensitive) → accepted
238
+ * - anything else → reject with `unrecognized value "<value>" for
239
+ * "<name>": Boolean expected`
240
+ *
241
+ * The optional `apply` callback receives the parsed boolean so callers can
242
+ * keep a derived `PsqlSettings` field in sync (e.g. `settings.onErrorStop`).
243
+ */
383
244
  const makeBoolHook = (name, apply) => {
384
- return (newValue) => {
385
- if (newValue === null) {
386
- // Upstream `bool_substitute_hook`: on `\unset NAME`, re-store "off"
387
- // so `\echo :NAME` shows the boolean default instead of the literal
388
- // `:NAME` token. Verified empirically against vanilla psql 18:
389
- // `\unset AUTOCOMMIT; \echo :AUTOCOMMIT` prints "off".
390
- apply?.(false);
391
- return { substitute: "off" };
392
- }
393
- if (newValue === "") {
394
- // `\set NAME` (empty value) substitutes "on". Verified empirically:
395
- // `\set AUTOCOMMIT; \echo :AUTOCOMMIT` prints "on".
396
- apply?.(true);
397
- return { substitute: "on" };
398
- }
399
- const parsed = parseOnOffBool(newValue);
400
- if (parsed === null) {
401
- return `unrecognized value "${newValue}" for "${name}": Boolean expected`;
402
- }
403
- apply?.(parsed);
404
- return true;
405
- };
245
+ return (newValue) => {
246
+ if (newValue === null) {
247
+ apply?.(false);
248
+ return { substitute: "off" };
249
+ }
250
+ if (newValue === "") {
251
+ apply?.(true);
252
+ return { substitute: "on" };
253
+ }
254
+ const parsed = parseOnOffBool(newValue);
255
+ if (parsed === null) return `unrecognized value "${newValue}" for "${name}": Boolean expected`;
256
+ apply?.(parsed);
257
+ return true;
258
+ };
406
259
  };
407
260
  /**
408
- * Build an enum-style hook with upstream's substitute / assign pair (the
409
- * VERBOSITY / SHOW_CONTEXT / ECHO / COMP_KEYWORD_CASE / HISTCONTROL pattern):
410
- *
411
- * - null → substitute the supplied `defaultValue` (matches
412
- * `verbosity_substitute_hook` etc. — `\unset NAME` re-stores the default)
413
- * - exact-match (case-insensitive) against `allowed` → accepted
414
- * - empty / unrecognized → reject with `unrecognized value "<value>" for
415
- * "<name>"\nAvailable values are: a, b, c.` (the substitute hooks only
416
- * handle NULL upstream — empty falls through to the assign hook's
417
- * PsqlVarEnumError path, preserving the prior value)
418
- *
419
- * `apply` is invoked with the canonical lowercase spelling whenever the
420
- * variable is set (including the default-on-null path).
421
- */
261
+ * Build an enum-style hook with upstream's substitute / assign pair (the
262
+ * VERBOSITY / SHOW_CONTEXT / ECHO / COMP_KEYWORD_CASE / HISTCONTROL pattern):
263
+ *
264
+ * - null → substitute the supplied `defaultValue` (matches
265
+ * `verbosity_substitute_hook` etc. — `\unset NAME` re-stores the default)
266
+ * - exact-match (case-insensitive) against `allowed` → accepted
267
+ * - empty / unrecognized → reject with `unrecognized value "<value>" for
268
+ * "<name>"\nAvailable values are: a, b, c.` (the substitute hooks only
269
+ * handle NULL upstream — empty falls through to the assign hook's
270
+ * PsqlVarEnumError path, preserving the prior value)
271
+ *
272
+ * `apply` is invoked with the canonical lowercase spelling whenever the
273
+ * variable is set (including the default-on-null path).
274
+ */
422
275
  const makeEnumHook = (name, allowed, defaultValue, apply) => {
423
- const list = allowed.join(", ");
424
- return (newValue) => {
425
- if (newValue === null) {
426
- apply?.(defaultValue);
427
- return { substitute: defaultValue };
428
- }
429
- const lower = newValue.toLowerCase();
430
- const match = allowed.find((a) => a.toLowerCase() === lower);
431
- if (match === undefined) {
432
- return `unrecognized value "${newValue}" for "${name}"\nAvailable values are: ${list}.`;
433
- }
434
- apply?.(match);
435
- return true;
436
- };
276
+ const list = allowed.join(", ");
277
+ return (newValue) => {
278
+ if (newValue === null) {
279
+ apply?.(defaultValue);
280
+ return { substitute: defaultValue };
281
+ }
282
+ const lower = newValue.toLowerCase();
283
+ const match = allowed.find((a) => a.toLowerCase() === lower);
284
+ if (match === void 0) return `unrecognized value "${newValue}" for "${name}"\nAvailable values are: ${list}.`;
285
+ apply?.(match);
286
+ return true;
287
+ };
437
288
  };
438
289
  /**
439
- * `ON_ERROR_ROLLBACK` is upstream's only tri-state boolean — `on` / `off` /
440
- * `interactive`. Empty / null → substitute "on" (matches
441
- * `on_error_rollback_substitute_hook`). Other values get the multi-line
442
- * diagnostic upstream emits from `on_error_rollback_assign_hook`.
443
- */
290
+ * `ON_ERROR_ROLLBACK` is upstream's only tri-state boolean — `on` / `off` /
291
+ * `interactive`. Empty / null → substitute "on" (matches
292
+ * `on_error_rollback_substitute_hook`). Other values get the multi-line
293
+ * diagnostic upstream emits from `on_error_rollback_assign_hook`.
294
+ */
444
295
  const makeOnErrorRollbackHook = (apply) => {
445
- return (newValue) => {
446
- if (newValue === null) {
447
- // Upstream `on_error_rollback_substitute_hook`:
448
- // if (newval == NULL) newval = pg_strdup("off");
449
- // — `\unset ON_ERROR_ROLLBACK` re-stores "off" so a follow-on
450
- // `\echo :ON_ERROR_ROLLBACK` shows the default rather than the
451
- // literal `:NAME` token.
452
- apply("off");
453
- return { substitute: "off" };
454
- }
455
- if (newValue === "") {
456
- apply("on");
457
- return { substitute: "on" };
458
- }
459
- const lower = newValue.toLowerCase();
460
- if (lower === "on" || lower === "off" || lower === "interactive") {
461
- apply(lower);
462
- return true;
463
- }
464
- return `unrecognized value "${newValue}" for "ON_ERROR_ROLLBACK"\nAvailable values are: on, off, interactive.`;
465
- };
296
+ return (newValue) => {
297
+ if (newValue === null) {
298
+ apply("off");
299
+ return { substitute: "off" };
300
+ }
301
+ if (newValue === "") {
302
+ apply("on");
303
+ return { substitute: "on" };
304
+ }
305
+ const lower = newValue.toLowerCase();
306
+ if (lower === "on" || lower === "off" || lower === "interactive") {
307
+ apply(lower);
308
+ return true;
309
+ }
310
+ return `unrecognized value "${newValue}" for "ON_ERROR_ROLLBACK"\nAvailable values are: on, off, interactive.`;
311
+ };
466
312
  };
467
313
  /**
468
- * ECHO_HIDDEN tri-state: `on` / `off` / `noexec`. Upstream uses
469
- * `bool_substitute_hook` (null → "off", empty → "on") plus
470
- * `echo_hidden_hook` which accepts `noexec` or falls back to
471
- * `ParseVariableBool`. Anything else gets the upstream "unrecognized
472
- * value" line with the three-element list.
473
- */
314
+ * ECHO_HIDDEN tri-state: `on` / `off` / `noexec`. Upstream uses
315
+ * `bool_substitute_hook` (null → "off", empty → "on") plus
316
+ * `echo_hidden_hook` which accepts `noexec` or falls back to
317
+ * `ParseVariableBool`. Anything else gets the upstream "unrecognized
318
+ * value" line with the three-element list.
319
+ */
474
320
  const makeEchoHiddenHook = (apply) => {
475
- return (newValue) => {
476
- if (newValue === null) {
477
- // Match `bool_substitute_hook(NULL)` → "off". On addHook registration
478
- // this seeds `\echo :ECHO_HIDDEN` → "off" (parity with vanilla).
479
- apply("off");
480
- return { substitute: "off" };
481
- }
482
- if (newValue === "") {
483
- apply("on");
484
- return { substitute: "on" };
485
- }
486
- const lower = newValue.toLowerCase();
487
- if (lower === "noexec") {
488
- apply("noexec");
489
- return true;
490
- }
491
- const parsed = parseOnOffBool(newValue);
492
- if (parsed === null) {
493
- return `unrecognized value "${newValue}" for "ECHO_HIDDEN"\nAvailable values are: on, off, noexec.`;
494
- }
495
- apply(parsed ? "on" : "off");
496
- return true;
497
- };
321
+ return (newValue) => {
322
+ if (newValue === null) {
323
+ apply("off");
324
+ return { substitute: "off" };
325
+ }
326
+ if (newValue === "") {
327
+ apply("on");
328
+ return { substitute: "on" };
329
+ }
330
+ if (newValue.toLowerCase() === "noexec") {
331
+ apply("noexec");
332
+ return true;
333
+ }
334
+ const parsed = parseOnOffBool(newValue);
335
+ if (parsed === null) return `unrecognized value "${newValue}" for "ECHO_HIDDEN"\nAvailable values are: on, off, noexec.`;
336
+ apply(parsed ? "on" : "off");
337
+ return true;
338
+ };
498
339
  };
499
340
  /**
500
- * Parse a non-empty string as a base-10 signed integer. Returns `null` on
501
- * any junk. Used by FETCH_COUNT / HISTSIZE — both invoke
502
- * `ParseVariableNum` upstream which internally uses `strtol(..., 0)`
503
- * (accepting hex/octal/decimal); we tighten to decimal-only here. The
504
- * deviation is observable for `\set FETCH_COUNT 0xff` (upstream accepts,
505
- * we reject); intentional for now because the regress doesn't exercise it.
506
- */
341
+ * Parse a non-empty string as a base-10 signed integer. Returns `null` on
342
+ * any junk. Used by FETCH_COUNT / HISTSIZE — both invoke
343
+ * `ParseVariableNum` upstream which internally uses `strtol(..., 0)`
344
+ * (accepting hex/octal/decimal); we tighten to decimal-only here. The
345
+ * deviation is observable for `\set FETCH_COUNT 0xff` (upstream accepts,
346
+ * we reject); intentional for now because the regress doesn't exercise it.
347
+ */
507
348
  const parseIntOrNull = (raw) => {
508
- const trimmed = raw.trim();
509
- if (trimmed.length === 0)
510
- return null;
511
- if (!/^[+-]?\d+$/.test(trimmed))
512
- return null;
513
- const n = parseInt(trimmed, 10);
514
- if (!Number.isFinite(n))
515
- return null;
516
- if (n < -0x80000000 || n > 0x7fffffff)
517
- return null;
518
- return n;
349
+ const trimmed = raw.trim();
350
+ if (trimmed.length === 0) return null;
351
+ if (!/^[+-]?\d+$/.test(trimmed)) return null;
352
+ const n = parseInt(trimmed, 10);
353
+ if (!Number.isFinite(n)) return null;
354
+ if (n < -2147483648 || n > 2147483647) return null;
355
+ return n;
519
356
  };
520
357
  /**
521
- * Parse a non-empty string as a strtol-base-0 signed integer (decimal,
522
- * `0x`/`0X` hex, leading-`0` octal). Returns `null` on any junk. Mirrors
523
- * upstream `ParseVariableNum`'s `strtol(value, &end, 0)` syntax — used by
524
- * IGNOREEOF's substitute hook, where the original string is preserved on
525
- * success (the hook only checks parsability to decide whether to swap in
526
- * "10").
527
- */
358
+ * Parse a non-empty string as a strtol-base-0 signed integer (decimal,
359
+ * `0x`/`0X` hex, leading-`0` octal). Returns `null` on any junk. Mirrors
360
+ * upstream `ParseVariableNum`'s `strtol(value, &end, 0)` syntax — used by
361
+ * IGNOREEOF's substitute hook, where the original string is preserved on
362
+ * success (the hook only checks parsability to decide whether to swap in
363
+ * "10").
364
+ */
528
365
  const parseStrtolBase0OrNull = (raw) => {
529
- // strtol skips leading whitespace; mirror that.
530
- const trimmed = raw.replace(/^\s+/, "");
531
- if (trimmed.length === 0)
532
- return null;
533
- let body = trimmed;
534
- let sign = 1;
535
- if (body.startsWith("+")) {
536
- body = body.slice(1);
537
- }
538
- else if (body.startsWith("-")) {
539
- sign = -1;
540
- body = body.slice(1);
541
- }
542
- if (body.length === 0)
543
- return null;
544
- let radix = 10;
545
- if (body.startsWith("0x") || body.startsWith("0X")) {
546
- radix = 16;
547
- body = body.slice(2);
548
- }
549
- else if (body.length > 1 && body.startsWith("0")) {
550
- // Leading zero on a multi-char body → octal (matches strtol base 0).
551
- radix = 8;
552
- body = body.slice(1);
553
- }
554
- if (body.length === 0)
555
- return null;
556
- // Reject trailing junk — strtol returns success only when the entire
557
- // body is consumed (`*end == '\0'`), and ParseVariableNum requires that.
558
- const digitRe = radix === 16 ? /^[0-9a-fA-F]+$/ : radix === 8 ? /^[0-7]+$/ : /^[0-9]+$/;
559
- if (!digitRe.test(body))
560
- return null;
561
- const parsed = sign * parseInt(body, radix);
562
- if (!Number.isFinite(parsed))
563
- return null;
564
- // 32-bit signed range (ParseVariableNum's `numval == (int) numval` check).
565
- if (parsed < -0x80000000 || parsed > 0x7fffffff)
566
- return null;
567
- return parsed;
366
+ const trimmed = raw.replace(/^\s+/, "");
367
+ if (trimmed.length === 0) return null;
368
+ let body = trimmed;
369
+ let sign = 1;
370
+ if (body.startsWith("+")) body = body.slice(1);
371
+ else if (body.startsWith("-")) {
372
+ sign = -1;
373
+ body = body.slice(1);
374
+ }
375
+ if (body.length === 0) return null;
376
+ let radix = 10;
377
+ if (body.startsWith("0x") || body.startsWith("0X")) {
378
+ radix = 16;
379
+ body = body.slice(2);
380
+ } else if (body.length > 1 && body.startsWith("0")) {
381
+ radix = 8;
382
+ body = body.slice(1);
383
+ }
384
+ if (body.length === 0) return null;
385
+ if (!(radix === 16 ? /^[0-9a-fA-F]+$/ : radix === 8 ? /^[0-7]+$/ : /^[0-9]+$/).test(body)) return null;
386
+ const parsed = sign * parseInt(body, radix);
387
+ if (!Number.isFinite(parsed)) return null;
388
+ if (parsed < -2147483648 || parsed > 2147483647) return null;
389
+ return parsed;
568
390
  };
569
391
  /**
570
- * Parse the `on`/`off`/`true`/`false`/`yes`/`no`/`1`/`0` boolean spelling
571
- * upstream uses for psql variables. Mirrors `ParseVariableBool` (without the
572
- * error reporting — callers handle that). Returns null on unrecognised input.
573
- */
392
+ * Parse the `on`/`off`/`true`/`false`/`yes`/`no`/`1`/`0` boolean spelling
393
+ * upstream uses for psql variables. Mirrors `ParseVariableBool` (without the
394
+ * error reporting — callers handle that). Returns null on unrecognised input.
395
+ */
574
396
  const parseOnOffBool = (raw) => {
575
- const v = raw.toLowerCase().trim();
576
- if (v === "" || v === "on" || v === "true" || v === "yes" || v === "1") {
577
- return true;
578
- }
579
- if (v === "off" || v === "false" || v === "no" || v === "0") {
580
- return false;
581
- }
582
- return null;
397
+ const v = raw.toLowerCase().trim();
398
+ if (v === "" || v === "on" || v === "true" || v === "yes" || v === "1") return true;
399
+ if (v === "off" || v === "false" || v === "no" || v === "0") return false;
400
+ return null;
583
401
  };
584
402
  /**
585
- * Strict validation for non-empty `WATCH_INTERVAL` values. Mirrors
586
- * upstream's `ParseVariableDouble(name, ..., 0, DEFAULT_WATCH_INTERVAL_MAX)`
587
- * three distinct error paths:
588
- *
589
- * - junk (strtod doesn't consume it) → `invalid value "<value>" for
590
- * variable "WATCH_INTERVAL"`
591
- * - parses but value < 0 → `must be greater than 0.00`
592
- * - parses but value > 1000000 → `must be less than 1000000.00`
593
- *
594
- * Returns `null` if the value is valid; otherwise returns the upstream
595
- * error string (sent through `trySet`'s hook-veto channel so the prior
596
- * value is preserved).
597
- */
403
+ * Strict validation for non-empty `WATCH_INTERVAL` values. Mirrors
404
+ * upstream's `ParseVariableDouble(name, ..., 0, DEFAULT_WATCH_INTERVAL_MAX)`
405
+ * three distinct error paths:
406
+ *
407
+ * - junk (strtod doesn't consume it) → `invalid value "<value>" for
408
+ * variable "WATCH_INTERVAL"`
409
+ * - parses but value < 0 → `must be greater than 0.00`
410
+ * - parses but value > 1000000 → `must be less than 1000000.00`
411
+ *
412
+ * Returns `null` if the value is valid; otherwise returns the upstream
413
+ * error string (sent through `trySet`'s hook-veto channel so the prior
414
+ * value is preserved).
415
+ */
598
416
  const validateWatchInterval = (raw) => {
599
- // strtod-style parse: leading whitespace + sign + (digits[.digits]|.digits)
600
- // [(eE)±digits]. We're stricter than strtod (no hex/inf), matching the
601
- // ParseVariableDouble syntax check.
602
- if (!/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/.test(raw)) {
603
- return `invalid value "${raw}" for variable "WATCH_INTERVAL"`;
604
- }
605
- const value = parseFloat(raw);
606
- // Upstream `ParseVariableDouble` consults `errno = ERANGE` after
607
- // `strtod` to distinguish overflow from generic junk: the overflow
608
- // case emits `is out of range` rather than the generic `invalid
609
- // value`. JS `parseFloat` returns Infinity on overflow — use that as
610
- // the ERANGE proxy.
611
- if (Number.isNaN(value)) {
612
- return `invalid value "${raw}" for variable "WATCH_INTERVAL"`;
613
- }
614
- if (!Number.isFinite(value)) {
615
- return `invalid value "${raw}" for variable "WATCH_INTERVAL": is out of range`;
616
- }
617
- if (value < 0) {
618
- return `invalid value "${raw}" for variable "WATCH_INTERVAL": must be greater than 0.00`;
619
- }
620
- if (value > WATCH_INTERVAL_MAX) {
621
- return `invalid value "${raw}" for variable "WATCH_INTERVAL": must be less than ${WATCH_INTERVAL_MAX.toFixed(2)}`;
622
- }
623
- return null;
417
+ if (!/^[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/.test(raw)) return `invalid value "${raw}" for variable "WATCH_INTERVAL"`;
418
+ const value = parseFloat(raw);
419
+ if (Number.isNaN(value)) return `invalid value "${raw}" for variable "WATCH_INTERVAL"`;
420
+ if (!Number.isFinite(value)) return `invalid value "${raw}" for variable "WATCH_INTERVAL": is out of range`;
421
+ if (value < 0) return `invalid value "${raw}" for variable "WATCH_INTERVAL": must be greater than 0.00`;
422
+ if (value > WATCH_INTERVAL_MAX) return `invalid value "${raw}" for variable "WATCH_INTERVAL": must be less than ${WATCH_INTERVAL_MAX.toFixed(2)}`;
423
+ return null;
624
424
  };
625
425
  /**
626
- * Bridge environment variables to psql state. Read-only with respect to the
627
- * environment; mutates `settings` (and its var store) in place.
628
- *
629
- * The variable names below match upstream where it reads them lazily:
630
- *
631
- * - PAGER, PSQL_PAGER, PSQL_WATCH_PAGER — consulted by `fe_utils/print.c`
632
- * (`PageOutput`) and `\watch`. Captured as psql vars so later WPs read
633
- * a single source of truth.
634
- * - EDITOR, VISUAL, PSQL_EDITOR, PSQL_EDITOR_LINENUMBER_ARG — consulted
635
- * by `\e`/`\ef`/`\ev` in `command.c::editFile`.
636
- * - PSQL_HISTORY — `src/bin/psql/input.c::initializeInput` uses it as the
637
- * history file path when readline is active. Stored as HISTFILE for
638
- * parity with psql's own `\set HISTFILE` convention.
639
- * - PSQL_HISTSIZE / PSQL_HISTCONTROL — likewise from `input.c`, stored
640
- * under the corresponding psql var names HISTSIZE / HISTCONTROL.
641
- * - PSQLRC — origin of the rc-file path (`startup.c::process_psqlrc`).
642
- * - COLUMNS — terminal width hint (`startup.c`: `pset.popt.topt.env_columns`).
643
- * - NO_COLOR — `https://no-color.org/` convention; consulted by the
644
- * printer/color code in upstream `fe_utils/print.c`. Captured here so
645
- * later WPs can opt out of ANSI coloring without re-reading `process.env`.
646
- */
647
- export const applyEnvOverrides = (settings, env = process.env) => {
648
- const bridge = (envName, varName) => {
649
- const value = env[envName];
650
- if (value !== undefined && value !== "") {
651
- settings.vars.set(varName, value);
652
- }
653
- };
654
- // Pager group: PSQL_PAGER takes precedence over PAGER in psql, so set
655
- // PAGER first and let PSQL_PAGER overwrite it.
656
- bridge("PAGER", "PAGER");
657
- bridge("PSQL_PAGER", "PAGER");
658
- bridge("PSQL_WATCH_PAGER", "PSQL_WATCH_PAGER");
659
- // Editor group: PSQL_EDITOR > VISUAL > EDITOR in psql. Set in
660
- // lowest-to-highest precedence so the final wins.
661
- bridge("EDITOR", "EDITOR");
662
- bridge("VISUAL", "EDITOR");
663
- bridge("PSQL_EDITOR", "EDITOR");
664
- bridge("PSQL_EDITOR_LINENUMBER_ARG", "EDITOR_LINENUMBER_ARG");
665
- // History group.
666
- bridge("PSQL_HISTORY", "HISTFILE");
667
- bridge("PSQL_HISTSIZE", "HISTSIZE");
668
- bridge("PSQL_HISTCONTROL", "HISTCONTROL");
669
- // RC file path.
670
- bridge("PSQLRC", "PSQLRC");
671
- // NO_COLOR — captured under a psql-style var name. The convention is
672
- // "any non-empty value disables color". We store the raw value so the
673
- // printer can decide via VarStore.asBool / its own check.
674
- if (env.NO_COLOR !== undefined && env.NO_COLOR !== "") {
675
- settings.vars.set("NO_COLOR", env.NO_COLOR);
676
- }
677
- // COLUMNS — feed into popt.topt.envColumns (upstream:
678
- // pset.popt.topt.env_columns = getenv("COLUMNS") ? atoi(...) : 0).
679
- const cols = env.COLUMNS;
680
- if (cols !== undefined && cols !== "") {
681
- const parsed = parseInt(cols, 10);
682
- if (Number.isFinite(parsed) && parsed >= 0) {
683
- settings.popt.topt.envColumns = parsed;
684
- }
685
- }
426
+ * Bridge environment variables to psql state. Read-only with respect to the
427
+ * environment; mutates `settings` (and its var store) in place.
428
+ *
429
+ * The variable names below match upstream where it reads them lazily:
430
+ *
431
+ * - PAGER, PSQL_PAGER, PSQL_WATCH_PAGER — consulted by `fe_utils/print.c`
432
+ * (`PageOutput`) and `\watch`. Captured as psql vars so later WPs read
433
+ * a single source of truth.
434
+ * - EDITOR, VISUAL, PSQL_EDITOR, PSQL_EDITOR_LINENUMBER_ARG — consulted
435
+ * by `\e`/`\ef`/`\ev` in `command.c::editFile`.
436
+ * - PSQL_HISTORY — `src/bin/psql/input.c::initializeInput` uses it as the
437
+ * history file path when readline is active. Stored as HISTFILE for
438
+ * parity with psql's own `\set HISTFILE` convention.
439
+ * - PSQL_HISTSIZE / PSQL_HISTCONTROL — likewise from `input.c`, stored
440
+ * under the corresponding psql var names HISTSIZE / HISTCONTROL.
441
+ * - PSQLRC — origin of the rc-file path (`startup.c::process_psqlrc`).
442
+ * - COLUMNS — terminal width hint (`startup.c`: `pset.popt.topt.env_columns`).
443
+ * - NO_COLOR — `https://no-color.org/` convention; consulted by the
444
+ * printer/color code in upstream `fe_utils/print.c`. Captured here so
445
+ * later WPs can opt out of ANSI coloring without re-reading `process.env`.
446
+ */
447
+ const applyEnvOverrides = (settings, env = process.env) => {
448
+ const bridge = (envName, varName) => {
449
+ const value = env[envName];
450
+ if (value !== void 0 && value !== "") settings.vars.set(varName, value);
451
+ };
452
+ bridge("PAGER", "PAGER");
453
+ bridge("PSQL_PAGER", "PAGER");
454
+ bridge("PSQL_WATCH_PAGER", "PSQL_WATCH_PAGER");
455
+ bridge("EDITOR", "EDITOR");
456
+ bridge("VISUAL", "EDITOR");
457
+ bridge("PSQL_EDITOR", "EDITOR");
458
+ bridge("PSQL_EDITOR_LINENUMBER_ARG", "EDITOR_LINENUMBER_ARG");
459
+ bridge("PSQL_HISTORY", "HISTFILE");
460
+ bridge("PSQL_HISTSIZE", "HISTSIZE");
461
+ bridge("PSQL_HISTCONTROL", "HISTCONTROL");
462
+ bridge("PSQLRC", "PSQLRC");
463
+ if (env.NO_COLOR !== void 0 && env.NO_COLOR !== "") settings.vars.set("NO_COLOR", env.NO_COLOR);
464
+ const cols = env.COLUMNS;
465
+ if (cols !== void 0 && cols !== "") {
466
+ const parsed = parseInt(cols, 10);
467
+ if (Number.isFinite(parsed) && parsed >= 0) settings.popt.topt.envColumns = parsed;
468
+ }
686
469
  };
470
+ //#endregion
471
+ export { DEFAULT_CSV_FIELD_SEP, DEFAULT_FIELD_SEP, DEFAULT_PROMPT1, DEFAULT_PROMPT2, DEFAULT_PROMPT3, DEFAULT_RECORD_SEP, applyEnvOverrides, defaultSettings };