uql-orm 0.51.0 → 0.53.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 +5 -5
- 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/abstractDialect.d.ts +5 -5
- package/dist/dialect/abstractDialect.js +7 -6
- package/dist/dialect/abstractSqlDialect.d.ts +59 -6
- package/dist/dialect/abstractSqlDialect.js +90 -32
- package/dist/dialect/aliases.d.ts +2 -0
- package/dist/dialect/aliases.js +2 -0
- package/dist/dialect/mergeSqlDialect.d.ts +45 -0
- package/dist/dialect/mergeSqlDialect.js +89 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +0 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +3 -3
- package/dist/dialect/pgLikeSqlDialect.js +3 -2
- package/dist/dialect/vectorSqlDialect.js +2 -1
- package/dist/http/handler.js +3 -12
- package/dist/http/query.js +4 -1
- package/dist/migrate/builder/expressions.js +5 -0
- package/dist/migrate/ddl/index.d.ts +8 -0
- package/dist/migrate/ddl/index.js +11 -0
- package/dist/migrate/ddl/mssqlTableDdl.d.ts +23 -0
- package/dist/migrate/ddl/mssqlTableDdl.js +61 -0
- package/dist/migrate/ddl/tableDdl.d.ts +34 -0
- package/dist/migrate/ddl/tableDdl.js +71 -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 +60 -0
- package/dist/migrate/introspection/mssqlIntrospector.js +205 -0
- 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/migrate/schemaGenerator.d.ts +8 -10
- package/dist/migrate/schemaGenerator.js +29 -74
- package/dist/mongo/mongoDialect.js +12 -14
- package/dist/mssql/index.d.ts +3 -0
- package/dist/mssql/index.js +3 -0
- package/dist/mssql/mssqlDialect.d.ts +155 -0
- package/dist/mssql/mssqlDialect.js +341 -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.js +13 -11
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/schema/canonicalType.js +96 -113
- package/dist/sqlite/sqliteDialect.js +3 -2
- package/dist/type/dialect.d.ts +24 -5
- package/dist/type/migratorDialect.d.ts +1 -1
- package/dist/type/migratorDialect.js +1 -0
- package/dist/type/query.d.ts +1 -1
- package/dist/type/queryWhere.d.ts +7 -19
- package/dist/type/vector.d.ts +3 -1
- package/dist/util/dialect.util.d.ts +10 -7
- package/dist/util/dialect.util.js +16 -29
- package/dist/util/object.util.d.ts +2 -0
- package/dist/util/object.util.js +4 -0
- 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,17 @@
|
|
|
1
|
+
import { ConnectionPool, type config as MsSqlConfig } from 'mssql';
|
|
2
|
+
import { AbstractSqlQuerierPool } from '../querier/index.js';
|
|
3
|
+
import type { ExtraOptions } from '../type/index.js';
|
|
4
|
+
import { MsSqlDialect } from './mssqlDialect.js';
|
|
5
|
+
import { MsSqlQuerier } from './mssqlQuerier.js';
|
|
6
|
+
export declare class MsSqlQuerierPool extends AbstractSqlQuerierPool<MsSqlQuerier, MsSqlDialect> {
|
|
7
|
+
#private;
|
|
8
|
+
readonly pool: ConnectionPool;
|
|
9
|
+
constructor(opts: MsSqlConfig, extra?: ExtraOptions);
|
|
10
|
+
/**
|
|
11
|
+
* `mssql` connects the pool as a whole rather than per checkout, so the promise is shared. A
|
|
12
|
+
* failed one is dropped rather than kept: memoized, a single transient failure would be handed to
|
|
13
|
+
* every later caller for the life of the pool.
|
|
14
|
+
*/
|
|
15
|
+
getQuerier(): Promise<MsSqlQuerier>;
|
|
16
|
+
end(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -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,12 +1,13 @@
|
|
|
1
1
|
import { assertSoleId, getMeta, idOf, namesKey, soleIdOf } from '../entity/index.js';
|
|
2
|
-
import { asSelectMap,
|
|
2
|
+
import { asSelectMap, childrenOf, clone, dataKeyed, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, idOnlyQuery, isScalarId, joinedColumns, keyColumns, LoggerWrapper, isBoundedPerParent, parentJoins, queryChildrenOfAll, parseRelationAtKey, parseRelationQueryValue, rowKey, runHooks, someKey, targetKeyColumns, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
3
|
import { enrichError } from './queryError.js';
|
|
4
4
|
import { fillRelationCounts, withIdForCounts } from './relationCount.js';
|
|
5
5
|
/**
|
|
6
6
|
* Rejects a nullish primary key before it reaches a statement. The by-id methods reduce to
|
|
7
|
-
* `{ $where: id }`, and a
|
|
8
|
-
* table. An entity declares its id optional, which puts `undefined` inside
|
|
9
|
-
* HTTP layer reaches these methods with parsed JSON regardless, so the guard
|
|
7
|
+
* `{ $where: { id } }`, and a key compared to `undefined` is *no filter*, so an unchecked one
|
|
8
|
+
* addresses the whole table. An entity declares its id optional, which puts `undefined` inside
|
|
9
|
+
* `IdValue<E>`, and the HTTP layer reaches these methods with parsed JSON regardless, so the guard
|
|
10
|
+
* belongs at runtime.
|
|
10
11
|
*
|
|
11
12
|
* Its callers are all `async` so this surfaces as a rejection on every one of them: a guard that
|
|
12
13
|
* threw synchronously from some and rejected from others would escape a caller's `.catch()`.
|
|
@@ -16,7 +17,7 @@ function assertIdValue(entity, id) {
|
|
|
16
17
|
throw new TypeError(`'${entity.name}' was addressed by id, but the id is ${String(id)}`);
|
|
17
18
|
}
|
|
18
19
|
if (isScalarId(id)) {
|
|
19
|
-
// One value names one column, which `
|
|
20
|
+
// One value names one column, which `whereIds` refuses on a composite.
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
23
|
// Every key, or the `$where` names only some of the columns and addresses each row that agrees on
|
|
@@ -119,7 +120,7 @@ export class AbstractQuerier {
|
|
|
119
120
|
}
|
|
120
121
|
async findOneById(entity, id, q = {}, opts) {
|
|
121
122
|
assertIdValue(entity, id);
|
|
122
|
-
return this.findOne(entity, { ...q, $where:
|
|
123
|
+
return this.findOne(entity, { ...q, $where: { ...q.$where, ...whereIds(getMeta(entity), id) } }, opts);
|
|
123
124
|
}
|
|
124
125
|
async findOne(entityOrQuery, maybeQueryOrOpts, maybeOpts) {
|
|
125
126
|
const [entity, q, opts] = this.resolveEntityQuery(entityOrQuery, maybeQueryOrOpts, maybeOpts);
|
|
@@ -194,21 +195,21 @@ export class AbstractQuerier {
|
|
|
194
195
|
}
|
|
195
196
|
async updateOneById(entity, id, payload, opts) {
|
|
196
197
|
assertIdValue(entity, id);
|
|
197
|
-
return this.updateMany(entity, { $where: id }, payload, opts);
|
|
198
|
+
return this.updateMany(entity, { $where: whereIds(getMeta(entity), id) }, payload, opts);
|
|
198
199
|
}
|
|
199
200
|
async updateMany(entity, q, payload, opts) {
|
|
200
201
|
return this.hooked(entity, 'Update', [payload], () => this.internalUpdateMany(entity, q, payload, opts));
|
|
201
202
|
}
|
|
202
203
|
async restoreOneById(entity, id) {
|
|
203
204
|
assertIdValue(entity, id);
|
|
204
|
-
return this.restoreMany(entity, { $where: id });
|
|
205
|
+
return this.restoreMany(entity, { $where: whereIds(getMeta(entity), id) });
|
|
205
206
|
}
|
|
206
207
|
async restoreMany(entity, q) {
|
|
207
208
|
const meta = getMeta(entity);
|
|
208
209
|
if (!meta.softDelete) {
|
|
209
210
|
throw new TypeError(`'${entity.name}' has not enabled 'softDelete'`);
|
|
210
211
|
}
|
|
211
|
-
const $where =
|
|
212
|
+
const $where = { ...q.$where, [meta.softDelete]: { $ne: null } };
|
|
212
213
|
return this.updateMany(entity, { ...q, $where }, { [meta.softDelete]: null }, {
|
|
213
214
|
filters: { softDelete: false },
|
|
214
215
|
});
|
|
@@ -234,7 +235,7 @@ export class AbstractQuerier {
|
|
|
234
235
|
}
|
|
235
236
|
async deleteOneById(entity, id, opts) {
|
|
236
237
|
assertIdValue(entity, id);
|
|
237
|
-
return this.deleteMany(entity, { $where: id }, opts);
|
|
238
|
+
return this.deleteMany(entity, { $where: whereIds(getMeta(entity), id) }, opts);
|
|
238
239
|
}
|
|
239
240
|
async deleteMany(entityOrQuery, qOrOpts, maybeOpts) {
|
|
240
241
|
const [entity, q, opts] = this.resolveEntityQuery(entityOrQuery, qOrOpts, maybeOpts);
|
|
@@ -248,7 +249,8 @@ export class AbstractQuerier {
|
|
|
248
249
|
let target = q;
|
|
249
250
|
if (doomed) {
|
|
250
251
|
const meta = getMeta(entity);
|
|
251
|
-
|
|
252
|
+
const ids = doomed.map((it) => idOf(meta, it));
|
|
253
|
+
target = { $where: whereIds(meta, ids) };
|
|
252
254
|
}
|
|
253
255
|
await this.emitHook(entity, 'beforeDelete', doomed ?? []);
|
|
254
256
|
const changes = await this.internalDeleteMany(entity, target, opts);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
|
|
2
2
|
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
3
3
|
import { getMeta, idOf, soleIdOf } from '../entity/index.js';
|
|
4
|
-
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, hasKeys, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
|
+
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, insertShapeOf, getRelationRequestSummary, hasKeys, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, whereIds, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
5
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
6
6
|
import { enrichError } from './queryError.js';
|
|
7
7
|
/**
|
|
@@ -418,7 +418,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
418
418
|
if (!ids.length) {
|
|
419
419
|
return 0;
|
|
420
420
|
}
|
|
421
|
-
target = { $where: ids };
|
|
421
|
+
target = { $where: whereIds(getMeta(entity), ids) };
|
|
422
422
|
}
|
|
423
423
|
const ctx = this.dialect.createContext();
|
|
424
424
|
this.dialect.update(ctx, entity, target, payload, opts);
|
|
@@ -507,7 +507,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
507
507
|
// outright by any schema that declares the constraint without `ON DELETE CASCADE`.
|
|
508
508
|
await this.deleteRelations(entity, ids, opts);
|
|
509
509
|
const deleteCtx = this.dialect.createContext();
|
|
510
|
-
this.dialect.delete(deleteCtx, entity, { $where: ids }, opts);
|
|
510
|
+
this.dialect.delete(deleteCtx, entity, { $where: whereIds(meta, ids) }, opts);
|
|
511
511
|
const { changes = 0 } = await this.run(deleteCtx.sql, deleteCtx.values);
|
|
512
512
|
return changes;
|
|
513
513
|
}
|