postgrejs 3.10.0 → 3.11.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/CHANGELOG.md +21 -1
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/interfaces/database-connection-params.d.ts +10 -0
- package/package.json +1 -1
- package/util/connection-config.js +54 -0
- package/util/is-multi-statement.d.ts +47 -0
- package/util/is-multi-statement.js +184 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
-
### [v3.
|
|
3
|
+
### [v3.11.0](https://github.com/panates/postgrejs/compare/v3.10.1...v3.11.0) -
|
|
4
|
+
|
|
5
|
+
#### 🚀 New Features
|
|
6
|
+
|
|
7
|
+
- feat: add isMultiStatement(), so a caller can tell which of the two to use @Eray Hanoğlu
|
|
8
|
+
|
|
9
|
+
#### 🛠 Refactoring and Updates
|
|
10
|
+
|
|
11
|
+
- refactor: rename `multi-statement` to `is-multi-statement` for clarity @Eray Hanoğlu
|
|
12
|
+
|
|
13
|
+
### [v3.10.1](https://github.com/panates/postgrejs/compare/v3.10.0...v3.10.1) - 22 September 2026
|
|
14
|
+
|
|
15
|
+
#### 🚀 New Features
|
|
16
|
+
|
|
17
|
+
- feat: accept `connectionString`, and answer a key from another client @Eray Hanoğlu
|
|
18
|
+
|
|
19
|
+
### [v3.10.0](https://github.com/panates/postgrejs/compare/v3.9.0...v3.10.0) - 22 September 2026
|
|
4
20
|
|
|
5
21
|
#### Breaking Changes :warning:
|
|
6
22
|
|
|
@@ -21,6 +37,10 @@
|
|
|
21
37
|
- fix: sign only the interval components that have one, and add toPostgres() @Eray Hanoğlu
|
|
22
38
|
- fix: read the database out of a `postgresql://` URL @Eray Hanoğlu
|
|
23
39
|
|
|
40
|
+
#### 📖 Documentation Changes
|
|
41
|
+
|
|
42
|
+
- docs: name the migration note after the release it is for @Eray Hanoğlu
|
|
43
|
+
|
|
24
44
|
### [v3.9.0](https://github.com/panates/postgrejs/compare/v3.8.0...v3.9.0) - 22 September 2026
|
|
25
45
|
|
|
26
46
|
#### 🚀 New Features
|
package/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from './util/connection-config.js';
|
|
|
37
37
|
export * from './util/decimal-as-string.js';
|
|
38
38
|
export * from './util/escape-identifier.js';
|
|
39
39
|
export * from './util/escape-literal.js';
|
|
40
|
+
export * from './util/is-multi-statement.js';
|
|
40
41
|
export * from './util/parse-datetime.js';
|
|
41
42
|
export * from './util/row-decoder.js';
|
|
42
43
|
export * from './util/sql-tag.js';
|
package/index.js
CHANGED
|
@@ -37,6 +37,7 @@ export * from './util/connection-config.js';
|
|
|
37
37
|
export * from './util/decimal-as-string.js';
|
|
38
38
|
export * from './util/escape-identifier.js';
|
|
39
39
|
export * from './util/escape-literal.js';
|
|
40
|
+
export * from './util/is-multi-statement.js';
|
|
40
41
|
export * from './util/parse-datetime.js';
|
|
41
42
|
export * from './util/row-decoder.js';
|
|
42
43
|
export * from './util/sql-tag.js';
|
|
@@ -5,6 +5,16 @@ import type { DebugLogger } from '../types.js';
|
|
|
5
5
|
import type { DataMappingOptions } from './data-mapping-options.js';
|
|
6
6
|
import type { QueryOptions } from './query-options.js';
|
|
7
7
|
export interface DatabaseConnectionParams {
|
|
8
|
+
/**
|
|
9
|
+
* A whole connection string, as an alternative to the fields below -
|
|
10
|
+
* `pg`'s spelling of what this client also accepts as a bare string
|
|
11
|
+
* (`new Connection('postgres://...')`).
|
|
12
|
+
*
|
|
13
|
+
* Like `pg`, what the string names wins over a field of the same name
|
|
14
|
+
* beside it, so a value the string carries is not quietly overridden
|
|
15
|
+
* by one that was left in the object.
|
|
16
|
+
*/
|
|
17
|
+
connectionString?: string;
|
|
8
18
|
host?: string;
|
|
9
19
|
port?: number;
|
|
10
20
|
user?: string;
|
package/package.json
CHANGED
|
@@ -1,12 +1,66 @@
|
|
|
1
1
|
import { merge } from '@jsopen/objects';
|
|
2
2
|
import { configFromEnv } from './config-from-env.js';
|
|
3
|
+
/**
|
|
4
|
+
* Spellings from other clients that mean something here under a
|
|
5
|
+
* different name.
|
|
6
|
+
*
|
|
7
|
+
* Nothing checks that a configuration's keys are keys this client knows,
|
|
8
|
+
* and it deliberately stays that way: a `PoolConfiguration` carries
|
|
9
|
+
* `lightning-pool`'s own options, which are not declared here, and
|
|
10
|
+
* callers pass configurations that carry their own fields. An allowlist
|
|
11
|
+
* would reject those, and would have to be kept current forever.
|
|
12
|
+
*
|
|
13
|
+
* What is caught instead is the short list of names that are *wrong* -
|
|
14
|
+
* each is the right idea under another client's spelling, so it can be
|
|
15
|
+
* answered with the name that works rather than merely refused.
|
|
16
|
+
*/
|
|
17
|
+
const FOREIGN_KEYS = {
|
|
18
|
+
connection_string: 'connectionString',
|
|
19
|
+
connectionUri: 'connectionString',
|
|
20
|
+
uri: 'connectionString',
|
|
21
|
+
url: 'connectionString',
|
|
22
|
+
dbname: 'database',
|
|
23
|
+
db: 'database',
|
|
24
|
+
username: 'user',
|
|
25
|
+
pass: 'password',
|
|
26
|
+
passwd: 'password',
|
|
27
|
+
hostname: 'host',
|
|
28
|
+
application_name: 'applicationName',
|
|
29
|
+
connect_timeout: 'connectTimeoutMs',
|
|
30
|
+
connectionTimeoutMillis: 'connectTimeoutMs',
|
|
31
|
+
};
|
|
32
|
+
function checkForeignKeys(config) {
|
|
33
|
+
let k;
|
|
34
|
+
for (k in config) {
|
|
35
|
+
const meant = FOREIGN_KEYS[k];
|
|
36
|
+
if (meant && config[k] !== undefined)
|
|
37
|
+
throw new TypeError(`"${k}" is not a connection option here - did you mean "${meant}"? ` +
|
|
38
|
+
'An option this client does not know is otherwise ignored, so a ' +
|
|
39
|
+
'name that is nearly right does nothing and says nothing.');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
3
42
|
export function getConnectionConfig(config) {
|
|
4
43
|
const cfg = configFromEnv();
|
|
5
44
|
if (typeof config === 'string') {
|
|
6
45
|
merge(cfg, parseConnectionString(config));
|
|
7
46
|
}
|
|
8
47
|
else if (typeof config === 'object') {
|
|
48
|
+
checkForeignKeys(config);
|
|
9
49
|
merge(cfg, config);
|
|
50
|
+
// `pg`'s spelling of the same thing, and what `drizzle-orm`'s first
|
|
51
|
+
// example writes - so it is the single most likely key someone
|
|
52
|
+
// moving from `pg` puts here. Ignored, it did not fail: it connected
|
|
53
|
+
// to whatever the environment defaults to, which in development is
|
|
54
|
+
// usually a database that exists, so the mistake surfaced as missing
|
|
55
|
+
// tables or as writes landing somewhere else. Applied after the
|
|
56
|
+
// merge and not before it, because `pg` lets the string win over the
|
|
57
|
+
// fields beside it.
|
|
58
|
+
const cs = cfg.connectionString;
|
|
59
|
+
if (cs !== undefined) {
|
|
60
|
+
delete cfg.connectionString;
|
|
61
|
+
if (cs)
|
|
62
|
+
merge(cfg, parseConnectionString('' + cs));
|
|
63
|
+
}
|
|
10
64
|
}
|
|
11
65
|
if (cfg.host) {
|
|
12
66
|
const explicitRequireSSL = cfg.requireSSL;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `sql` holds more than one statement.
|
|
3
|
+
*
|
|
4
|
+
* This client has two ways to send SQL and they are not interchangeable:
|
|
5
|
+
* `query()` speaks the extended protocol, which PostgreSQL will not let
|
|
6
|
+
* carry more than one command, and `execute()` sends a simple `Query`,
|
|
7
|
+
* which may hold a whole script but gives up the prepared plan. A caller
|
|
8
|
+
* that knows which it has picks the right one and needs nothing from
|
|
9
|
+
* here. A caller with a single SQL-taking API above it - an ORM adapter,
|
|
10
|
+
* a migration runner, a REPL, anything taking SQL from a user - has to
|
|
11
|
+
* choose before it can send, and both shapes arrive at the same method.
|
|
12
|
+
*
|
|
13
|
+
* Nobody can answer that from the wire: the server does the parsing, and
|
|
14
|
+
* its count arrives with the results, long after the choice was made.
|
|
15
|
+
* `pg` picks from whether the call happened to carry parameters, which
|
|
16
|
+
* is a guess about intent rather than an answer. So the question belongs
|
|
17
|
+
* here, beside the two functions whose difference raises it.
|
|
18
|
+
*
|
|
19
|
+
* It is a scanner and not a parser - one pass, one boolean - and what it
|
|
20
|
+
* skips is everything a `;` can hide inside:
|
|
21
|
+
*
|
|
22
|
+
* | construct | skipped to |
|
|
23
|
+
* | --- | --- |
|
|
24
|
+
* | `'...'` | the closing quote, `''` being an escaped one |
|
|
25
|
+
* | `E'...'` | the same, and `\'` too |
|
|
26
|
+
* | `"..."` | the closing quote of an identifier, `""` escaped |
|
|
27
|
+
* | `$tag$...$tag$` | the matching tag - a function body |
|
|
28
|
+
* | `-- ...` | end of line |
|
|
29
|
+
* | `/* ... *\/` | the matching close, which PostgreSQL nests |
|
|
30
|
+
*
|
|
31
|
+
* A trailing `;`, or a run of them with only whitespace and comments
|
|
32
|
+
* after, is not a second statement: the server takes `select 1;;` on the
|
|
33
|
+
* extended protocol without complaint.
|
|
34
|
+
*
|
|
35
|
+
* Which way a wrong answer goes is not symmetric, and the tests are built
|
|
36
|
+
* on that: saying *several* about one statement costs it its prepared
|
|
37
|
+
* plan and nothing else, while saying *one* about several reaches the
|
|
38
|
+
* caller as `42601 cannot insert multiple commands into a prepared
|
|
39
|
+
* statement`.
|
|
40
|
+
*
|
|
41
|
+
* One boundary, left as it is: under `standard_conforming_strings = off`
|
|
42
|
+
* a backslash escapes inside a plain `'...'` too, so `select 'a\'; select
|
|
43
|
+
* 2` is one malformed statement to the server and two to this. Both roads
|
|
44
|
+
* end in an error, nothing is decoded wrongly, and that setting has
|
|
45
|
+
* defaulted to `on` since PostgreSQL 9.1.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isMultiStatement(sql: string): boolean;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether `sql` holds more than one statement.
|
|
3
|
+
*
|
|
4
|
+
* This client has two ways to send SQL and they are not interchangeable:
|
|
5
|
+
* `query()` speaks the extended protocol, which PostgreSQL will not let
|
|
6
|
+
* carry more than one command, and `execute()` sends a simple `Query`,
|
|
7
|
+
* which may hold a whole script but gives up the prepared plan. A caller
|
|
8
|
+
* that knows which it has picks the right one and needs nothing from
|
|
9
|
+
* here. A caller with a single SQL-taking API above it - an ORM adapter,
|
|
10
|
+
* a migration runner, a REPL, anything taking SQL from a user - has to
|
|
11
|
+
* choose before it can send, and both shapes arrive at the same method.
|
|
12
|
+
*
|
|
13
|
+
* Nobody can answer that from the wire: the server does the parsing, and
|
|
14
|
+
* its count arrives with the results, long after the choice was made.
|
|
15
|
+
* `pg` picks from whether the call happened to carry parameters, which
|
|
16
|
+
* is a guess about intent rather than an answer. So the question belongs
|
|
17
|
+
* here, beside the two functions whose difference raises it.
|
|
18
|
+
*
|
|
19
|
+
* It is a scanner and not a parser - one pass, one boolean - and what it
|
|
20
|
+
* skips is everything a `;` can hide inside:
|
|
21
|
+
*
|
|
22
|
+
* | construct | skipped to |
|
|
23
|
+
* | --- | --- |
|
|
24
|
+
* | `'...'` | the closing quote, `''` being an escaped one |
|
|
25
|
+
* | `E'...'` | the same, and `\'` too |
|
|
26
|
+
* | `"..."` | the closing quote of an identifier, `""` escaped |
|
|
27
|
+
* | `$tag$...$tag$` | the matching tag - a function body |
|
|
28
|
+
* | `-- ...` | end of line |
|
|
29
|
+
* | `/* ... *\/` | the matching close, which PostgreSQL nests |
|
|
30
|
+
*
|
|
31
|
+
* A trailing `;`, or a run of them with only whitespace and comments
|
|
32
|
+
* after, is not a second statement: the server takes `select 1;;` on the
|
|
33
|
+
* extended protocol without complaint.
|
|
34
|
+
*
|
|
35
|
+
* Which way a wrong answer goes is not symmetric, and the tests are built
|
|
36
|
+
* on that: saying *several* about one statement costs it its prepared
|
|
37
|
+
* plan and nothing else, while saying *one* about several reaches the
|
|
38
|
+
* caller as `42601 cannot insert multiple commands into a prepared
|
|
39
|
+
* statement`.
|
|
40
|
+
*
|
|
41
|
+
* One boundary, left as it is: under `standard_conforming_strings = off`
|
|
42
|
+
* a backslash escapes inside a plain `'...'` too, so `select 'a\'; select
|
|
43
|
+
* 2` is one malformed statement to the server and two to this. Both roads
|
|
44
|
+
* end in an error, nothing is decoded wrongly, and that setting has
|
|
45
|
+
* defaulted to `on` since PostgreSQL 9.1.
|
|
46
|
+
*/
|
|
47
|
+
export function isMultiStatement(sql) {
|
|
48
|
+
const len = sql.length;
|
|
49
|
+
let i = 0;
|
|
50
|
+
let separator = false;
|
|
51
|
+
let c;
|
|
52
|
+
while (i < len) {
|
|
53
|
+
c = sql.charCodeAt(i);
|
|
54
|
+
// Whatever follows a `;` decides what that `;` was: more whitespace,
|
|
55
|
+
// comments or semicolons leave it a terminator, and anything else
|
|
56
|
+
// makes it the seam between two statements.
|
|
57
|
+
if (separator && !isSpace(c) && c !== SEMI && c !== DASH && c !== SLASH)
|
|
58
|
+
return true;
|
|
59
|
+
if (c === SEMI) {
|
|
60
|
+
separator = true;
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
else if (c === QUOTE) {
|
|
64
|
+
i = skipQuoted(sql, len, i, QUOTE, escapesBackslash(sql, i));
|
|
65
|
+
}
|
|
66
|
+
else if (c === DQUOTE) {
|
|
67
|
+
i = skipQuoted(sql, len, i, DQUOTE, false);
|
|
68
|
+
}
|
|
69
|
+
else if (c === DOLLAR) {
|
|
70
|
+
const end = skipDollarQuoted(sql, len, i);
|
|
71
|
+
// Not a dollar quote - a `$1` placeholder, or a bare `$`.
|
|
72
|
+
i = end < 0 ? i + 1 : end;
|
|
73
|
+
}
|
|
74
|
+
else if (c === DASH && sql.charCodeAt(i + 1) === DASH) {
|
|
75
|
+
const nl = sql.indexOf('\n', i + 2);
|
|
76
|
+
i = nl < 0 ? len : nl + 1;
|
|
77
|
+
}
|
|
78
|
+
else if (c === SLASH && sql.charCodeAt(i + 1) === STAR) {
|
|
79
|
+
i = skipBlockComment(sql, len, i);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// A `-` or `/` that began no comment is content like any other.
|
|
83
|
+
if (separator && !isSpace(c))
|
|
84
|
+
return true;
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const SEMI = 59; // ;
|
|
91
|
+
const QUOTE = 39; // '
|
|
92
|
+
const DQUOTE = 34; // "
|
|
93
|
+
const DOLLAR = 36; // $
|
|
94
|
+
const DASH = 45; // -
|
|
95
|
+
const SLASH = 47; // /
|
|
96
|
+
const STAR = 42; // *
|
|
97
|
+
const BACKSLASH = 92; // \
|
|
98
|
+
const UNDERSCORE = 95; // _
|
|
99
|
+
function isSpace(c) {
|
|
100
|
+
return c === 32 || c === 10 || c === 9 || c === 13 || c === 12;
|
|
101
|
+
}
|
|
102
|
+
function isDigit(c) {
|
|
103
|
+
return c >= 48 && c <= 57;
|
|
104
|
+
}
|
|
105
|
+
/** A letter or `_`, which is what a dollar-quote tag may start with. */
|
|
106
|
+
function isIdentStart(c) {
|
|
107
|
+
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === UNDERSCORE;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Whether the quote at `at` opens an `E'...'` literal, where a backslash
|
|
111
|
+
* escapes. The letter has to stand alone: the `e` of `value` is not a
|
|
112
|
+
* prefix.
|
|
113
|
+
*/
|
|
114
|
+
function escapesBackslash(sql, at) {
|
|
115
|
+
const prev = sql.charCodeAt(at - 1);
|
|
116
|
+
if (prev !== 69 /* E */ && prev !== 101 /* e */)
|
|
117
|
+
return false;
|
|
118
|
+
if (at < 2)
|
|
119
|
+
return true;
|
|
120
|
+
const before = sql.charCodeAt(at - 2);
|
|
121
|
+
return !(isIdentStart(before) ||
|
|
122
|
+
isDigit(before) ||
|
|
123
|
+
before === DOLLAR ||
|
|
124
|
+
before === UNDERSCORE);
|
|
125
|
+
}
|
|
126
|
+
/** Past the closing quote, or the end of the string if it never closes. */
|
|
127
|
+
function skipQuoted(sql, len, start, quote, backslash) {
|
|
128
|
+
let i = start + 1;
|
|
129
|
+
let c;
|
|
130
|
+
while (i < len) {
|
|
131
|
+
c = sql.charCodeAt(i);
|
|
132
|
+
if (backslash && c === BACKSLASH)
|
|
133
|
+
i += 2;
|
|
134
|
+
else if (c !== quote)
|
|
135
|
+
i++;
|
|
136
|
+
// A doubled quote is an escaped one and the literal carries on.
|
|
137
|
+
else if (sql.charCodeAt(i + 1) === quote)
|
|
138
|
+
i += 2;
|
|
139
|
+
else
|
|
140
|
+
return i + 1;
|
|
141
|
+
}
|
|
142
|
+
return len;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Past `$tag$ ... $tag$`, or -1 when the `$` opens none - which is the
|
|
146
|
+
* common case, since `$1` is a parameter placeholder. The tag is read
|
|
147
|
+
* character by character so that a placeholder costs no allocation.
|
|
148
|
+
*/
|
|
149
|
+
function skipDollarQuoted(sql, len, start) {
|
|
150
|
+
let i = start + 1;
|
|
151
|
+
let c;
|
|
152
|
+
while (i < len) {
|
|
153
|
+
c = sql.charCodeAt(i);
|
|
154
|
+
if (c === DOLLAR)
|
|
155
|
+
break;
|
|
156
|
+
if (!(isIdentStart(c) || (i > start + 1 && isDigit(c))))
|
|
157
|
+
return -1;
|
|
158
|
+
i++;
|
|
159
|
+
}
|
|
160
|
+
if (i >= len)
|
|
161
|
+
return -1;
|
|
162
|
+
const tag = sql.substring(start, i + 1);
|
|
163
|
+
const close = sql.indexOf(tag, i + 1);
|
|
164
|
+
return close < 0 ? len : close + tag.length;
|
|
165
|
+
}
|
|
166
|
+
/** Past the matching `*\/`. PostgreSQL nests block comments. */
|
|
167
|
+
function skipBlockComment(sql, len, start) {
|
|
168
|
+
let depth = 0;
|
|
169
|
+
let i = start;
|
|
170
|
+
while (i < len) {
|
|
171
|
+
if (sql.charCodeAt(i) === SLASH && sql.charCodeAt(i + 1) === STAR) {
|
|
172
|
+
depth++;
|
|
173
|
+
i += 2;
|
|
174
|
+
}
|
|
175
|
+
else if (sql.charCodeAt(i) === STAR && sql.charCodeAt(i + 1) === SLASH) {
|
|
176
|
+
i += 2;
|
|
177
|
+
if (--depth === 0)
|
|
178
|
+
return i;
|
|
179
|
+
}
|
|
180
|
+
else
|
|
181
|
+
i++;
|
|
182
|
+
}
|
|
183
|
+
return len;
|
|
184
|
+
}
|