postgrejs 3.1.3 → 3.3.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +25 -9
  3. package/connection/cursor.d.ts +2 -1
  4. package/connection/cursor.js +6 -6
  5. package/connection/intl-connection.js +10 -14
  6. package/connection/prepared-statement.js +2 -1
  7. package/data-types/bool-type.js +0 -1
  8. package/data-types/box-type.js +0 -1
  9. package/data-types/bytea-type.js +5 -3
  10. package/data-types/char-type.js +2 -2
  11. package/data-types/circle-type.js +0 -1
  12. package/data-types/date-type.js +1 -2
  13. package/data-types/float4-type.js +2 -4
  14. package/data-types/float8-type.js +2 -5
  15. package/data-types/int2-type.js +0 -1
  16. package/data-types/int2-vector-type.js +3 -2
  17. package/data-types/int4-type.js +0 -1
  18. package/data-types/int8-type.js +0 -1
  19. package/data-types/json-type.js +2 -2
  20. package/data-types/jsonb-type.js +6 -5
  21. package/data-types/lseg-type.js +0 -1
  22. package/data-types/numeric-type.js +2 -4
  23. package/data-types/oid-type.js +0 -1
  24. package/data-types/oid-vector-type.js +3 -2
  25. package/data-types/point-type.js +0 -1
  26. package/data-types/time-type.js +1 -2
  27. package/data-types/timestamp-type.js +13 -3
  28. package/data-types/timestamptz-type.js +27 -10
  29. package/data-types/uuid-type.js +1 -2
  30. package/data-types/varchar-type.js +2 -2
  31. package/index.d.ts +1 -0
  32. package/index.js +1 -0
  33. package/interfaces/command-result.d.ts +3 -2
  34. package/interfaces/data-type.d.ts +0 -1
  35. package/interfaces/query-options.d.ts +12 -0
  36. package/interfaces/script-execute-options.d.ts +12 -0
  37. package/package.json +14 -2
  38. package/protocol/backend.js +6 -6
  39. package/protocol/buffer-reader.d.ts +7 -12
  40. package/protocol/buffer-reader.js +11 -58
  41. package/protocol/frontend.js +86 -96
  42. package/protocol/pg-socket.d.ts +6 -33
  43. package/protocol/pg-socket.js +19 -74
  44. package/protocol/pgoutput.js +2 -2
  45. package/protocol/smart-buffer.d.ts +11 -29
  46. package/protocol/smart-buffer.js +19 -162
  47. package/types.d.ts +13 -1
  48. package/util/bigint-methods.d.ts +0 -1
  49. package/util/bigint-methods.js +0 -22
  50. package/util/decode-binaryarray.d.ts +1 -1
  51. package/util/decode-binaryarray.js +7 -7
  52. package/util/encode-binaryarray.js +6 -6
  53. package/util/fast-parsefloat.d.ts +15 -0
  54. package/util/fast-parsefloat.js +141 -0
  55. package/util/get-parsers.js +4 -8
  56. package/util/parse-datetime.js +7 -0
  57. package/util/parse-pg-timestamp.d.ts +31 -0
  58. package/util/parse-pg-timestamp.js +199 -0
  59. package/util/parse-row.js +120 -0
  60. package/util/row-decoder.d.ts +49 -0
  61. package/util/row-decoder.js +63 -0
@@ -2,13 +2,13 @@ import { DataTypeOIDs } from '../constants.js';
2
2
  import { SmartBuffer } from '../protocol/smart-buffer.js';
3
3
  import { formatTimestamptz } from '../util/format-datetime.js';
4
4
  import { parseDateTimeTz } from '../util/parse-datetime.js';
5
+ import { parsePgTimestampBuffer } from '../util/parse-pg-timestamp.js';
5
6
  const timeShift = 946684800000;
6
7
  const timeMul = 4294967296;
