uql-orm 0.50.0 → 0.52.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/README.md +1 -1
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/bunSql/bunSql.util.d.ts +33 -10
- package/dist/bunSql/bunSql.util.js +57 -42
- package/dist/bunSql/bunSqlQuerier.d.ts +13 -8
- package/dist/bunSql/bunSqlQuerier.js +17 -8
- package/dist/bunSql/bunSqlQuerierPool.d.ts +10 -2
- package/dist/bunSql/bunSqlQuerierPool.js +38 -25
- package/dist/bunSql/index.d.ts +1 -3
- package/dist/bunSql/index.js +0 -3
- package/dist/dialect/abstractSqlDialect.d.ts +61 -7
- package/dist/dialect/abstractSqlDialect.js +88 -27
- package/dist/dialect/aliases.d.ts +2 -0
- package/dist/dialect/aliases.js +2 -0
- package/dist/dialect/jsonSql.d.ts +2 -2
- package/dist/dialect/jsonSql.js +2 -2
- package/dist/dialect/mergeSqlDialect.d.ts +45 -0
- package/dist/dialect/mergeSqlDialect.js +89 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +4 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -3
- package/dist/dialect/pgLikeSqlDialect.js +15 -12
- package/dist/migrate/builder/expressions.js +5 -0
- package/dist/migrate/introspection/index.d.ts +2 -0
- package/dist/migrate/introspection/index.js +2 -0
- package/dist/migrate/introspection/mssqlIntrospector.d.ts +63 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +198 -0
- package/dist/migrate/introspection/postgresIntrospector.js +3 -3
- package/dist/migrate/introspection/registry.d.ts +3 -0
- package/dist/migrate/introspection/registry.js +28 -0
- package/dist/migrate/migrator.js +2 -21
- package/dist/mongo/mongoDialect.js +4 -1
- package/dist/mongo/mongodbQuerier.d.ts +2 -2
- package/dist/mongo/mongodbQuerier.js +8 -6
- package/dist/mssql/index.d.ts +3 -0
- package/dist/mssql/index.js +3 -0
- package/dist/mssql/mssqlDialect.d.ts +144 -0
- package/dist/mssql/mssqlDialect.js +328 -0
- package/dist/mssql/mssqlQuerier.d.ts +23 -0
- package/dist/mssql/mssqlQuerier.js +137 -0
- package/dist/mssql/mssqlQuerierPool.d.ts +17 -0
- package/dist/mssql/mssqlQuerierPool.js +32 -0
- package/dist/mssql/mssqlWireTypes.d.ts +23 -0
- package/dist/mssql/mssqlWireTypes.js +44 -0
- package/dist/pglite/pgliteQuerier.d.ts +4 -2
- package/dist/pglite/pgliteQuerier.js +7 -2
- package/dist/postgres/pgCursorStream.d.ts +20 -0
- package/dist/postgres/pgCursorStream.js +49 -0
- package/dist/postgres/pgDialect.d.ts +1 -1
- package/dist/postgres/pgDialect.js +1 -1
- package/dist/postgres/postgresWireDriverCapabilities.d.ts +12 -10
- package/dist/postgres/postgresWireDriverCapabilities.js +12 -10
- package/dist/querier/abstractQuerier.d.ts +3 -7
- package/dist/querier/abstractQuerier.js +22 -2
- package/dist/querier/abstractQuerierPool.d.ts +3 -3
- package/dist/schema/canonicalType.js +96 -113
- package/dist/sqlite/sqliteDialect.d.ts +7 -7
- package/dist/sqlite/sqliteDialect.js +21 -18
- package/dist/type/dialect.d.ts +31 -4
- package/dist/type/migratorDialect.d.ts +1 -1
- package/dist/type/migratorDialect.js +1 -0
- package/dist/type/query.d.ts +29 -6
- package/dist/type/universalQuerier.d.ts +3 -3
- package/package.json +13 -3
- package/dist/bunSql/bunSqlCockroachDialect.d.ts +0 -12
- package/dist/bunSql/bunSqlCockroachDialect.js +0 -15
- package/dist/bunSql/bunSqlPostgresDialect.d.ts +0 -11
- package/dist/bunSql/bunSqlPostgresDialect.js +0 -14
- package/dist/bunSql/bunSqliteDialect.d.ts +0 -6
- package/dist/bunSql/bunSqliteDialect.js +0 -6
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ConnectionPool } from 'mssql';
|
|
2
|
+
import { dialectOptionsFrom } from '../dialect/abstractDialect.js';
|
|
3
|
+
import { AbstractSqlQuerierPool } from '../querier/index.js';
|
|
4
|
+
import { MsSqlDialect } from './mssqlDialect.js';
|
|
5
|
+
import { MsSqlQuerier } from './mssqlQuerier.js';
|
|
6
|
+
export class MsSqlQuerierPool extends AbstractSqlQuerierPool {
|
|
7
|
+
pool;
|
|
8
|
+
#connected;
|
|
9
|
+
constructor(opts, extra) {
|
|
10
|
+
super(new MsSqlDialect(dialectOptionsFrom(extra)), extra);
|
|
11
|
+
this.pool = new ConnectionPool(opts);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* `mssql` connects the pool as a whole rather than per checkout, so the promise is shared. A
|
|
15
|
+
* failed one is dropped rather than kept: memoized, a single transient failure would be handed to
|
|
16
|
+
* every later caller for the life of the pool.
|
|
17
|
+
*/
|
|
18
|
+
async getQuerier() {
|
|
19
|
+
return new MsSqlQuerier(() => this.#connect(), this.dialect, this.extra);
|
|
20
|
+
}
|
|
21
|
+
#connect() {
|
|
22
|
+
this.#connected ??= this.pool.connect().catch((err) => {
|
|
23
|
+
this.#connected = undefined;
|
|
24
|
+
throw err;
|
|
25
|
+
});
|
|
26
|
+
return this.#connected;
|
|
27
|
+
}
|
|
28
|
+
async end() {
|
|
29
|
+
this.#connected = undefined;
|
|
30
|
+
await this.pool.close();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The column metadata `tedious` reports beside a recordset, narrowed to the one field read here. */
|
|
2
|
+
type ColumnTypes = Record<string, {
|
|
3
|
+
readonly type: unknown;
|
|
4
|
+
}>;
|
|
5
|
+
/**
|
|
6
|
+
* Decode `BIGINT` as a JS number, leaving every other type to the driver.
|
|
7
|
+
*
|
|
8
|
+
* uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
|
|
9
|
+
* `schema/canonicalType.ts`), and `tedious` hands that back as a string to protect the digits past
|
|
10
|
+
* 2^53 - so without this a field declared `number` reads back as `'9'`, including every generated
|
|
11
|
+
* primary key on every entity.
|
|
12
|
+
*
|
|
13
|
+
* At the wire, for the reason `pgNumericTypes` gives: everything crosses it exactly once - entity
|
|
14
|
+
* reads, the ids an `OUTPUT` reports, raw SQL, counts, aggregates - where the ORM's own hydration
|
|
15
|
+
* only ever sees entity reads.
|
|
16
|
+
*
|
|
17
|
+
* Exact to 2^53, which covers any generated id. A value past it keeps the string the driver gave,
|
|
18
|
+
* because a number could no longer represent it: that is the one case where handing back the exact
|
|
19
|
+
* text is more useful than handing back the type that was asked for. The lighter escape hatch for a
|
|
20
|
+
* column that big is the declaration - `@Field({ type: String, columnType: 'bigint' })`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function decodeWireTypes<T>(rows: T[] | undefined, columns: ColumnTypes | undefined): T[];
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode `BIGINT` as a JS number, leaving every other type to the driver.
|
|
3
|
+
*
|
|
4
|
+
* uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
|
|
5
|
+
* `schema/canonicalType.ts`), and `tedious` hands that back as a string to protect the digits past
|
|
6
|
+
* 2^53 - so without this a field declared `number` reads back as `'9'`, including every generated
|
|
7
|
+
* primary key on every entity.
|
|
8
|
+
*
|
|
9
|
+
* At the wire, for the reason `pgNumericTypes` gives: everything crosses it exactly once - entity
|
|
10
|
+
* reads, the ids an `OUTPUT` reports, raw SQL, counts, aggregates - where the ORM's own hydration
|
|
11
|
+
* only ever sees entity reads.
|
|
12
|
+
*
|
|
13
|
+
* Exact to 2^53, which covers any generated id. A value past it keeps the string the driver gave,
|
|
14
|
+
* because a number could no longer represent it: that is the one case where handing back the exact
|
|
15
|
+
* text is more useful than handing back the type that was asked for. The lighter escape hatch for a
|
|
16
|
+
* column that big is the declaration - `@Field({ type: String, columnType: 'bigint' })`.
|
|
17
|
+
*/
|
|
18
|
+
export function decodeWireTypes(rows, columns) {
|
|
19
|
+
if (!rows?.length || !columns) {
|
|
20
|
+
return rows ?? [];
|
|
21
|
+
}
|
|
22
|
+
const wide = Object.keys(columns).filter((name) => typeName(columns[name]?.type) === 'BigInt');
|
|
23
|
+
if (!wide.length) {
|
|
24
|
+
// The overwhelmingly common case, so an ordinary read copies nothing.
|
|
25
|
+
return rows;
|
|
26
|
+
}
|
|
27
|
+
return rows.map((row) => {
|
|
28
|
+
const decoded = { ...row };
|
|
29
|
+
for (const name of wide) {
|
|
30
|
+
const value = decoded[name];
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
const asNumber = Number(value);
|
|
33
|
+
if (Number.isSafeInteger(asNumber)) {
|
|
34
|
+
decoded[name] = asNumber;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return decoded;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** `tedious` reports a column's type as its factory function, whose `name` is the type's own. */
|
|
42
|
+
function typeName(type) {
|
|
43
|
+
return typeof type === 'function' ? type.name : undefined;
|
|
44
|
+
}
|
|
@@ -21,8 +21,8 @@ export type PgliteDatabase = {
|
|
|
21
21
|
* Querier for PGlite, Postgres compiled to WASM and run in this process.
|
|
22
22
|
*
|
|
23
23
|
* @remarks Extends {@link AbstractSqlQuerier} rather than `AbstractPgQuerier`, whose `internalStream`
|
|
24
|
-
* hands a `pg-query-stream` object to `query()
|
|
25
|
-
*
|
|
24
|
+
* hands a `pg-query-stream` object to `query()`, which PGlite's client has no equivalent of - so
|
|
25
|
+
* streaming pages the rows in SQL instead. `BEGIN`/`COMMIT` are plain statements on the single
|
|
26
26
|
* connection, leaving transactions to the base class.
|
|
27
27
|
*/
|
|
28
28
|
export declare class PgliteQuerier extends AbstractSqlQuerier {
|
|
@@ -31,6 +31,8 @@ export declare class PgliteQuerier extends AbstractSqlQuerier {
|
|
|
31
31
|
constructor(db: PgliteDatabase, dialect: PgliteDialect, extra?: ExtraOptions | undefined);
|
|
32
32
|
internalAll<T>(query: string, values?: unknown[]): Promise<T[]>;
|
|
33
33
|
internalRun(query: string, values?: unknown[]): Promise<import("../type/query.js").QueryUpdateResult>;
|
|
34
|
+
/** Postgres compiled to WASM is still Postgres: `DECLARE`/`FETCH` streams what the client cannot. */
|
|
35
|
+
protected internalStream<T>(query: string, values?: unknown[]): AsyncGenerator<Awaited<T>, void, any>;
|
|
34
36
|
/** The handle belongs to the pool, which hands out one querier per unit of work over it. */
|
|
35
37
|
internalRelease(): Promise<void>;
|
|
36
38
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { streamViaCursor } from '../postgres/pgCursorStream.js';
|
|
1
2
|
import { AbstractSqlQuerier } from '../querier/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* Querier for PGlite, Postgres compiled to WASM and run in this process.
|
|
4
5
|
*
|
|
5
6
|
* @remarks Extends {@link AbstractSqlQuerier} rather than `AbstractPgQuerier`, whose `internalStream`
|
|
6
|
-
* hands a `pg-query-stream` object to `query()
|
|
7
|
-
*
|
|
7
|
+
* hands a `pg-query-stream` object to `query()`, which PGlite's client has no equivalent of - so
|
|
8
|
+
* streaming pages the rows in SQL instead. `BEGIN`/`COMMIT` are plain statements on the single
|
|
8
9
|
* connection, leaving transactions to the base class.
|
|
9
10
|
*/
|
|
10
11
|
export class PgliteQuerier extends AbstractSqlQuerier {
|
|
@@ -25,6 +26,10 @@ export class PgliteQuerier extends AbstractSqlQuerier {
|
|
|
25
26
|
// where the latter also counts a `SELECT`'s rows and is absent altogether from a DDL tag.
|
|
26
27
|
return this.buildUpdateResult({ rows: res.rows, changes: res.affectedRows ?? 0 });
|
|
27
28
|
}
|
|
29
|
+
/** Postgres compiled to WASM is still Postgres: `DECLARE`/`FETCH` streams what the client cannot. */
|
|
30
|
+
async *internalStream(query, values) {
|
|
31
|
+
yield* streamViaCursor((sql, params) => this.internalAll(sql, params), query, values, this.hasOpenTransaction);
|
|
32
|
+
}
|
|
28
33
|
/** The handle belongs to the pool, which hands out one querier per unit of work over it. */
|
|
29
34
|
async internalRelease() { }
|
|
30
35
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one statement of the cursor protocol. `internalAll` for every caller so far: the cursor holds
|
|
3
|
+
* a connection's session state, so it must be the querier's own connection, and it must not go
|
|
4
|
+
* through `all()`, whose `serialize` is not re-entrant.
|
|
5
|
+
*/
|
|
6
|
+
export type CursorExecutor<T> = (query: string, values?: unknown[]) => Promise<T[]>;
|
|
7
|
+
/**
|
|
8
|
+
* Stream a Postgres-wire result through a server-side cursor, for a driver whose client exposes none:
|
|
9
|
+
* `bun:sql` (no cursor API at all, [oven-sh/bun#17181](https://github.com/oven-sh/bun/issues/17181))
|
|
10
|
+
* and PGlite. `pg` has `pg-query-stream` and keeps using it.
|
|
11
|
+
*
|
|
12
|
+
* `DECLARE` is only legal inside a transaction, so one is opened here when the caller has none - and
|
|
13
|
+
* then committed, or rolled back if the stream failed. That `BEGIN` goes straight to the connection
|
|
14
|
+
* rather than through `beginTransaction`, so the querier's own transaction state stays untouched:
|
|
15
|
+
* this one is the generator's, and ends with it.
|
|
16
|
+
*
|
|
17
|
+
* The cleanup lives in `finally` because a consumer that stops early (`break`, a `throw` downstream)
|
|
18
|
+
* ends the generator there and nowhere else, and an abandoned cursor holds its transaction open.
|
|
19
|
+
*/
|
|
20
|
+
export declare function streamViaCursor<T>(exec: CursorExecutor<T>, query: string, values?: unknown[], inTransaction?: boolean): AsyncIterable<T>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** The cursor a stream declares. Suffixed per call, so two streams on one connection cannot collide. */
|
|
2
|
+
const CURSOR_ALIAS = '_uql_cursor';
|
|
3
|
+
/** Rows per round trip, matching `pg-query-stream`'s own default so both paths read the same. */
|
|
4
|
+
const FETCH_SIZE = 100;
|
|
5
|
+
let cursorSeq = 0;
|
|
6
|
+
/**
|
|
7
|
+
* Stream a Postgres-wire result through a server-side cursor, for a driver whose client exposes none:
|
|
8
|
+
* `bun:sql` (no cursor API at all, [oven-sh/bun#17181](https://github.com/oven-sh/bun/issues/17181))
|
|
9
|
+
* and PGlite. `pg` has `pg-query-stream` and keeps using it.
|
|
10
|
+
*
|
|
11
|
+
* `DECLARE` is only legal inside a transaction, so one is opened here when the caller has none - and
|
|
12
|
+
* then committed, or rolled back if the stream failed. That `BEGIN` goes straight to the connection
|
|
13
|
+
* rather than through `beginTransaction`, so the querier's own transaction state stays untouched:
|
|
14
|
+
* this one is the generator's, and ends with it.
|
|
15
|
+
*
|
|
16
|
+
* The cleanup lives in `finally` because a consumer that stops early (`break`, a `throw` downstream)
|
|
17
|
+
* ends the generator there and nowhere else, and an abandoned cursor holds its transaction open.
|
|
18
|
+
*/
|
|
19
|
+
export async function* streamViaCursor(exec, query, values, inTransaction = false) {
|
|
20
|
+
const cursor = `${CURSOR_ALIAS}_${++cursorSeq}`;
|
|
21
|
+
const ownsTransaction = !inTransaction;
|
|
22
|
+
if (ownsTransaction) {
|
|
23
|
+
await exec('BEGIN');
|
|
24
|
+
}
|
|
25
|
+
let failed = false;
|
|
26
|
+
try {
|
|
27
|
+
await exec(`DECLARE ${cursor} CURSOR FOR ${query}`, values);
|
|
28
|
+
for (;;) {
|
|
29
|
+
const rows = await exec(`FETCH FORWARD ${FETCH_SIZE} FROM ${cursor}`);
|
|
30
|
+
yield* rows;
|
|
31
|
+
if (rows.length < FETCH_SIZE) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
failed = true;
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
// Best-effort once the stream has failed: an error raised here would replace the one that brought
|
|
42
|
+
// us here, which is the one worth reporting, and the rollback discards the cursor either way.
|
|
43
|
+
const end = failed ? (sql) => exec(sql).catch(() => []) : exec;
|
|
44
|
+
await end(`CLOSE ${cursor}`);
|
|
45
|
+
if (ownsTransaction) {
|
|
46
|
+
await end(failed ? 'ROLLBACK' : 'COMMIT');
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -4,7 +4,7 @@ import { PostgresDialect } from './postgresDialect.js';
|
|
|
4
4
|
*
|
|
5
5
|
* @remarks Uses base {@link PostgresDialect} capabilities: native JS arrays for `ANY` / `ALL`
|
|
6
6
|
* (`nativeArrays: true`) and `$n::jsonb` without a text re-cast. Bun SQL Postgres needs
|
|
7
|
-
* `
|
|
7
|
+
* `BunSqlQuerierPool` from `uql-orm/bunSql` instead (wire array literals + text json cast).
|
|
8
8
|
*/
|
|
9
9
|
export declare class PgDialect extends PostgresDialect {
|
|
10
10
|
}
|
|
@@ -4,7 +4,7 @@ import { PostgresDialect } from './postgresDialect.js';
|
|
|
4
4
|
*
|
|
5
5
|
* @remarks Uses base {@link PostgresDialect} capabilities: native JS arrays for `ANY` / `ALL`
|
|
6
6
|
* (`nativeArrays: true`) and `$n::jsonb` without a text re-cast. Bun SQL Postgres needs
|
|
7
|
-
* `
|
|
7
|
+
* `BunSqlQuerierPool` from `uql-orm/bunSql` instead (wire array literals + text json cast).
|
|
8
8
|
*/
|
|
9
9
|
export class PgDialect extends PostgresDialect {
|
|
10
10
|
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* arrays
|
|
4
|
-
* `
|
|
2
|
+
* Driver-shaped parameter handling for **Bun SQL** (and any client like it) on a Postgres-wire
|
|
3
|
+
* dialect: arrays go as string literals (`nativeArrays: false`, {@link PgLikeSqlDialect}'s `toPgArray`
|
|
4
|
+
* path) and a JSON bind is re-cast through text (`explicitJsonCast: true`). Both are measured, on a
|
|
5
|
+
* live server: `bun:sql` binds neither `sql.array(...)` nor a plain JS array through `unsafe()`
|
|
6
|
+
* (verified again on Bun 1.4.2), and without the text re-cast a `$set`/`$push` on a JSONB column
|
|
7
|
+
* silently writes the wrong value or throws - on Postgres and, identically, on CockroachDB, which
|
|
8
|
+
* `bun:sql` reaches through its own Postgres wire implementation.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* implementation (see `bunSql.util.ts#normalizeBunOpts`), so it needs the identical fix: verified
|
|
11
|
-
* directly that without it, `$set`/`$push` on a JSONB column silently produce the wrong value
|
|
12
|
-
* or throw on a live CockroachDB instance.
|
|
10
|
+
* The pair is one constant because it is one driver's shape, and `BunSqlQuerierPool` hands it to
|
|
11
|
+
* `PostgresDialect`/`CockroachDialect` as their `driverCapabilities` rather than subclassing either:
|
|
12
|
+
* Bun changes how a parameter binds, never the SQL. `PgDialect` uses neither, keeping the base
|
|
13
|
+
* {@link PgLikeSqlDialect} defaults, since node-`pg` needs no fix.
|
|
13
14
|
*
|
|
14
15
|
* @remarks Optional import for custom pools. Neon uses its own serverless driver (not `bun:sql`),
|
|
15
16
|
* so `NeonDialect` is a separate, unverified case - do not assume it needs this without testing.
|
|
16
17
|
*/
|
|
17
18
|
export declare const POSTGRES_WIRE_DRIVER_CAPABILITIES: {
|
|
18
19
|
readonly nativeArrays: false;
|
|
20
|
+
readonly explicitJsonCast: true;
|
|
19
21
|
};
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* arrays
|
|
4
|
-
* `
|
|
2
|
+
* Driver-shaped parameter handling for **Bun SQL** (and any client like it) on a Postgres-wire
|
|
3
|
+
* dialect: arrays go as string literals (`nativeArrays: false`, {@link PgLikeSqlDialect}'s `toPgArray`
|
|
4
|
+
* path) and a JSON bind is re-cast through text (`explicitJsonCast: true`). Both are measured, on a
|
|
5
|
+
* live server: `bun:sql` binds neither `sql.array(...)` nor a plain JS array through `unsafe()`
|
|
6
|
+
* (verified again on Bun 1.4.2), and without the text re-cast a `$set`/`$push` on a JSONB column
|
|
7
|
+
* silently writes the wrong value or throws - on Postgres and, identically, on CockroachDB, which
|
|
8
|
+
* `bun:sql` reaches through its own Postgres wire implementation.
|
|
5
9
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* implementation (see `bunSql.util.ts#normalizeBunOpts`), so it needs the identical fix: verified
|
|
11
|
-
* directly that without it, `$set`/`$push` on a JSONB column silently produce the wrong value
|
|
12
|
-
* or throw on a live CockroachDB instance.
|
|
10
|
+
* The pair is one constant because it is one driver's shape, and `BunSqlQuerierPool` hands it to
|
|
11
|
+
* `PostgresDialect`/`CockroachDialect` as their `driverCapabilities` rather than subclassing either:
|
|
12
|
+
* Bun changes how a parameter binds, never the SQL. `PgDialect` uses neither, keeping the base
|
|
13
|
+
* {@link PgLikeSqlDialect} defaults, since node-`pg` needs no fix.
|
|
13
14
|
*
|
|
14
15
|
* @remarks Optional import for custom pools. Neon uses its own serverless driver (not `bun:sql`),
|
|
15
16
|
* so `NeonDialect` is a separate, unverified case - do not assume it needs this without testing.
|
|
16
17
|
*/
|
|
17
18
|
export const POSTGRES_WIRE_DRIVER_CAPABILITIES = {
|
|
18
19
|
nativeArrays: false,
|
|
20
|
+
explicitJsonCast: true,
|
|
19
21
|
};
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
|
|
1
|
+
import type { EntityData, EntityId, ExtraOptions, FieldKey, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryPopulate, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult, QueryUpsertOneResult, QueryUpsertManyResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
|
|
2
2
|
import { LoggerWrapper, type ParentJoin, type ParentPartition } from '../util/index.js';
|
|
3
|
-
/**
|
|
4
|
-
* Base class for all database queriers.
|
|
5
|
-
* It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
|
|
6
|
-
*/
|
|
7
3
|
export declare abstract class AbstractQuerier implements Querier {
|
|
8
4
|
readonly extra?: ExtraOptions | undefined;
|
|
9
5
|
/**
|
|
@@ -129,8 +125,8 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
129
125
|
* but the upsert itself is a fact known before and after, and a row written with no hook at all
|
|
130
126
|
* was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
|
|
131
127
|
*/
|
|
132
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<
|
|
133
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<
|
|
128
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
|
|
129
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
134
130
|
protected abstract internalUpsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
135
131
|
protected abstract internalUpsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
136
132
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -43,6 +43,19 @@ function soleParentColumn(relOpts) {
|
|
|
43
43
|
* Base class for all database queriers.
|
|
44
44
|
* It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
|
|
45
45
|
*/
|
|
46
|
+
/**
|
|
47
|
+
* The ids an upsert reports, payload-aligned so the result zips with the rows that were passed.
|
|
48
|
+
*
|
|
49
|
+
* A composite is named from the payload, as an insert's is: no column holds that key, so no
|
|
50
|
+
* statement reports one. A sole key takes what the statement reported when it spoke for every row,
|
|
51
|
+
* and otherwise falls back to the key the caller supplied - a `firstId` dialect reports nothing for
|
|
52
|
+
* a batch, which is not the same as those rows having no id.
|
|
53
|
+
*/
|
|
54
|
+
function upsertIds(meta, payload, reported) {
|
|
55
|
+
return meta.ids.length === 1 && reported?.length === payload.length
|
|
56
|
+
? reported
|
|
57
|
+
: payload.map((it) => (namesKey(meta, it) ? idOf(meta, it) : undefined));
|
|
58
|
+
}
|
|
46
59
|
export class AbstractQuerier {
|
|
47
60
|
extra;
|
|
48
61
|
/**
|
|
@@ -207,10 +220,17 @@ export class AbstractQuerier {
|
|
|
207
220
|
* was how an `@Id({ onInsert })` or an audit trail silently skipped this path.
|
|
208
221
|
*/
|
|
209
222
|
async upsertOne(entity, conflictPaths, payload) {
|
|
210
|
-
return this.hooked(entity, 'Upsert', [payload], () =>
|
|
223
|
+
return this.hooked(entity, 'Upsert', [payload], async () => {
|
|
224
|
+
const { ids, changes, created } = await this.internalUpsertOne(entity, conflictPaths, payload);
|
|
225
|
+
const [id] = upsertIds(getMeta(entity), [payload], ids);
|
|
226
|
+
return { id, changes, created };
|
|
227
|
+
});
|
|
211
228
|
}
|
|
212
229
|
async upsertMany(entity, conflictPaths, payload) {
|
|
213
|
-
return this.hooked(entity, 'Upsert', payload, () =>
|
|
230
|
+
return this.hooked(entity, 'Upsert', payload, async () => {
|
|
231
|
+
const { ids, changes } = await this.internalUpsertMany(entity, conflictPaths, payload);
|
|
232
|
+
return { ids: upsertIds(getMeta(entity), payload, ids), changes };
|
|
233
|
+
});
|
|
214
234
|
}
|
|
215
235
|
async deleteOneById(entity, id, opts) {
|
|
216
236
|
assertIdValue(entity, id);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AbstractDialect } from '../dialect/index.js';
|
|
2
|
-
import type { EntityData, EntityId, ExtraOptions, FieldKey, PoolRunOptions, Querier, QuerierPool, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected,
|
|
2
|
+
import type { EntityData, EntityId, ExtraOptions, FieldKey, PoolRunOptions, Querier, QuerierPool, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryFindResult, QueryGroupMap, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpsertOneResult, QueryUpsertManyResult, RelationKey, TransactionOptions, Type, UpdatePayload, WrittenId } from '../type/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Base pool: dialect id and behavior come only from the `dialect` instance (see {@link QuerierPool}).
|
|
5
5
|
*/
|
|
@@ -41,8 +41,8 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
|
|
|
41
41
|
insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
42
42
|
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
43
43
|
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
44
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<
|
|
45
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<
|
|
44
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpsertOneResult<E>>;
|
|
45
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
46
46
|
saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<WrittenId<E> | undefined>;
|
|
47
47
|
saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
48
48
|
deleteOneById<E extends object>(entity: Type<E>, id: EntityId<E>, opts?: QueryOptions): Promise<number>;
|