@tablecraft-app/plugin-postgres 1.12.0 → 1.13.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 +109 -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",
|
|
@@ -25337,7 +25378,8 @@ var CAPABILITIES = [
|
|
|
25337
25378
|
"objects",
|
|
25338
25379
|
"serverMessages",
|
|
25339
25380
|
"sessions",
|
|
25340
|
-
"routineCall"
|
|
25381
|
+
"routineCall",
|
|
25382
|
+
"serverTimeout"
|
|
25341
25383
|
];
|
|
25342
25384
|
/** Function bodies live in dollar quotes; `BEGIN ATOMIC … END` holds a SQL-standard body. */
|
|
25343
25385
|
var DIALECT = {
|
|
@@ -25433,6 +25475,7 @@ var RELEASE_SQL = "RELEASE SAVEPOINT table_craft_insert";
|
|
|
25433
25475
|
var ROLLBACK_TO_SQL = "ROLLBACK TO SAVEPOINT table_craft_insert";
|
|
25434
25476
|
var OPEN_TRANSACTION_MESSAGE = "A transaction is open; commit or roll it back before switching to auto-commit.";
|
|
25435
25477
|
var BUSY_MESSAGE = "Another statement is still running in this session.";
|
|
25478
|
+
var CANCELLED_MESSAGE = "The statement was cancelled.";
|
|
25436
25479
|
var DEFAULT_PORT = 5432;
|
|
25437
25480
|
var MAX_PORT = 65535;
|
|
25438
25481
|
var DEFAULT_CONNECT_TIMEOUT_SECONDS = 10;
|
|
@@ -25560,6 +25603,18 @@ async function openClient(config, options) {
|
|
|
25560
25603
|
}
|
|
25561
25604
|
return client;
|
|
25562
25605
|
}
|
|
25606
|
+
function startRunning() {
|
|
25607
|
+
return {
|
|
25608
|
+
cancelRequested: false,
|
|
25609
|
+
startedAt: Date.now(),
|
|
25610
|
+
limitMs: null
|
|
25611
|
+
};
|
|
25612
|
+
}
|
|
25613
|
+
/**
|
|
25614
|
+
* `#appliedTimeoutMs` once a transaction ended after the plugin set the limit: a `ROLLBACK` may
|
|
25615
|
+
* have undone it, so the next statement sets it again whatever it asks for.
|
|
25616
|
+
*/
|
|
25617
|
+
var UNKNOWN_TIMEOUT = -1;
|
|
25563
25618
|
function readBatch(cursor) {
|
|
25564
25619
|
return new Promise((resolve, reject) => {
|
|
25565
25620
|
cursor.read(FETCH_ROWS, (error, rows, result) => {
|
|
@@ -25607,7 +25662,14 @@ var PostgresSession = class PostgresSession {
|
|
|
25607
25662
|
mode: "auto",
|
|
25608
25663
|
open: false
|
|
25609
25664
|
};
|
|
25665
|
+
/** The session's own `statement_timeout` in ms, 0 for none; read before the first limited statement. */
|
|
25666
|
+
#ownTimeoutMs = null;
|
|
25667
|
+
/** The limit this plugin last set; null before it set one; `UNKNOWN_TIMEOUT` after a transaction ended. */
|
|
25668
|
+
#appliedTimeoutMs = null;
|
|
25610
25669
|
serverVersion;
|
|
25670
|
+
/** The manifest's one brand. */
|
|
25671
|
+
serverBrand = "postgresql";
|
|
25672
|
+
serverTimeout = POSTGRES_SERVER_TIMEOUT;
|
|
25611
25673
|
tls;
|
|
25612
25674
|
introspector;
|
|
25613
25675
|
transactions;
|
|
@@ -25661,7 +25723,7 @@ var PostgresSession = class PostgresSession {
|
|
|
25661
25723
|
if (this.#broken !== null) throw new PluginError("connectionLost", `The connection was lost: ${this.#broken.message}`);
|
|
25662
25724
|
if (this.#running !== null) throw new PluginError("sessionBusy", BUSY_MESSAGE);
|
|
25663
25725
|
await this.#cancelling.catch(() => void 0);
|
|
25664
|
-
const running =
|
|
25726
|
+
const running = startRunning();
|
|
25665
25727
|
this.#running = running;
|
|
25666
25728
|
const effect = transactionEffect(sql, DIALECT);
|
|
25667
25729
|
const state = this.#transaction;
|
|
@@ -25670,6 +25732,10 @@ var PostgresSession = class PostgresSession {
|
|
|
25670
25732
|
await this.#client.query(BEGIN_SQL);
|
|
25671
25733
|
state.open = true;
|
|
25672
25734
|
}
|
|
25735
|
+
running.limitMs = await this.#applyTimeout(options.serverTimeoutMs);
|
|
25736
|
+
if (namesServerTimeout(sql)) this.#appliedTimeoutMs = null;
|
|
25737
|
+
if (running.cancelRequested) throw new Error(CANCELLED_MESSAGE);
|
|
25738
|
+
running.startedAt = Date.now();
|
|
25673
25739
|
} catch (cause) {
|
|
25674
25740
|
this.#running = null;
|
|
25675
25741
|
throw this.#failure(cause, running);
|
|
@@ -25682,10 +25748,11 @@ var PostgresSession = class PostgresSession {
|
|
|
25682
25748
|
}));
|
|
25683
25749
|
try {
|
|
25684
25750
|
const first = await readBatch(cursor).catch((cause) => {
|
|
25685
|
-
if (effect === "end")
|
|
25751
|
+
if (effect === "end") this.#transactionEnded();
|
|
25686
25752
|
throw cause;
|
|
25687
25753
|
});
|
|
25688
|
-
if (effect
|
|
25754
|
+
if (effect === "end") this.#transactionEnded();
|
|
25755
|
+
else if (effect !== null) state.open = true;
|
|
25689
25756
|
const fields = first.result.fields;
|
|
25690
25757
|
if (fields.length === 0) {
|
|
25691
25758
|
this.#notices = null;
|
|
@@ -25740,7 +25807,7 @@ var PostgresSession = class PostgresSession {
|
|
|
25740
25807
|
if (this.#running !== null) throw new PluginError("sessionBusy", BUSY_MESSAGE);
|
|
25741
25808
|
if (rows.length === 0) return;
|
|
25742
25809
|
await this.#cancelling.catch(() => void 0);
|
|
25743
|
-
const running =
|
|
25810
|
+
const running = startRunning();
|
|
25744
25811
|
this.#running = running;
|
|
25745
25812
|
const state = this.#transaction;
|
|
25746
25813
|
try {
|
|
@@ -25783,7 +25850,8 @@ var PostgresSession = class PostgresSession {
|
|
|
25783
25850
|
if (!verdict.ok) throw new PluginError("explainUnsupported", verdict.reason);
|
|
25784
25851
|
const statement = explainStatement(sql, options.analyze);
|
|
25785
25852
|
const rows = [];
|
|
25786
|
-
const
|
|
25853
|
+
const limit = options.serverTimeoutMs;
|
|
25854
|
+
const result = await this.execute(statement, [], limit === void 0 ? {} : { serverTimeoutMs: limit });
|
|
25787
25855
|
for await (const row of result.rows) rows.push(row);
|
|
25788
25856
|
return {
|
|
25789
25857
|
...readPlan(planText(rows), statement, options.analyze),
|
|
@@ -25815,7 +25883,9 @@ var PostgresSession = class PostgresSession {
|
|
|
25815
25883
|
}
|
|
25816
25884
|
#failure(cause, running, sql) {
|
|
25817
25885
|
const code = cause.code;
|
|
25818
|
-
|
|
25886
|
+
const limit = running.limitMs;
|
|
25887
|
+
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 });
|
|
25888
|
+
if (running.cancelRequested || code === QUERY_CANCELED) return new PluginError("cancelled", CANCELLED_MESSAGE, { cause });
|
|
25819
25889
|
const position = sql === void 0 ? void 0 : serverErrorPosition(sql, cause);
|
|
25820
25890
|
return new PluginError("statementFailed", messageOf(cause), {
|
|
25821
25891
|
cause,
|
|
@@ -25852,7 +25922,37 @@ var PostgresSession = class PostgresSession {
|
|
|
25852
25922
|
} catch (cause) {
|
|
25853
25923
|
throw new PluginError("statementFailed", messageOf(cause), { cause });
|
|
25854
25924
|
} finally {
|
|
25855
|
-
this.#
|
|
25925
|
+
this.#transactionEnded();
|
|
25926
|
+
}
|
|
25927
|
+
}
|
|
25928
|
+
/** `statement_timeout` is transactional: a `ROLLBACK` undoes a `SET` made inside it. */
|
|
25929
|
+
#transactionEnded() {
|
|
25930
|
+
this.#transaction.open = false;
|
|
25931
|
+
if (this.#appliedTimeoutMs !== null) this.#appliedTimeoutMs = UNKNOWN_TIMEOUT;
|
|
25932
|
+
}
|
|
25933
|
+
/**
|
|
25934
|
+
* Sets `statement_timeout` to `requested`, never above the session's own, or puts the own back
|
|
25935
|
+
* when nothing is requested; sends nothing when the value would not change (decision 129). In a
|
|
25936
|
+
* failed transaction, which refuses the `SET`, it is skipped and the statement fails on its own.
|
|
25937
|
+
* Returns the limit the statement runs under, null for none.
|
|
25938
|
+
*/
|
|
25939
|
+
async #applyTimeout(requested) {
|
|
25940
|
+
if (requested === void 0 && this.#appliedTimeoutMs === null) return null;
|
|
25941
|
+
try {
|
|
25942
|
+
if (this.#ownTimeoutMs === null) {
|
|
25943
|
+
const shown = await this.#client.query(READ_TIMEOUT_SQL);
|
|
25944
|
+
this.#ownTimeoutMs = postgresDurationMs(shown.rows[0]?.statement_timeout ?? "0");
|
|
25945
|
+
}
|
|
25946
|
+
const own = this.#ownTimeoutMs;
|
|
25947
|
+
const want = requested === void 0 ? own : own === 0 ? requested : Math.min(own, requested);
|
|
25948
|
+
if (want !== this.#appliedTimeoutMs) {
|
|
25949
|
+
await this.#client.query(SET_TIMEOUT_SQL, [String(want)]);
|
|
25950
|
+
this.#appliedTimeoutMs = want;
|
|
25951
|
+
}
|
|
25952
|
+
return want === 0 ? null : want;
|
|
25953
|
+
} catch (cause) {
|
|
25954
|
+
if (cause.code !== "25P02") throw cause;
|
|
25955
|
+
return null;
|
|
25856
25956
|
}
|
|
25857
25957
|
}
|
|
25858
25958
|
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.13.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.6.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.13.0",
|
|
5
|
+
"apiVersion": "1.6.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
|
{
|