@tablecraft-app/plugin-postgres 1.12.1 → 1.14.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/index.js +114 -9
- package/package.json +2 -2
- package/table-craft-plugin.json +10 -3
package/index.js
CHANGED
|
@@ -10823,7 +10823,8 @@ var CAPABILITIES$1 = [
|
|
|
10823
10823
|
"commands",
|
|
10824
10824
|
"keyValue",
|
|
10825
10825
|
"nodes",
|
|
10826
|
-
"serverInfo"
|
|
10826
|
+
"serverInfo",
|
|
10827
|
+
"serverTimeout"
|
|
10827
10828
|
];
|
|
10828
10829
|
/** The message that stands for the ones past `SERVER_MESSAGE_LIMIT`. */
|
|
10829
10830
|
function omittedMessages(count) {
|
|
@@ -11025,6 +11026,11 @@ function urlSetsDeclared(connection) {
|
|
|
11025
11026
|
if (url?.sets === void 0) return true;
|
|
11026
11027
|
return Object.entries(url.sets).every(([scheme, values]) => url.schemes.includes(scheme) && Object.keys(values).every((key) => connection.fields.some((field) => field.key === key)));
|
|
11027
11028
|
}
|
|
11029
|
+
/** A database product a plugin speaks; `id` is a simple-icons slug, `name` what a row shows. */
|
|
11030
|
+
var brandSchema = strictObject({
|
|
11031
|
+
id: string$1().regex(/^[a-z0-9_]+$/, { message: "expected a simple-icons slug: lower-case letters, digits and underscores" }),
|
|
11032
|
+
name: string$1().min(1)
|
|
11033
|
+
});
|
|
11028
11034
|
function includesAny(capabilities, ...wanted) {
|
|
11029
11035
|
return wanted.some((capability) => capabilities.includes(capability));
|
|
11030
11036
|
}
|
|
@@ -11038,6 +11044,8 @@ strictObject({
|
|
|
11038
11044
|
capabilities: array(_enum(CAPABILITIES$1)),
|
|
11039
11045
|
/** The highlighter the console uses for this plugin's dialect (decision 008). */
|
|
11040
11046
|
highlight: string$1().min(1),
|
|
11047
|
+
/** The database products the plugin speaks, its default first (decision 128). Absent: rows name the plugin. */
|
|
11048
|
+
brands: array(brandSchema).min(1).refine((brands) => new Set(brands.map((brand) => brand.id)).size === brands.length, { message: "brand ids must be unique" }).optional(),
|
|
11041
11049
|
/** The connection form, readable without running plugin code (decision 019). */
|
|
11042
11050
|
connection: strictObject({
|
|
11043
11051
|
fields: array(connectionFieldSchema).min(1),
|
|
@@ -25225,6 +25233,39 @@ function postgresSessions(client, open) {
|
|
|
25225
25233
|
terminateSession: (target) => stop(target, terminateStatement, "terminate")
|
|
25226
25234
|
};
|
|
25227
25235
|
}
|
|
25236
|
+
// ../../plugins/postgres/src/timeout.ts
|
|
25237
|
+
/**
|
|
25238
|
+
* PostgreSQL's `statement_timeout` covers every statement and, measured against PostgreSQL 17,
|
|
25239
|
+
* keeps counting while a `pg-cursor` result waits to be read: the cursor's `Execute` is sent
|
|
25240
|
+
* without a `Sync`, so the statement is still running (decision 129).
|
|
25241
|
+
*/
|
|
25242
|
+
var POSTGRES_SERVER_TIMEOUT = {
|
|
25243
|
+
setting: "statement_timeout",
|
|
25244
|
+
covers: "all",
|
|
25245
|
+
counts: "statement"
|
|
25246
|
+
};
|
|
25247
|
+
var READ_TIMEOUT_SQL = "SHOW statement_timeout";
|
|
25248
|
+
var SET_TIMEOUT_SQL = "SELECT set_config('statement_timeout', $1, false)";
|
|
25249
|
+
/** A statement naming the setting may have changed it behind the plugin's back. */
|
|
25250
|
+
var TIMEOUT_SETTING = /statement_timeout/i;
|
|
25251
|
+
function namesServerTimeout(sql) {
|
|
25252
|
+
return TIMEOUT_SETTING.test(sql);
|
|
25253
|
+
}
|
|
25254
|
+
/** `SHOW`'s units for a time setting, in milliseconds; a bare number is milliseconds already. */
|
|
25255
|
+
var UNIT_MS = {
|
|
25256
|
+
ms: 1,
|
|
25257
|
+
s: 1e3,
|
|
25258
|
+
min: 6e4,
|
|
25259
|
+
h: 36e5,
|
|
25260
|
+
d: 864e5
|
|
25261
|
+
};
|
|
25262
|
+
var DURATION = /^(\d+(?:\.\d+)?)\s*(ms|s|min|h|d)?$/;
|
|
25263
|
+
/** `SHOW statement_timeout` as milliseconds: `5s`, `1min`, `250ms`, `0`; anything unreadable is 0, no limit. */
|
|
25264
|
+
function postgresDurationMs(text) {
|
|
25265
|
+
const match = DURATION.exec(text.trim());
|
|
25266
|
+
if (match === null) return 0;
|
|
25267
|
+
return Math.round(Number(match[1]) * (UNIT_MS[match[2] ?? "ms"] ?? 1));
|
|
25268
|
+
}
|
|
25228
25269
|
// ../../plugins/postgres/src/tls.ts
|
|
25229
25270
|
var SSL_MODES = [
|
|
25230
25271
|
"disable",
|
|
@@ -25323,6 +25364,10 @@ function tlsInfoOf(socket) {
|
|
|
25323
25364
|
}
|
|
25324
25365
|
// ../../plugins/postgres/src/index.ts
|
|
25325
25366
|
var PLUGIN_ID = "postgres";
|
|
25367
|
+
var CANCEL_REACH = {
|
|
25368
|
+
method: "pg_cancel_backend",
|
|
25369
|
+
reaches: "statement"
|
|
25370
|
+
};
|
|
25326
25371
|
var CAPABILITIES = [
|
|
25327
25372
|
"sql",
|
|
25328
25373
|
"cancel",
|
|
@@ -25337,7 +25382,8 @@ var CAPABILITIES = [
|
|
|
25337
25382
|
"objects",
|
|
25338
25383
|
"serverMessages",
|
|
25339
25384
|
"sessions",
|
|
25340
|
-
"routineCall"
|
|
25385
|
+
"routineCall",
|
|
25386
|
+
"serverTimeout"
|
|
25341
25387
|
];
|
|
25342
25388
|
/** Function bodies live in dollar quotes; `BEGIN ATOMIC … END` holds a SQL-standard body. */
|
|
25343
25389
|
var DIALECT = {
|
|
@@ -25433,6 +25479,7 @@ var RELEASE_SQL = "RELEASE SAVEPOINT table_craft_insert";
|
|
|
25433
25479
|
var ROLLBACK_TO_SQL = "ROLLBACK TO SAVEPOINT table_craft_insert";
|
|
25434
25480
|
var OPEN_TRANSACTION_MESSAGE = "A transaction is open; commit or roll it back before switching to auto-commit.";
|
|
25435
25481
|
var BUSY_MESSAGE = "Another statement is still running in this session.";
|
|
25482
|
+
var CANCELLED_MESSAGE = "The statement was cancelled.";
|
|
25436
25483
|
var DEFAULT_PORT = 5432;
|
|
25437
25484
|
var MAX_PORT = 65535;
|
|
25438
25485
|
var DEFAULT_CONNECT_TIMEOUT_SECONDS = 10;
|
|
@@ -25560,6 +25607,18 @@ async function openClient(config, options) {
|
|
|
25560
25607
|
}
|
|
25561
25608
|
return client;
|
|
25562
25609
|
}
|
|
25610
|
+
function startRunning() {
|
|
25611
|
+
return {
|
|
25612
|
+
cancelRequested: false,
|
|
25613
|
+
startedAt: Date.now(),
|
|
25614
|
+
limitMs: null
|
|
25615
|
+
};
|
|
25616
|
+
}
|
|
25617
|
+
/**
|
|
25618
|
+
* `#appliedTimeoutMs` once a transaction ended after the plugin set the limit: a `ROLLBACK` may
|
|
25619
|
+
* have undone it, so the next statement sets it again whatever it asks for.
|
|
25620
|
+
*/
|
|
25621
|
+
var UNKNOWN_TIMEOUT = -1;
|
|
25563
25622
|
function readBatch(cursor) {
|
|
25564
25623
|
return new Promise((resolve, reject) => {
|
|
25565
25624
|
cursor.read(FETCH_ROWS, (error, rows, result) => {
|
|
@@ -25607,7 +25666,15 @@ var PostgresSession = class PostgresSession {
|
|
|
25607
25666
|
mode: "auto",
|
|
25608
25667
|
open: false
|
|
25609
25668
|
};
|
|
25669
|
+
/** The session's own `statement_timeout` in ms, 0 for none; read before the first limited statement. */
|
|
25670
|
+
#ownTimeoutMs = null;
|
|
25671
|
+
/** The limit this plugin last set; null before it set one; `UNKNOWN_TIMEOUT` after a transaction ended. */
|
|
25672
|
+
#appliedTimeoutMs = null;
|
|
25610
25673
|
serverVersion;
|
|
25674
|
+
/** The manifest's one brand. */
|
|
25675
|
+
serverBrand = "postgresql";
|
|
25676
|
+
serverTimeout = POSTGRES_SERVER_TIMEOUT;
|
|
25677
|
+
cancelReach = CANCEL_REACH;
|
|
25611
25678
|
tls;
|
|
25612
25679
|
introspector;
|
|
25613
25680
|
transactions;
|
|
@@ -25661,7 +25728,7 @@ var PostgresSession = class PostgresSession {
|
|
|
25661
25728
|
if (this.#broken !== null) throw new PluginError("connectionLost", `The connection was lost: ${this.#broken.message}`);
|
|
25662
25729
|
if (this.#running !== null) throw new PluginError("sessionBusy", BUSY_MESSAGE);
|
|
25663
25730
|
await this.#cancelling.catch(() => void 0);
|
|
25664
|
-
const running =
|
|
25731
|
+
const running = startRunning();
|
|
25665
25732
|
this.#running = running;
|
|
25666
25733
|
const effect = transactionEffect(sql, DIALECT);
|
|
25667
25734
|
const state = this.#transaction;
|
|
@@ -25670,6 +25737,10 @@ var PostgresSession = class PostgresSession {
|
|
|
25670
25737
|
await this.#client.query(BEGIN_SQL);
|
|
25671
25738
|
state.open = true;
|
|
25672
25739
|
}
|
|
25740
|
+
running.limitMs = await this.#applyTimeout(options.serverTimeoutMs);
|
|
25741
|
+
if (namesServerTimeout(sql)) this.#appliedTimeoutMs = null;
|
|
25742
|
+
if (running.cancelRequested) throw new Error(CANCELLED_MESSAGE);
|
|
25743
|
+
running.startedAt = Date.now();
|
|
25673
25744
|
} catch (cause) {
|
|
25674
25745
|
this.#running = null;
|
|
25675
25746
|
throw this.#failure(cause, running);
|
|
@@ -25682,10 +25753,11 @@ var PostgresSession = class PostgresSession {
|
|
|
25682
25753
|
}));
|
|
25683
25754
|
try {
|
|
25684
25755
|
const first = await readBatch(cursor).catch((cause) => {
|
|
25685
|
-
if (effect === "end")
|
|
25756
|
+
if (effect === "end") this.#transactionEnded();
|
|
25686
25757
|
throw cause;
|
|
25687
25758
|
});
|
|
25688
|
-
if (effect
|
|
25759
|
+
if (effect === "end") this.#transactionEnded();
|
|
25760
|
+
else if (effect !== null) state.open = true;
|
|
25689
25761
|
const fields = first.result.fields;
|
|
25690
25762
|
if (fields.length === 0) {
|
|
25691
25763
|
this.#notices = null;
|
|
@@ -25740,7 +25812,7 @@ var PostgresSession = class PostgresSession {
|
|
|
25740
25812
|
if (this.#running !== null) throw new PluginError("sessionBusy", BUSY_MESSAGE);
|
|
25741
25813
|
if (rows.length === 0) return;
|
|
25742
25814
|
await this.#cancelling.catch(() => void 0);
|
|
25743
|
-
const running =
|
|
25815
|
+
const running = startRunning();
|
|
25744
25816
|
this.#running = running;
|
|
25745
25817
|
const state = this.#transaction;
|
|
25746
25818
|
try {
|
|
@@ -25783,7 +25855,8 @@ var PostgresSession = class PostgresSession {
|
|
|
25783
25855
|
if (!verdict.ok) throw new PluginError("explainUnsupported", verdict.reason);
|
|
25784
25856
|
const statement = explainStatement(sql, options.analyze);
|
|
25785
25857
|
const rows = [];
|
|
25786
|
-
const
|
|
25858
|
+
const limit = options.serverTimeoutMs;
|
|
25859
|
+
const result = await this.execute(statement, [], limit === void 0 ? {} : { serverTimeoutMs: limit });
|
|
25787
25860
|
for await (const row of result.rows) rows.push(row);
|
|
25788
25861
|
return {
|
|
25789
25862
|
...readPlan(planText(rows), statement, options.analyze),
|
|
@@ -25815,7 +25888,9 @@ var PostgresSession = class PostgresSession {
|
|
|
25815
25888
|
}
|
|
25816
25889
|
#failure(cause, running, sql) {
|
|
25817
25890
|
const code = cause.code;
|
|
25818
|
-
|
|
25891
|
+
const limit = running.limitMs;
|
|
25892
|
+
if (!running.cancelRequested && code === QUERY_CANCELED && limit !== null && Date.now() - running.startedAt >= limit) return new PluginError("timedOut", `PostgreSQL stopped the statement at its ${String(limit / MS_PER_SECOND)} s limit (statement_timeout).`, { cause });
|
|
25893
|
+
if (running.cancelRequested || code === QUERY_CANCELED) return new PluginError("cancelled", CANCELLED_MESSAGE, { cause });
|
|
25819
25894
|
const position = sql === void 0 ? void 0 : serverErrorPosition(sql, cause);
|
|
25820
25895
|
return new PluginError("statementFailed", messageOf(cause), {
|
|
25821
25896
|
cause,
|
|
@@ -25852,7 +25927,37 @@ var PostgresSession = class PostgresSession {
|
|
|
25852
25927
|
} catch (cause) {
|
|
25853
25928
|
throw new PluginError("statementFailed", messageOf(cause), { cause });
|
|
25854
25929
|
} finally {
|
|
25855
|
-
this.#
|
|
25930
|
+
this.#transactionEnded();
|
|
25931
|
+
}
|
|
25932
|
+
}
|
|
25933
|
+
/** `statement_timeout` is transactional: a `ROLLBACK` undoes a `SET` made inside it. */
|
|
25934
|
+
#transactionEnded() {
|
|
25935
|
+
this.#transaction.open = false;
|
|
25936
|
+
if (this.#appliedTimeoutMs !== null) this.#appliedTimeoutMs = UNKNOWN_TIMEOUT;
|
|
25937
|
+
}
|
|
25938
|
+
/**
|
|
25939
|
+
* Sets `statement_timeout` to `requested`, never above the session's own, or puts the own back
|
|
25940
|
+
* when nothing is requested; sends nothing when the value would not change (decision 129). In a
|
|
25941
|
+
* failed transaction, which refuses the `SET`, it is skipped and the statement fails on its own.
|
|
25942
|
+
* Returns the limit the statement runs under, null for none.
|
|
25943
|
+
*/
|
|
25944
|
+
async #applyTimeout(requested) {
|
|
25945
|
+
if (requested === void 0 && this.#appliedTimeoutMs === null) return null;
|
|
25946
|
+
try {
|
|
25947
|
+
if (this.#ownTimeoutMs === null) {
|
|
25948
|
+
const shown = await this.#client.query(READ_TIMEOUT_SQL);
|
|
25949
|
+
this.#ownTimeoutMs = postgresDurationMs(shown.rows[0]?.statement_timeout ?? "0");
|
|
25950
|
+
}
|
|
25951
|
+
const own = this.#ownTimeoutMs;
|
|
25952
|
+
const want = requested === void 0 ? own : own === 0 ? requested : Math.min(own, requested);
|
|
25953
|
+
if (want !== this.#appliedTimeoutMs) {
|
|
25954
|
+
await this.#client.query(SET_TIMEOUT_SQL, [String(want)]);
|
|
25955
|
+
this.#appliedTimeoutMs = want;
|
|
25956
|
+
}
|
|
25957
|
+
return want === 0 ? null : want;
|
|
25958
|
+
} catch (cause) {
|
|
25959
|
+
if (cause.code !== "25P02") throw cause;
|
|
25960
|
+
return null;
|
|
25856
25961
|
}
|
|
25857
25962
|
}
|
|
25858
25963
|
async close() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tablecraft-app/plugin-postgres",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
4
|
"description": "The PostgreSQL datasource plugin for TableCraft, downloaded by the app when a data source needs it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,6 @@
|
|
|
24
24
|
},
|
|
25
25
|
"tableCraft": {
|
|
26
26
|
"id": "postgres",
|
|
27
|
-
"apiVersion": "1.
|
|
27
|
+
"apiVersion": "1.7.0"
|
|
28
28
|
}
|
|
29
29
|
}
|
package/table-craft-plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "postgres",
|
|
3
3
|
"name": "PostgreSQL",
|
|
4
|
-
"version": "1.
|
|
5
|
-
"apiVersion": "1.
|
|
4
|
+
"version": "1.14.0",
|
|
5
|
+
"apiVersion": "1.7.0",
|
|
6
6
|
"entry": "index.js",
|
|
7
7
|
"capabilities": [
|
|
8
8
|
"sql",
|
|
@@ -18,9 +18,16 @@
|
|
|
18
18
|
"objects",
|
|
19
19
|
"serverMessages",
|
|
20
20
|
"sessions",
|
|
21
|
-
"routineCall"
|
|
21
|
+
"routineCall",
|
|
22
|
+
"serverTimeout"
|
|
22
23
|
],
|
|
23
24
|
"highlight": "postgresql",
|
|
25
|
+
"brands": [
|
|
26
|
+
{
|
|
27
|
+
"id": "postgresql",
|
|
28
|
+
"name": "PostgreSQL"
|
|
29
|
+
}
|
|
30
|
+
],
|
|
24
31
|
"connection": {
|
|
25
32
|
"fields": [
|
|
26
33
|
{
|