latticesql 4.3.2 → 4.3.3
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/dist/cli.js +106 -30
- package/dist/desktop-entry.js +106 -30
- package/dist/index.cjs +105 -29
- package/dist/index.js +105 -29
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1704,7 +1704,7 @@ async function registerPostgresPolyfills(run) {
|
|
|
1704
1704
|
await run(sql);
|
|
1705
1705
|
} catch (err) {
|
|
1706
1706
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1707
|
-
if (/permission denied/i.test(msg)) {
|
|
1707
|
+
if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
|
|
1708
1708
|
permissionDenied = true;
|
|
1709
1709
|
} else {
|
|
1710
1710
|
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
@@ -2222,40 +2222,111 @@ var init_postgres = __esm({
|
|
|
2222
2222
|
SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
|
|
2223
2223
|
$fn$;
|
|
2224
2224
|
END IF;
|
|
2225
|
+
END $do$;`
|
|
2226
|
+
},
|
|
2227
|
+
{
|
|
2228
|
+
warn: "could not register strftime format helper:",
|
|
2229
|
+
// Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
|
|
2230
|
+
// Factored out so the 2-arg and 3-arg strftime overloads share one definition.
|
|
2231
|
+
// Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
|
|
2232
|
+
// UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
|
|
2233
|
+
// scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
|
|
2234
|
+
// that runs registration inside a transaction is NOT aborted. ("must be owner of
|
|
2235
|
+
// function", raised when a non-owner CREATE OR REPLACEs another role's function, is
|
|
2236
|
+
// SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
|
|
2237
|
+
// owner already owns + replaced the function; the member simply uses the owner's
|
|
2238
|
+
// copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
|
|
2239
|
+
// member can replace them on a later upgrade.
|
|
2240
|
+
sql: `DO $do$ BEGIN
|
|
2241
|
+
CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
|
|
2242
|
+
RETURNS text
|
|
2243
|
+
LANGUAGE sql
|
|
2244
|
+
IMMUTABLE
|
|
2245
|
+
AS $fn$
|
|
2246
|
+
SELECT to_char(
|
|
2247
|
+
ts AT TIME ZONE 'UTC',
|
|
2248
|
+
replace(replace(replace(replace(replace(replace(replace(replace(
|
|
2249
|
+
format,
|
|
2250
|
+
'%Y', 'YYYY'),
|
|
2251
|
+
'%m', 'MM'),
|
|
2252
|
+
'%d', 'DD'),
|
|
2253
|
+
'%H', 'HH24'),
|
|
2254
|
+
'%M', 'MI'),
|
|
2255
|
+
'%S', 'SS'),
|
|
2256
|
+
'%f', 'MS'),
|
|
2257
|
+
'T', '"T"')
|
|
2258
|
+
);
|
|
2259
|
+
$fn$;
|
|
2260
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
2225
2261
|
END $do$;`
|
|
2226
2262
|
},
|
|
2227
2263
|
{
|
|
2228
2264
|
warn: "could not register strftime polyfill:",
|
|
2265
|
+
// The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
|
|
2266
|
+
// `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
|
|
2267
|
+
// value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
|
|
2268
|
+
// open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
|
|
2269
|
+
// strftime semantics) instead of aborting the query.
|
|
2229
2270
|
sql: `DO $do$ BEGIN
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2271
|
+
CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
|
|
2272
|
+
RETURNS text
|
|
2273
|
+
LANGUAGE plpgsql
|
|
2274
|
+
IMMUTABLE
|
|
2275
|
+
AS $fn$
|
|
2276
|
+
DECLARE ts timestamptz;
|
|
2277
|
+
BEGIN
|
|
2278
|
+
IF modifier = 'now' THEN
|
|
2279
|
+
ts := now();
|
|
2280
|
+
ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
|
|
2281
|
+
RETURN NULL;
|
|
2282
|
+
ELSE
|
|
2283
|
+
BEGIN
|
|
2241
2284
|
ts := modifier::timestamptz;
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2285
|
+
EXCEPTION WHEN others THEN
|
|
2286
|
+
RETURN NULL;
|
|
2287
|
+
END;
|
|
2288
|
+
END IF;
|
|
2289
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
2290
|
+
END;
|
|
2291
|
+
$fn$;
|
|
2292
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
2293
|
+
END $do$;`
|
|
2294
|
+
},
|
|
2295
|
+
{
|
|
2296
|
+
warn: "could not register 3-arg strftime polyfill:",
|
|
2297
|
+
// SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
|
|
2298
|
+
// retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
|
|
2299
|
+
// 3-arg overload, so that prune threw `function strftime(...) does not exist` on
|
|
2300
|
+
// every PG cloud with retention. Resolve the base time, apply the modifier as an
|
|
2301
|
+
// interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
|
|
2302
|
+
sql: `DO $do$ BEGIN
|
|
2303
|
+
CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
|
|
2304
|
+
RETURNS text
|
|
2305
|
+
LANGUAGE plpgsql
|
|
2306
|
+
IMMUTABLE
|
|
2307
|
+
AS $fn$
|
|
2308
|
+
DECLARE ts timestamptz;
|
|
2309
|
+
BEGIN
|
|
2310
|
+
IF timestring = 'now' THEN
|
|
2311
|
+
ts := now();
|
|
2312
|
+
ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
|
|
2313
|
+
RETURN NULL;
|
|
2314
|
+
ELSE
|
|
2315
|
+
BEGIN
|
|
2316
|
+
ts := timestring::timestamptz;
|
|
2317
|
+
EXCEPTION WHEN others THEN
|
|
2318
|
+
RETURN NULL;
|
|
2319
|
+
END;
|
|
2320
|
+
END IF;
|
|
2321
|
+
BEGIN
|
|
2322
|
+
ts := ts + modifier::interval;
|
|
2323
|
+
EXCEPTION WHEN others THEN
|
|
2324
|
+
RETURN NULL;
|
|
2256
2325
|
END;
|
|
2257
|
-
|
|
2258
|
-
|
|
2326
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
2327
|
+
END;
|
|
2328
|
+
$fn$;
|
|
2329
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
2259
2330
|
END $do$;`
|
|
2260
2331
|
}
|
|
2261
2332
|
];
|
|
@@ -9180,7 +9251,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
|
|
|
9180
9251
|
async function ownPolyfillsByGroup(db) {
|
|
9181
9252
|
if (!isPg(db)) return;
|
|
9182
9253
|
const group3 = await memberGroupFor(db);
|
|
9183
|
-
for (const sig of [
|
|
9254
|
+
for (const sig of [
|
|
9255
|
+
"json_extract(text, text)",
|
|
9256
|
+
"strftime(text, text)",
|
|
9257
|
+
"strftime(text, text, text)",
|
|
9258
|
+
"__lattice_strftime_fmt(timestamptz, text)"
|
|
9259
|
+
]) {
|
|
9184
9260
|
try {
|
|
9185
9261
|
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
|
|
9186
9262
|
if (reg?.reg == null) continue;
|
|
@@ -82020,7 +82096,7 @@ function printHelp() {
|
|
|
82020
82096
|
);
|
|
82021
82097
|
}
|
|
82022
82098
|
function getVersion() {
|
|
82023
|
-
if (true) return "4.3.
|
|
82099
|
+
if (true) return "4.3.3";
|
|
82024
82100
|
try {
|
|
82025
82101
|
const pkgPath = new URL("../package.json", import.meta.url).pathname;
|
|
82026
82102
|
const pkg = JSON.parse(readFileSync26(pkgPath, "utf-8"));
|
package/dist/desktop-entry.js
CHANGED
|
@@ -835,7 +835,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
|
|
|
835
835
|
async function ownPolyfillsByGroup(db) {
|
|
836
836
|
if (!isPg(db)) return;
|
|
837
837
|
const group3 = await memberGroupFor(db);
|
|
838
|
-
for (const sig of [
|
|
838
|
+
for (const sig of [
|
|
839
|
+
"json_extract(text, text)",
|
|
840
|
+
"strftime(text, text)",
|
|
841
|
+
"strftime(text, text, text)",
|
|
842
|
+
"__lattice_strftime_fmt(timestamptz, text)"
|
|
843
|
+
]) {
|
|
839
844
|
try {
|
|
840
845
|
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
|
|
841
846
|
if (reg?.reg == null) continue;
|
|
@@ -1781,7 +1786,7 @@ async function registerPostgresPolyfills(run) {
|
|
|
1781
1786
|
await run(sql);
|
|
1782
1787
|
} catch (err) {
|
|
1783
1788
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1784
|
-
if (/permission denied/i.test(msg)) {
|
|
1789
|
+
if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
|
|
1785
1790
|
permissionDenied = true;
|
|
1786
1791
|
} else {
|
|
1787
1792
|
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
@@ -2299,40 +2304,111 @@ var init_postgres = __esm({
|
|
|
2299
2304
|
SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
|
|
2300
2305
|
$fn$;
|
|
2301
2306
|
END IF;
|
|
2307
|
+
END $do$;`
|
|
2308
|
+
},
|
|
2309
|
+
{
|
|
2310
|
+
warn: "could not register strftime format helper:",
|
|
2311
|
+
// Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
|
|
2312
|
+
// Factored out so the 2-arg and 3-arg strftime overloads share one definition.
|
|
2313
|
+
// Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
|
|
2314
|
+
// UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
|
|
2315
|
+
// scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
|
|
2316
|
+
// that runs registration inside a transaction is NOT aborted. ("must be owner of
|
|
2317
|
+
// function", raised when a non-owner CREATE OR REPLACEs another role's function, is
|
|
2318
|
+
// SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
|
|
2319
|
+
// owner already owns + replaced the function; the member simply uses the owner's
|
|
2320
|
+
// copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
|
|
2321
|
+
// member can replace them on a later upgrade.
|
|
2322
|
+
sql: `DO $do$ BEGIN
|
|
2323
|
+
CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
|
|
2324
|
+
RETURNS text
|
|
2325
|
+
LANGUAGE sql
|
|
2326
|
+
IMMUTABLE
|
|
2327
|
+
AS $fn$
|
|
2328
|
+
SELECT to_char(
|
|
2329
|
+
ts AT TIME ZONE 'UTC',
|
|
2330
|
+
replace(replace(replace(replace(replace(replace(replace(replace(
|
|
2331
|
+
format,
|
|
2332
|
+
'%Y', 'YYYY'),
|
|
2333
|
+
'%m', 'MM'),
|
|
2334
|
+
'%d', 'DD'),
|
|
2335
|
+
'%H', 'HH24'),
|
|
2336
|
+
'%M', 'MI'),
|
|
2337
|
+
'%S', 'SS'),
|
|
2338
|
+
'%f', 'MS'),
|
|
2339
|
+
'T', '"T"')
|
|
2340
|
+
);
|
|
2341
|
+
$fn$;
|
|
2342
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
2302
2343
|
END $do$;`
|
|
2303
2344
|
},
|
|
2304
2345
|
{
|
|
2305
2346
|
warn: "could not register strftime polyfill:",
|
|
2347
|
+
// The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
|
|
2348
|
+
// `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
|
|
2349
|
+
// value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
|
|
2350
|
+
// open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
|
|
2351
|
+
// strftime semantics) instead of aborting the query.
|
|
2306
2352
|
sql: `DO $do$ BEGIN
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2353
|
+
CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
|
|
2354
|
+
RETURNS text
|
|
2355
|
+
LANGUAGE plpgsql
|
|
2356
|
+
IMMUTABLE
|
|
2357
|
+
AS $fn$
|
|
2358
|
+
DECLARE ts timestamptz;
|
|
2359
|
+
BEGIN
|
|
2360
|
+
IF modifier = 'now' THEN
|
|
2361
|
+
ts := now();
|
|
2362
|
+
ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
|
|
2363
|
+
RETURN NULL;
|
|
2364
|
+
ELSE
|
|
2365
|
+
BEGIN
|
|
2318
2366
|
ts := modifier::timestamptz;
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2367
|
+
EXCEPTION WHEN others THEN
|
|
2368
|
+
RETURN NULL;
|
|
2369
|
+
END;
|
|
2370
|
+
END IF;
|
|
2371
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
2372
|
+
END;
|
|
2373
|
+
$fn$;
|
|
2374
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
2375
|
+
END $do$;`
|
|
2376
|
+
},
|
|
2377
|
+
{
|
|
2378
|
+
warn: "could not register 3-arg strftime polyfill:",
|
|
2379
|
+
// SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
|
|
2380
|
+
// retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
|
|
2381
|
+
// 3-arg overload, so that prune threw `function strftime(...) does not exist` on
|
|
2382
|
+
// every PG cloud with retention. Resolve the base time, apply the modifier as an
|
|
2383
|
+
// interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
|
|
2384
|
+
sql: `DO $do$ BEGIN
|
|
2385
|
+
CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
|
|
2386
|
+
RETURNS text
|
|
2387
|
+
LANGUAGE plpgsql
|
|
2388
|
+
IMMUTABLE
|
|
2389
|
+
AS $fn$
|
|
2390
|
+
DECLARE ts timestamptz;
|
|
2391
|
+
BEGIN
|
|
2392
|
+
IF timestring = 'now' THEN
|
|
2393
|
+
ts := now();
|
|
2394
|
+
ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
|
|
2395
|
+
RETURN NULL;
|
|
2396
|
+
ELSE
|
|
2397
|
+
BEGIN
|
|
2398
|
+
ts := timestring::timestamptz;
|
|
2399
|
+
EXCEPTION WHEN others THEN
|
|
2400
|
+
RETURN NULL;
|
|
2401
|
+
END;
|
|
2402
|
+
END IF;
|
|
2403
|
+
BEGIN
|
|
2404
|
+
ts := ts + modifier::interval;
|
|
2405
|
+
EXCEPTION WHEN others THEN
|
|
2406
|
+
RETURN NULL;
|
|
2333
2407
|
END;
|
|
2334
|
-
|
|
2335
|
-
|
|
2408
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
2409
|
+
END;
|
|
2410
|
+
$fn$;
|
|
2411
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
2336
2412
|
END $do$;`
|
|
2337
2413
|
}
|
|
2338
2414
|
];
|
|
@@ -81598,7 +81674,7 @@ ${e6.stack ?? ""}`
|
|
|
81598
81674
|
}
|
|
81599
81675
|
|
|
81600
81676
|
// src/desktop-entry.ts
|
|
81601
|
-
var VERSION2 = true ? "4.3.
|
|
81677
|
+
var VERSION2 = true ? "4.3.3" : "unknown";
|
|
81602
81678
|
export {
|
|
81603
81679
|
VERSION2 as VERSION,
|
|
81604
81680
|
ensureRootForGui,
|
package/dist/index.cjs
CHANGED
|
@@ -917,7 +917,7 @@ async function registerPostgresPolyfills(run) {
|
|
|
917
917
|
await run(sql);
|
|
918
918
|
} catch (err) {
|
|
919
919
|
const msg = err instanceof Error ? err.message : String(err);
|
|
920
|
-
if (/permission denied/i.test(msg)) {
|
|
920
|
+
if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
|
|
921
921
|
permissionDenied = true;
|
|
922
922
|
} else {
|
|
923
923
|
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
@@ -1439,40 +1439,111 @@ var init_postgres = __esm({
|
|
|
1439
1439
|
SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
|
|
1440
1440
|
$fn$;
|
|
1441
1441
|
END IF;
|
|
1442
|
+
END $do$;`
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
warn: "could not register strftime format helper:",
|
|
1446
|
+
// Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
|
|
1447
|
+
// Factored out so the 2-arg and 3-arg strftime overloads share one definition.
|
|
1448
|
+
// Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
|
|
1449
|
+
// UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
|
|
1450
|
+
// scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
|
|
1451
|
+
// that runs registration inside a transaction is NOT aborted. ("must be owner of
|
|
1452
|
+
// function", raised when a non-owner CREATE OR REPLACEs another role's function, is
|
|
1453
|
+
// SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
|
|
1454
|
+
// owner already owns + replaced the function; the member simply uses the owner's
|
|
1455
|
+
// copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
|
|
1456
|
+
// member can replace them on a later upgrade.
|
|
1457
|
+
sql: `DO $do$ BEGIN
|
|
1458
|
+
CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
|
|
1459
|
+
RETURNS text
|
|
1460
|
+
LANGUAGE sql
|
|
1461
|
+
IMMUTABLE
|
|
1462
|
+
AS $fn$
|
|
1463
|
+
SELECT to_char(
|
|
1464
|
+
ts AT TIME ZONE 'UTC',
|
|
1465
|
+
replace(replace(replace(replace(replace(replace(replace(replace(
|
|
1466
|
+
format,
|
|
1467
|
+
'%Y', 'YYYY'),
|
|
1468
|
+
'%m', 'MM'),
|
|
1469
|
+
'%d', 'DD'),
|
|
1470
|
+
'%H', 'HH24'),
|
|
1471
|
+
'%M', 'MI'),
|
|
1472
|
+
'%S', 'SS'),
|
|
1473
|
+
'%f', 'MS'),
|
|
1474
|
+
'T', '"T"')
|
|
1475
|
+
);
|
|
1476
|
+
$fn$;
|
|
1477
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1442
1478
|
END $do$;`
|
|
1443
1479
|
},
|
|
1444
1480
|
{
|
|
1445
1481
|
warn: "could not register strftime polyfill:",
|
|
1482
|
+
// The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
|
|
1483
|
+
// `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
|
|
1484
|
+
// value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
|
|
1485
|
+
// open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
|
|
1486
|
+
// strftime semantics) instead of aborting the query.
|
|
1446
1487
|
sql: `DO $do$ BEGIN
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1488
|
+
CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
|
|
1489
|
+
RETURNS text
|
|
1490
|
+
LANGUAGE plpgsql
|
|
1491
|
+
IMMUTABLE
|
|
1492
|
+
AS $fn$
|
|
1493
|
+
DECLARE ts timestamptz;
|
|
1494
|
+
BEGIN
|
|
1495
|
+
IF modifier = 'now' THEN
|
|
1496
|
+
ts := now();
|
|
1497
|
+
ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
|
|
1498
|
+
RETURN NULL;
|
|
1499
|
+
ELSE
|
|
1500
|
+
BEGIN
|
|
1458
1501
|
ts := modifier::timestamptz;
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1502
|
+
EXCEPTION WHEN others THEN
|
|
1503
|
+
RETURN NULL;
|
|
1504
|
+
END;
|
|
1505
|
+
END IF;
|
|
1506
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
1507
|
+
END;
|
|
1508
|
+
$fn$;
|
|
1509
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1510
|
+
END $do$;`
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
warn: "could not register 3-arg strftime polyfill:",
|
|
1514
|
+
// SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
|
|
1515
|
+
// retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
|
|
1516
|
+
// 3-arg overload, so that prune threw `function strftime(...) does not exist` on
|
|
1517
|
+
// every PG cloud with retention. Resolve the base time, apply the modifier as an
|
|
1518
|
+
// interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
|
|
1519
|
+
sql: `DO $do$ BEGIN
|
|
1520
|
+
CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
|
|
1521
|
+
RETURNS text
|
|
1522
|
+
LANGUAGE plpgsql
|
|
1523
|
+
IMMUTABLE
|
|
1524
|
+
AS $fn$
|
|
1525
|
+
DECLARE ts timestamptz;
|
|
1526
|
+
BEGIN
|
|
1527
|
+
IF timestring = 'now' THEN
|
|
1528
|
+
ts := now();
|
|
1529
|
+
ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
|
|
1530
|
+
RETURN NULL;
|
|
1531
|
+
ELSE
|
|
1532
|
+
BEGIN
|
|
1533
|
+
ts := timestring::timestamptz;
|
|
1534
|
+
EXCEPTION WHEN others THEN
|
|
1535
|
+
RETURN NULL;
|
|
1536
|
+
END;
|
|
1537
|
+
END IF;
|
|
1538
|
+
BEGIN
|
|
1539
|
+
ts := ts + modifier::interval;
|
|
1540
|
+
EXCEPTION WHEN others THEN
|
|
1541
|
+
RETURN NULL;
|
|
1473
1542
|
END;
|
|
1474
|
-
|
|
1475
|
-
|
|
1543
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
1544
|
+
END;
|
|
1545
|
+
$fn$;
|
|
1546
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1476
1547
|
END $do$;`
|
|
1477
1548
|
}
|
|
1478
1549
|
];
|
|
@@ -9391,7 +9462,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
|
|
|
9391
9462
|
async function ownPolyfillsByGroup(db) {
|
|
9392
9463
|
if (!isPg(db)) return;
|
|
9393
9464
|
const group3 = await memberGroupFor(db);
|
|
9394
|
-
for (const sig of [
|
|
9465
|
+
for (const sig of [
|
|
9466
|
+
"json_extract(text, text)",
|
|
9467
|
+
"strftime(text, text)",
|
|
9468
|
+
"strftime(text, text, text)",
|
|
9469
|
+
"__lattice_strftime_fmt(timestamptz, text)"
|
|
9470
|
+
]) {
|
|
9395
9471
|
try {
|
|
9396
9472
|
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
|
|
9397
9473
|
if (reg?.reg == null) continue;
|
package/dist/index.js
CHANGED
|
@@ -909,7 +909,7 @@ async function registerPostgresPolyfills(run) {
|
|
|
909
909
|
await run(sql);
|
|
910
910
|
} catch (err) {
|
|
911
911
|
const msg = err instanceof Error ? err.message : String(err);
|
|
912
|
-
if (/permission denied/i.test(msg)) {
|
|
912
|
+
if (/permission denied/i.test(msg) || /must be owner of/i.test(msg)) {
|
|
913
913
|
permissionDenied = true;
|
|
914
914
|
} else {
|
|
915
915
|
console.warn(`[PostgresAdapter] ${warn}`, msg);
|
|
@@ -1427,40 +1427,111 @@ var init_postgres = __esm({
|
|
|
1427
1427
|
SELECT doc::jsonb #>> string_to_array(regexp_replace(path, '^\\$\\.?', ''), '.')
|
|
1428
1428
|
$fn$;
|
|
1429
1429
|
END IF;
|
|
1430
|
+
END $do$;`
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
warn: "could not register strftime format helper:",
|
|
1434
|
+
// Shared format translator (SQLite strftime tokens → to_char patterns), in UTC.
|
|
1435
|
+
// Factored out so the 2-arg and 3-arg strftime overloads share one definition.
|
|
1436
|
+
// Each polyfill below is CREATE OR REPLACE (so an existing cloud's prior copy is
|
|
1437
|
+
// UPGRADED when the owner opens), wrapped in a DO block whose EXCEPTION swallows a
|
|
1438
|
+
// scoped member's insufficient-privilege failure IN A SUBTRANSACTION — so a member
|
|
1439
|
+
// that runs registration inside a transaction is NOT aborted. ("must be owner of
|
|
1440
|
+
// function", raised when a non-owner CREATE OR REPLACEs another role's function, is
|
|
1441
|
+
// SQLSTATE 42501 / `insufficient_privilege`, same class as "permission denied".) The
|
|
1442
|
+
// owner already owns + replaced the function; the member simply uses the owner's
|
|
1443
|
+
// copy — and `ownPolyfillsByGroup` re-owns the whole set to the member group so any
|
|
1444
|
+
// member can replace them on a later upgrade.
|
|
1445
|
+
sql: `DO $do$ BEGIN
|
|
1446
|
+
CREATE OR REPLACE FUNCTION __lattice_strftime_fmt(ts timestamptz, format text)
|
|
1447
|
+
RETURNS text
|
|
1448
|
+
LANGUAGE sql
|
|
1449
|
+
IMMUTABLE
|
|
1450
|
+
AS $fn$
|
|
1451
|
+
SELECT to_char(
|
|
1452
|
+
ts AT TIME ZONE 'UTC',
|
|
1453
|
+
replace(replace(replace(replace(replace(replace(replace(replace(
|
|
1454
|
+
format,
|
|
1455
|
+
'%Y', 'YYYY'),
|
|
1456
|
+
'%m', 'MM'),
|
|
1457
|
+
'%d', 'DD'),
|
|
1458
|
+
'%H', 'HH24'),
|
|
1459
|
+
'%M', 'MI'),
|
|
1460
|
+
'%S', 'SS'),
|
|
1461
|
+
'%f', 'MS'),
|
|
1462
|
+
'T', '"T"')
|
|
1463
|
+
);
|
|
1464
|
+
$fn$;
|
|
1465
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1430
1466
|
END $do$;`
|
|
1431
1467
|
},
|
|
1432
1468
|
{
|
|
1433
1469
|
warn: "could not register strftime polyfill:",
|
|
1470
|
+
// The pre-4.3.3 strftime cast the modifier straight to timestamptz and threw
|
|
1471
|
+
// `invalid input syntax for type timestamp with time zone: ""` on a legacy ''
|
|
1472
|
+
// value (3.x stored nullable TEXT timestamps as ''), bricking the whole workspace
|
|
1473
|
+
// open. Now an empty, whitespace, or unparseable time string returns NULL (SQLite's
|
|
1474
|
+
// strftime semantics) instead of aborting the query.
|
|
1434
1475
|
sql: `DO $do$ BEGIN
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1476
|
+
CREATE OR REPLACE FUNCTION strftime(format text, modifier text)
|
|
1477
|
+
RETURNS text
|
|
1478
|
+
LANGUAGE plpgsql
|
|
1479
|
+
IMMUTABLE
|
|
1480
|
+
AS $fn$
|
|
1481
|
+
DECLARE ts timestamptz;
|
|
1482
|
+
BEGIN
|
|
1483
|
+
IF modifier = 'now' THEN
|
|
1484
|
+
ts := now();
|
|
1485
|
+
ELSIF NULLIF(btrim(modifier), '') IS NULL THEN
|
|
1486
|
+
RETURN NULL;
|
|
1487
|
+
ELSE
|
|
1488
|
+
BEGIN
|
|
1446
1489
|
ts := modifier::timestamptz;
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1490
|
+
EXCEPTION WHEN others THEN
|
|
1491
|
+
RETURN NULL;
|
|
1492
|
+
END;
|
|
1493
|
+
END IF;
|
|
1494
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
1495
|
+
END;
|
|
1496
|
+
$fn$;
|
|
1497
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1498
|
+
END $do$;`
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
warn: "could not register 3-arg strftime polyfill:",
|
|
1502
|
+
// SQLite's 3-arg strftime(format, timestring, modifier) — used by the changelog
|
|
1503
|
+
// retention prune (e.g. strftime('%Y-...','now','-30 days')). Postgres had no
|
|
1504
|
+
// 3-arg overload, so that prune threw `function strftime(...) does not exist` on
|
|
1505
|
+
// every PG cloud with retention. Resolve the base time, apply the modifier as an
|
|
1506
|
+
// interval, then reuse the shared formatter. Empty/invalid → NULL, never throws.
|
|
1507
|
+
sql: `DO $do$ BEGIN
|
|
1508
|
+
CREATE OR REPLACE FUNCTION strftime(format text, timestring text, modifier text)
|
|
1509
|
+
RETURNS text
|
|
1510
|
+
LANGUAGE plpgsql
|
|
1511
|
+
IMMUTABLE
|
|
1512
|
+
AS $fn$
|
|
1513
|
+
DECLARE ts timestamptz;
|
|
1514
|
+
BEGIN
|
|
1515
|
+
IF timestring = 'now' THEN
|
|
1516
|
+
ts := now();
|
|
1517
|
+
ELSIF NULLIF(btrim(timestring), '') IS NULL THEN
|
|
1518
|
+
RETURN NULL;
|
|
1519
|
+
ELSE
|
|
1520
|
+
BEGIN
|
|
1521
|
+
ts := timestring::timestamptz;
|
|
1522
|
+
EXCEPTION WHEN others THEN
|
|
1523
|
+
RETURN NULL;
|
|
1524
|
+
END;
|
|
1525
|
+
END IF;
|
|
1526
|
+
BEGIN
|
|
1527
|
+
ts := ts + modifier::interval;
|
|
1528
|
+
EXCEPTION WHEN others THEN
|
|
1529
|
+
RETURN NULL;
|
|
1461
1530
|
END;
|
|
1462
|
-
|
|
1463
|
-
|
|
1531
|
+
RETURN __lattice_strftime_fmt(ts, format);
|
|
1532
|
+
END;
|
|
1533
|
+
$fn$;
|
|
1534
|
+
EXCEPTION WHEN insufficient_privilege THEN NULL;
|
|
1464
1535
|
END $do$;`
|
|
1465
1536
|
}
|
|
1466
1537
|
];
|
|
@@ -9397,7 +9468,12 @@ CREATE TRIGGER "${trg}" AFTER INSERT OR UPDATE OR DELETE ON ${q3}
|
|
|
9397
9468
|
async function ownPolyfillsByGroup(db) {
|
|
9398
9469
|
if (!isPg(db)) return;
|
|
9399
9470
|
const group3 = await memberGroupFor(db);
|
|
9400
|
-
for (const sig of [
|
|
9471
|
+
for (const sig of [
|
|
9472
|
+
"json_extract(text, text)",
|
|
9473
|
+
"strftime(text, text)",
|
|
9474
|
+
"strftime(text, text, text)",
|
|
9475
|
+
"__lattice_strftime_fmt(timestamptz, text)"
|
|
9476
|
+
]) {
|
|
9401
9477
|
try {
|
|
9402
9478
|
const reg = await getAsyncOrSync(db.adapter, `SELECT to_regprocedure($1) AS reg`, [sig]);
|
|
9403
9479
|
if (reg?.reg == null) continue;
|
package/package.json
CHANGED