postgrejs 2.23.1 → 3.0.2

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 (107) hide show
  1. package/CHANGELOG.md +171 -1
  2. package/LICENSE +26 -17
  3. package/README.md +195 -175
  4. package/connection/connection.d.ts +169 -16
  5. package/connection/connection.js +318 -56
  6. package/connection/copy-stream.d.ts +88 -0
  7. package/connection/copy-stream.js +244 -0
  8. package/connection/cursor.js +20 -13
  9. package/connection/intl-connection.d.ts +113 -5
  10. package/connection/intl-connection.js +500 -70
  11. package/connection/large-object.d.ts +65 -0
  12. package/connection/large-object.js +151 -0
  13. package/connection/logical-replication.d.ts +121 -0
  14. package/connection/logical-replication.js +279 -0
  15. package/connection/pool.d.ts +61 -4
  16. package/connection/pool.js +177 -29
  17. package/connection/portal.d.ts +18 -4
  18. package/connection/portal.js +53 -59
  19. package/connection/prepared-statement.d.ts +22 -1
  20. package/connection/prepared-statement.js +129 -109
  21. package/data-type-map.js +4 -2
  22. package/data-types/bool-type.js +14 -10
  23. package/data-types/box-type.js +15 -8
  24. package/data-types/bytea-type.js +9 -5
  25. package/data-types/char-type.js +11 -5
  26. package/data-types/circle-type.js +15 -7
  27. package/data-types/date-type.js +25 -19
  28. package/data-types/float4-type.js +10 -3
  29. package/data-types/float8-type.js +12 -3
  30. package/data-types/int2-type.js +9 -4
  31. package/data-types/int2-vector-type.js +9 -4
  32. package/data-types/int4-type.js +11 -5
  33. package/data-types/int8-type.js +22 -7
  34. package/data-types/json-type.js +17 -8
  35. package/data-types/jsonb-type.js +20 -14
  36. package/data-types/lseg-type.js +16 -8
  37. package/data-types/numeric-type.d.ts +8 -0
  38. package/data-types/numeric-type.js +118 -9
  39. package/data-types/oid-type.d.ts +0 -1
  40. package/data-types/oid-type.js +9 -10
  41. package/data-types/oid-vector-type.d.ts +9 -0
  42. package/data-types/oid-vector-type.js +51 -0
  43. package/data-types/point-type.js +14 -6
  44. package/data-types/time-type.js +20 -13
  45. package/data-types/timestamp-type.js +32 -25
  46. package/data-types/timestamptz-type.js +33 -21
  47. package/data-types/uuid-type.js +21 -11
  48. package/data-types/varchar-type.js +16 -4
  49. package/index.d.ts +5 -0
  50. package/index.js +5 -0
  51. package/interfaces/command-result.d.ts +2 -1
  52. package/interfaces/data-type.d.ts +5 -3
  53. package/interfaces/database-connection-params.d.ts +108 -0
  54. package/interfaces/query-options.d.ts +23 -0
  55. package/interfaces/script-execute-options.d.ts +23 -0
  56. package/interfaces/script-result.d.ts +3 -2
  57. package/package.json +2 -3
  58. package/protocol/backend.d.ts +6 -1
  59. package/protocol/backend.js +70 -45
  60. package/protocol/buffer-reader.d.ts +0 -2
  61. package/protocol/buffer-reader.js +3 -10
  62. package/protocol/cert-signature.d.ts +12 -0
  63. package/protocol/cert-signature.js +114 -0
  64. package/protocol/frontend.d.ts +16 -0
  65. package/protocol/frontend.js +70 -11
  66. package/protocol/pg-socket.d.ts +220 -9
  67. package/protocol/pg-socket.js +539 -102
  68. package/protocol/pgoutput.d.ts +76 -0
  69. package/protocol/pgoutput.js +155 -0
  70. package/protocol/protocol.d.ts +3 -4
  71. package/protocol/protocol.js +1 -1
  72. package/protocol/sasl.d.ts +11 -1
  73. package/protocol/sasl.js +36 -5
  74. package/protocol/smart-buffer.d.ts +0 -2
  75. package/protocol/smart-buffer.js +11 -16
  76. package/types.d.ts +5 -3
  77. package/util/abort-signal.d.ts +34 -0
  78. package/util/abort-signal.js +76 -0
  79. package/util/array-calculatedim.js +3 -1
  80. package/util/connection-config.js +77 -3
  81. package/util/decode-binaryarray.d.ts +1 -1
  82. package/util/decode-binaryarray.js +15 -6
  83. package/util/encode-binaryarray.js +7 -3
  84. package/util/escape-identifier.d.ts +14 -0
  85. package/util/escape-identifier.js +32 -0
  86. package/util/escape-literal.js +3 -0
  87. package/util/fast-parseint.d.ts +1 -0
  88. package/util/fast-parseint.js +12 -17
  89. package/util/format-datetime.d.ts +10 -0
  90. package/util/format-datetime.js +71 -0
  91. package/util/get-parsers.js +31 -10
  92. package/util/parse-array.js +2 -2
  93. package/util/parse-bytea.d.ts +2 -0
  94. package/util/parse-bytea.js +83 -0
  95. package/util/parse-datetime.d.ts +3 -1
  96. package/util/parse-datetime.js +86 -17
  97. package/util/parse-row.d.ts +3 -1
  98. package/util/parse-row.js +60 -5
  99. package/util/parse-time.js +8 -1
  100. package/util/sql-tag.d.ts +60 -0
  101. package/util/sql-tag.js +202 -0
  102. package/util/starts-transaction.d.ts +38 -0
  103. package/util/starts-transaction.js +148 -0
  104. package/util/stringify-arrayliteral.js +13 -1
  105. package/util/wrap-row-description.js +13 -4
  106. package/util/convert-row-to-object.d.ts +0 -2
  107. package/util/convert-row-to-object.js +0 -9
