uql-orm 0.46.0 → 0.47.1
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/bunSql/bunSqlQuerier.d.ts +7 -0
- package/dist/bunSql/bunSqlQuerier.js +7 -0
- package/dist/bunSql/bunSqlQuerierPool.d.ts +1 -0
- package/dist/bunSql/bunSqlQuerierPool.js +6 -1
- package/dist/bunSql/index.d.ts +1 -0
- package/dist/bunSql/index.js +1 -0
- package/dist/dialect/abstractDialect.d.ts +35 -1
- package/dist/dialect/abstractDialect.js +29 -0
- package/dist/dialect/abstractSqlDialect.d.ts +25 -4
- package/dist/dialect/abstractSqlDialect.js +61 -45
- package/dist/dialect/aliases.d.ts +4 -0
- package/dist/dialect/aliases.js +4 -0
- package/dist/dialect/pgLikeSqlDialect.d.ts +14 -0
- package/dist/dialect/pgLikeSqlDialect.js +40 -1
- package/dist/mongo/mongoDialect.d.ts +11 -1
- package/dist/mongo/mongoDialect.js +34 -10
- package/dist/mongo/mongodbQuerier.d.ts +16 -0
- package/dist/mongo/mongodbQuerier.js +50 -1
- package/dist/querier/abstractQuerier.d.ts +18 -1
- package/dist/querier/abstractQuerier.js +57 -19
- package/dist/querier/abstractSqlQuerier.d.ts +14 -0
- package/dist/querier/abstractSqlQuerier.js +17 -0
- package/dist/querier/relationCount.js +15 -9
- package/dist/type/queryWhere.d.ts +11 -1
- package/dist/util/relationQuery.util.d.ts +43 -7
- package/dist/util/relationQuery.util.js +51 -14
- package/dist/util/rowKey.util.d.ts +18 -4
- package/dist/util/rowKey.util.js +29 -5
- package/package.json +1 -1
|
@@ -2,6 +2,13 @@ import type { ReservedSQL, SQL } from 'bun';
|
|
|
2
2
|
import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
3
3
|
import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
|
|
4
4
|
import type { ExtraOptions } from '../type/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* Querier for `bun:sql`, Bun's built-in driver for Postgres, MySQL, MariaDB, CockroachDB and SQLite.
|
|
7
|
+
*
|
|
8
|
+
* @remarks Deliberately does not override `internalStream`, which every other SQL driver here does:
|
|
9
|
+
* Bun's `SQL.Query` is a `Promise` with no cursor or async-iterator API, so `findManyStream` falls back
|
|
10
|
+
* to the base class buffering the whole result, as `PgliteQuerier` does for the same reason.
|
|
11
|
+
*/
|
|
5
12
|
export declare class BunSqlQuerier extends AbstractPoolQuerier<ReservedSQL> {
|
|
6
13
|
readonly sql: SQL;
|
|
7
14
|
readonly extra?: ExtraOptions | undefined;
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { AbstractPoolQuerier } from '../querier/abstractPoolQuerier.js';
|
|
2
2
|
import { getAffectedRows, getInsertId, isReservedConnection, normalizeRows } from './bunSql.util.js';
|
|
3
|
+
/**
|
|
4
|
+
* Querier for `bun:sql`, Bun's built-in driver for Postgres, MySQL, MariaDB, CockroachDB and SQLite.
|
|
5
|
+
*
|
|
6
|
+
* @remarks Deliberately does not override `internalStream`, which every other SQL driver here does:
|
|
7
|
+
* Bun's `SQL.Query` is a `Promise` with no cursor or async-iterator API, so `findManyStream` falls back
|
|
8
|
+
* to the base class buffering the whole result, as `PgliteQuerier` does for the same reason.
|
|
9
|
+
*/
|
|
3
10
|
export class BunSqlQuerier extends AbstractPoolQuerier {
|
|
4
11
|
sql;
|
|
5
12
|
extra;
|
|
@@ -7,6 +7,7 @@ export declare class BunSqlQuerierPool extends AbstractSqlQuerierPool<BunSqlQuer
|
|
|
7
7
|
readonly config: SQL.Options;
|
|
8
8
|
readonly sql: SQL;
|
|
9
9
|
readonly sqlDialectName: SqlDialectName;
|
|
10
|
+
private foreignKeysOn?;
|
|
10
11
|
constructor(config: SQL.Options, extra?: ExtraOptions);
|
|
11
12
|
/**
|
|
12
13
|
* Provides a pg-compatible interface for libraries like connect-pg-simple.
|
|
@@ -19,6 +19,7 @@ export class BunSqlQuerierPool extends AbstractSqlQuerierPool {
|
|
|
19
19
|
config;
|
|
20
20
|
sql;
|
|
21
21
|
sqlDialectName;
|
|
22
|
+
foreignKeysOn;
|
|
22
23
|
constructor(config, extra) {
|
|
23
24
|
const dialectName = inferDialectName(config);
|
|
24
25
|
super(new DialectMap[dialectName](dialectOptionsFrom(extra)), extra);
|
|
@@ -44,8 +45,12 @@ export class BunSqlQuerierPool extends AbstractSqlQuerierPool {
|
|
|
44
45
|
}
|
|
45
46
|
async getQuerier() {
|
|
46
47
|
const connFactory = async () => {
|
|
47
|
-
// Bun's SQLite adapter does not support connection reservation (it's unpooled)
|
|
48
|
+
// Bun's SQLite adapter does not support connection reservation (it's unpooled), and leaves
|
|
49
|
+
// `foreign_keys` off as `bun:sqlite` does, so without the pragma the constraints uql emits in
|
|
50
|
+
// its own DDL are decorative. One connection means one pragma, issued on the first acquisition.
|
|
48
51
|
if (!isPoolableDialect(this.sqlDialectName)) {
|
|
52
|
+
this.foreignKeysOn ??= this.sql.unsafe('PRAGMA foreign_keys = ON');
|
|
53
|
+
await this.foreignKeysOn;
|
|
49
54
|
return this.sql;
|
|
50
55
|
}
|
|
51
56
|
return this.sql.reserve();
|
package/dist/bunSql/index.d.ts
CHANGED
package/dist/bunSql/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DialectFeatures, DialectName, EntityMeta, ExtraOptions, FieldOptions, InsertIdSource, NamingStrategy, QueryOptions, QueryWhere, QueryWhereMap } from '../type/index.js';
|
|
1
|
+
import type { DialectFeatures, DialectName, EntityMeta, ExtraOptions, FieldOptions, InsertIdSource, NamingStrategy, QueryGroupOp, QueryOptions, QueryWhere, QueryWhereArray, QueryWhereMap } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Options for initializing a dialect.
|
|
4
4
|
*/
|
|
@@ -77,4 +77,38 @@ export declare abstract class AbstractDialect {
|
|
|
77
77
|
* filters skipped. Recursion within one scope renders the returned map directly instead.
|
|
78
78
|
*/
|
|
79
79
|
protected scopedWhereMap<E>(meta: EntityMeta<E>, where?: QueryWhere<E>, opts?: QueryOptions): QueryWhereMap<E>;
|
|
80
|
+
/**
|
|
81
|
+
* How each clause-grouping operator renders: which operator joins its clauses, and whether the
|
|
82
|
+
* group is negated afterwards - so `$not` is `NOT (a AND b)` and `$nor` is `NOT (a OR b)`. SQL
|
|
83
|
+
* negates the rendered group; MongoDB spells the same thing with its own `$nor`.
|
|
84
|
+
*
|
|
85
|
+
* Total over {@link QueryGroupOp}, so a fifth operator cannot reach a dialect without both being
|
|
86
|
+
* told how to render it.
|
|
87
|
+
*/
|
|
88
|
+
protected static readonly GROUP_OPS: {
|
|
89
|
+
readonly $and: {
|
|
90
|
+
readonly join: '$and';
|
|
91
|
+
readonly negate: false;
|
|
92
|
+
};
|
|
93
|
+
readonly $or: {
|
|
94
|
+
readonly join: '$or';
|
|
95
|
+
readonly negate: false;
|
|
96
|
+
};
|
|
97
|
+
readonly $not: {
|
|
98
|
+
readonly join: '$and';
|
|
99
|
+
readonly negate: true;
|
|
100
|
+
};
|
|
101
|
+
readonly $nor: {
|
|
102
|
+
readonly join: '$or';
|
|
103
|
+
readonly negate: true;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
/** Whether a `$where` key groups clauses, narrowing it for the renderers that read {@link GROUP_OPS}. */
|
|
107
|
+
protected static isGroupOp(key: string): key is QueryGroupOp;
|
|
108
|
+
/**
|
|
109
|
+
* A group operator's clauses, rejecting what the types do not cover: `/http` casts client JSON
|
|
110
|
+
* straight to `Query`, so a scalar can arrive where an array belongs. Shared so both backends
|
|
111
|
+
* refuse the same payload rather than one throwing and the other failing further in.
|
|
112
|
+
*/
|
|
113
|
+
protected static groupClauses<E>(key: QueryGroupOp, val: QueryWhereArray<E>): QueryWhereArray<E>;
|
|
80
114
|
}
|
|
@@ -88,4 +88,33 @@ export class AbstractDialect {
|
|
|
88
88
|
scopedWhereMap(meta, where = {}, opts) {
|
|
89
89
|
return applyFilters(meta, buildQueryWhereAsMap(meta, where), opts);
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* How each clause-grouping operator renders: which operator joins its clauses, and whether the
|
|
93
|
+
* group is negated afterwards - so `$not` is `NOT (a AND b)` and `$nor` is `NOT (a OR b)`. SQL
|
|
94
|
+
* negates the rendered group; MongoDB spells the same thing with its own `$nor`.
|
|
95
|
+
*
|
|
96
|
+
* Total over {@link QueryGroupOp}, so a fifth operator cannot reach a dialect without both being
|
|
97
|
+
* told how to render it.
|
|
98
|
+
*/
|
|
99
|
+
static GROUP_OPS = {
|
|
100
|
+
$and: { join: '$and', negate: false },
|
|
101
|
+
$or: { join: '$or', negate: false },
|
|
102
|
+
$not: { join: '$and', negate: true },
|
|
103
|
+
$nor: { join: '$or', negate: true },
|
|
104
|
+
};
|
|
105
|
+
/** Whether a `$where` key groups clauses, narrowing it for the renderers that read {@link GROUP_OPS}. */
|
|
106
|
+
static isGroupOp(key) {
|
|
107
|
+
return Object.hasOwn(AbstractDialect.GROUP_OPS, key);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A group operator's clauses, rejecting what the types do not cover: `/http` casts client JSON
|
|
111
|
+
* straight to `Query`, so a scalar can arrive where an array belongs. Shared so both backends
|
|
112
|
+
* refuse the same payload rather than one throwing and the other failing further in.
|
|
113
|
+
*/
|
|
114
|
+
static groupClauses(key, val) {
|
|
115
|
+
if (val !== undefined && !Array.isArray(val)) {
|
|
116
|
+
throw TypeError(`${key} expects an array, got ${val === null ? 'null' : typeof val}`);
|
|
117
|
+
}
|
|
118
|
+
return val ?? [];
|
|
119
|
+
}
|
|
91
120
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryBuildFn, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryFilter, type QueryGroupMap, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorNear, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
|
|
1
|
+
import { type EntityMeta, type FieldKey, type FieldOptions, type IsolationLevel, type JsonColumnType, type JsonUpdateOp, type Query, type QueryAggMap, type QueryAggregate, type QueryBuildFn, type QueryComparisonOptions, type QueryConflictPaths, type QueryContext, type QueryDialect, type QueryExclude, type QueryFilter, type QueryGroupMap, type QueryGroupOp, type QueryHavingMap, type QueryOptions, type QueryPager, QueryRaw, type QueryRawFnOptions, type QuerySearch, type QuerySelectOptions, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorNear, type QueryWhere, type QueryWhereArray, type QueryWhereFieldOperatorMap, type QueryWhereMap, type QueryWhereOptions, type RelationMeta, type SqlDialectName, type SqlQueryDialect, type Type, type UpdatePayload } from '../type/index.js';
|
|
2
|
+
import { type ParentPartition } from '../util/index.js';
|
|
2
3
|
import type { HydrateKind } from './hydrateColumn.js';
|
|
3
4
|
import { type JsonAccessMode } from './jsonSql.js';
|
|
4
5
|
import { type QueryJoins, type QuerySortOptions } from './queryJoins.js';
|
|
@@ -52,6 +53,18 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
52
53
|
*/
|
|
53
54
|
readonly maxBindValues: number;
|
|
54
55
|
getBeginTransactionStatements(isolationLevel?: IsolationLevel): string[];
|
|
56
|
+
/**
|
|
57
|
+
* Every parent's own bounded page in one statement: a subquery per parent, each filtered to that
|
|
58
|
+
* parent alone and carrying its own `ORDER BY`, `LIMIT` and `OFFSET`. Universal, and reads
|
|
59
|
+
* `parents x (skip + limit)` rows where a `ROW_NUMBER` window reads every matching child.
|
|
60
|
+
* [The design](../../../../architecture/populate-limits.md).
|
|
61
|
+
*
|
|
62
|
+
* Each branch is a wrapped derived table rather than a bare parenthesised select: SQLite rejects
|
|
63
|
+
* `ORDER BY`/`LIMIT` on the latter, and the wrapper costs nothing elsewhere.
|
|
64
|
+
*/
|
|
65
|
+
findPerParent<E extends object>(ctx: QueryContext, entity: Type<E>, q: Query<E>, partition: ParentPartition): void;
|
|
66
|
+
/** The shape {@link findPerParent} emits, which the Postgres family replaces with a `LATERAL` join. */
|
|
67
|
+
protected appendPerParent<E extends object>(ctx: QueryContext, entity: Type<E>, q: Query<E>, { joins, parents }: ParentPartition): void;
|
|
55
68
|
createContext(): QueryContext;
|
|
56
69
|
/**
|
|
57
70
|
* Builds SQL text in isolation via `build`, so the caller can embed it inline (e.g.
|
|
@@ -63,6 +76,15 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
63
76
|
* shared for the same reason - see {@link SqlQueryContext}.
|
|
64
77
|
*/
|
|
65
78
|
protected buildFragment(ctx: QueryContext, build: QueryBuildFn): string;
|
|
79
|
+
/**
|
|
80
|
+
* Each operand rendered into its own fragment, keeping only those that emitted SQL.
|
|
81
|
+
*
|
|
82
|
+
* Nothing reaches `ctx` until every one has rendered, because an operand that emits nothing - an
|
|
83
|
+
* empty `$and`, an `{}` entry - must leave behind neither a dangling separator nor a clause with no
|
|
84
|
+
* condition after it. How many terms really emit is also what decides the parentheses, which is why
|
|
85
|
+
* the caller counts what comes back rather than what it passed in.
|
|
86
|
+
*/
|
|
87
|
+
protected renderOperands<T>(ctx: QueryContext, operands: readonly T[], render: (ctx: QueryContext, operand: T) => void): string[];
|
|
66
88
|
addValue(values: unknown[], value: unknown): string;
|
|
67
89
|
/**
|
|
68
90
|
* Normalizes a parameter value for the database driver.
|
|
@@ -121,13 +143,12 @@ export declare abstract class AbstractSqlDialect extends VectorSqlDialect implem
|
|
|
121
143
|
protected selectRelationFields(ctx: QueryContext, joins: QueryJoins): void;
|
|
122
144
|
protected selectRelationJoins<E>(ctx: QueryContext, meta: EntityMeta<E>, rootAlias: string, joins: QueryJoins): void;
|
|
123
145
|
where<E>(ctx: QueryContext, entity: Type<E>, where?: QueryWhere<E>, opts?: QueryWhereOptions): void;
|
|
124
|
-
/** Renders a `$where` tree without applying entity filters (used for same-scope
|
|
146
|
+
/** Renders a `$where` tree without applying entity filters (used for same-scope group-operator recursion). */
|
|
125
147
|
protected renderWhere<E>(ctx: QueryContext, entity: Type<E>, where?: QueryWhere<E>, opts?: QueryWhereOptions): void;
|
|
126
148
|
compare<E>(ctx: QueryContext, entity: Type<E>, key: string, val: unknown, opts?: QueryComparisonOptions): void;
|
|
127
|
-
protected compareLogicalOperator<E>(ctx: QueryContext, entity: Type<E>, key:
|
|
149
|
+
protected compareLogicalOperator<E>(ctx: QueryContext, entity: Type<E>, key: QueryGroupOp, val: QueryWhereArray<E>, opts: QueryComparisonOptions): void;
|
|
128
150
|
/** Memoizes {@link escapedColumnName}; see there for why it is per dialect instance. */
|
|
129
151
|
private readonly escapedColumns;
|
|
130
|
-
private static readonly NEGATE_OP_MAP;
|
|
131
152
|
private static readonly COMPARE_OP_MAP;
|
|
132
153
|
/** What a `$near` says about the search itself; everything else in it is a bound. */
|
|
133
154
|
private static readonly VECTOR_QUERY_KEYS;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getMeta, soleIdOf } from '../entity/index.js';
|
|
2
2
|
import { parseQueryLock, QueryRaw, RAW_ALIAS, RAW_VALUE, VECTOR_QUERY_KEYS, } from '../type/index.js';
|
|
3
3
|
import { computedExpression, isInlinedExpression } from '../util/field.util.js';
|
|
4
|
-
import { asSelectMap, assertNonNegativeInteger, buildQueryWhereAsMap, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, columnFamily, isJsonUpdateOp, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, targetKeyColumns, parseGroupMap, parseRelationSize, parseSortByCount, populatesRelations, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
|
+
import { asSelectMap, assertNonNegativeInteger, buildQueryWhereAsMap, escapeSqlId, fillOnFields, filterFieldKeys, getInsertFieldKeys, getKeys, getSoftDeleteValue, hasKeys, columnFamily, isJsonUpdateOp, isOperatorMap, isOperatorObject, isOperatorOnlyObject, isVectorSearch, normalizeScalarFieldSelection, parentJoins, targetKeyColumns, parseGroupMap, parseRelationSize, parseSortByCount, populatesRelations, queryChildrenOf, raw, someValue, throwUnknownAggregateColumn, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
5
|
import { escapeAnsiSqlLiteral, escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
6
|
-
import { COUNT_ALIAS, DISTINCT_DERIVED_ALIAS, JSON_ELEM_ALIAS_PREFIX } from './aliases.js';
|
|
6
|
+
import { COUNT_ALIAS, DISTINCT_DERIVED_ALIAS, JSON_ELEM_ALIAS_PREFIX, PER_PARENT_BRANCH_ALIAS } from './aliases.js';
|
|
7
7
|
import { buildElemMatchConditions } from './jsonArrayElemMatchUtils.js';
|
|
8
8
|
import { isJsonbOp, jsonCompareMode, jsonElemExists } from './jsonSql.js';
|
|
9
9
|
import { SqlQueryContext } from './queryContext.js';
|
|
@@ -55,6 +55,34 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
55
55
|
// 'set-before' - MySQL/MariaDB pattern
|
|
56
56
|
return [`SET TRANSACTION ISOLATION LEVEL ${level}`, this.beginTransactionCommand];
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Every parent's own bounded page in one statement: a subquery per parent, each filtered to that
|
|
60
|
+
* parent alone and carrying its own `ORDER BY`, `LIMIT` and `OFFSET`. Universal, and reads
|
|
61
|
+
* `parents x (skip + limit)` rows where a `ROW_NUMBER` window reads every matching child.
|
|
62
|
+
* [The design](../../../../architecture/populate-limits.md).
|
|
63
|
+
*
|
|
64
|
+
* Each branch is a wrapped derived table rather than a bare parenthesised select: SQLite rejects
|
|
65
|
+
* `ORDER BY`/`LIMIT` on the latter, and the wrapper costs nothing elsewhere.
|
|
66
|
+
*/
|
|
67
|
+
findPerParent(ctx, entity, q, partition) {
|
|
68
|
+
if (!partition.parents.length) {
|
|
69
|
+
// Guarded on the contract rather than in either shape: this is the end that would otherwise
|
|
70
|
+
// append nothing and hand the driver an empty statement, and both shapes owe the same promise.
|
|
71
|
+
throw new TypeError('cannot read a bounded relation for no parents at all');
|
|
72
|
+
}
|
|
73
|
+
this.appendPerParent(ctx, entity, q, partition);
|
|
74
|
+
}
|
|
75
|
+
/** The shape {@link findPerParent} emits, which the Postgres family replaces with a `LATERAL` join. */
|
|
76
|
+
appendPerParent(ctx, entity, q, { joins, parents }) {
|
|
77
|
+
parents.forEach((parent, index) => {
|
|
78
|
+
if (index) {
|
|
79
|
+
ctx.append(' UNION ALL ');
|
|
80
|
+
}
|
|
81
|
+
ctx.append('SELECT * FROM (');
|
|
82
|
+
this.find(ctx, entity, queryChildrenOf(q, joins, parent));
|
|
83
|
+
ctx.append(`) ${this.escapeId(ctx.nextAlias(PER_PARENT_BRANCH_ALIAS))}`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
58
86
|
createContext() {
|
|
59
87
|
return new SqlQueryContext(this);
|
|
60
88
|
}
|
|
@@ -72,6 +100,19 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
72
100
|
build(fragmentCtx);
|
|
73
101
|
return fragmentCtx.sql;
|
|
74
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Each operand rendered into its own fragment, keeping only those that emitted SQL.
|
|
105
|
+
*
|
|
106
|
+
* Nothing reaches `ctx` until every one has rendered, because an operand that emits nothing - an
|
|
107
|
+
* empty `$and`, an `{}` entry - must leave behind neither a dangling separator nor a clause with no
|
|
108
|
+
* condition after it. How many terms really emit is also what decides the parentheses, which is why
|
|
109
|
+
* the caller counts what comes back rather than what it passed in.
|
|
110
|
+
*/
|
|
111
|
+
renderOperands(ctx, operands, render) {
|
|
112
|
+
return operands
|
|
113
|
+
.map((operand) => this.buildFragment(ctx, (fragmentCtx) => render(fragmentCtx, operand)))
|
|
114
|
+
.filter((part) => part !== '');
|
|
115
|
+
}
|
|
75
116
|
addValue(values, value) {
|
|
76
117
|
values.push(this.normalizeValue(value));
|
|
77
118
|
return this.placeholder(values.length);
|
|
@@ -278,40 +319,29 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
278
319
|
// Filters are applied once, here at the scope entry point; recursion uses `renderWhere`.
|
|
279
320
|
this.renderWhere(ctx, entity, this.scopedWhereMap(meta, where, opts), opts);
|
|
280
321
|
}
|
|
281
|
-
/** Renders a `$where` tree without applying entity filters (used for same-scope
|
|
322
|
+
/** Renders a `$where` tree without applying entity filters (used for same-scope group-operator recursion). */
|
|
282
323
|
renderWhere(ctx, entity, where = {}, opts = {}) {
|
|
283
324
|
const meta = getMeta(entity);
|
|
284
325
|
const { clause = 'WHERE' } = opts;
|
|
285
|
-
|
|
326
|
+
const whereMap = buildQueryWhereAsMap(meta, where);
|
|
286
327
|
// An `undefined` value emits nothing, so it must not count towards the terms either: it decides
|
|
287
|
-
//
|
|
288
|
-
const whereKeys = getKeys(
|
|
289
|
-
|
|
328
|
+
// whether the keys below render as operands of an `AND`.
|
|
329
|
+
const whereKeys = getKeys(whereMap).filter((key) => whereMap[key] !== undefined);
|
|
330
|
+
// Each key is an operand of the `AND` joining them; a lone key emits this fragment verbatim, so
|
|
331
|
+
// it inherits this one's position instead.
|
|
332
|
+
const childOperand = whereKeys.length > 1 || opts.operand || clause === 'AND';
|
|
333
|
+
const childOpts = opts.operand === childOperand ? opts : { ...opts, operand: childOperand };
|
|
334
|
+
const parts = this.renderOperands(ctx, whereKeys, (fragmentCtx, key) => this.compare(fragmentCtx, entity, key, whereMap[key], childOpts));
|
|
335
|
+
if (!parts.length) {
|
|
290
336
|
return;
|
|
291
337
|
}
|
|
292
338
|
if (clause) {
|
|
293
339
|
ctx.append(` ${clause} `);
|
|
294
340
|
}
|
|
295
|
-
const multipleKeys = whereKeys.length > 1;
|
|
296
341
|
// This fragment joins its own keys with `AND`, so appending it after one (a JOIN's `ON`) needs no
|
|
297
342
|
// parentheses - but anything nested in it is still an operand, since that may be an `OR`.
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
ctx.append('(');
|
|
301
|
-
}
|
|
302
|
-
// Each key is an operand of the `AND` joining them; a lone key emits this fragment verbatim, so
|
|
303
|
-
// it inherits this one's position instead.
|
|
304
|
-
const childOperand = multipleKeys || opts.operand || clause === 'AND';
|
|
305
|
-
const childOpts = opts.operand === childOperand ? opts : { ...opts, operand: childOperand };
|
|
306
|
-
whereKeys.forEach((key, index) => {
|
|
307
|
-
if (index > 0) {
|
|
308
|
-
ctx.append(' AND ');
|
|
309
|
-
}
|
|
310
|
-
this.compare(ctx, entity, key, where[key], childOpts);
|
|
311
|
-
});
|
|
312
|
-
if (parenthesize) {
|
|
313
|
-
ctx.append(')');
|
|
314
|
-
}
|
|
343
|
+
const body = parts.join(' AND ');
|
|
344
|
+
ctx.append(parts.length > 1 && opts.operand ? `(${body})` : body);
|
|
315
345
|
}
|
|
316
346
|
compare(ctx, entity, key, val, opts = {}) {
|
|
317
347
|
const meta = getMeta(entity);
|
|
@@ -338,7 +368,7 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
338
368
|
this.appendTextSearch(ctx, entity, meta, val);
|
|
339
369
|
return;
|
|
340
370
|
}
|
|
341
|
-
if (key
|
|
371
|
+
if (AbstractSqlDialect.isGroupOp(key)) {
|
|
342
372
|
this.compareLogicalOperator(ctx, entity, key, val, opts);
|
|
343
373
|
return;
|
|
344
374
|
}
|
|
@@ -377,43 +407,29 @@ export class AbstractSqlDialect extends VectorSqlDialect {
|
|
|
377
407
|
}
|
|
378
408
|
}
|
|
379
409
|
compareLogicalOperator(ctx, entity, key, val, opts) {
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
if (val !== undefined && !Array.isArray(val)) {
|
|
383
|
-
// Not covered by the types: `/http` casts client JSON straight to `Query`, so this arrives untyped.
|
|
384
|
-
throw TypeError(`${key} expects an array, got ${val === null ? 'null' : typeof val}`);
|
|
385
|
-
}
|
|
386
|
-
const items = val ?? [];
|
|
410
|
+
const { join, negate } = AbstractSqlDialect.GROUP_OPS[key];
|
|
411
|
+
const items = AbstractSqlDialect.groupClauses(key, val);
|
|
387
412
|
// With more than one item each is an operand of the operator joining them, so a compound item
|
|
388
413
|
// parenthesizes itself and precedence never applies; a lone item is this group verbatim, so it
|
|
389
414
|
// inherits the group's own position. A negation always makes its subject an operand.
|
|
390
415
|
const childOperand = items.length > 1 || negate || opts.operand;
|
|
391
|
-
|
|
392
|
-
// `undefined` entry) must leave no dangling separator behind, and how many terms this fragment
|
|
393
|
-
// really emits is what decides whether it needs parentheses.
|
|
394
|
-
const parts = items
|
|
395
|
-
.map((entry) => this.buildFragment(ctx, (fragmentCtx) => {
|
|
416
|
+
const parts = this.renderOperands(ctx, items, (fragmentCtx, entry) => {
|
|
396
417
|
if (entry instanceof QueryRaw) {
|
|
397
418
|
this.getRawValue(fragmentCtx, { value: entry });
|
|
398
419
|
}
|
|
399
420
|
else if (entry) {
|
|
400
421
|
this.renderWhere(fragmentCtx, entity, entry, { prefix: opts.prefix, operand: childOperand, clause: false });
|
|
401
422
|
}
|
|
402
|
-
})
|
|
403
|
-
.filter((part) => part !== '');
|
|
423
|
+
});
|
|
404
424
|
if (!parts.length) {
|
|
405
425
|
return;
|
|
406
426
|
}
|
|
407
|
-
const body = parts.join(
|
|
427
|
+
const body = parts.join(join === '$or' ? ' OR ' : ' AND ');
|
|
408
428
|
const parenthesize = parts.length > 1 && (opts.operand || negate);
|
|
409
429
|
ctx.append((negate ? 'NOT ' : '') + (parenthesize ? `(${body})` : body));
|
|
410
430
|
}
|
|
411
431
|
/** Memoizes {@link escapedColumnName}; see there for why it is per dialect instance. */
|
|
412
432
|
escapedColumns = new WeakMap();
|
|
413
|
-
static NEGATE_OP_MAP = new Map([
|
|
414
|
-
['$not', '$and'],
|
|
415
|
-
['$nor', '$or'],
|
|
416
|
-
]);
|
|
417
433
|
static COMPARE_OP_MAP = new Map([
|
|
418
434
|
['$gt', ' > '],
|
|
419
435
|
['$gte', ' >= '],
|
|
@@ -14,6 +14,10 @@ export declare const COUNT_ALIAS = "_uql_count";
|
|
|
14
14
|
export declare const TOTAL_ALIAS = "_uql_total";
|
|
15
15
|
/** The derived table a `$distinct` count wraps its deduplicated set in. MySQL requires the alias. */
|
|
16
16
|
export declare const DISTINCT_DERIVED_ALIAS = "_uql_distinct";
|
|
17
|
+
/** Prefix for the derived table each branch of a per-parent bounded read is wrapped in. */
|
|
18
|
+
export declare const PER_PARENT_BRANCH_ALIAS = "_uql_p";
|
|
19
|
+
/** The row source a `LATERAL` per-parent read correlates each of its branches against. */
|
|
20
|
+
export declare const PER_PARENT_KEYS_ALIAS = "_uql_keys";
|
|
17
21
|
/** Prefix for the alias an exploded JSON array element is read through. */
|
|
18
22
|
export declare const JSON_ELEM_ALIAS_PREFIX = "_uql_elem";
|
|
19
23
|
/** The alias a `$pull` reads its surviving elements through, kept distinct from {@link JSON_ELEM_ALIAS_PREFIX}. */
|
package/dist/dialect/aliases.js
CHANGED
|
@@ -14,6 +14,10 @@ export const COUNT_ALIAS = '_uql_count';
|
|
|
14
14
|
export const TOTAL_ALIAS = '_uql_total';
|
|
15
15
|
/** The derived table a `$distinct` count wraps its deduplicated set in. MySQL requires the alias. */
|
|
16
16
|
export const DISTINCT_DERIVED_ALIAS = '_uql_distinct';
|
|
17
|
+
/** Prefix for the derived table each branch of a per-parent bounded read is wrapped in. */
|
|
18
|
+
export const PER_PARENT_BRANCH_ALIAS = '_uql_p';
|
|
19
|
+
/** The row source a `LATERAL` per-parent read correlates each of its branches against. */
|
|
20
|
+
export const PER_PARENT_KEYS_ALIAS = '_uql_keys';
|
|
17
21
|
/** Prefix for the alias an exploded JSON array element is read through. */
|
|
18
22
|
export const JSON_ELEM_ALIAS_PREFIX = '_uql_elem';
|
|
19
23
|
/** The alias a `$pull` reads its surviving elements through, kept distinct from {@link JSON_ELEM_ALIAS_PREFIX}. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DialectFeatures, type EntityMeta, type FieldOptions, type JsonColumnType, type Query, type QueryContext, type QuerySizeComparisonOps, type QueryTextSearchOptions, type Type, type VectorDistance, type VectorOperatorMetric } from '../type/index.js';
|
|
2
|
+
import { type ParentPartition } from '../util/relationQuery.util.js';
|
|
2
3
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
3
4
|
/**
|
|
4
5
|
* Shared AST/quoting/JSONB/full-text-search/vector-search implementation between Postgres and
|
|
@@ -21,6 +22,19 @@ export declare abstract class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
21
22
|
readonly commitTransactionCommand = "COMMIT";
|
|
22
23
|
readonly rollbackTransactionCommand = "ROLLBACK";
|
|
23
24
|
readonly alterColumnStrategy = "separate-clauses";
|
|
25
|
+
/**
|
|
26
|
+
* One `LATERAL` branch correlated against an array of the parent keys, in place of the base
|
|
27
|
+
* `UNION ALL` of a subquery per parent. Same rows and the same `parents x (skip + limit)` read, but
|
|
28
|
+
* the planner sees one correlated index loop rather than N branches to plan: flat in page size where
|
|
29
|
+
* `UNION ALL` is linear, and the statement's text stops changing with the number of parents, so one
|
|
30
|
+
* prepared statement serves every page.
|
|
31
|
+
*
|
|
32
|
+
* Postgres, CockroachDB, PGlite, Neon and bun-sql inherit it together. **MySQL has `LATERAL` and must
|
|
33
|
+
* not use it** - it does not plan this as a correlated index loop and measured slower than both its
|
|
34
|
+
* own `UNION ALL` and a query per parent, which is why this is an override rather than a capability
|
|
35
|
+
* flag. [The design](../../../../architecture/populate-limits.md).
|
|
36
|
+
*/
|
|
37
|
+
protected appendPerParent<E extends object>(ctx: QueryContext, entity: Type<E>, q: Query<E>, { joins, parents, parentFields }: ParentPartition): void;
|
|
24
38
|
/** `$N` placeholders carry their own index, so the upsert's assignments need no scratch context. */
|
|
25
39
|
protected readonly upsertUpdateBindsInPlace = true;
|
|
26
40
|
readonly insertIdSource = "returning";
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { canonicalToSql, fieldOptionsToCanonical } from '../schema/canonicalType.js';
|
|
1
2
|
import { QueryRaw, } from '../type/index.js';
|
|
2
3
|
import { hasVectorNear } from '../util/dialect.util.js';
|
|
4
|
+
import { raw } from '../util/raw.js';
|
|
5
|
+
import { queryNarrowedTo } from '../util/relationQuery.util.js';
|
|
3
6
|
import { escapeSingleQuotes } from '../util/sqlLiteral.js';
|
|
4
7
|
import { AbstractSqlDialect } from './abstractSqlDialect.js';
|
|
5
|
-
import { JSON_PULL_ALIAS } from './aliases.js';
|
|
8
|
+
import { JSON_PULL_ALIAS, PER_PARENT_BRANCH_ALIAS, PER_PARENT_KEYS_ALIAS } from './aliases.js';
|
|
6
9
|
import { jsonSetTarget } from './jsonSql.js';
|
|
7
10
|
import { resolveVectorCast, toSparsevecLiteral } from './vectorCast.js';
|
|
8
11
|
/**
|
|
@@ -47,6 +50,42 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
47
50
|
commitTransactionCommand = 'COMMIT';
|
|
48
51
|
rollbackTransactionCommand = 'ROLLBACK';
|
|
49
52
|
alterColumnStrategy = 'separate-clauses';
|
|
53
|
+
/**
|
|
54
|
+
* One `LATERAL` branch correlated against an array of the parent keys, in place of the base
|
|
55
|
+
* `UNION ALL` of a subquery per parent. Same rows and the same `parents x (skip + limit)` read, but
|
|
56
|
+
* the planner sees one correlated index loop rather than N branches to plan: flat in page size where
|
|
57
|
+
* `UNION ALL` is linear, and the statement's text stops changing with the number of parents, so one
|
|
58
|
+
* prepared statement serves every page.
|
|
59
|
+
*
|
|
60
|
+
* Postgres, CockroachDB, PGlite, Neon and bun-sql inherit it together. **MySQL has `LATERAL` and must
|
|
61
|
+
* not use it** - it does not plan this as a correlated index loop and measured slower than both its
|
|
62
|
+
* own `UNION ALL` and a query per parent, which is why this is an override rather than a capability
|
|
63
|
+
* flag. [The design](../../../../architecture/populate-limits.md).
|
|
64
|
+
*/
|
|
65
|
+
appendPerParent(ctx, entity, q, { joins, parents, parentFields }) {
|
|
66
|
+
const keys = this.escapeId(ctx.nextAlias(PER_PARENT_KEYS_ALIAS));
|
|
67
|
+
const branch = this.escapeId(ctx.nextAlias(PER_PARENT_BRANCH_ALIAS));
|
|
68
|
+
const column = (index) => `${keys}.k${index}`;
|
|
69
|
+
// `unnest` resolves an uncast parameter to `unknown` and refuses it ("function unnest(unknown) is
|
|
70
|
+
// not unique"), so the array says its type. It comes from the parent's key column, which always
|
|
71
|
+
// declares one, rather than the child's foreign key, which would have to be resolved through the
|
|
72
|
+
// reference it takes its own type from.
|
|
73
|
+
const sources = joins.map(({ parent }) => {
|
|
74
|
+
const field = parentFields[parent];
|
|
75
|
+
if (!field) {
|
|
76
|
+
throw new TypeError(`cannot page a relation per parent: '${parent}' is not a field of the parent entity`);
|
|
77
|
+
}
|
|
78
|
+
const values = parents.map((it) => it[parent]);
|
|
79
|
+
return `${this.addValue(ctx.values, values)}::${canonicalToSql(fieldOptionsToCanonical(field), this)}[]`;
|
|
80
|
+
});
|
|
81
|
+
const rowSource = `unnest(${sources.join(', ')}) AS ${keys}(${joins.map((_, index) => `k${index}`).join(', ')})`;
|
|
82
|
+
// The keys come from the row source rather than as values, which is the whole point of correlating:
|
|
83
|
+
// one branch, planned once, instead of one per parent.
|
|
84
|
+
const correlated = Object.fromEntries(joins.map(({ joined }, index) => [joined, raw(({ ctx: inner }) => inner.append(column(index)))]));
|
|
85
|
+
ctx.append(`SELECT ${branch}.* FROM ${rowSource} JOIN LATERAL (`);
|
|
86
|
+
this.find(ctx, entity, queryNarrowedTo(q, correlated));
|
|
87
|
+
ctx.append(`) ${branch} ON TRUE`);
|
|
88
|
+
}
|
|
50
89
|
/** `$N` placeholders carry their own index, so the upsert's assignments need no scratch context. */
|
|
51
90
|
upsertUpdateBindsInPlace = true;
|
|
52
91
|
insertIdSource = 'returning';
|
|
@@ -43,11 +43,21 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
43
43
|
/** Whether a `$where` constrains any relation, and so needs the aggregation path rather than a cursor. */
|
|
44
44
|
constrainsRelations<E extends Document>(entity: Type<E>, where: QueryWhere<E> | undefined): boolean;
|
|
45
45
|
/**
|
|
46
|
-
* Renders a `$where` tree without applying entity filters (used for same-scope
|
|
46
|
+
* Renders a `$where` tree without applying entity filters (used for same-scope group-operator
|
|
47
47
|
* recursion). Relation keys need `$lookup` stages, so they are only accepted when `lookups` is
|
|
48
48
|
* given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
49
49
|
*/
|
|
50
50
|
private renderFilter;
|
|
51
|
+
/**
|
|
52
|
+
* Renders `$and`/`$or`/`$not`/`$nor` into `filter`. MongoDB has no root-level `$not`, so both
|
|
53
|
+
* negating operators become its `$nor`, which is exactly `NOT (a OR b)` - and by De Morgan that
|
|
54
|
+
* makes a `$nor` list its clauses directly while a `$not` wraps them in one `$and` first.
|
|
55
|
+
*
|
|
56
|
+
* Clauses that render to nothing are dropped and an empty operator emits no key at all: MongoDB
|
|
57
|
+
* rejects an empty `$and`/`$or`/`$nor` outright, where the SQL dialects contribute no term.
|
|
58
|
+
* Negations accumulate into the one `$nor`, since `NOT a AND NOT b` is `$nor: [a, b]`.
|
|
59
|
+
*/
|
|
60
|
+
private appendLogicalOperator;
|
|
51
61
|
/**
|
|
52
62
|
* Emits the correlated `$lookup` for one relation condition and returns the condition that tests its
|
|
53
63
|
* result: presence of a row for a plain relation filter, a comparison against the row count for
|
|
@@ -83,12 +83,12 @@ export class MongoDialect extends AbstractDialect {
|
|
|
83
83
|
}
|
|
84
84
|
const meta = getMeta(entity);
|
|
85
85
|
const whereMap = buildQueryWhereAsMap(meta, where);
|
|
86
|
-
return someKey(whereMap, (key) => key
|
|
87
|
-
? whereMap[key].some((it) => this.constrainsRelations(entity, it))
|
|
86
|
+
return someKey(whereMap, (key) => MongoDialect.isGroupOp(key)
|
|
87
|
+
? (whereMap[key] ?? []).some((it) => this.constrainsRelations(entity, it))
|
|
88
88
|
: Boolean(meta.relations[key]));
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
|
-
* Renders a `$where` tree without applying entity filters (used for same-scope
|
|
91
|
+
* Renders a `$where` tree without applying entity filters (used for same-scope group-operator
|
|
92
92
|
* recursion). Relation keys need `$lookup` stages, so they are only accepted when `lookups` is
|
|
93
93
|
* given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
94
94
|
*/
|
|
@@ -99,13 +99,8 @@ export class MongoDialect extends AbstractDialect {
|
|
|
99
99
|
for (const [rawKey, rawVal] of Object.entries(whereMap)) {
|
|
100
100
|
let key = rawKey;
|
|
101
101
|
let val = rawVal;
|
|
102
|
-
if (key
|
|
103
|
-
filter
|
|
104
|
-
// A `QueryRaw` here would recurse forever: `buildQueryWhereAsMap` re-wraps it as
|
|
105
|
-
// `{ $and: [raw] }`, which lands back on this branch.
|
|
106
|
-
this.assertNoRaw(filterIt);
|
|
107
|
-
return this.renderFilter(entity, filterIt, opts, lookups);
|
|
108
|
-
});
|
|
102
|
+
if (MongoDialect.isGroupOp(key)) {
|
|
103
|
+
this.appendLogicalOperator(filter, entity, key, val, opts, lookups);
|
|
109
104
|
}
|
|
110
105
|
else if (key === '$text') {
|
|
111
106
|
// MongoDB's text index declares which fields it covers, so `$fields` cannot narrow the search
|
|
@@ -137,6 +132,35 @@ export class MongoDialect extends AbstractDialect {
|
|
|
137
132
|
}
|
|
138
133
|
return filter;
|
|
139
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Renders `$and`/`$or`/`$not`/`$nor` into `filter`. MongoDB has no root-level `$not`, so both
|
|
137
|
+
* negating operators become its `$nor`, which is exactly `NOT (a OR b)` - and by De Morgan that
|
|
138
|
+
* makes a `$nor` list its clauses directly while a `$not` wraps them in one `$and` first.
|
|
139
|
+
*
|
|
140
|
+
* Clauses that render to nothing are dropped and an empty operator emits no key at all: MongoDB
|
|
141
|
+
* rejects an empty `$and`/`$or`/`$nor` outright, where the SQL dialects contribute no term.
|
|
142
|
+
* Negations accumulate into the one `$nor`, since `NOT a AND NOT b` is `$nor: [a, b]`.
|
|
143
|
+
*/
|
|
144
|
+
appendLogicalOperator(filter, entity, key, val, opts, lookups) {
|
|
145
|
+
const { join, negate } = MongoDialect.GROUP_OPS[key];
|
|
146
|
+
const parts = MongoDialect.groupClauses(key, val)
|
|
147
|
+
.map((filterIt) => {
|
|
148
|
+
// A `QueryRaw` here would recurse forever: `buildQueryWhereAsMap` re-wraps it as
|
|
149
|
+
// `{ $and: [raw] }`, which lands back on this branch.
|
|
150
|
+
this.assertNoRaw(filterIt);
|
|
151
|
+
return this.renderFilter(entity, filterIt, opts, lookups);
|
|
152
|
+
})
|
|
153
|
+
.filter((part) => Object.keys(part).length > 0);
|
|
154
|
+
if (!parts.length) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (!negate) {
|
|
158
|
+
filter[key] = parts;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const negated = join === '$and' && parts.length > 1 ? [{ $and: parts }] : parts;
|
|
162
|
+
filter['$nor'] = [...(filter['$nor'] ?? []), ...negated];
|
|
163
|
+
}
|
|
140
164
|
/**
|
|
141
165
|
* Emits the correlated `$lookup` for one relation condition and returns the condition that tests its
|
|
142
166
|
* result: presence of a row for a plain relation filter, a comparison against the row count for
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Document, MongoClient } from 'mongodb';
|
|
2
2
|
import { AbstractQuerier } from '../querier/index.js';
|
|
3
3
|
import type { EntityData, ExtraOptions, IdValue, PrimaryKey, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryGroupMap, QueryOptions, QuerySearch, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
4
|
+
import { type ParentPartition } from '../util/index.js';
|
|
4
5
|
import type { MongoDialect } from './mongoDialect.js';
|
|
5
6
|
export declare class MongodbQuerier extends AbstractQuerier {
|
|
6
7
|
readonly dialect: MongoDialect;
|
|
@@ -10,6 +11,21 @@ export declare class MongodbQuerier extends AbstractQuerier {
|
|
|
10
11
|
constructor(dialect: MongoDialect, conn: MongoClient, extra?: ExtraOptions | undefined);
|
|
11
12
|
private execute;
|
|
12
13
|
protected internalFindMany<E extends Document>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<E[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Every parent's own bounded page. One `$unionWith` per parent after the first, so the whole page is
|
|
16
|
+
* one round trip - measured ~6x faster than a query each (11.0 ms -> 1.9 ms at 50 parents, 87.5 ms
|
|
17
|
+
* -> 14.1 ms at 500), because `execute` serializes on the session and a query each is N round trips
|
|
18
|
+
* rather than N concurrent ones.
|
|
19
|
+
*
|
|
20
|
+
* Both arms return documents with their own relations already filled, so this only chooses between
|
|
21
|
+
* them: leaving that to the caller once meant the arm that fills its own did it twice.
|
|
22
|
+
* [The design](../../../../architecture/populate-limits.md).
|
|
23
|
+
*/
|
|
24
|
+
protected internalFindManyPerParent<E extends Document>(entity: Type<E>, q: Query<E>, { joins, parents }: ParentPartition): Promise<E[]>;
|
|
25
|
+
/** Every parent's page as one `$unionWith` pipeline. */
|
|
26
|
+
private readInOnePipeline;
|
|
27
|
+
/** A query each, for what one pipeline cannot carry. `internalFindMany` fills its own relations. */
|
|
28
|
+
private readEachInTurn;
|
|
13
29
|
protected internalFindManyStream<E extends Document>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): AsyncGenerator<E, void, unknown>;
|
|
14
30
|
private buildScalarProjection;
|
|
15
31
|
/** Build a MongoDB FindCursor with filter, projection, sort, skip, and limit from the query. */
|