neonctl 2.28.0 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/analytics.js +35 -33
- package/dist/api.js +34 -34
- package/dist/auth.js +50 -44
- package/dist/cli.js +2 -2
- package/dist/commands/auth.js +58 -52
- package/dist/commands/bootstrap.js +115 -157
- package/dist/commands/branches.js +154 -147
- package/dist/commands/bucket.js +124 -118
- package/dist/commands/checkout.js +49 -49
- package/dist/commands/config.js +212 -88
- package/dist/commands/connection_string.js +62 -62
- package/dist/commands/data_api.js +96 -96
- package/dist/commands/databases.js +23 -23
- package/dist/commands/deploy.js +12 -12
- package/dist/commands/dev.js +114 -114
- package/dist/commands/env.js +43 -43
- package/dist/commands/functions.js +97 -98
- package/dist/commands/index.js +26 -26
- package/dist/commands/init.js +23 -22
- package/dist/commands/ip_allow.js +29 -29
- package/dist/commands/link.js +223 -166
- package/dist/commands/neon_auth.js +381 -363
- package/dist/commands/operations.js +11 -11
- package/dist/commands/orgs.js +8 -8
- package/dist/commands/projects.js +101 -99
- package/dist/commands/psql.js +31 -31
- package/dist/commands/roles.js +21 -21
- package/dist/commands/schema_diff.js +23 -23
- package/dist/commands/set_context.js +17 -17
- package/dist/commands/status.js +17 -17
- package/dist/commands/user.js +5 -5
- package/dist/commands/vpc_endpoints.js +50 -50
- package/dist/config.js +7 -7
- package/dist/config_format.js +5 -5
- package/dist/context.js +23 -16
- package/dist/current_branch_fast_path.js +6 -6
- package/dist/dev/env.js +33 -33
- package/dist/dev/functions.js +4 -4
- package/dist/dev/inputs.js +6 -6
- package/dist/dev/runtime.js +25 -25
- package/dist/env.js +14 -14
- package/dist/env_file.js +13 -13
- package/dist/errors.js +19 -19
- package/dist/functions_api.js +10 -10
- package/dist/help.js +15 -15
- package/dist/index.js +94 -92
- package/dist/log.js +2 -2
- package/dist/pkg.js +5 -5
- package/dist/psql/cli.js +4 -2
- package/dist/psql/command/cmd_cond.js +61 -61
- package/dist/psql/command/cmd_connect.js +159 -154
- package/dist/psql/command/cmd_copy.js +107 -97
- package/dist/psql/command/cmd_describe.js +368 -363
- package/dist/psql/command/cmd_format.js +276 -263
- package/dist/psql/command/cmd_io.js +269 -263
- package/dist/psql/command/cmd_lo.js +74 -66
- package/dist/psql/command/cmd_meta.js +148 -148
- package/dist/psql/command/cmd_misc.js +17 -17
- package/dist/psql/command/cmd_pipeline.js +142 -135
- package/dist/psql/command/cmd_restrict.js +25 -25
- package/dist/psql/command/cmd_show.js +183 -168
- package/dist/psql/command/dispatch.js +26 -26
- package/dist/psql/command/shared.js +14 -14
- package/dist/psql/complete/filenames.js +16 -16
- package/dist/psql/complete/index.js +4 -4
- package/dist/psql/complete/matcher.js +33 -32
- package/dist/psql/complete/psqlVars.js +173 -173
- package/dist/psql/complete/queries.js +5 -3
- package/dist/psql/complete/rules.js +900 -863
- package/dist/psql/core/common.js +136 -133
- package/dist/psql/core/help.js +343 -343
- package/dist/psql/core/mainloop.js +160 -153
- package/dist/psql/core/prompt.js +126 -123
- package/dist/psql/core/settings.js +111 -111
- package/dist/psql/core/sqlHelp.js +150 -150
- package/dist/psql/core/startup.js +211 -205
- package/dist/psql/core/syncVars.js +14 -14
- package/dist/psql/core/variables.js +24 -24
- package/dist/psql/describe/formatters.js +302 -289
- package/dist/psql/describe/processNamePattern.js +28 -28
- package/dist/psql/describe/queries.js +656 -651
- package/dist/psql/index.js +436 -411
- package/dist/psql/io/history.js +36 -36
- package/dist/psql/io/input.js +15 -15
- package/dist/psql/io/lineEditor/buffer.js +27 -25
- package/dist/psql/io/lineEditor/complete.js +15 -15
- package/dist/psql/io/lineEditor/filename.js +22 -22
- package/dist/psql/io/lineEditor/index.js +65 -62
- package/dist/psql/io/lineEditor/keymap.js +325 -318
- package/dist/psql/io/lineEditor/vt100.js +60 -60
- package/dist/psql/io/pgpass.js +18 -18
- package/dist/psql/io/pgservice.js +14 -14
- package/dist/psql/io/psqlrc.js +46 -46
- package/dist/psql/print/aligned.js +175 -166
- package/dist/psql/print/asciidoc.js +51 -51
- package/dist/psql/print/crosstab.js +34 -31
- package/dist/psql/print/csv.js +25 -22
- package/dist/psql/print/html.js +54 -54
- package/dist/psql/print/json.js +12 -12
- package/dist/psql/print/latex.js +118 -118
- package/dist/psql/print/pager.js +28 -26
- package/dist/psql/print/troff.js +48 -48
- package/dist/psql/print/unaligned.js +15 -14
- package/dist/psql/print/units.js +17 -17
- package/dist/psql/scanner/slash.js +48 -46
- package/dist/psql/scanner/sql.js +88 -84
- package/dist/psql/scanner/stringutils.js +21 -17
- package/dist/psql/types/index.js +7 -7
- package/dist/psql/types/scanner.js +8 -8
- package/dist/psql/wire/connection.js +341 -327
- package/dist/psql/wire/copy.js +7 -7
- package/dist/psql/wire/pipeline.js +26 -24
- package/dist/psql/wire/protocol.js +102 -102
- package/dist/psql/wire/sasl.js +62 -62
- package/dist/psql/wire/tls.js +79 -73
- package/dist/storage_api.js +15 -15
- package/dist/test_utils/fixtures.js +34 -31
- package/dist/test_utils/oauth_server.js +5 -5
- package/dist/utils/api_enums.js +13 -13
- package/dist/utils/branch_notice.js +5 -5
- package/dist/utils/branch_picker.js +26 -26
- package/dist/utils/compute_units.js +4 -4
- package/dist/utils/enrichers.js +20 -15
- package/dist/utils/esbuild.js +28 -28
- package/dist/utils/formats.js +1 -1
- package/dist/utils/middlewares.js +3 -3
- package/dist/utils/package_manager.js +68 -0
- package/dist/utils/point_in_time.js +12 -12
- package/dist/utils/psql.js +30 -30
- package/dist/utils/string.js +2 -2
- package/dist/utils/ui.js +9 -9
- package/dist/utils/zip.js +1 -1
- package/dist/writer.js +17 -17
- package/package.json +6 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatNumericLocale } from
|
|
1
|
+
import { formatNumericLocale } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* AsciiDoc printer.
|
|
4
4
|
*
|
|
@@ -49,10 +49,10 @@ import { formatNumericLocale } from './units.js';
|
|
|
49
49
|
// INT2, INT4, INT8, FLOAT4, FLOAT8, NUMERIC, INTERVAL.
|
|
50
50
|
const NUMERIC_OIDS = new Set([21, 23, 20, 700, 701, 1700, 1186]);
|
|
51
51
|
export const asciidocPrinter = {
|
|
52
|
-
format:
|
|
52
|
+
format: "asciidoc",
|
|
53
53
|
printQuery(rs, opts, out) {
|
|
54
54
|
const topt = opts.topt;
|
|
55
|
-
if (topt.expanded ===
|
|
55
|
+
if (topt.expanded === "on") {
|
|
56
56
|
return printExpanded(rs, opts, out);
|
|
57
57
|
}
|
|
58
58
|
return printFlat(rs, opts, out);
|
|
@@ -63,63 +63,63 @@ const printFlat = (rs, opts, out) => {
|
|
|
63
63
|
const tuplesOnly = topt.tuplesOnly;
|
|
64
64
|
const startTable = topt.startTable;
|
|
65
65
|
const stopTable = topt.stopTable;
|
|
66
|
-
const nullPrint = opts.nullPrint !==
|
|
66
|
+
const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
|
|
67
67
|
const title = opts.title ?? topt.title;
|
|
68
68
|
const footers = opts.footers ?? topt.footers;
|
|
69
69
|
const headers = rs.fields.map((f) => f.name);
|
|
70
|
-
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ?
|
|
70
|
+
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
71
71
|
const ncols = rs.fields.length;
|
|
72
72
|
const cells = rs.rows.map((row) => row.map((cell) => renderCell(cell, nullPrint, topt.numericLocale)));
|
|
73
|
-
let buf =
|
|
73
|
+
let buf = "";
|
|
74
74
|
if (startTable) {
|
|
75
75
|
// Force a paragraph break (upstream always emits a leading "\n").
|
|
76
|
-
buf +=
|
|
76
|
+
buf += "\n";
|
|
77
77
|
if (!tuplesOnly && title) {
|
|
78
|
-
buf +=
|
|
78
|
+
buf += "." + title + "\n";
|
|
79
79
|
}
|
|
80
|
-
buf +=
|
|
80
|
+
buf += "[";
|
|
81
81
|
if (!tuplesOnly)
|
|
82
82
|
buf += 'options="header",';
|
|
83
83
|
buf += 'cols="';
|
|
84
|
-
buf += aligns.map((a) => (a ===
|
|
84
|
+
buf += aligns.map((a) => (a === "r" ? ">l" : "<l")).join(",");
|
|
85
85
|
buf += '"';
|
|
86
86
|
buf += borderClause(topt.border);
|
|
87
|
-
buf +=
|
|
88
|
-
buf +=
|
|
87
|
+
buf += "]\n";
|
|
88
|
+
buf += "|====\n";
|
|
89
89
|
if (!tuplesOnly) {
|
|
90
90
|
headers.forEach((h, idx) => {
|
|
91
91
|
if (idx !== 0)
|
|
92
|
-
buf +=
|
|
93
|
-
buf +=
|
|
92
|
+
buf += " ";
|
|
93
|
+
buf += "^l|" + escapeAsciidoc(h);
|
|
94
94
|
});
|
|
95
|
-
buf +=
|
|
95
|
+
buf += "\n";
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
for (const row of cells) {
|
|
99
99
|
row.forEach((value, idx) => {
|
|
100
100
|
if (idx !== 0)
|
|
101
|
-
buf +=
|
|
102
|
-
buf +=
|
|
101
|
+
buf += " ";
|
|
102
|
+
buf += "|";
|
|
103
103
|
if (isWhitespaceOnly(value)) {
|
|
104
104
|
// The upstream code emits a trailing space only for cells
|
|
105
105
|
// that are not the last in their row.
|
|
106
106
|
if (idx !== ncols - 1)
|
|
107
|
-
buf +=
|
|
107
|
+
buf += " ";
|
|
108
108
|
}
|
|
109
109
|
else {
|
|
110
110
|
buf += escapeAsciidoc(value);
|
|
111
111
|
}
|
|
112
112
|
});
|
|
113
|
-
buf +=
|
|
113
|
+
buf += "\n";
|
|
114
114
|
}
|
|
115
|
-
buf +=
|
|
115
|
+
buf += "|====\n";
|
|
116
116
|
if (stopTable && !tuplesOnly) {
|
|
117
117
|
const effective = effectiveFooters(rs, topt, footers);
|
|
118
118
|
if (effective.length > 0) {
|
|
119
|
-
buf +=
|
|
119
|
+
buf += "\n....\n";
|
|
120
120
|
for (const f of effective)
|
|
121
|
-
buf += f +
|
|
122
|
-
buf +=
|
|
121
|
+
buf += f + "\n";
|
|
122
|
+
buf += "....\n";
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
out.write(buf);
|
|
@@ -130,22 +130,22 @@ const printExpanded = (rs, opts, out) => {
|
|
|
130
130
|
const tuplesOnly = topt.tuplesOnly;
|
|
131
131
|
const startTable = topt.startTable;
|
|
132
132
|
const stopTable = topt.stopTable;
|
|
133
|
-
const nullPrint = opts.nullPrint !==
|
|
133
|
+
const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
|
|
134
134
|
const title = opts.title ?? topt.title;
|
|
135
135
|
const footers = opts.footers ?? topt.footers;
|
|
136
136
|
const headers = rs.fields.map((f) => f.name);
|
|
137
|
-
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ?
|
|
137
|
+
const aligns = rs.fields.map((f) => NUMERIC_OIDS.has(f.dataTypeID) ? "r" : "l");
|
|
138
138
|
const cells = rs.rows.map((row) => row.map((cell) => renderCell(cell, nullPrint, topt.numericLocale)));
|
|
139
|
-
let buf =
|
|
139
|
+
let buf = "";
|
|
140
140
|
if (startTable) {
|
|
141
|
-
buf +=
|
|
141
|
+
buf += "\n";
|
|
142
142
|
if (!tuplesOnly && title) {
|
|
143
|
-
buf +=
|
|
143
|
+
buf += "." + title + "\n";
|
|
144
144
|
}
|
|
145
145
|
buf += '[cols="h,l"';
|
|
146
146
|
buf += borderClause(topt.border);
|
|
147
|
-
buf +=
|
|
148
|
-
buf +=
|
|
147
|
+
buf += "]\n";
|
|
148
|
+
buf += "|====\n";
|
|
149
149
|
}
|
|
150
150
|
let record = topt.prior + 1;
|
|
151
151
|
cells.forEach((row) => {
|
|
@@ -154,29 +154,29 @@ const printExpanded = (rs, opts, out) => {
|
|
|
154
154
|
record += 1;
|
|
155
155
|
}
|
|
156
156
|
else {
|
|
157
|
-
buf +=
|
|
157
|
+
buf += "2+|\n";
|
|
158
158
|
}
|
|
159
159
|
row.forEach((value, idx) => {
|
|
160
|
-
buf +=
|
|
161
|
-
buf +=
|
|
160
|
+
buf += "<l|" + escapeAsciidoc(headers[idx]);
|
|
161
|
+
buf += " " + (aligns[idx] === "r" ? ">l" : "<l") + "|";
|
|
162
162
|
if (isWhitespaceOnly(value)) {
|
|
163
|
-
buf +=
|
|
163
|
+
buf += " ";
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
166
166
|
buf += escapeAsciidoc(value);
|
|
167
167
|
}
|
|
168
|
-
buf +=
|
|
168
|
+
buf += "\n";
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
|
-
buf +=
|
|
171
|
+
buf += "|====\n";
|
|
172
172
|
if (stopTable && !tuplesOnly) {
|
|
173
173
|
// Expanded mode does NOT emit the default "(N rows)" footer —
|
|
174
174
|
// only user-supplied footers (matches print_asciidoc_vertical).
|
|
175
175
|
if (footers && footers.length > 0) {
|
|
176
|
-
buf +=
|
|
176
|
+
buf += "\n....\n";
|
|
177
177
|
for (const f of footers)
|
|
178
|
-
buf += f +
|
|
179
|
-
buf +=
|
|
178
|
+
buf += f + "\n";
|
|
179
|
+
buf += "....\n";
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
out.write(buf);
|
|
@@ -191,7 +191,7 @@ const borderClause = (border) => {
|
|
|
191
191
|
case 2:
|
|
192
192
|
return ',frame="all",grid="all"';
|
|
193
193
|
default:
|
|
194
|
-
return
|
|
194
|
+
return "";
|
|
195
195
|
}
|
|
196
196
|
};
|
|
197
197
|
const effectiveFooters = (rs, topt, footers) => {
|
|
@@ -199,7 +199,7 @@ const effectiveFooters = (rs, topt, footers) => {
|
|
|
199
199
|
return footers;
|
|
200
200
|
if (topt.defaultFooter) {
|
|
201
201
|
const n = rs.rows.length;
|
|
202
|
-
return [`(${String(n)} ${n === 1 ?
|
|
202
|
+
return [`(${String(n)} ${n === 1 ? "row" : "rows"})`];
|
|
203
203
|
}
|
|
204
204
|
return [];
|
|
205
205
|
};
|
|
@@ -207,7 +207,7 @@ const isWhitespaceOnly = (s) => {
|
|
|
207
207
|
if (s.length === 0)
|
|
208
208
|
return true;
|
|
209
209
|
for (const ch of s) {
|
|
210
|
-
if (ch !==
|
|
210
|
+
if (ch !== " " && ch !== "\t")
|
|
211
211
|
return false;
|
|
212
212
|
}
|
|
213
213
|
return true;
|
|
@@ -216,10 +216,10 @@ const escapeAsciidoc = (input) => {
|
|
|
216
216
|
// Only `|` is structurally hostile (closes a cell). Newlines and
|
|
217
217
|
// every other character pass through; AsciiDoc treats embedded `\n`
|
|
218
218
|
// as a soft line break within a cell.
|
|
219
|
-
let out =
|
|
219
|
+
let out = "";
|
|
220
220
|
for (const ch of input) {
|
|
221
|
-
if (ch ===
|
|
222
|
-
out +=
|
|
221
|
+
if (ch === "|")
|
|
222
|
+
out += "\\|";
|
|
223
223
|
else
|
|
224
224
|
out += ch;
|
|
225
225
|
}
|
|
@@ -228,20 +228,20 @@ const escapeAsciidoc = (input) => {
|
|
|
228
228
|
const renderCell = (cell, nullPrint, numericLocale) => {
|
|
229
229
|
if (cell === null || cell === undefined)
|
|
230
230
|
return nullPrint;
|
|
231
|
-
if (typeof cell ===
|
|
231
|
+
if (typeof cell === "string") {
|
|
232
232
|
return formatNumericLocale(cell, numericLocale);
|
|
233
233
|
}
|
|
234
|
-
if (typeof cell ===
|
|
234
|
+
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
235
235
|
return formatNumericLocale(cell.toString(), numericLocale);
|
|
236
236
|
}
|
|
237
|
-
if (typeof cell ===
|
|
238
|
-
return cell ?
|
|
237
|
+
if (typeof cell === "boolean")
|
|
238
|
+
return cell ? "t" : "f";
|
|
239
239
|
if (cell instanceof Date)
|
|
240
240
|
return cell.toISOString();
|
|
241
241
|
if (cell instanceof Uint8Array) {
|
|
242
|
-
let hex =
|
|
242
|
+
let hex = "\\x";
|
|
243
243
|
for (const b of cell)
|
|
244
|
-
hex += b.toString(16).padStart(2,
|
|
244
|
+
hex += b.toString(16).padStart(2, "0");
|
|
245
245
|
return hex;
|
|
246
246
|
}
|
|
247
247
|
return JSON.stringify(cell);
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
* - "maximum number of columns (1600) exceeded",
|
|
35
35
|
* - "query result contains multiple data values for row \"…\", column \"…\"".
|
|
36
36
|
*/
|
|
37
|
-
import { alignedPrinter } from
|
|
37
|
+
import { alignedPrinter } from "./aligned.js";
|
|
38
38
|
const DIGIT_RE = /^\d+$/;
|
|
39
39
|
const SIGNED_DIGIT_RE = /^[+-]?\d+$/;
|
|
40
40
|
const dequoteDowncase = (raw) => {
|
|
41
|
-
let out =
|
|
41
|
+
let out = "";
|
|
42
42
|
let inquotes = false;
|
|
43
43
|
let hadQuotes = false;
|
|
44
44
|
let i = 0;
|
|
@@ -62,7 +62,7 @@ const dequoteDowncase = (raw) => {
|
|
|
62
62
|
};
|
|
63
63
|
const indexOfColumn = (arg, fields, allowSign) => {
|
|
64
64
|
// Numeric arg path: number type, or string that parses as integer.
|
|
65
|
-
if (typeof arg ===
|
|
65
|
+
if (typeof arg === "number") {
|
|
66
66
|
if (!Number.isInteger(arg)) {
|
|
67
67
|
return {
|
|
68
68
|
ok: false,
|
|
@@ -81,8 +81,8 @@ const indexOfColumn = (arg, fields, allowSign) => {
|
|
|
81
81
|
}
|
|
82
82
|
let str = arg.trim();
|
|
83
83
|
let sign = 1;
|
|
84
|
-
if (allowSign && (str.startsWith(
|
|
85
|
-
if (str.startsWith(
|
|
84
|
+
if (allowSign && (str.startsWith("+") || str.startsWith("-"))) {
|
|
85
|
+
if (str.startsWith("-"))
|
|
86
86
|
sign = -1;
|
|
87
87
|
// Peel only when the rest looks like it could be a referencing token.
|
|
88
88
|
const rest = str.slice(1);
|
|
@@ -90,7 +90,7 @@ const indexOfColumn = (arg, fields, allowSign) => {
|
|
|
90
90
|
str = rest;
|
|
91
91
|
}
|
|
92
92
|
if (str.length === 0) {
|
|
93
|
-
return { ok: false, error:
|
|
93
|
+
return { ok: false, error: "empty column reference" };
|
|
94
94
|
}
|
|
95
95
|
if (DIGIT_RE.test(str)) {
|
|
96
96
|
const n = parseInt(str, 10);
|
|
@@ -120,7 +120,10 @@ const indexOfColumn = (arg, fields, allowSign) => {
|
|
|
120
120
|
// in the error message, not the raw arg with its leading
|
|
121
121
|
// quotes. Matches `pg_log_error("ambiguous column name: \"%s\"")`
|
|
122
122
|
// after the in-place `dequote_downcase_identifier(arg)` mutation.
|
|
123
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: `ambiguous column name: "${needle}"`,
|
|
126
|
+
};
|
|
124
127
|
}
|
|
125
128
|
found = i;
|
|
126
129
|
}
|
|
@@ -147,24 +150,24 @@ const indexOfColumn = (arg, fields, allowSign) => {
|
|
|
147
150
|
* Null is represented by a sentinel so the printer can substitute
|
|
148
151
|
* `popt.nullPrint`.
|
|
149
152
|
*/
|
|
150
|
-
const NULL_SENTINEL = Symbol.for(
|
|
153
|
+
const NULL_SENTINEL = Symbol.for("neonctl.psql.crosstab.null");
|
|
151
154
|
const headerKey = (value) => {
|
|
152
155
|
if (value === null || value === undefined)
|
|
153
156
|
return NULL_SENTINEL;
|
|
154
|
-
if (typeof value ===
|
|
157
|
+
if (typeof value === "string")
|
|
155
158
|
return `s:${value}`;
|
|
156
|
-
if (typeof value ===
|
|
159
|
+
if (typeof value === "number")
|
|
157
160
|
return `n:${String(value)}`;
|
|
158
|
-
if (typeof value ===
|
|
161
|
+
if (typeof value === "bigint")
|
|
159
162
|
return `i:${String(value)}`;
|
|
160
|
-
if (typeof value ===
|
|
163
|
+
if (typeof value === "boolean")
|
|
161
164
|
return `b:${String(value)}`;
|
|
162
165
|
if (value instanceof Date)
|
|
163
166
|
return `d:${value.toISOString()}`;
|
|
164
167
|
if (value instanceof Uint8Array) {
|
|
165
|
-
let hex =
|
|
168
|
+
let hex = "x:";
|
|
166
169
|
for (const b of value)
|
|
167
|
-
hex += b.toString(16).padStart(2,
|
|
170
|
+
hex += b.toString(16).padStart(2, "0");
|
|
168
171
|
return hex;
|
|
169
172
|
}
|
|
170
173
|
return `j:${JSON.stringify(value)}`;
|
|
@@ -177,19 +180,19 @@ const headerKey = (value) => {
|
|
|
177
180
|
const headerDisplay = (value, nullPrint) => {
|
|
178
181
|
if (value === null || value === undefined)
|
|
179
182
|
return nullPrint;
|
|
180
|
-
if (typeof value ===
|
|
183
|
+
if (typeof value === "string")
|
|
181
184
|
return value;
|
|
182
|
-
if (typeof value ===
|
|
185
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
183
186
|
return value.toString();
|
|
184
187
|
}
|
|
185
|
-
if (typeof value ===
|
|
186
|
-
return value ?
|
|
188
|
+
if (typeof value === "boolean")
|
|
189
|
+
return value ? "t" : "f";
|
|
187
190
|
if (value instanceof Date)
|
|
188
191
|
return value.toISOString();
|
|
189
192
|
if (value instanceof Uint8Array) {
|
|
190
|
-
let hex =
|
|
193
|
+
let hex = "\\x";
|
|
191
194
|
for (const b of value)
|
|
192
|
-
hex += b.toString(16).padStart(2,
|
|
195
|
+
hex += b.toString(16).padStart(2, "0");
|
|
193
196
|
return hex;
|
|
194
197
|
}
|
|
195
198
|
return JSON.stringify(value);
|
|
@@ -209,8 +212,8 @@ const cmpForSort = (a, b, sign) => {
|
|
|
209
212
|
return 1; // null last
|
|
210
213
|
if (bNull)
|
|
211
214
|
return -1;
|
|
212
|
-
const aStr = typeof a ===
|
|
213
|
-
const bStr = typeof b ===
|
|
215
|
+
const aStr = typeof a === "string" ? a : headerDisplay(a, "");
|
|
216
|
+
const bStr = typeof b === "string" ? b : headerDisplay(b, "");
|
|
214
217
|
if (SIGNED_DIGIT_RE.test(aStr) && SIGNED_DIGIT_RE.test(bStr)) {
|
|
215
218
|
const an = parseInt(aStr, 10);
|
|
216
219
|
const bn = parseInt(bStr, 10);
|
|
@@ -264,13 +267,13 @@ export const pivotResultSet = (rs, opts,
|
|
|
264
267
|
* empty string by default). Callers that don't care can omit; tests
|
|
265
268
|
* that drive the function in isolation can supply a sentinel.
|
|
266
269
|
*/
|
|
267
|
-
nullPrint =
|
|
270
|
+
nullPrint = "") => {
|
|
268
271
|
// (1) Field resolution. Upstream `crosstabview.c` requires PQnfields >= 3
|
|
269
272
|
// unconditionally — pivoting two columns is degenerate (V × H with no
|
|
270
273
|
// payload). Match the error text verbatim so the conformance test sees
|
|
271
274
|
// the same line.
|
|
272
275
|
if (rs.fields.length < 3) {
|
|
273
|
-
return { error:
|
|
276
|
+
return { error: "query must return at least three columns" };
|
|
274
277
|
}
|
|
275
278
|
const colV = opts.colV ?? 1;
|
|
276
279
|
const colH = opts.colH ?? 2;
|
|
@@ -282,7 +285,7 @@ nullPrint = '') => {
|
|
|
282
285
|
return { error: hRes.error };
|
|
283
286
|
if (vRes.index === hRes.index) {
|
|
284
287
|
return {
|
|
285
|
-
error:
|
|
288
|
+
error: "vertical and horizontal headers must be different columns",
|
|
286
289
|
};
|
|
287
290
|
}
|
|
288
291
|
let dataIdx;
|
|
@@ -299,7 +302,7 @@ nullPrint = '') => {
|
|
|
299
302
|
}
|
|
300
303
|
}
|
|
301
304
|
if (candidate < 0) {
|
|
302
|
-
return { error:
|
|
305
|
+
return { error: "no data column available" };
|
|
303
306
|
}
|
|
304
307
|
dataIdx = candidate;
|
|
305
308
|
}
|
|
@@ -349,7 +352,7 @@ nullPrint = '') => {
|
|
|
349
352
|
// result wouldn't be printable in a reasonable width anyway, so
|
|
350
353
|
// we mirror the cap and the error text verbatim.
|
|
351
354
|
if (hHeaders.size >= 1600) {
|
|
352
|
-
return { error:
|
|
355
|
+
return { error: "maximum number of columns (1600) exceeded" };
|
|
353
356
|
}
|
|
354
357
|
hEntry = {
|
|
355
358
|
key: hk,
|
|
@@ -361,8 +364,8 @@ nullPrint = '') => {
|
|
|
361
364
|
}
|
|
362
365
|
const cellKey = `${String(vEntry.rank)}|${String(hEntry.rank)}`;
|
|
363
366
|
if (matrix.has(cellKey)) {
|
|
364
|
-
const vDisp = headerDisplay(vEntry.value,
|
|
365
|
-
const hDisp = headerDisplay(hEntry.value,
|
|
367
|
+
const vDisp = headerDisplay(vEntry.value, "(null)");
|
|
368
|
+
const hDisp = headerDisplay(hEntry.value, "(null)");
|
|
366
369
|
return {
|
|
367
370
|
error: `query result contains multiple data values for row "${vDisp}", column "${hDisp}"`,
|
|
368
371
|
};
|
|
@@ -417,7 +420,7 @@ nullPrint = '') => {
|
|
|
417
420
|
// Unfilled cells: empty string so the aligned printer emits nothing
|
|
418
421
|
// (rather than substituting nullPrint). Matches upstream's
|
|
419
422
|
// "non-initialized cells must be set to an empty string" pass.
|
|
420
|
-
row[i + 1] = matrix.has(key) ? matrix.get(key) :
|
|
423
|
+
row[i + 1] = matrix.has(key) ? matrix.get(key) : "";
|
|
421
424
|
}
|
|
422
425
|
return row;
|
|
423
426
|
});
|
|
@@ -453,7 +456,7 @@ export const printCrosstab = async (rs, opts, printOpts, out) => {
|
|
|
453
456
|
// cell). Without this, the synthetic FieldDescription.name comes out
|
|
454
457
|
// as the empty string and the column header is just whitespace.
|
|
455
458
|
const result = pivotResultSet(rs, opts, printOpts.topt.nullPrint);
|
|
456
|
-
if (
|
|
459
|
+
if ("error" in result)
|
|
457
460
|
return result;
|
|
458
461
|
await alignedPrinter.printQuery(result.rs, printOpts, out);
|
|
459
462
|
return undefined;
|
package/dist/psql/print/csv.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatNumericLocale } from
|
|
1
|
+
import { formatNumericLocale } from "./units.js";
|
|
2
2
|
/**
|
|
3
3
|
* RFC 4180 CSV printer.
|
|
4
4
|
*
|
|
@@ -22,35 +22,38 @@ import { formatNumericLocale } from './units.js';
|
|
|
22
22
|
* sentinel) when the separator is `\` or `.`, matching upstream.
|
|
23
23
|
*/
|
|
24
24
|
export const csvPrinter = {
|
|
25
|
-
format:
|
|
25
|
+
format: "csv",
|
|
26
26
|
printQuery(rs, opts, out) {
|
|
27
27
|
const topt = opts.topt;
|
|
28
|
-
const sep = topt.csvFieldSep !== undefined && topt.csvFieldSep !==
|
|
28
|
+
const sep = topt.csvFieldSep !== undefined && topt.csvFieldSep !== ""
|
|
29
29
|
? topt.csvFieldSep
|
|
30
|
-
:
|
|
31
|
-
if (sep.length !== 1 || sep === '"' || sep ===
|
|
30
|
+
: ",";
|
|
31
|
+
if (sep.length !== 1 || sep === '"' || sep === "\n" || sep === "\r") {
|
|
32
32
|
throw new RangeError(`csv_fieldsep must be a single character other than '"', '\\n', or '\\r' (got ${JSON.stringify(sep)})`);
|
|
33
33
|
}
|
|
34
|
-
const nullPrint = opts.nullPrint !==
|
|
35
|
-
const expanded = topt.expanded ===
|
|
34
|
+
const nullPrint = opts.nullPrint !== "" ? opts.nullPrint : topt.nullPrint;
|
|
35
|
+
const expanded = topt.expanded === "on";
|
|
36
36
|
const headers = rs.fields.map((f) => f.name);
|
|
37
37
|
const cells = rs.rows.map((row) => row.map((cell) => renderCell(cell, nullPrint, topt.numericLocale)));
|
|
38
|
-
let outBuf =
|
|
38
|
+
let outBuf = "";
|
|
39
39
|
if (expanded) {
|
|
40
40
|
for (const row of cells) {
|
|
41
41
|
row.forEach((value, colIdx) => {
|
|
42
42
|
outBuf +=
|
|
43
|
-
csvField(headers[colIdx], sep) +
|
|
43
|
+
csvField(headers[colIdx], sep) +
|
|
44
|
+
sep +
|
|
45
|
+
csvField(value, sep) +
|
|
46
|
+
"\n";
|
|
44
47
|
});
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
else {
|
|
48
51
|
// Header is gated on startTable && !tuplesOnly (cf. print.c).
|
|
49
52
|
if (topt.startTable && !topt.tuplesOnly) {
|
|
50
|
-
outBuf += headers.map((h) => csvField(h, sep)).join(sep) +
|
|
53
|
+
outBuf += headers.map((h) => csvField(h, sep)).join(sep) + "\n";
|
|
51
54
|
}
|
|
52
55
|
for (const row of cells) {
|
|
53
|
-
outBuf += row.map((c) => csvField(c, sep)).join(sep) +
|
|
56
|
+
outBuf += row.map((c) => csvField(c, sep)).join(sep) + "\n";
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
out.write(outBuf);
|
|
@@ -60,20 +63,20 @@ export const csvPrinter = {
|
|
|
60
63
|
const renderCell = (cell, nullPrint, numericLocale) => {
|
|
61
64
|
if (cell === null || cell === undefined)
|
|
62
65
|
return nullPrint;
|
|
63
|
-
if (typeof cell ===
|
|
66
|
+
if (typeof cell === "string") {
|
|
64
67
|
return formatNumericLocale(cell, numericLocale);
|
|
65
68
|
}
|
|
66
|
-
if (typeof cell ===
|
|
69
|
+
if (typeof cell === "number" || typeof cell === "bigint") {
|
|
67
70
|
return formatNumericLocale(cell.toString(), numericLocale);
|
|
68
71
|
}
|
|
69
|
-
if (typeof cell ===
|
|
70
|
-
return cell ?
|
|
72
|
+
if (typeof cell === "boolean")
|
|
73
|
+
return cell ? "t" : "f";
|
|
71
74
|
if (cell instanceof Date)
|
|
72
75
|
return cell.toISOString();
|
|
73
76
|
if (cell instanceof Uint8Array) {
|
|
74
|
-
let hex =
|
|
77
|
+
let hex = "\\x";
|
|
75
78
|
for (const b of cell)
|
|
76
|
-
hex += b.toString(16).padStart(2,
|
|
79
|
+
hex += b.toString(16).padStart(2, "0");
|
|
77
80
|
return hex;
|
|
78
81
|
}
|
|
79
82
|
return JSON.stringify(cell);
|
|
@@ -81,11 +84,11 @@ const renderCell = (cell, nullPrint, numericLocale) => {
|
|
|
81
84
|
const csvField = (value, sep) => {
|
|
82
85
|
const needsQuote = value.includes(sep) ||
|
|
83
86
|
value.includes('"') ||
|
|
84
|
-
value.includes(
|
|
85
|
-
value.includes(
|
|
86
|
-
value ===
|
|
87
|
-
sep ===
|
|
88
|
-
sep ===
|
|
87
|
+
value.includes("\n") ||
|
|
88
|
+
value.includes("\r") ||
|
|
89
|
+
value === "\\." ||
|
|
90
|
+
sep === "\\" ||
|
|
91
|
+
sep === ".";
|
|
89
92
|
if (!needsQuote)
|
|
90
93
|
return value;
|
|
91
94
|
return `"${value.replace(/"/g, '""')}"`;
|