@@ -1,3 +1,5 @@
1
1
  import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
2
+ import type { FieldInfo } from '../interfaces/field-info.js';
2
3
  import type { AnyParseFunction } from '../types.js';
3
- export declare function parseRow(parsers: AnyParseFunction[], row: any[], options: DataMappingOptions): void;
4
+ export declare function parseRow(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions): any[];
5
+ export declare function parseObjectRow(parsers: AnyParseFunction[], data: Buffer, columnCount: number, options: DataMappingOptions, fields: FieldInfo[]): object;
package/util/parse-row.js CHANGED
@@ -1,8 +1,63 @@
1
- export function parseRow(parsers, row, options) {
2
- const l = row.length;
1
+ export function parseRow(parsers, data, columnCount, options) {
2
+ const row = new Array(columnCount);
3
+ let offset = 0;
4
+ let len;
3
5
  let i;
4
- for (i = 0; i < l; i++) {
5
- row[i] =
6
- row[i] == null ? null : parsers[i].call(undefined, row[i], options);
6
+ for (i = 0; i < columnCount; i++) {
7
+ // The length of the column value, in bytes (this count does not
8
+ // include itself). Can be zero. As a special case, -1 indicates a
9
+ // NULL column value - no value bytes follow in that case.
10
+ len = data.readInt32BE(offset);
11
+ offset += 4;
12
+ if (len < 0) {
13
+ row[i] = null;
14
+ }
15
+ else {
16
+ // None of the parsers use `this`, so a direct call avoids .call()'s
17
+ // extra indirection (measured ~5.7x slower than invoking directly).
18
+ row[i] = parsers[i](data, offset, len, options);
19
+ offset += len;
20
+ }
7
21
  }
22
+ return row;
23
+ }
24
+ export function parseObjectRow(parsers, data, columnCount, options, fields) {
25
+ const row = {};
26
+ let offset = 0;
27
+ let len;
28
+ let i;
29
+ let value;
30
+ for (i = 0; i < columnCount; i++) {
31
+ // The length of the column value, in bytes (this count does not
32
+ // include itself). Can be zero. As a special case, -1 indicates a
33
+ // NULL column value - no value bytes follow in that case.
34
+ len = data.readInt32BE(offset);
35
+ offset += 4;
36
+ if (len < 0) {
37
+ value = null;
38
+ }
39
+ else {
40
+ // None of the parsers use `this`, so a direct call avoids .call()'s
41
+ // extra indirection (measured ~5.7x slower than invoking directly).
42
+ value = parsers[i](data, offset, len, options);
43
+ offset += len;
44
+ }
45
+ const name = fields[i].fieldName;
46
+ if (name === '__proto__') {
47
+ // `value`, not row[i] - nothing is ever assigned by numeric index
48
+ // here (row is a plain object keyed by field name), so reading row[i]
49
+ // handed defineProperty an undefined and silently dropped the
50
+ // column's actual value.
51
+ Object.defineProperty(row, name, {
52
+ value,
53
+ writable: true,
54
+ enumerable: true,
55
+ configurable: true,
56
+ });
57
+ }
58
+ else {
59
+ row[name] = value;
60
+ }
61
+ }
62
+ return row;
8
63
  }
@@ -14,8 +14,15 @@ export function parseTime(str, parseTimeZone, utc) {
14
14
  const s = m[i];
15
15
  args[i + 2] = fastParseInt(s) || 0;
16
16
  }
17
+ // Fractional seconds (m[4]) were never read here at all - pad/truncate to
18
+ // exactly 3 digits, same reasoning as parse-datetime.ts's equivalent fix.
19
+ if (m[4])
20
+ args[6] = fastParseInt((m[4] + '000').slice(0, 3));
17
21
  if (parseTimeZone && m[6]) {
18
- const r = m[9] === '-' ? -1 : 1;
22
+ // m[6] is the sign group itself ('+'/'-') - there is no m[9] in this
23
+ // pattern (only 8 capture groups), so the old `m[9] === '-'` check was
24
+ // always false, silently treating every offset as positive.
25
+ const r = m[6] === '-' ? -1 : 1;
19
26
  args[3] -= (fastParseInt(m[7]) || 0) * r;
20
27
  args[4] -= (fastParseInt(m[8]) || 0) * r;
21
28
  return new Date(Date.UTC(...args));
@@ -0,0 +1,60 @@
1
+ import { DataTypeMap } from '../data-type-map.js';
2
+ import type { DataMappingOptions } from '../interfaces/data-mapping-options.js';
3
+ /**
4
+ * A statement built by the `sql` tag: its text with the values pulled out.
5
+ *
6
+ * The same object serves both protocols, which is the point of building it
7
+ * rather than a string. query() takes `sql` and `params` and lets the server
8
+ * receive the values out of band, where they can never be read as SQL.
9
+ * execute() has no such option - the Simple Query protocol carries no
10
+ * parameters at all - so it takes stringify(), which writes the values into
11
+ * the statement as properly encoded literals.
12
+ *
13
+ * Those two are not equally strong. Parameters are safe by construction;
14
+ * literals are only as safe as the encoder, which is why stringify() refuses
15
+ * to guess (see below) instead of producing something that looks plausible.
16
+ */
17
+ export declare class QueryRequest {
18
+ readonly sql: string;
19
+ readonly params: any[];
20
+ constructor(text: string, params: any[]);
21
+ /**
22
+ * The statement with its values written in as literals, for the Simple
23
+ * Query protocol.
24
+ *
25
+ * Every value is encoded by its own data type and given an explicit cast:
26
+ * a parameter carries its OID in the Bind message, but a literal carries
27
+ * nothing, so without the cast the server would infer a type from context
28
+ * and the same statement could mean different things through query() and
29
+ * execute(). A value whose type has no text encoding throws rather than
30
+ * falling back to a generic conversion.
31
+ */
32
+ stringify(options?: DataMappingOptions & {
33
+ typeMap?: DataTypeMap;
34
+ }): string;
35
+ }
36
+ /**
37
+ * Builds a QueryRequest from a template literal, turning every interpolated
38
+ * value into a bind parameter.
39
+ *
40
+ * ```ts
41
+ * await connection.query(sql`select * from t where id = any(${[1, 3]})`);
42
+ * ```
43
+ *
44
+ * A nested QueryRequest is spliced in as a fragment with its parameters
45
+ * renumbered, so a statement can be assembled from pieces:
46
+ *
47
+ * ```ts
48
+ * const filter = city ? sql`where city = ${city}` : sql``;
49
+ * await connection.query(sql`select * from t ${filter} order by id`);
50
+ * ```
51
+ *
52
+ * Interpolated values are never written into the text, so this is not string
53
+ * building with extra steps - there is no way for a value to be read as SQL.
54
+ */
55
+ export declare function sql(strings: TemplateStringsArray, ...values: any[]): QueryRequest;
56
+ export declare namespace sql {
57
+ var ident: (name: string) => QueryRequest;
58
+ var values: (data: Record<string, any> | Record<string, any>[], columns?: string[]) => QueryRequest;
59
+ var set: (data: Record<string, any>, columns?: string[]) => QueryRequest;
60
+ }
@@ -0,0 +1,202 @@
1
+ import { BindParam } from '../connection/bind-param.js';
2
+ import { DataTypeMap, GlobalTypeMap } from '../data-type-map.js';
3
+ import { escapeIdentifier } from './escape-identifier.js';
4
+ import { escapeLiteral } from './escape-literal.js';
5
+ /**
6
+ * A statement built by the `sql` tag: its text with the values pulled out.
7
+ *
8
+ * The same object serves both protocols, which is the point of building it
9
+ * rather than a string. query() takes `sql` and `params` and lets the server
10
+ * receive the values out of band, where they can never be read as SQL.
11
+ * execute() has no such option - the Simple Query protocol carries no
12
+ * parameters at all - so it takes stringify(), which writes the values into
13
+ * the statement as properly encoded literals.
14
+ *
15
+ * Those two are not equally strong. Parameters are safe by construction;
16
+ * literals are only as safe as the encoder, which is why stringify() refuses
17
+ * to guess (see below) instead of producing something that looks plausible.
18
+ */
19
+ export class QueryRequest {
20
+ sql;
21
+ params;
22
+ constructor(text, params) {
23
+ this.sql = text;
24
+ this.params = params;
25
+ }
26
+ /**
27
+ * The statement with its values written in as literals, for the Simple
28
+ * Query protocol.
29
+ *
30
+ * Every value is encoded by its own data type and given an explicit cast:
31
+ * a parameter carries its OID in the Bind message, but a literal carries
32
+ * nothing, so without the cast the server would infer a type from context
33
+ * and the same statement could mean different things through query() and
34
+ * execute(). A value whose type has no text encoding throws rather than
35
+ * falling back to a generic conversion.
36
+ */
37
+ stringify(options) {
38
+ const typeMap = options?.typeMap || GlobalTypeMap;
39
+ const params = this.params;
40
+ if (!params.length)
41
+ return this.sql;
42
+ // $1..$n, longest first so $10 is not matched as $1 followed by "0".
43
+ return this.sql.replace(/\$(\d+)/g, (match, n) => {
44
+ const i = +n - 1;
45
+ if (i < 0 || i >= params.length)
46
+ return match;
47
+ return encodeLiteral(params[i], typeMap, options);
48
+ });
49
+ }
50
+ }
51
+ /**
52
+ * Builds a QueryRequest from a template literal, turning every interpolated
53
+ * value into a bind parameter.
54
+ *
55
+ * ```ts
56
+ * await connection.query(sql`select * from t where id = any(${[1, 3]})`);
57
+ * ```
58
+ *
59
+ * A nested QueryRequest is spliced in as a fragment with its parameters
60
+ * renumbered, so a statement can be assembled from pieces:
61
+ *
62
+ * ```ts
63
+ * const filter = city ? sql`where city = ${city}` : sql``;
64
+ * await connection.query(sql`select * from t ${filter} order by id`);
65
+ * ```
66
+ *
67
+ * Interpolated values are never written into the text, so this is not string
68
+ * building with extra steps - there is no way for a value to be read as SQL.
69
+ */
70
+ export function sql(strings, ...values) {
71
+ const params = [];
72
+ let text = '';
73
+ const l = strings.length;
74
+ let i;
75
+ for (i = 0; i < l; i++) {
76
+ text += strings[i];
77
+ if (i >= values.length)
78
+ continue;
79
+ const v = values[i];
80
+ if (v instanceof QueryRequest) {
81
+ // Renumber the fragment's placeholders onto this statement's params.
82
+ const offset = params.length;
83
+ text += v.sql.replace(/\$(\d+)/g, (_m, n) => '$' + (+n + offset));
84
+ params.push(...v.params);
85
+ continue;
86
+ }
87
+ params.push(v);
88
+ text += '$' + params.length;
89
+ }
90
+ return new QueryRequest(text, params);
91
+ }
92
+ /**
93
+ * Quotes a table, column or schema name for use inside a `sql` template.
94
+ *
95
+ * Values become parameters, but names cannot: `$1` is always a value, so
96
+ * `sql`select ${col} from t`` selects the string, not the column. This
97
+ * writes the name into the statement instead, quoted the way PostgreSQL's
98
+ * own `quote_ident()` does.
99
+ *
100
+ * ```ts
101
+ * await connection.query(sql`select ${sql.ident(col)} from ${sql.ident(table)}`);
102
+ * ```
103
+ */
104
+ sql.ident = function ident(name) {
105
+ return new QueryRequest(escapeIdentifier(name), []);
106
+ };
107
+ /**
108
+ * Builds the column list and VALUES clause of an INSERT from an object, or
109
+ * from an array of objects for a multi-row insert.
110
+ *
111
+ * ```ts
112
+ * await connection.query(sql`insert into users ${sql.values(user)}`);
113
+ * // insert into users ("id","name") values ($1,$2)
114
+ * ```
115
+ *
116
+ * `columns` restricts and orders what is written. Pass it whenever the
117
+ * object comes from outside - a request body, say. Without it the columns
118
+ * are whatever keys the object happens to carry, so a caller could add one
119
+ * you did not intend to accept. The values are always parameters, so this
120
+ * is not an SQL injection either way; it is the column list that needs
121
+ * deciding by you rather than by the input.
122
+ */
123
+ sql.values = function values(data, columns) {
124
+ const rows = Array.isArray(data) ? data : [data];
125
+ if (!rows.length)
126
+ throw new TypeError('sql.values() needs at least one row');
127
+ const cols = columns ?? Object.keys(rows[0]);
128
+ if (!cols.length)
129
+ throw new TypeError('sql.values() needs at least one column');
130
+ const params = [];
131
+ const tuples = rows.map(row => {
132
+ const placeholders = cols.map(c => {
133
+ params.push(row[c]);
134
+ return '$' + params.length;
135
+ });
136
+ return `(${placeholders.join(',')})`;
137
+ });
138
+ const names = cols.map(escapeIdentifier).join(',');
139
+ return new QueryRequest(`(${names}) values ${tuples.join(',')}`, params);
140
+ };
141
+ /**
142
+ * Builds the assignment list of an UPDATE from an object.
143
+ *
144
+ * ```ts
145
+ * await connection.query(
146
+ * sql`update users set ${sql.set({ city })} where id = ${id}`,
147
+ * );
148
+ * // update users set "city" = $1 where id = $2
149
+ * ```
150
+ *
151
+ * `columns` restricts what is written, for the same reason as in
152
+ * sql.values().
153
+ */
154
+ sql.set = function set(data, columns) {
155
+ const cols = columns ?? Object.keys(data);
156
+ if (!cols.length)
157
+ throw new TypeError('sql.set() needs at least one column');
158
+ const params = [];
159
+ const assignments = cols.map(c => {
160
+ params.push(data[c]);
161
+ return `${escapeIdentifier(c)} = $${params.length}`;
162
+ });
163
+ return new QueryRequest(assignments.join(', '), params);
164
+ };
165
+ function encodeLiteral(value, typeMap, options) {
166
+ let v = value;
167
+ let oid;
168
+ if (v instanceof BindParam) {
169
+ oid = v.oid;
170
+ v = v.value;
171
+ }
172
+ if (v == null)
173
+ return 'null';
174
+ oid = oid ?? typeMap.determine(v);
175
+ /* c8 ignore start - typeMap.determine() always falls back to the
176
+ "unknown" oid rather than returning null/undefined, so this can't
177
+ currently be reached through any public call path. */
178
+ if (oid == null)
179
+ throw new TypeError(`Cannot write ${describe(v)} into a statement as a literal: no data type matches it. ` +
180
+ `Use query() instead, which sends it as a parameter.`);
181
+ /* c8 ignore stop */
182
+ const dataType = typeMap.get(oid);
183
+ if (!dataType?.encodeText)
184
+ throw new TypeError(`Cannot write ${describe(v)} into a statement as a literal: ` +
185
+ `data type "${dataType?.name ?? oid}" has no text encoding. ` +
186
+ `Use query() instead, which sends it as a parameter.`);
187
+ const cast = dataType.name;
188
+ if (dataType.elementsOID) {
189
+ const elementType = typeMap.get(dataType.elementsOID);
190
+ const arr = (Array.isArray(v) ? v : [v]).map(x => x == null
191
+ ? 'null'
192
+ : escapeLiteral(elementType.encodeText(x, options || {})));
193
+ return `ARRAY[${arr.join(',')}]::${cast}`;
194
+ }
195
+ return `${escapeLiteral(dataType.encodeText(v, options || {}))}::${cast}`;
196
+ }
197
+ function describe(v) {
198
+ const t = typeof v;
199
+ if (t === 'object')
200
+ return v.constructor?.name || 'object';
201
+ return t;
202
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Does this SQL open a transaction?
3
+ *
4
+ * Pool.query()/Pool.execute() may share one connection between several
5
+ * in-flight queries, which is only safe while none of them opens a
6
+ * transaction: the others are already dispatched on that connection by the
7
+ * time a BEGIN takes effect, so they would silently join it. Knowing
8
+ * BEFORE dispatch is what makes the difference between routing the
9
+ * statement to a connection of its own - where it simply works - and
10
+ * finding out afterwards, when the damage is done and all that is left is
11
+ * to report it.
12
+ *
13
+ * Two passes: a cheap regex rejects the overwhelming majority, and only a
14
+ * hit pays for the accurate scan below, which skips comments and quoted
15
+ * text so `select 'begin'`, `-- begin`, and a `DO $$ BEGIN ... END $$`
16
+ * block (PL/pgSQL, not a transaction) are not mistaken for one. It looks
17
+ * ANYWHERE rather than only at the start, since execute() runs
18
+ * multi-statement scripts where the BEGIN can be the second statement.
19
+ *
20
+ * What it cannot see is a transaction opened inside a called procedure -
21
+ * no reading of the SQL can reveal that. Pool keeps a second, authoritative
22
+ * check on the server's own transaction status for exactly that case.
23
+ */
24
+ export declare function startsTransaction(sql: string): boolean;
25
+ /**
26
+ * Reports whether `sql` contains a COPY statement.
27
+ *
28
+ * Pooled pipelining has to refuse these outright: a COPY puts the server
29
+ * into copy-in/copy-out mode, where the only messages it will accept are
30
+ * CopyData/CopyDone/CopyFail - the next pipelined query's Query message
31
+ * arriving mid-copy is a protocol error that takes the whole shared
32
+ * connection down with it, not just that one caller.
33
+ *
34
+ * Deliberately blunt: it matches the word anywhere outside comments and
35
+ * quoted text, so a column actually named "copy" costs its query a
36
+ * dedicated connection. That is the harmless direction to be wrong in.
37
+ */
38
+ export declare function startsCopy(sql: string): boolean;
@@ -0,0 +1,148 @@
1
+ // Cheap first pass. Almost no statement contains either word, so almost
2
+ // every pooled query is decided by this one scan and never reaches the
3
+ // accurate (and more expensive) pass below. It over-matches on purpose -
4
+ // comments, string literals and PL/pgSQL blocks all get through here.
5
+ const MAYBE_TRANSACTION = /\b(?:begin|start)\b/i;
6
+ const MAYBE_COPY = /\bcopy\b/i;
7
+ function isWordChar(code) {
8
+ return ((code >= 48 && code <= 57) || // 0-9
9
+ (code >= 65 && code <= 90) || // A-Z
10
+ (code >= 97 && code <= 122) || // a-z
11
+ code === 95 || // _
12
+ code === 36 // $
13
+ );
14
+ }
15
+ /** Case-insensitive whole-word match of `word` at `at`. */
16
+ function matchesWord(sql, at, word) {
17
+ if (at + word.length > sql.length)
18
+ return false;
19
+ for (let i = 0; i < word.length; i++) {
20
+ if ((sql.charCodeAt(at + i) | 32) !== word.charCodeAt(i))
21
+ return false;
22
+ }
23
+ if (at > 0 && isWordChar(sql.charCodeAt(at - 1)))
24
+ return false;
25
+ const after = at + word.length;
26
+ return after >= sql.length || !isWordChar(sql.charCodeAt(after));
27
+ }
28
+ /** Skips whitespace, then reports whether `word` follows. */
29
+ function nextWordIs(sql, from, word) {
30
+ let i = from;
31
+ while (i < sql.length && /\s/.test(sql[i]))
32
+ i++;
33
+ return matchesWord(sql, i, word);
34
+ }
35
+ /**
36
+ * Walks `sql` calling `at(i)` at every position that is real code, skipping
37
+ * line and (nestable) block comments, single- and double-quoted text and
38
+ * dollar-quoted bodies. Reports whether `at` ever returned true, stopping
39
+ * at the first one that does.
40
+ */
41
+ function scanCode(sql, at) {
42
+ const n = sql.length;
43
+ let i = 0;
44
+ while (i < n) {
45
+ const c = sql[i];
46
+ if (c === '-' && sql[i + 1] === '-') {
47
+ const nl = sql.indexOf('\n', i + 2);
48
+ i = nl === -1 ? n : nl + 1;
49
+ continue;
50
+ }
51
+ if (c === '/' && sql[i + 1] === '*') {
52
+ // Block comments nest in PostgreSQL, unlike in most SQL dialects.
53
+ let depth = 1;
54
+ i += 2;
55
+ while (i < n && depth > 0) {
56
+ if (sql[i] === '/' && sql[i + 1] === '*') {
57
+ depth++;
58
+ i += 2;
59
+ }
60
+ else if (sql[i] === '*' && sql[i + 1] === '/') {
61
+ depth--;
62
+ i += 2;
63
+ }
64
+ else
65
+ i++;
66
+ }
67
+ continue;
68
+ }
69
+ // '...' literal or "..." identifier; a doubled quote escapes itself.
70
+ if (c === "'" || c === '"') {
71
+ i++;
72
+ while (i < n) {
73
+ if (sql[i] === c) {
74
+ if (sql[i + 1] === c)
75
+ i += 2;
76
+ else {
77
+ i++;
78
+ break;
79
+ }
80
+ }
81
+ else
82
+ i++;
83
+ }
84
+ continue;
85
+ }
86
+ // $tag$ ... $tag$ - function bodies live in these, and the BEGIN of a
87
+ // PL/pgSQL block is not a transaction.
88
+ if (c === '$') {
89
+ const tag = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(i))?.[0];
90
+ if (tag) {
91
+ const end = sql.indexOf(tag, i + tag.length);
92
+ i = end === -1 ? n : end + tag.length;
93
+ continue;
94
+ }
95
+ }
96
+ if (at(i))
97
+ return true;
98
+ i++;
99
+ }
100
+ return false;
101
+ }
102
+ /**
103
+ * Does this SQL open a transaction?
104
+ *
105
+ * Pool.query()/Pool.execute() may share one connection between several
106
+ * in-flight queries, which is only safe while none of them opens a
107
+ * transaction: the others are already dispatched on that connection by the
108
+ * time a BEGIN takes effect, so they would silently join it. Knowing
109
+ * BEFORE dispatch is what makes the difference between routing the
110
+ * statement to a connection of its own - where it simply works - and
111
+ * finding out afterwards, when the damage is done and all that is left is
112
+ * to report it.
113
+ *
114
+ * Two passes: a cheap regex rejects the overwhelming majority, and only a
115
+ * hit pays for the accurate scan below, which skips comments and quoted
116
+ * text so `select 'begin'`, `-- begin`, and a `DO $$ BEGIN ... END $$`
117
+ * block (PL/pgSQL, not a transaction) are not mistaken for one. It looks
118
+ * ANYWHERE rather than only at the start, since execute() runs
119
+ * multi-statement scripts where the BEGIN can be the second statement.
120
+ *
121
+ * What it cannot see is a transaction opened inside a called procedure -
122
+ * no reading of the SQL can reveal that. Pool keeps a second, authoritative
123
+ * check on the server's own transaction status for exactly that case.
124
+ */
125
+ export function startsTransaction(sql) {
126
+ if (!MAYBE_TRANSACTION.test(sql))
127
+ return false;
128
+ return scanCode(sql, i => matchesWord(sql, i, 'begin') ||
129
+ (matchesWord(sql, i, 'start') && nextWordIs(sql, i + 5, 'transaction')));
130
+ }
131
+ /**
132
+ * Reports whether `sql` contains a COPY statement.
133
+ *
134
+ * Pooled pipelining has to refuse these outright: a COPY puts the server
135
+ * into copy-in/copy-out mode, where the only messages it will accept are
136
+ * CopyData/CopyDone/CopyFail - the next pipelined query's Query message
137
+ * arriving mid-copy is a protocol error that takes the whole shared
138
+ * connection down with it, not just that one caller.
139
+ *
140
+ * Deliberately blunt: it matches the word anywhere outside comments and
141
+ * quoted text, so a column actually named "copy" costs its query a
142
+ * dedicated connection. That is the harmless direction to be wrong in.
143
+ */
144
+ export function startsCopy(sql) {
145
+ if (!MAYBE_COPY.test(sql))
146
+ return false;
147
+ return scanCode(sql, i => matchesWord(sql, i, 'copy'));
148
+ }
@@ -1,12 +1,19 @@
1
1
  import { arrayCalculateDim } from './array-calculatedim.js';
2
2
  export function stringifyArrayLiteral(value, options, encode) {
3
3
  const dim = arrayCalculateDim(value);
4
+ // `dim` is built once above and never mutated, so its last index is
5
+ // invariant across the whole recursion - not just this loop. Reading
6
+ // dim.length (a property load) and subtracting on every element of every
7
+ // level was the only per-element work here that didn't depend on the
8
+ // element.
9
+ const lastLevel = dim.length - 1;
4
10
  const writeDim = (arr, level) => {
5
11
  const elemCount = dim[level];
6
12
  const out = [];
13
+ const isLeafLevel = level >= lastLevel;
7
14
  for (let i = 0; i < elemCount; i++) {
8
15
  let x = arr && arr[i];
9
- if (level < dim.length - 1) {
16
+ if (!isLeafLevel) {
10
17
  if (x != null && !Array.isArray(x))
11
18
  x = [x];
12
19
  out.push(writeDim(x, level + 1));
@@ -17,10 +24,15 @@ export function stringifyArrayLiteral(value, options, encode) {
17
24
  out.push('NULL');
18
25
  continue;
19
26
  }
27
+ /* c8 ignore start - dim is (re)computed above from a DFS that visits
28
+ every array node in `value` and deepens dim to match, so a value
29
+ can never actually be an array once `level` reaches the leaf -
30
+ if it were, dim would already have gone one level deeper there. */
20
31
  if (Array.isArray(x)) {
21
32
  out.push(stringifyArrayLiteral(x, options, encode));
22
33
  continue;
23
34
  }
35
+ /* c8 ignore stop */
24
36
  if (encode)
25
37
  x = encode(x, options || {});
26
38
  out.push(escapeArrayItem('' + x));
@@ -1,4 +1,14 @@
1
1
  import { DataFormat, DataTypeNames } from '../constants.js';
2
+ // DataTypeNames (oid -> name) is a static, never-mutated map, so its
3
+ // reverse (name -> oid) can be precomputed once here instead of rebuilding
4
+ // Object.keys(DataTypeNames) and linearly rescanning it (with no early
5
+ // exit) for every array-typed column in every query. Duplicate names would
6
+ // resolve to the same "last one wins" oid either way, matching the old
7
+ // loop's behavior exactly.
8
+ const DataTypeOIDByName = {};
9
+ for (const oid of Object.keys(DataTypeNames)) {
10
+ DataTypeOIDByName[DataTypeNames[oid]] = Number(oid);
11
+ }
2
12
  export function wrapRowDescription(typeMap, fields, columnFormat) {
3
13
  return fields.map((f, idx) => {
4
14
  const cf = Array.isArray(columnFormat) ? columnFormat[idx] : columnFormat;
@@ -13,10 +23,9 @@ export function wrapRowDescription(typeMap, fields, columnFormat) {
13
23
  x.isArray = x.dataTypeName.startsWith('_');
14
24
  if (x.isArray) {
15
25
  x.elementDataTypeName = x.dataTypeName.substring(1);
16
- for (const oid of Object.keys(DataTypeNames)) {
17
- if (DataTypeNames[oid] === x.elementDataTypeName)
18
- x.elementDataTypeId = parseInt(oid, 10);
19
- }
26
+ const elementOid = DataTypeOIDByName[x.elementDataTypeName];
27
+ if (elementOid !== undefined)
28
+ x.elementDataTypeId = elementOid;
20
29
  }
21
30
  if (f.fixedSize && f.fixedSize > 0)
22
31
  x.fixedSize = f.fixedSize;
@@ -1,2 +0,0 @@
1
- import type { FieldInfo } from '../interfaces/field-info.js';
2
- export declare function convertRowToObject(fields: FieldInfo[], row: any[]): any;
@@ -1,9 +0,0 @@
1
- export function convertRowToObject(fields, row) {
2
- const out = {};
3
- const l = row.length;
4
- let i;
5
- for (i = 0; i < l; i++) {
6
- out[fields[i].fieldName] = row[i];
7
- }
8
- return out;
9
- }