tina4-nodejs 3.13.138 → 3.13.140
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +683 -329
- package/packages/cli/src/commands/generate.ts +7 -13
- package/packages/core/dist/index.js +688 -329
- package/packages/core/src/api.ts +33 -4
- package/packages/core/src/auth.ts +73 -6
- package/packages/core/src/authGate.ts +27 -20
- package/packages/core/src/graphql.ts +92 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/core/src/job.ts +8 -1
- package/packages/core/src/mcp.ts +3 -1
- package/packages/core/src/middleware.ts +4 -2
- package/packages/core/src/push.ts +16 -0
- package/packages/core/src/queue.ts +15 -2
- package/packages/core/src/queueBackends/liteBackend.ts +15 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +40 -6
- package/packages/core/src/server.ts +6 -1
- package/packages/core/src/sessionHandlers/databaseHandler.ts +60 -3
- package/packages/core/src/sso.ts +12 -4
- package/packages/core/src/ssoIdentity.ts +19 -0
- package/packages/core/src/ssrf.ts +221 -0
- package/packages/core/src/testClient.ts +3 -0
- package/packages/core/src/websocket.ts +4 -2
- package/packages/frond/dist/index.js +6 -0
- package/packages/frond/src/engine.ts +9 -0
- package/packages/orm/dist/index.js +675 -320
- package/packages/orm/src/adapters/firebird.ts +14 -45
- package/packages/orm/src/adapters/mssql.ts +21 -58
- package/packages/orm/src/adapters/mysql.ts +10 -49
- package/packages/orm/src/adapters/odbc.ts +11 -42
- package/packages/orm/src/adapters/postgres.ts +20 -60
- package/packages/orm/src/adapters/sqlCrud.ts +178 -0
- package/packages/orm/src/adapters/sqlite.ts +46 -66
- package/packages/orm/src/index.ts +1 -1
- package/types/core/src/api.d.ts +6 -0
- package/types/core/src/auth.d.ts +30 -0
- package/types/core/src/graphql.d.ts +25 -0
- package/types/core/src/index.d.ts +1 -0
- package/types/core/src/job.d.ts +1 -0
- package/types/core/src/push.d.ts +5 -0
- package/types/core/src/queue.d.ts +7 -1
- package/types/core/src/queueBackends/liteBackend.d.ts +9 -0
- package/types/core/src/queueBackends/mongoBackend.d.ts +8 -1
- package/types/core/src/sessionHandlers/databaseHandler.d.ts +25 -1
- package/types/core/src/sso.d.ts +1 -0
- package/types/core/src/ssoIdentity.d.ts +10 -0
- package/types/core/src/ssrf.d.ts +19 -0
- package/types/orm/src/adapters/sqlCrud.d.ts +90 -0
- package/types/orm/src/adapters/sqlite.d.ts +17 -0
- package/types/orm/src/index.d.ts +1 -1
|
@@ -12,7 +12,8 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
12
12
|
* Install: npm install node-firebird
|
|
13
13
|
* URL format: firebird://user:pass@host:port/path/to/database.fdb
|
|
14
14
|
*/
|
|
15
|
-
import { firebirdDialect,
|
|
15
|
+
import { firebirdDialect, quoteIdentifierWith } from "./sqlDialect.js";
|
|
16
|
+
import { buildDelete, buildInsertRow, buildInsertRows, buildUpdate } from "./sqlCrud.js";
|
|
16
17
|
import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
|
|
17
18
|
import { SQLTranslator } from "../sqlTranslator.js";
|
|
18
19
|
import { connectTimeoutMillis, withConnectTimeout } from "../connectTimeout.js";
|
|
@@ -643,19 +644,15 @@ export class FirebirdAdapter implements DatabaseAdapter {
|
|
|
643
644
|
// the batch reports affectedRows == row count and no lastInsertId (same as the
|
|
644
645
|
// single-row path). See PostgresAdapter for the array-crash rationale.
|
|
645
646
|
if (Array.isArray(data)) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
const
|
|
649
|
-
const paramsList = data.map((row) => keys.map((k) => row[k]));
|
|
650
|
-
const result = await this.executeManyAsync(sql, paramsList);
|
|
647
|
+
const batch = buildInsertRows(FB_DIALECT, table, data);
|
|
648
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
649
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
651
650
|
// The generator holds the LAST inserted id after the batch (FB-LASTID-GAP).
|
|
652
651
|
const lastId = (await this.readGeneratorId(table)) ?? result.lastId;
|
|
653
652
|
return { success: true, affectedRows: result.totalAffected, lastId: lastId ?? undefined };
|
|
654
653
|
}
|
|
655
654
|
|
|
656
|
-
const
|
|
657
|
-
const sql = buildInsert(FB_DIALECT, table, keys);
|
|
658
|
-
const values = Object.values(data);
|
|
655
|
+
const { sql, values } = buildInsertRow(FB_DIALECT, table, data);
|
|
659
656
|
|
|
660
657
|
// FAIL LOUD, like fetch/execute and the other three frameworks: a bad statement
|
|
661
658
|
// RAISES and never returns a falsy result. Swallowing it into {success:false}
|
|
@@ -674,29 +671,12 @@ export class FirebirdAdapter implements DatabaseAdapter {
|
|
|
674
671
|
|
|
675
672
|
async updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
676
673
|
this.ensureConnected();
|
|
677
|
-
//
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
// A raw WHERE fragment + params is half the write_path contract's filter
|
|
685
|
-
// form. Without this branch Object.keys("id = ?") yields the STRING INDICES
|
|
686
|
-
// ["0","1",...] and the statement addresses columns that do not exist.
|
|
687
|
-
// Firebird already uses `?`, so the fragment needs no rewriting.
|
|
688
|
-
if (typeof filter === "string") {
|
|
689
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
690
|
-
const affected = await this.executeReturningCount(
|
|
691
|
-
`UPDATE ${fbQuote(table)} SET ${setClauses}${where}`,
|
|
692
|
-
[...Object.values(data), ...(params ?? [])],
|
|
693
|
-
);
|
|
694
|
-
return { success: true, affectedRows: affected };
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
698
|
-
const sql = `UPDATE ${FB_DIALECT.quote(table)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
699
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
674
|
+
// FB_DIALECT.quote is fbQuote: Firebird folds an UNQUOTED identifier to
|
|
675
|
+
// uppercase, so a conventional `CREATE TABLE probe_t` stores PROBE_T/ID and a
|
|
676
|
+
// lowercase-quoted "id" would be a different, non-existent column. The
|
|
677
|
+
// composer routes every identifier through it. Firebird uses `?` natively, so
|
|
678
|
+
// a raw string filter needs no rewrite.
|
|
679
|
+
const { sql, values } = buildUpdate(FB_DIALECT, table, data, filter, params);
|
|
700
680
|
|
|
701
681
|
const affected = await this.executeReturningCount(sql, values);
|
|
702
682
|
return { success: true, affectedRows: affected };
|
|
@@ -708,19 +688,8 @@ export class FirebirdAdapter implements DatabaseAdapter {
|
|
|
708
688
|
|
|
709
689
|
async deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
710
690
|
this.ensureConnected();
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
// string as an object — db.truncate() was broken outright.
|
|
714
|
-
if (typeof filter === "string") {
|
|
715
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
716
|
-
const affected = await this.executeReturningCount(`DELETE FROM ${fbQuote(table)}${where}`, params ?? []);
|
|
717
|
-
return { success: true, affectedRows: affected };
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Same fbQuote policy as insert/update — see updateAsync.
|
|
721
|
-
const whereClauses = buildWhereClause(FB_DIALECT, Object.keys(filter));
|
|
722
|
-
const sql = `DELETE FROM ${FB_DIALECT.quote(table)} WHERE ${whereClauses}`;
|
|
723
|
-
const values = Object.values(filter);
|
|
691
|
+
// Same fbQuote (FB_DIALECT.quote) policy as insert/update; `?` markers native.
|
|
692
|
+
const { sql, values } = buildDelete(FB_DIALECT, table, filter, params);
|
|
724
693
|
|
|
725
694
|
const affected = await this.executeReturningCount(sql, values);
|
|
726
695
|
return { success: true, affectedRows: affected };
|
|
@@ -12,7 +12,8 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
12
12
|
* Install: npm install tedious
|
|
13
13
|
* URL format: mssql://user:pass@host:port/database
|
|
14
14
|
*/
|
|
15
|
-
import { ANSI_DIALECT, MSSQL_DIALECT
|
|
15
|
+
import { ANSI_DIALECT, MSSQL_DIALECT } from "./sqlDialect.js";
|
|
16
|
+
import { buildDelete, buildInsertRow, buildInsertRows, buildUpdate } from "./sqlCrud.js";
|
|
16
17
|
import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
|
|
17
18
|
import { SQLTranslator } from "../sqlTranslator.js";
|
|
18
19
|
import { connectTimeoutMillis, driverConnectTimeoutMillis, withConnectTimeout } from "../connectTimeout.js";
|
|
@@ -360,15 +361,13 @@ export class MssqlAdapter implements DatabaseAdapter {
|
|
|
360
361
|
// tracked for a batch — affectedRows == row count is what callers rely on).
|
|
361
362
|
// See PostgresAdapter for the array-crash rationale this branch fixes.
|
|
362
363
|
if (Array.isArray(data)) {
|
|
363
|
-
if (data.length === 0) return { success: true, affectedRows: 0 };
|
|
364
|
-
const keys = Object.keys(data[0]);
|
|
365
364
|
// `?` placeholders — executeManyAsync -> executeAsync runs convertPlaceholders,
|
|
366
|
-
// which rewrites them to @p0, @p1, ... for tedious.
|
|
367
|
-
//
|
|
368
|
-
const
|
|
369
|
-
|
|
365
|
+
// which rewrites them to @p0, @p1, ... for tedious. So the batch INSERT is
|
|
366
|
+
// built with MSSQL's bracket quoting but the ANSI `?` marker.
|
|
367
|
+
const batch = buildInsertRows({ quote: MSSQL_DIALECT.quote, marker: ANSI_DIALECT.marker }, table, data);
|
|
368
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
370
369
|
try {
|
|
371
|
-
const result = await this.executeManyAsync(sql, paramsList);
|
|
370
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
372
371
|
if (result.lastId !== undefined) this._lastInsertId = result.lastId;
|
|
373
372
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
374
373
|
} catch (e) {
|
|
@@ -376,11 +375,9 @@ export class MssqlAdapter implements DatabaseAdapter {
|
|
|
376
375
|
}
|
|
377
376
|
}
|
|
378
377
|
|
|
379
|
-
const keys = Object.keys(data);
|
|
380
378
|
// startAt 0: MSSQL BINDS by the marker name, so @p must start where the
|
|
381
379
|
// binding loop starts. Shifting to 1 would name parameters that do not exist.
|
|
382
|
-
const sql =
|
|
383
|
-
const values = Object.values(data);
|
|
380
|
+
const { sql, values } = buildInsertRow(MSSQL_DIALECT, table, data, "; SELECT SCOPE_IDENTITY() AS id", { startAt: 0 });
|
|
384
381
|
|
|
385
382
|
try {
|
|
386
383
|
const result = await this.execSqlPromise(sql, values);
|
|
@@ -406,32 +403,13 @@ export class MssqlAdapter implements DatabaseAdapter {
|
|
|
406
403
|
|
|
407
404
|
async updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
408
405
|
this.ensureConnected();
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// ["0","1",...], producing `WHERE [0] = @p1 AND [1] = @p2` — SQL Server then
|
|
417
|
-
// reports an invalid column name '0'.
|
|
418
|
-
if (typeof filter === "string") {
|
|
419
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
420
|
-
const sql = `UPDATE ${MSSQL_DIALECT.quote(table)} SET ${setClauses}${where}`;
|
|
421
|
-
const values = [...Object.values(data), ...(params ?? [])];
|
|
422
|
-
try {
|
|
423
|
-
const result = await this.execSqlPromise(sql, values);
|
|
424
|
-
return { success: true, affectedRows: result.rowCount };
|
|
425
|
-
} catch (e) {
|
|
426
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const filterKeys = Object.keys(filter);
|
|
431
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
432
|
-
paramIndex += filterKeys.length;
|
|
433
|
-
const sql = `UPDATE ${MSSQL_DIALECT.quote(table)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
434
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
406
|
+
// @pN markers name from 0 (MSSQL binds by name); the WHERE markers continue
|
|
407
|
+
// past the SET values. A raw string filter's `?` are rewritten to @pN via
|
|
408
|
+
// convertPlaceholders at the correct start position.
|
|
409
|
+
const { sql, values } = buildUpdate(MSSQL_DIALECT, table, data, filter, params, {
|
|
410
|
+
startAt: 0,
|
|
411
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt),
|
|
412
|
+
});
|
|
435
413
|
|
|
436
414
|
try {
|
|
437
415
|
const result = await this.execSqlPromise(sql, values);
|
|
@@ -447,27 +425,12 @@ export class MssqlAdapter implements DatabaseAdapter {
|
|
|
447
425
|
|
|
448
426
|
async deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
449
427
|
this.ensureConnected();
|
|
450
|
-
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
: `DELETE FROM [${table}]`;
|
|
457
|
-
try {
|
|
458
|
-
const result = await this.execSqlPromise(sql, params ?? []);
|
|
459
|
-
return { success: true, affectedRows: result.rowCount };
|
|
460
|
-
} catch (e) {
|
|
461
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
const filterKeys = Object.keys(filter);
|
|
466
|
-
let paramIndex = 0;
|
|
467
|
-
const whereClauses = buildWhereClause(MSSQL_DIALECT, filterKeys, paramIndex);
|
|
468
|
-
paramIndex += filterKeys.length;
|
|
469
|
-
const sql = `DELETE FROM ${MSSQL_DIALECT.quote(table)} WHERE ${whereClauses}`;
|
|
470
|
-
const values = Object.values(filter);
|
|
428
|
+
// bindable() rewrites a raw fragment's `?` to @pN only when params are bound
|
|
429
|
+
// (truncate() passes "1 = 1" with none). A hash filter numbers @pN from 0.
|
|
430
|
+
const { sql, values } = buildDelete(MSSQL_DIALECT, table, filter, params, {
|
|
431
|
+
startAt: 0,
|
|
432
|
+
convertFragment: (fragment) => this.bindable(fragment, params),
|
|
433
|
+
});
|
|
471
434
|
|
|
472
435
|
try {
|
|
473
436
|
const result = await this.execSqlPromise(sql, values);
|
|
@@ -12,7 +12,8 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
12
12
|
* Install: npm install mysql2
|
|
13
13
|
* URL format: mysql://user:pass@host:port/database
|
|
14
14
|
*/
|
|
15
|
-
import { MYSQL_DIALECT,
|
|
15
|
+
import { MYSQL_DIALECT, quoteIdentifierWith } from "./sqlDialect.js";
|
|
16
|
+
import { buildDelete, buildInsertRow, buildInsertRows, buildUpdate } from "./sqlCrud.js";
|
|
16
17
|
import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
|
|
17
18
|
import { SQLTranslator } from "../sqlTranslator.js";
|
|
18
19
|
import { connectTarget, connectTimeoutMillis, driverConnectTimeoutMillis, withConnectTimeout } from "../connectTimeout.js";
|
|
@@ -263,12 +264,10 @@ export class MysqlAdapter implements DatabaseAdapter {
|
|
|
263
264
|
// executeManyAsync (ONE connection). See PostgresAdapter for the rationale;
|
|
264
265
|
// without this branch a list crashed/mis-built SQL via Object.keys() on the array.
|
|
265
266
|
if (Array.isArray(data)) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const sql = buildInsert(MYSQL_DIALECT, table, keys);
|
|
269
|
-
const paramsList = data.map((row) => keys.map((k) => row[k]));
|
|
267
|
+
const batch = buildInsertRows(MYSQL_DIALECT, table, data);
|
|
268
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
270
269
|
try {
|
|
271
|
-
const result = await this.executeManyAsync(sql, paramsList);
|
|
270
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
272
271
|
if (result.lastId !== undefined) this._lastInsertId = result.lastId;
|
|
273
272
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
274
273
|
} catch (e) {
|
|
@@ -276,9 +275,7 @@ export class MysqlAdapter implements DatabaseAdapter {
|
|
|
276
275
|
}
|
|
277
276
|
}
|
|
278
277
|
|
|
279
|
-
const
|
|
280
|
-
const sql = buildInsert(MYSQL_DIALECT, table, keys);
|
|
281
|
-
const values = Object.values(data);
|
|
278
|
+
const { sql, values } = buildInsertRow(MYSQL_DIALECT, table, data);
|
|
282
279
|
|
|
283
280
|
try {
|
|
284
281
|
const result = await this.queryPromise(sql, values);
|
|
@@ -299,28 +296,9 @@ export class MysqlAdapter implements DatabaseAdapter {
|
|
|
299
296
|
|
|
300
297
|
async updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
301
298
|
this.ensureConnected();
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
// form. Without this branch Object.keys("id = ?") yields the STRING INDICES
|
|
306
|
-
// ["0","1",...], producing `WHERE \`0\` = ? AND \`1\` = ?` — MySQL then
|
|
307
|
-
// reports an unknown column '0'. MySQL already uses `?`, so the fragment
|
|
308
|
-
// needs no placeholder rewriting.
|
|
309
|
-
if (typeof filter === "string") {
|
|
310
|
-
const where = filter ? ` WHERE ${filter}` : "";
|
|
311
|
-
const sql = `UPDATE ${MYSQL_DIALECT.quote(table)} SET ${setClauses}${where}`;
|
|
312
|
-
const values = [...Object.values(data), ...(params ?? [])];
|
|
313
|
-
try {
|
|
314
|
-
const result = await this.queryPromise(sql, values);
|
|
315
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
316
|
-
} catch (e) {
|
|
317
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
322
|
-
const sql = `UPDATE ${MYSQL_DIALECT.quote(table)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
323
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
299
|
+
// MySQL uses `?` markers natively, so a raw string filter needs no rewrite;
|
|
300
|
+
// the string vs hash branch and value assembly are the shared composer's.
|
|
301
|
+
const { sql, values } = buildUpdate(MYSQL_DIALECT, table, data, filter, params);
|
|
324
302
|
|
|
325
303
|
try {
|
|
326
304
|
const result = await this.queryPromise(sql, values);
|
|
@@ -336,24 +314,7 @@ export class MysqlAdapter implements DatabaseAdapter {
|
|
|
336
314
|
|
|
337
315
|
async deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
338
316
|
this.ensureConnected();
|
|
339
|
-
|
|
340
|
-
// See updateAsync: truncate() calls this with "1 = 1", which became
|
|
341
|
-
// `WHERE \`0\` = ? AND \`1\` = ? ...` — db.truncate() was broken outright.
|
|
342
|
-
if (typeof filter === "string") {
|
|
343
|
-
const sql = filter
|
|
344
|
-
? `DELETE FROM \`${table}\` WHERE ${filter}`
|
|
345
|
-
: `DELETE FROM \`${table}\``;
|
|
346
|
-
try {
|
|
347
|
-
const result = await this.queryPromise(sql, params ?? []);
|
|
348
|
-
return { success: true, affectedRows: result.affectedRows ?? 0 };
|
|
349
|
-
} catch (e) {
|
|
350
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const whereClauses = buildWhereClause(MYSQL_DIALECT, Object.keys(filter));
|
|
355
|
-
const sql = `DELETE FROM ${MYSQL_DIALECT.quote(table)} WHERE ${whereClauses}`;
|
|
356
|
-
const values = Object.values(filter);
|
|
317
|
+
const { sql, values } = buildDelete(MYSQL_DIALECT, table, filter, params);
|
|
357
318
|
|
|
358
319
|
try {
|
|
359
320
|
const result = await this.queryPromise(sql, values);
|
|
@@ -16,7 +16,8 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
16
16
|
* The connection string after stripping the "odbc:///" prefix is passed
|
|
17
17
|
* directly to odbc.connect(), so any valid ODBC connection string works.
|
|
18
18
|
*/
|
|
19
|
-
import { ANSI_DIALECT
|
|
19
|
+
import { ANSI_DIALECT } from "./sqlDialect.js";
|
|
20
|
+
import { buildDelete, buildInsertRow, buildInsertRows, buildUpdate } from "./sqlCrud.js";
|
|
20
21
|
import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
|
|
21
22
|
import { createRequire } from "node:module";
|
|
22
23
|
import { SQLTranslator } from "../sqlTranslator.js";
|
|
@@ -303,17 +304,13 @@ export class OdbcAdapter implements DatabaseAdapter {
|
|
|
303
304
|
// Python master. The single-object path used to run Object.keys() over the
|
|
304
305
|
// array here -> ["0","1",...], a broken INSERT, so batch insert never worked.
|
|
305
306
|
if (Array.isArray(data)) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
const paramsList = data.map((row) => keys.map((k) => (row as Record<string, unknown>)[k]));
|
|
310
|
-
const { totalAffected } = await this.executeManyAsync(sql, paramsList);
|
|
307
|
+
const batch = buildInsertRows(ANSI_DIALECT, table, data);
|
|
308
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
309
|
+
const { totalAffected } = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
311
310
|
return { success: true, affectedRows: totalAffected };
|
|
312
311
|
}
|
|
313
312
|
|
|
314
|
-
const
|
|
315
|
-
const sql = buildInsert(ANSI_DIALECT, table, keys);
|
|
316
|
-
const values = Object.values(data);
|
|
313
|
+
const { sql, values } = buildInsertRow(ANSI_DIALECT, table, data);
|
|
317
314
|
|
|
318
315
|
try {
|
|
319
316
|
const result = await this.connection.query(sql, values);
|
|
@@ -331,23 +328,10 @@ export class OdbcAdapter implements DatabaseAdapter {
|
|
|
331
328
|
params?: unknown[],
|
|
332
329
|
): Promise<DatabaseResult> {
|
|
333
330
|
this.ensureConnected();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (typeof filter === "string") {
|
|
339
|
-
// The string form ("id = ?" + params). Without this branch Object.keys()
|
|
340
|
-
// walked the STRING -> ["0","1",...], building a nonsense WHERE clause -
|
|
341
|
-
// the exact bug the pg/mysql/mssql adapters guard against. params was also
|
|
342
|
-
// dropped entirely (the method never took it), so a parameterised string
|
|
343
|
-
// filter could not bind at all.
|
|
344
|
-
whereSql = filter;
|
|
345
|
-
values = [...Object.values(data), ...(params ?? [])];
|
|
346
|
-
} else {
|
|
347
|
-
whereSql = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
348
|
-
values = [...Object.values(data), ...Object.values(filter)];
|
|
349
|
-
}
|
|
350
|
-
const sql = `UPDATE ${ANSI_DIALECT.quote(table)} SET ${setClauses} WHERE ${whereSql}`;
|
|
331
|
+
// ODBC (the lab DSN is PostgreSQL) still passes `?` markers through — the
|
|
332
|
+
// driver binds them positionally — so no fragment rewrite. The string vs
|
|
333
|
+
// hash branch and value assembly are the shared composer's.
|
|
334
|
+
const { sql, values } = buildUpdate(ANSI_DIALECT, table, data, filter, params);
|
|
351
335
|
|
|
352
336
|
try {
|
|
353
337
|
const result = await this.connection.query(sql, values);
|
|
@@ -374,22 +358,7 @@ export class OdbcAdapter implements DatabaseAdapter {
|
|
|
374
358
|
return { success: true, affectedRows: totalAffected };
|
|
375
359
|
}
|
|
376
360
|
|
|
377
|
-
|
|
378
|
-
// The string form binds its own params (was dropped: query ran with []).
|
|
379
|
-
const sql = filter
|
|
380
|
-
? `DELETE FROM "${table}" WHERE ${filter}`
|
|
381
|
-
: `DELETE FROM "${table}"`;
|
|
382
|
-
try {
|
|
383
|
-
const result = await this.connection.query(sql, params ?? []);
|
|
384
|
-
return { success: true, affectedRows: this.affectedCount(result) };
|
|
385
|
-
} catch (e) {
|
|
386
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const whereClauses = buildWhereClause(ANSI_DIALECT, Object.keys(filter));
|
|
391
|
-
const sql = `DELETE FROM ${ANSI_DIALECT.quote(table)} WHERE ${whereClauses}`;
|
|
392
|
-
const values = Object.values(filter);
|
|
361
|
+
const { sql, values } = buildDelete(ANSI_DIALECT, table, filter, params);
|
|
393
362
|
|
|
394
363
|
try {
|
|
395
364
|
const result = await this.connection.query(sql, values);
|
|
@@ -12,7 +12,8 @@ file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
|
12
12
|
* Install: npm install pg @types/pg
|
|
13
13
|
* URL format: postgresql://user:pass@host:port/database
|
|
14
14
|
*/
|
|
15
|
-
import { ANSI_DIALECT, POSTGRES_DIALECT
|
|
15
|
+
import { ANSI_DIALECT, POSTGRES_DIALECT } from "./sqlDialect.js";
|
|
16
|
+
import { buildDelete, buildInsertRow, buildInsertRows, buildUpdate } from "./sqlCrud.js";
|
|
16
17
|
import type { DatabaseAdapter, DatabaseResult, ColumnInfo, FieldDefinition } from "../types.js";
|
|
17
18
|
import { SQLTranslator } from "../sqlTranslator.js";
|
|
18
19
|
import { connectTarget, connectTimeoutMillis, driverConnectTimeoutMillis, withConnectTimeout } from "../connectTimeout.js";
|
|
@@ -315,13 +316,11 @@ export class PostgresAdapter implements DatabaseAdapter {
|
|
|
315
316
|
// — producing garbage SQL (mirrors the Python `'list' has no attribute keys`
|
|
316
317
|
// crash this fix addresses).
|
|
317
318
|
if (Array.isArray(data)) {
|
|
318
|
-
if (data.length === 0) return { success: true, affectedRows: 0 };
|
|
319
|
-
const keys = Object.keys(data[0]);
|
|
320
319
|
// The batch path binds through executeManyAsync, which converts "?" itself.
|
|
321
|
-
const
|
|
322
|
-
|
|
320
|
+
const batch = buildInsertRows(ANSI_DIALECT, table, data);
|
|
321
|
+
if (!batch) return { success: true, affectedRows: 0 };
|
|
323
322
|
try {
|
|
324
|
-
const result = await this.executeManyAsync(sql, paramsList);
|
|
323
|
+
const result = await this.executeManyAsync(batch.sql, batch.paramsList);
|
|
325
324
|
if (result.lastId !== undefined) this._lastInsertId = result.lastId;
|
|
326
325
|
return { success: true, affectedRows: result.totalAffected, lastId: result.lastId };
|
|
327
326
|
} catch (e) {
|
|
@@ -329,9 +328,7 @@ export class PostgresAdapter implements DatabaseAdapter {
|
|
|
329
328
|
}
|
|
330
329
|
}
|
|
331
330
|
|
|
332
|
-
const
|
|
333
|
-
const sql = buildInsert(POSTGRES_DIALECT, table, keys, " RETURNING *");
|
|
334
|
-
const values = Object.values(data);
|
|
331
|
+
const { sql, values } = buildInsertRow(POSTGRES_DIALECT, table, data, " RETURNING *");
|
|
335
332
|
|
|
336
333
|
try {
|
|
337
334
|
const result = await this.client!.query(sql, values);
|
|
@@ -354,34 +351,13 @@ export class PostgresAdapter implements DatabaseAdapter {
|
|
|
354
351
|
|
|
355
352
|
async updateAsync(table: string, data: Record<string, unknown>, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
356
353
|
this.ensureConnected();
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// Without this branch Object.keys("id = ?") yields the STRING INDICES
|
|
365
|
-
// ["0","1",...], producing `WHERE "0" = $2 AND "1" = $3` — the engine then
|
|
366
|
-
// reports `column "0" does not exist`. sqlite/mongodb/odbc already carried
|
|
367
|
-
// this branch; postgres/mysql/mssql/firebird did not.
|
|
368
|
-
if (typeof filter === "string") {
|
|
369
|
-
const where = filter ? ` WHERE ${this.convertPlaceholders(filter, paramIndex)}` : "";
|
|
370
|
-
const sql = `UPDATE ${POSTGRES_DIALECT.quote(table)} SET ${setClauses}${where}`;
|
|
371
|
-
const values = [...Object.values(data), ...(params ?? [])];
|
|
372
|
-
try {
|
|
373
|
-
const result = await this.client!.query(sql, values);
|
|
374
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
375
|
-
} catch (e) {
|
|
376
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
const filterKeys = Object.keys(filter);
|
|
381
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
382
|
-
paramIndex += filterKeys.length;
|
|
383
|
-
const sql = `UPDATE ${POSTGRES_DIALECT.quote(table)} SET ${setClauses} WHERE ${whereClauses}`;
|
|
384
|
-
const values = [...Object.values(data), ...Object.values(filter)];
|
|
354
|
+
// The composer numbers the SET markers from $1 and continues the WHERE
|
|
355
|
+
// markers past them; a raw string filter's `?` are rewritten to $N via
|
|
356
|
+
// convertPlaceholders (identity would leave `?`, which pg rejects).
|
|
357
|
+
const { sql, values } = buildUpdate(POSTGRES_DIALECT, table, data, filter, params, {
|
|
358
|
+
startAt: 1,
|
|
359
|
+
convertFragment: (fragment, startAt) => this.convertPlaceholders(fragment, startAt),
|
|
360
|
+
});
|
|
385
361
|
|
|
386
362
|
try {
|
|
387
363
|
const result = await this.client!.query(sql, values);
|
|
@@ -397,29 +373,13 @@ export class PostgresAdapter implements DatabaseAdapter {
|
|
|
397
373
|
|
|
398
374
|
async deleteAsync(table: string, filter: Record<string, unknown> | string, params?: unknown[]): Promise<DatabaseResult> {
|
|
399
375
|
this.ensureConnected();
|
|
400
|
-
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
? `DELETE FROM "${table}" WHERE ${this.bindable(filter, params)}`
|
|
408
|
-
: `DELETE FROM "${table}"`;
|
|
409
|
-
try {
|
|
410
|
-
const result = await this.client!.query(sql, params ?? []);
|
|
411
|
-
return { success: true, affectedRows: result.rowCount ?? 0 };
|
|
412
|
-
} catch (e) {
|
|
413
|
-
return { success: false, affectedRows: 0, error: (e as Error).message };
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const filterKeys = Object.keys(filter);
|
|
418
|
-
let paramIndex = 1;
|
|
419
|
-
const whereClauses = buildWhereClause(POSTGRES_DIALECT, filterKeys, paramIndex);
|
|
420
|
-
paramIndex += filterKeys.length;
|
|
421
|
-
const sql = `DELETE FROM ${POSTGRES_DIALECT.quote(table)} WHERE ${whereClauses}`;
|
|
422
|
-
const values = Object.values(filter);
|
|
376
|
+
// bindable() rewrites a raw fragment's `?` to $N only when params are bound
|
|
377
|
+
// (truncate() passes "1 = 1" with none — no rewrite, no error). A hash filter
|
|
378
|
+
// numbers its `$N` from 1.
|
|
379
|
+
const { sql, values } = buildDelete(POSTGRES_DIALECT, table, filter, params, {
|
|
380
|
+
startAt: 1,
|
|
381
|
+
convertFragment: (fragment) => this.bindable(fragment, params),
|
|
382
|
+
});
|
|
423
383
|
|
|
424
384
|
try {
|
|
425
385
|
const result = await this.client!.query(sql, values);
|