7
8
  export const TimestamptzType = {
8
9
  name: 'timestamptz',
9
10
  oid: DataTypeOIDs.timestamptz,
10
11
  jsType: 'Date',
11
- fixedBinarySize: 8,
12
12
  encodeText(v) {
13
13
  return formatTimestamptz(v);
14
14
  },
@@ -34,7 +34,7 @@ export const TimestamptzType = {
34
34
  buf.writeInt32BE(hi);
35
35
  buf.writeUInt32BE(lo);
36
36
  },
37
- decodeBinary(v, offset = 0, options) {
37
+ decodeBinary(v, offset = 0, _len, options) {
38
38
  const fetchAsString = options.fetchAsString?.includes(DataTypeOIDs.timestamptz);
39
39
  const hi = v.readInt32BE(offset);
40
40
  const lo = v.readUInt32BE(offset + 4);
@@ -42,11 +42,16 @@ export const TimestamptzType = {
42
42
  return fetchAsString ? 'infinity' : Infinity;
43
43
  if (lo === 0x00000000 && hi === -0x80000000)
44
44
  return fetchAsString ? '-infinity' : -Infinity;
45
- // Shift from 2000 to 1970
46
- let d = new Date((lo + hi * timeMul) / 1000 + timeShift);
47
- if (fetchAsString || !options.utcDates) {
48
- d = new Date(d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
49
- }
45
+ // Shift from 2000 to 1970. A timestamptz is an absolute instant, so
46
+ // this is already the value - there is nothing to reinterpret against
47
+ // the local zone the way `timestamp` (which carries no zone of its
48
+ // own) has to. Rebuilding the Date from its own local getters, as this
49
+ // used to, gave back the same instant for every value except one: an
50
+ // instant inside the hour that repeats when local time falls back is
51
+ // ambiguous read as wall-clock, so the rebuild silently picked the
52
+ // other one and moved the value an hour. The text path never did that,
53
+ // so the same row decoded binary and text disagreed.
54
+ const d = new Date((lo + hi * timeMul) / 1000 + timeShift);
50
55
  return fetchAsString ? dateToTimestamptzString(d) : d;
51
56
  },
52
57
  decodeText(v, options) {
@@ -64,11 +69,23 @@ export const TimestamptzType = {
64
69
  }
65
70
  return d;
66
71
  },
67
- // See date-type.ts's decodeTextBuffer comment - same rationale. Note this
68
- // type's decodeText always parses first and only branches on
72
+ // Reads PostgreSQL's own timestamp shape straight from the wire bytes.
73
+ // Building the string first and handing it to decodeText() costs about
74
+ // as much again as the parse itself, for a string that exists only to be
75
+ // scanned a character at a time. Anything not in that exact shape
76
+ // (infinity, a BC suffix, an LMT-style offset) has no Date to give back
77
+ // and takes the original path, which still needs the string.
78
+ //
79
+ // Note this type's decodeText always parses first and only branches on
69
80
  // fetchAsString afterward (unlike date/time/timestamp, which early-return
70
- // the raw string) - delegating by reference preserves that as-is.
81
+ // the raw string), so the same order is kept here.
71
82
  decodeTextBuffer(buf, offset, len, options) {
83
+ const d = parsePgTimestampBuffer(buf, offset, offset + len);
84
+ if (d !== undefined) {
85
+ return options.fetchAsString?.includes(DataTypeOIDs.timestamptz)
86
+ ? dateToTimestamptzString(d)
87
+ : d;
88
+ }
72
89
  return TimestamptzType.decodeText(buf.toString('latin1', offset, offset + len), options);
73
90
  },
74
91
  isType(v) {
@@ -4,7 +4,6 @@ export const UuidType = {
4
4
  name: 'uuid',
5
5
  oid: DataTypeOIDs.uuid,
6
6
  jsType: 'String',
7
- fixedBinarySize: 16,
8
7
  encodeText(v) {
9
8
  return '' + v;
10
9
  },
@@ -12,7 +11,7 @@ export const UuidType = {
12
11
  if (!GUID_PATTERN.test(v))
13
12
  throw new Error(`"${v}" is not a valid guid value`);
14
13
  const b = Buffer.from(v.replace(/-/g, ''), 'hex');
15
- buf.writeBuffer(b);
14
+ buf.writeBytes(b);
16
15
  },
17
16
  decodeBinary(v, offset = 0) {
18
17
  return (v.toString('hex', offset, offset + 4) +
@@ -9,8 +9,8 @@ export const VarcharType = {
9
9
  encodeBinary(buf, v) {
10
10
  buf.writeString('' + v, 'utf8');
11
11
  },
12
- decodeBinary(v, offset = 0) {
13
- return v.toString('utf8', offset);
12
+ decodeBinary(v, offset = 0, len) {
13
+ return v.toString('utf8', offset, offset + len);
14
14
  },
15
15
  decodeText(v) {
16
16
  // The wire decoder always hands this a string (see intl-connection.ts's
package/index.d.ts CHANGED
@@ -27,6 +27,7 @@ export * from './util/connection-config.js';
27
27
  export * from './util/escape-identifier.js';
28
28
  export * from './util/escape-literal.js';
29
29
  export * from './util/parse-datetime.js';
30
+ export * from './util/row-decoder.js';
30
31
  export * from './util/sql-tag.js';
31
32
  export * from './util/stringify-arrayliteral.js';
32
33
  export * from './util/stringify-for-sql.js';
package/index.js CHANGED
@@ -27,6 +27,7 @@ export * from './util/connection-config.js';
27
27
  export * from './util/escape-identifier.js';
28
28
  export * from './util/escape-literal.js';
29
29
  export * from './util/parse-datetime.js';
30
+ export * from './util/row-decoder.js';
30
31
  export * from './util/sql-tag.js';
31
32
  export * from './util/stringify-arrayliteral.js';
32
33
  export * from './util/stringify-for-sql.js';
@@ -14,9 +14,10 @@ export interface CommandResult {
14
14
  */
15
15
  rows?: Row[];
16
16
  /**
17
- * Contains row type
17
+ * Contains row type - `'custom'` when a custom `RowDecoder` instance was
18
+ * supplied via `rowDecoder` (there's no way to know what shape it returns)
18
19
  */
19
- rowType?: 'array' | 'object';
20
+ rowType?: 'array' | 'object' | 'custom';
20
21
  /**
21
22
  * Time elapsed to execute command - only measured when `timing` is
22
23
  * enabled (off by default; see `DatabaseConnectionParams.timing`).
@@ -10,7 +10,6 @@ export interface DataType {
10
10
  decodeBinary: DecodeBinaryFunction;
11
11
  decodeText: DecodeTextFunction;
12
12
  decodeTextBuffer?: DecodeTextBufferFunction;
13
- fixedBinarySize?: number;
14
13
  encodeAsNull?: EncodeAsNullFunction;
15
14
  encodeBinary?: EncodeBinaryFunction;
16
15
  encodeText?: EncodeTextFunction;
@@ -1,6 +1,7 @@
1
1
  import type { BindParam } from '../connection/bind-param.js';
2
2
  import type { DataFormat } from '../constants.js';
3
3
  import type { DataTypeMap } from '../data-type-map.js';
4
+ import type { RowDecoder } from '../util/row-decoder.js';
4
5
  import type { DataMappingOptions } from './data-mapping-options.js';
5
6
  export interface QueryOptions extends DataMappingOptions {
6
7
  /**
@@ -10,9 +11,20 @@ export interface QueryOptions extends DataMappingOptions {
10
11
  autoCommit?: boolean;
11
12
  /**
12
13
  * Specifies if rows will be fetched as <FieldName, Value> pair objects or array of values
14
+ * @deprecated Use `rowDecoder: 'object'` instead - `objectRows: true` still works exactly
15
+ * as before, it's just the same thing `rowDecoder` now also expresses.
13
16
  * @default false
14
17
  */
15
18
  objectRows?: boolean;
19
+ /**
20
+ * Controls how each row's raw column data is turned into the value handed back to the
21
+ * caller. `'array'` and `'object'` select the built-in decoders (the same output
22
+ * `objectRows` has always produced); pass an instance of your own `RowDecoder` subclass
23
+ * to take over decoding entirely instead - e.g. for lazy per-cell decoding. Takes
24
+ * precedence over `objectRows` when both are set.
25
+ * @default 'array'
26
+ */
27
+ rowDecoder?: 'array' | 'object' | RowDecoder;
16
28
  /**
17
29
  * Data type map instance
18
30
  * @default GlobalTypeMap
@@ -1,4 +1,5 @@
1
1
  import type { DataTypeMap } from '../data-type-map.js';
2
+ import type { RowDecoder } from '../util/row-decoder.js';
2
3
  import type { DataMappingOptions } from './data-mapping-options.js';
3
4
  export interface ScriptExecuteOptions extends DataMappingOptions {
4
5
  /**
@@ -8,9 +9,20 @@ export interface ScriptExecuteOptions extends DataMappingOptions {
8
9
  autoCommit?: boolean;
9
10
  /**
10
11
  * Specifies if rows will be fetched as <FieldName, Value> pair objects or array of values
12
+ * @deprecated Use `rowDecoder: 'object'` instead - `objectRows: true` still works exactly
13
+ * as before, it's just the same thing `rowDecoder` now also expresses.
11
14
  * @default false
12
15
  */
13
16
  objectRows?: boolean;
17
+ /**
18
+ * Controls how each row's raw column data is turned into the value handed back to the
19
+ * caller. `'array'` and `'object'` select the built-in decoders (the same output
20
+ * `objectRows` has always produced); pass an instance of your own `RowDecoder` subclass
21
+ * to take over decoding entirely instead - e.g. for lazy per-cell decoding. Takes
22
+ * precedence over `objectRows` when both are set.
23
+ * @default 'array'
24
+ */
25
+ rowDecoder?: 'array' | 'object' | RowDecoder;
14
26
  /**
15
27
  * Data type map instance
16
28
  * @default GlobalTypeMap
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "postgrejs",
3
3
  "description": "Professional PostgreSQL client NodeJS",
4
- "version": "3.1.3",
4
+ "version": "3.3.0",
5
5
  "author": "Panates",
6
6
  "license": "BSD-3-Clause",
7
7
  "dependencies": {
8
8
  "@jsopen/objects": "^2.2.3",
9
9
  "doublylinked": "^2.5.6",
10
+ "flexy-buffer": "^1.1.0",
10
11
  "lightning-pool": "^4.12.0",
11
12
  "power-tasks": "^1.14.2",
12
13
  "putil-promisify": "^1.10.1",
@@ -45,7 +46,18 @@
45
46
  "client",
46
47
  "backend",
47
48
  "rdbms",
48
- "sql"
49
+ "sql",
50
+ "typescript",
51
+ "wire-protocol",
52
+ "no-native-bindings",
53
+ "cursor",
54
+ "server-side-cursor",
55
+ "logical-replication",
56
+ "copy",
57
+ "connection-pool",
58
+ "prepared-statements",
59
+ "bun",
60
+ "bunjs"
49
61
  ],
50
62
  "publishConfig": {
51
63
  "access": "public"
@@ -124,7 +124,7 @@ function parseAuthentication(io, code, len) {
124
124
  case 5:
125
125
  return {
126
126
  kind: 'MD5Password',
127
- salt: io.readBuffer(len - 8),
127
+ salt: io.readBytes(len - 8),
128
128
  };
129
129
  case 6:
130
130
  return {
@@ -141,7 +141,7 @@ function parseAuthentication(io, code, len) {
141
141
  case 8:
142
142
  return {
143
143
  kind: 'GSSContinue',
144
- data: io.readBuffer(len - 8),
144
+ data: io.readBytes(len - 8),
145
145
  };
146
146
  case 10: {
147
147
  const out = {
@@ -174,7 +174,7 @@ function parseBackendKeyData(io) {
174
174
  // this message's body (see Backend.parse()), so the secret key is
175
175
  // simply whatever bytes remain - 4 of them before protocol 3.2, up to
176
176
  // 256 with it (see VERSION_MINOR_LONG_CANCEL_KEY).
177
- const secretKey = io.readBuffer();
177
+ const secretKey = io.readBytes();
178
178
  return { processID, secretKey };
179
179
  }
180
180
  function parseCommandComplete(io) {
@@ -184,7 +184,7 @@ function parseCommandComplete(io) {
184
184
  }
185
185
  function parseCopyData(io, code, len) {
186
186
  return {
187
- data: io.readBuffer(len - 4),
187
+ data: io.readBytes(len - 4),
188
188
  };
189
189
  }
190
190
  function parseCopyResponse(io) {
@@ -208,7 +208,7 @@ function parseCopyResponse(io) {
208
208
  }
209
209
  function parseDataRow(io, code, len) {
210
210
  const columnCount = io.readUInt16BE();
211
- const data = io.readBuffer(len - 6);
211
+ const data = io.readBytes(len - 6);
212
212
  return { columnCount, data };
213
213
  }
214
214
  function parseErrorResponse(io) {
@@ -234,7 +234,7 @@ function parseFunctionCallResponse(io) {
234
234
  // bytes following) - not deducible from the outer message length alone,
235
235
  // unlike most fixed-shape messages this codebase parses.
236
236
  const len = io.readInt32BE();
237
- return { result: len < 0 ? null : io.readBuffer(len) };
237
+ return { result: len < 0 ? null : io.readBytes(len) };
238
238
  }
239
239
  function parseNegotiateProtocolVersion(io) {
240
240
  const supportedVersionMinor = io.readUInt32BE();
@@ -1,15 +1,10 @@
1
- export declare class BufferReader {
2
- buffer: Buffer;
3
- offset: number;
4
- constructor(buffer: Buffer);
5
- get length(): number;
6
- readUInt8(): number;
7
- readUInt16BE(): number;
8
- readInt16BE(): number;
9
- readUInt32BE(): number;
10
- readInt32BE(): number;
1
+ import { BufferReader as FlexyBufferReader } from 'flexy-buffer';
2
+ /**
3
+ * Adds the PostgreSQL wire format's two string encodings on top of
4
+ * flexy-buffer's `BufferReader`: a NUL-terminated C string, and a
5
+ * length-prefixed string where a negative length is the NULL sentinel.
6
+ */
7
+ export declare class BufferReader extends FlexyBufferReader {
11
8
  readCString(encoding?: BufferEncoding): string;
12
9
  readLString(len: number, encoding?: BufferEncoding): string | null;
13
- readBuffer(len?: number): Buffer;
14
- private _checkReadable;
15
10
  }
@@ -1,68 +1,21 @@
1
- export class BufferReader {
2
- buffer;
3
- offset = 0;
4
- constructor(buffer) {
5
- this.buffer = buffer;
6
- }
7
- get length() {
8
- return this.buffer.length;
9
- }
10
- readUInt8() {
11
- this._checkReadable(1);
12
- const v = this.buffer.readUInt8(this.offset);
13
- this.offset++;
14
- return v;
15
- }
16
- readUInt16BE() {
17
- this._checkReadable(2);
18
- const v = this.buffer.readUInt16BE(this.offset);
19
- this.offset += 2;
20
- return v;
21
- }
22
- readInt16BE() {
23
- this._checkReadable(2);
24
- const v = this.buffer.readInt16BE(this.offset);
25
- this.offset += 2;
26
- return v;
27
- }
28
- readUInt32BE() {
29
- this._checkReadable(4);
30
- const v = this.buffer.readUInt32BE(this.offset);
31
- this.offset += 4;
32
- return v;
33
- }
34
- readInt32BE() {
35
- this._checkReadable(4);
36
- const v = this.buffer.readInt32BE(this.offset);
37
- this.offset += 4;
38
- return v;
39
- }
1
+ import { BufferReader as FlexyBufferReader } from 'flexy-buffer';
2
+ /**
3
+ * Adds the PostgreSQL wire format's two string encodings on top of
4
+ * flexy-buffer's `BufferReader`: a NUL-terminated C string, and a
5
+ * length-prefixed string where a negative length is the NULL sentinel.
6
+ */
7
+ export class BufferReader extends FlexyBufferReader {
40
8
  readCString(encoding) {
41
- const idx = this.buffer.indexOf(0, this.offset);
9
+ const idx = this.buffer.indexOf(0, this.position);
42
10
  if (idx === -1)
43
11
  throw new Error('Eof in buffer detected (readCString)');
44
- const v = this.buffer.toString(encoding, this.offset, idx);
45
- this.offset = idx + 1;
12
+ const v = this.buffer.toString(encoding, this.position, idx);
13
+ this.position = idx + 1;
46
14
  return v;
47
15
  }
48
16
  readLString(len, encoding) {
49
17
  if (len < 0)
50
18
  return null;
51
- this._checkReadable(len);
52
- const v = this.buffer.toString(encoding, this.offset, this.offset + len);
53
- this.offset += len;
54
- return v;
55
- }
56
- readBuffer(len) {
57
- if (len)
58
- this._checkReadable(len);
59
- const end = len !== undefined ? this.offset + len : this.length;
60
- const buf = this.buffer.subarray(this.offset, end);
61
- this.offset = end;
62
- return buf;
63
- }
64
- _checkReadable(size) {
65
- if (this.offset + size - 1 >= this.length)
66
- throw new Error('Eof in buffer detected');
19
+ return this.readString(len, encoding);
67
20
  }
68
21
  }