uql-orm 0.72.1 → 0.72.2
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/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +3 -3
- package/dist/dialect/abstractDialect.d.ts +6 -3
- package/dist/dialect/abstractDialect.js +22 -1
- package/dist/dialect/abstractSqlDialect.d.ts +7 -6
- package/dist/dialect/abstractSqlDialect.js +39 -30
- package/dist/dialect/aliases.d.ts +1 -1
- package/dist/dialect/aliases.js +1 -1
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +2 -2
- package/dist/dialect/mysqlLikeSqlDialect.js +4 -5
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -3
- package/dist/dialect/pgLikeSqlDialect.js +6 -7
- package/dist/dialect/queryJoins.d.ts +11 -1
- package/dist/dialect/queryJoins.js +14 -0
- package/dist/dialect/vectorSqlDialect.d.ts +0 -5
- package/dist/dialect/vectorSqlDialect.js +0 -15
- package/dist/migrate/generator/mongoCommand.d.ts +2 -0
- package/dist/migrate/generator/mongoSchemaGenerator.js +3 -2
- package/dist/migrate/introspection/mongoIntrospector.js +8 -3
- package/dist/mongo/mongoDialect.d.ts +15 -2
- package/dist/mongo/mongoDialect.js +49 -8
- package/dist/mongo/mongodbQuerier.js +6 -7
- package/dist/mongo/mongodbQuerierPool.js +4 -1
- package/dist/schema/indexDifferences.d.ts +2 -2
- package/dist/schema/indexDifferences.js +9 -2
- package/dist/sqlite/sqliteDialect.d.ts +4 -4
- package/dist/sqlite/sqliteDialect.js +15 -6
- package/dist/type/query.d.ts +11 -3
- package/dist/type/queryWhere.d.ts +3 -2
- package/dist/util/dialect.util.d.ts +6 -1
- package/dist/util/dialect.util.js +8 -0
- package/dist/util/wideNumber.d.ts +5 -3
- package/dist/util/wideNumber.js +8 -4
- package/package.json +1 -1
|
@@ -129,15 +129,15 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
129
129
|
* index over these fields, which the planner serves it from. `WEBSEARCH_TO_TSQUERY` takes free-form
|
|
130
130
|
* input (quoted phrases, `or`, `-negation`) and never raises a syntax error, unlike `TO_TSQUERY`.
|
|
131
131
|
*/
|
|
132
|
-
appendTextSearch(ctx,
|
|
133
|
-
const { document, query } = this.textSearchParts(meta, search);
|
|
132
|
+
appendTextSearch(ctx, meta, search, prefix) {
|
|
133
|
+
const { document, query } = this.textSearchParts(meta, search, prefix);
|
|
134
134
|
ctx.append(`${document} @@ ${query}`);
|
|
135
135
|
ctx.addValue(search.$value);
|
|
136
136
|
ctx.append(')');
|
|
137
137
|
}
|
|
138
138
|
/** `TS_RANK` of the document over `keys` against the same search the match reads. */
|
|
139
|
-
appendTextScore(ctx, meta, search, keys) {
|
|
140
|
-
const { document, query } = this.textSearchParts(meta, search, keys);
|
|
139
|
+
appendTextScore(ctx, meta, search, keys, prefix) {
|
|
140
|
+
const { document, query } = this.textSearchParts(meta, search, prefix, keys);
|
|
141
141
|
ctx.append(`TS_RANK(${document}, ${query}`);
|
|
142
142
|
ctx.addValue(search.$value);
|
|
143
143
|
ctx.append('))');
|
|
@@ -146,13 +146,12 @@ export class PgLikeSqlDialect extends AbstractSqlDialect {
|
|
|
146
146
|
* The document over `keys` and the search itself, open for its value, under the `$config` asked for,
|
|
147
147
|
* else that of the fulltext index over every field searched, which is what serves the match.
|
|
148
148
|
*/
|
|
149
|
-
textSearchParts(meta, search, keys) {
|
|
149
|
+
textSearchParts(meta, search, prefix, keys) {
|
|
150
150
|
const fields = textSearchFields(meta, search);
|
|
151
151
|
const index = fulltextIndexOver(meta, fields);
|
|
152
152
|
const config = search.$config ?? (index && fulltextConfig(index));
|
|
153
|
-
const columns = (keys ?? fields).map((key) => this.escapeId(this.resolveColumnName(key, meta.fields[key])));
|
|
154
153
|
return {
|
|
155
|
-
document: this.textSearchTarget(
|
|
154
|
+
document: this.textSearchTarget(this.textColumns(meta, keys ?? fields, prefix), config),
|
|
156
155
|
query: `${this.textQueryFn}(${this.textConfigArg(config)}`,
|
|
157
156
|
};
|
|
158
157
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { EntityMeta, Query, QueryGroupMap, QuerySortMap, QueryWhere, RelationMeta, RelationQuery, Type } from '../type/index.js';
|
|
1
|
+
import type { EntityMeta, FieldMeta, Query, QueryGroupMap, QuerySortMap, QueryWhere, RelationMeta, RelationQuery, Type } from '../type/index.js';
|
|
2
|
+
import { type ParsedGroupEntry } from '../util/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* One relation a statement joins, keyed by the alias its columns are addressed by (`tax`,
|
|
4
5
|
* `tax.category`). `projected` tells a `$populate` join, whose columns are selected, from one only
|
|
@@ -56,6 +57,15 @@ export declare function groupPathField(joins: QueryJoins, path: readonly string[
|
|
|
56
57
|
readonly key: string;
|
|
57
58
|
readonly join: QueryJoin | undefined;
|
|
58
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* The field an aggregate's column reads as, and the join it reads it through, none for the entity's own:
|
|
62
|
+
* a group key's, or the one a `$sum`, `$min` or `$max` aggregates. None at all for `$count` and `$avg`,
|
|
63
|
+
* which the engine widens to a number whatever they read.
|
|
64
|
+
*/
|
|
65
|
+
export declare function aggregateColumnField<E>(meta: EntityMeta<E>, joins: QueryJoins, entry: ParsedGroupEntry<E>): {
|
|
66
|
+
readonly field: FieldMeta | undefined;
|
|
67
|
+
readonly join?: QueryJoin;
|
|
68
|
+
} | undefined;
|
|
59
69
|
/**
|
|
60
70
|
* Whether a join drops parents that have no match, which is the one thing a join does to *how many*
|
|
61
71
|
* rows a read returns rather than how wide they are. A count that skips the joins has to be told, or
|
|
@@ -53,6 +53,20 @@ export function groupPathField(joins, path) {
|
|
|
53
53
|
}
|
|
54
54
|
return { key, join };
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* The field an aggregate's column reads as, and the join it reads it through, none for the entity's own:
|
|
58
|
+
* a group key's, or the one a `$sum`, `$min` or `$max` aggregates. None at all for `$count` and `$avg`,
|
|
59
|
+
* which the engine widens to a number whatever they read.
|
|
60
|
+
*/
|
|
61
|
+
export function aggregateColumnField(meta, joins, entry) {
|
|
62
|
+
if (entry.kind === 'fn') {
|
|
63
|
+
return entry.op === '$count' || entry.op === '$avg'
|
|
64
|
+
? undefined
|
|
65
|
+
: { field: meta.fields[entry.fieldRef] };
|
|
66
|
+
}
|
|
67
|
+
const { key, join } = groupPathField(joins, entry.path);
|
|
68
|
+
return join ? { field: join.meta.fields[key], join } : { field: meta.fields[key] };
|
|
69
|
+
}
|
|
56
70
|
/**
|
|
57
71
|
* Whether a join drops parents that have no match, which is the one thing a join does to *how many*
|
|
58
72
|
* rows a read returns rather than how wide they are. A count that skips the joins has to be told, or
|
|
@@ -51,11 +51,6 @@ export declare abstract class VectorSqlDialect extends AbstractDialect {
|
|
|
51
51
|
* rather than naming a type the engine does not define.
|
|
52
52
|
*/
|
|
53
53
|
supportedVectorType(cast: VectorCast): VectorCast;
|
|
54
|
-
/**
|
|
55
|
-
* The distance a vector `$sort` projects, which the projection names after `$project`. Delegates to
|
|
56
|
-
* `appendVectorDistance` so each dialect's distance syntax is written once.
|
|
57
|
-
*/
|
|
58
|
-
protected appendVectorProjection<E>(ctx: QueryContext, meta: EntityMeta<E>, key: string, search: QueryVectorSearch): void;
|
|
59
54
|
/**
|
|
60
55
|
* The distance expression, in whichever of the two shapes this dialect spells it. One method for
|
|
61
56
|
* both, so the metric lookup and its refusal exist once rather than per shape.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_VECTOR_DISTANCE, unsupportedVectorMetric } from '../type/vector.js';
|
|
2
2
|
import { findVectorIndex, findVectorSort, vectorCandidates } from '../util/dialect.util.js';
|
|
3
|
-
import { entityName } from '../util/object.util.js';
|
|
4
3
|
import { AbstractDialect } from './abstractDialect.js';
|
|
5
4
|
import { encodeFloat32s } from './vectorCast.js';
|
|
6
5
|
/**
|
|
@@ -69,20 +68,6 @@ export class VectorSqlDialect extends AbstractDialect {
|
|
|
69
68
|
supportedVectorType(cast) {
|
|
70
69
|
return this.features.narrowVectorTypes ? cast : 'vector';
|
|
71
70
|
}
|
|
72
|
-
/**
|
|
73
|
-
* The distance a vector `$sort` projects, which the projection names after `$project`. Delegates to
|
|
74
|
-
* `appendVectorDistance` so each dialect's distance syntax is written once.
|
|
75
|
-
*/
|
|
76
|
-
appendVectorProjection(ctx, meta, key, search) {
|
|
77
|
-
const alias = search.$project;
|
|
78
|
-
// `$project` names a new column, so it cannot be one the entity already has: both come back
|
|
79
|
-
// under that name and the driver keeps whichever it read last. Checked here rather than in the
|
|
80
|
-
// type because TypeScript cannot say "any string except these".
|
|
81
|
-
if (meta.fields[alias]) {
|
|
82
|
-
throw new TypeError(`$project '${alias}' collides with a field of '${entityName(meta)}'`);
|
|
83
|
-
}
|
|
84
|
-
this.appendVectorDistance(ctx, meta, key, search);
|
|
85
|
-
}
|
|
86
71
|
/**
|
|
87
72
|
* The distance expression, in whichever of the two shapes this dialect spells it. One method for
|
|
88
73
|
* both, so the metric lookup and its refusal exist once rather than per shape.
|
|
@@ -7,6 +7,8 @@ export type MongoIndexOptions = {
|
|
|
7
7
|
readonly partialFilterExpression?: Readonly<Record<string, unknown>>;
|
|
8
8
|
/** A text index's weight per field, which `textScore` multiplies a match in it by. */
|
|
9
9
|
readonly weights?: Readonly<Record<string, number>>;
|
|
10
|
+
/** The language a text index stems and drops stop words in, a search's own `$language` aside. */
|
|
11
|
+
readonly default_language?: string;
|
|
10
12
|
};
|
|
11
13
|
/** A field of an Atlas vector search index: the vector itself, or one its `filter` pre-filters on. */
|
|
12
14
|
export type MongoVectorSearchField = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { getMeta } from '../../entity/index.js';
|
|
2
|
-
import { MongoDialect } from '../../mongo/mongoDialect.js';
|
|
2
|
+
import { MongoDialect, textLanguage } from '../../mongo/mongoDialect.js';
|
|
3
3
|
import { QueryRaw, } from '../../type/index.js';
|
|
4
4
|
import { indexDistance, unsupportedVectorMetric } from '../../type/vector.js';
|
|
5
5
|
import { declaredIndexes, declaredIndexName, renderIndexColumn } from '../../util/ddlExpression.util.js';
|
|
6
|
-
import { fulltextWeights } from '../../util/dialect.util.js';
|
|
6
|
+
import { fulltextConfig, fulltextWeights } from '../../util/dialect.util.js';
|
|
7
7
|
import { assertIndexFeatures, assertIndexType } from '../ddl/indexDdl.js';
|
|
8
8
|
import { assertIndexPredicate, refusedIndexPredicate } from '../indexPredicate.js';
|
|
9
9
|
import { renderIndexDefinition } from './definitionToNode.js';
|
|
@@ -133,6 +133,7 @@ export class MongoSchemaGenerator extends MongoDialect {
|
|
|
133
133
|
name: index.name,
|
|
134
134
|
partialFilterExpression: index.where && JSON.parse(index.where),
|
|
135
135
|
weights: weights && Object.fromEntries(index.entries.map((entry, at) => [entry.column, weights[at]])),
|
|
136
|
+
default_language: index.type === 'fulltext' ? textLanguage(fulltextConfig(index)) : undefined,
|
|
136
137
|
},
|
|
137
138
|
});
|
|
138
139
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { textConfigOf } from '../../mongo/mongoDialect.js';
|
|
1
2
|
import { createTableNode, SchemaAST } from '../../schema/schemaAST.js';
|
|
2
3
|
import { isMongoQuerier, } from '../../type/index.js';
|
|
3
4
|
/** What a server without Atlas Search answers a search index command with. */
|
|
@@ -9,7 +10,7 @@ const SEARCH_NOT_ENABLED = 31082;
|
|
|
9
10
|
export class MongoSchemaIntrospector {
|
|
10
11
|
pool;
|
|
11
12
|
/** `listIndexes` reports keys, uniqueness and text weights; a `partialFilterExpression` is no SQL predicate. */
|
|
12
|
-
indexFacets = new Set(['
|
|
13
|
+
indexFacets = new Set(['textIndex']);
|
|
13
14
|
constructor(pool) {
|
|
14
15
|
this.pool = pool;
|
|
15
16
|
}
|
|
@@ -38,11 +39,15 @@ export class MongoSchemaIntrospector {
|
|
|
38
39
|
name: tableName,
|
|
39
40
|
columns: [],
|
|
40
41
|
indexes: [
|
|
41
|
-
...indexes.map(({ name, key, unique, weights }) => ({
|
|
42
|
+
...indexes.map(({ name, key, unique, weights, default_language }) => ({
|
|
42
43
|
name: name ?? Object.keys(key).join('_'),
|
|
43
44
|
unique: !!unique,
|
|
44
45
|
...(weights
|
|
45
|
-
? {
|
|
46
|
+
? {
|
|
47
|
+
entries: Object.entries(weights).map(textIndexEntry),
|
|
48
|
+
type: 'fulltext',
|
|
49
|
+
config: default_language && textConfigOf(default_language),
|
|
50
|
+
}
|
|
46
51
|
: { entries: Object.keys(key).map((column) => ({ column })) }),
|
|
47
52
|
})),
|
|
48
53
|
...searchIndexes
|
|
@@ -7,14 +7,25 @@ type MongoReadStages = {
|
|
|
7
7
|
/** Ordering, which runs after the lookups when it reads one of their fields. */
|
|
8
8
|
readonly sort?: Sort;
|
|
9
9
|
readonly pager?: MongoAggregationPipelineEntry<Document>[];
|
|
10
|
-
/**
|
|
11
|
-
readonly
|
|
10
|
+
/** A score the read answers as a field, a vector search's or a text search's; a temporary one leaves again. */
|
|
11
|
+
readonly score?: {
|
|
12
|
+
readonly field: string;
|
|
13
|
+
readonly meta: 'vectorSearchScore' | 'textScore';
|
|
14
|
+
readonly temporary?: boolean;
|
|
15
|
+
};
|
|
12
16
|
};
|
|
13
17
|
/** Accumulator threaded through `$where` rendering: the relation lookups it needs, and their temp fields. */
|
|
14
18
|
type RelationLookups = {
|
|
15
19
|
readonly stages: MongoAggregationPipelineEntry<Document>[];
|
|
16
20
|
readonly temps: string[];
|
|
17
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* A text-search config as MongoDB names the language: the same word for each language both know, and
|
|
24
|
+
* `'none'` for the no-stemming parser Postgres calls `'simple'`. {@link textConfigOf} reads one back.
|
|
25
|
+
*/
|
|
26
|
+
export declare function textLanguage(config: string): string;
|
|
27
|
+
/** A MongoDB language as the text-search config it is, the inverse of {@link textLanguage}. */
|
|
28
|
+
export declare function textConfigOf(language: string): string;
|
|
18
29
|
/** Default {@link DialectFeatures} for MongoDB. */
|
|
19
30
|
export declare const mongoDialectFeatures: DialectFeatures;
|
|
20
31
|
export declare class MongoDialect extends AbstractDialect {
|
|
@@ -220,6 +231,8 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
220
231
|
normalizeIds<E extends Document>(meta: EntityMeta<E>, docs: Document[]): E[];
|
|
221
232
|
/** `doc` is the wire shape - `_id`, stored names, `ObjectId`s - and what comes back is the code's. */
|
|
222
233
|
normalizeId<E extends Document>(meta: EntityMeta<E>, doc: Document | undefined): E | undefined;
|
|
234
|
+
/** An aggregate's rows with each 64-bit integer decoded as a document's is: a `bigint` only where the column reads a `BigInt` field. */
|
|
235
|
+
normalizeAggregateRows<E extends Document, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>, R extends Document>(entity: Type<E>, q: QueryAggregate<E, G, A>, rows: R[]): R[];
|
|
223
236
|
/**
|
|
224
237
|
* A key as MongoDB stores it: a 24-hex string as an `ObjectId`, so a write matches the filter looking for
|
|
225
238
|
* it, and anything else as given. Only 24-hex, not any 12-byte string. Arrays convert element-wise.
|
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
import { ObjectId } from 'mongodb';
|
|
2
2
|
import { AbstractDialect } from '../dialect/abstractDialect.js';
|
|
3
3
|
import { AGGREGATE_VALUE_ALIAS, REL_NESTED_KEY, REL_TEMP_PREFIX, SUM_COUNT_ALIAS, sortCountField, TEXT_SCORE_ALIAS, } from '../dialect/aliases.js';
|
|
4
|
-
import { groupPathField, resolveGroupJoins, resolveQueryJoins, resolveSortableJoin, } from '../dialect/queryJoins.js';
|
|
4
|
+
import { aggregateColumnField, groupPathField, resolveGroupJoins, resolveQueryJoins, resolveSortableJoin, } from '../dialect/queryJoins.js';
|
|
5
5
|
import { assertSoleId, fieldOf, getMeta, relationOf, soleIdOf } from '../entity/index.js';
|
|
6
6
|
import { COUNT_RESULT_KEY } from '../type/query.js';
|
|
7
7
|
import { QueryRaw } from '../type/queryRaw.js';
|
|
8
|
-
import { aggregateOf, asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fieldUpdateOf, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, rankedTextSearch, someKey, targetKeyColumns, } from '../util/index.js';
|
|
8
|
+
import { aggregateOf, asSelectMap, assertAggregateColumns, assertNonNegativeInteger, columnFamily, countedRelations, entityName, fieldUpdateOf, fillOnFields, filterFieldKeys, findVectorIndex, findVectorSort, getKeys, getRelationRequestSummary, hasKeys, isFieldUpdateOp, isJsonObject, isJsonUpdateOp, isOperatorMap, isOperatorObject, isRecord, isVectorSearch, normalizeScalarFieldSelection, parentJoins, parseGroupMap, parseRelationAtKey, parseRelationSize, parseSortByCount, rankedTextSearch, someKey, targetKeyColumns, textSortOf, } from '../util/index.js';
|
|
9
|
+
import { decodeBigIntsExcept } from '../util/wideNumber.js';
|
|
9
10
|
/** A scalar field's operator as the aggregation operator computing it. */
|
|
10
11
|
const MONGO_ARITHMETIC = { $inc: '$add', $mul: '$multiply' };
|
|
12
|
+
/**
|
|
13
|
+
* A text-search config as MongoDB names the language: the same word for each language both know, and
|
|
14
|
+
* `'none'` for the no-stemming parser Postgres calls `'simple'`. {@link textConfigOf} reads one back.
|
|
15
|
+
*/
|
|
16
|
+
export function textLanguage(config) {
|
|
17
|
+
return config === 'simple' ? 'none' : config;
|
|
18
|
+
}
|
|
19
|
+
/** A MongoDB language as the text-search config it is, the inverse of {@link textLanguage}. */
|
|
20
|
+
export function textConfigOf(language) {
|
|
21
|
+
return language === 'none' ? 'simple' : language;
|
|
22
|
+
}
|
|
11
23
|
/** Default {@link DialectFeatures} for MongoDB. */
|
|
12
24
|
export const mongoDialectFeatures = {
|
|
13
25
|
ifNotExists: false,
|
|
@@ -99,7 +111,8 @@ export class MongoDialect extends AbstractDialect {
|
|
|
99
111
|
else if (key === '$text') {
|
|
100
112
|
// MongoDB's text index declares which fields it covers, so `$fields` cannot narrow the search
|
|
101
113
|
// the way it does elsewhere - the same shape as `$distance` being index-defined here.
|
|
102
|
-
|
|
114
|
+
const { $value, $config } = val;
|
|
115
|
+
filter['$text'] = { $search: $value, ...($config && { $language: textLanguage($config) }) };
|
|
103
116
|
}
|
|
104
117
|
else if (meta.relations[key]) {
|
|
105
118
|
this.assertNoRaw(val);
|
|
@@ -477,7 +490,8 @@ export class MongoDialect extends AbstractDialect {
|
|
|
477
490
|
if (path) {
|
|
478
491
|
throw new TypeError(`$sort by $text is only supported on the queried entity, not on relation '${path.slice(0, -1)}'`);
|
|
479
492
|
}
|
|
480
|
-
|
|
493
|
+
const { order, project } = textSortOf(sort);
|
|
494
|
+
out[project ?? TEXT_SCORE_ALIAS] = sortDirection(order);
|
|
481
495
|
continue;
|
|
482
496
|
}
|
|
483
497
|
if (!relation) {
|
|
@@ -679,11 +693,14 @@ export class MongoDialect extends AbstractDialect {
|
|
|
679
693
|
return this.columnOf(meta, key.slice(0, dot)) + key.slice(dot);
|
|
680
694
|
}
|
|
681
695
|
aggregationPipeline(entity, q, opts) {
|
|
696
|
+
// Sorted as a field, which goes either way where a `$meta` sort only descends.
|
|
697
|
+
const text = textSortOf(q.$sort);
|
|
682
698
|
return [
|
|
683
699
|
...this.matchStages(entity, q.$where, opts, this.aggregateKeys(entity, q)),
|
|
684
700
|
...this.readStages(entity, q, {
|
|
685
701
|
sort: this.sort(entity, q),
|
|
686
702
|
pager: this.pagerStages(q),
|
|
703
|
+
score: text && { field: text.project ?? TEXT_SCORE_ALIAS, meta: 'textScore', temporary: !text.project },
|
|
687
704
|
}),
|
|
688
705
|
];
|
|
689
706
|
}
|
|
@@ -709,10 +726,17 @@ export class MongoDialect extends AbstractDialect {
|
|
|
709
726
|
const related = this.relationReadStages(entity, q);
|
|
710
727
|
const sort = hasKeys(extra.sort) ? [{ $sort: extra.sort }] : [];
|
|
711
728
|
const pager = extra.pager ?? [];
|
|
712
|
-
//
|
|
713
|
-
//
|
|
729
|
+
// The score becomes a real field before anything reads it, so the lookups, the sort and the projection
|
|
730
|
+
// that follow treat it like any other; merged into the query's own projection rather than standing in
|
|
731
|
+
// for one, since a query that asked for no columns wants the whole document as well.
|
|
732
|
+
const { score } = extra;
|
|
733
|
+
if (score && !score.temporary) {
|
|
734
|
+
this.assertProjectable(meta, score.field);
|
|
735
|
+
}
|
|
736
|
+
const scored = score ? [{ $addFields: { [score.field]: { $meta: score.meta } } }] : [];
|
|
737
|
+
const unscored = score?.temporary ? [{ $unset: [score.field] }] : [];
|
|
714
738
|
const projection = this.pipelineProjection(entity, q);
|
|
715
|
-
const projected = projection ? { ...projection,
|
|
739
|
+
const projected = projection && score ? { ...projection, [score.field]: 1 } : projection;
|
|
716
740
|
const project = projected ? [{ $project: projected }] : [];
|
|
717
741
|
// A `$lookup` the ordering asked for puts a field on the document the caller never requested,
|
|
718
742
|
// which is the one way this differs from a SQL join. Taken back out once the `$sort` that needed
|
|
@@ -734,7 +758,7 @@ export class MongoDialect extends AbstractDialect {
|
|
|
734
758
|
// ordering and the page have to run after it to address the set the caller actually receives.
|
|
735
759
|
const dedup = q.$distinct ? this.distinctStages(projected) : [];
|
|
736
760
|
if (dedup.length) {
|
|
737
|
-
return [...lookups, ...related, ...project, ...dedup, ...sort, ...pager];
|
|
761
|
+
return [...scored, ...lookups, ...related, ...project, ...dedup, ...sort, ...pager, ...unscored];
|
|
738
762
|
}
|
|
739
763
|
// A `$required` relation drops parents when it unwinds, and an ordering may read a field only a
|
|
740
764
|
// lookup produces: either one puts the lookups first, as an INNER JOIN does. Otherwise paging
|
|
@@ -742,10 +766,12 @@ export class MongoDialect extends AbstractDialect {
|
|
|
742
766
|
const lookupsFirst = this.sortsRelations(entity, q.$sort) ||
|
|
743
767
|
lookups.some((stage) => stage.$unwind?.preserveNullAndEmptyArrays === false);
|
|
744
768
|
return [
|
|
769
|
+
...scored,
|
|
745
770
|
...(lookupsFirst ? [...lookups, ...sort, ...pager] : [...sort, ...pager, ...lookups]),
|
|
746
771
|
...related,
|
|
747
772
|
...unset,
|
|
748
773
|
...project,
|
|
774
|
+
...unscored,
|
|
749
775
|
];
|
|
750
776
|
}
|
|
751
777
|
/**
|
|
@@ -894,6 +920,9 @@ export class MongoDialect extends AbstractDialect {
|
|
|
894
920
|
res[key] = this.fromWireId(res[key]);
|
|
895
921
|
}
|
|
896
922
|
}
|
|
923
|
+
// A 64-bit integer, which the pool reads as a `bigint`: kept for a `BigInt` field, and elsewhere the
|
|
924
|
+
// number it is where exact and its exact text past 2^53, as every SQL driver decodes one.
|
|
925
|
+
decodeBigIntsExcept(res, (key) => meta.fields[key]?.type === BigInt);
|
|
897
926
|
const relKeys = getKeys(meta.relations).filter((key) => res[key]);
|
|
898
927
|
for (const relKey of relKeys) {
|
|
899
928
|
const relMeta = getMeta(relationOf(meta, relKey).entity());
|
|
@@ -903,6 +932,18 @@ export class MongoDialect extends AbstractDialect {
|
|
|
903
932
|
}
|
|
904
933
|
return res;
|
|
905
934
|
}
|
|
935
|
+
/** An aggregate's rows with each 64-bit integer decoded as a document's is: a `bigint` only where the column reads a `BigInt` field. */
|
|
936
|
+
normalizeAggregateRows(entity, q, rows) {
|
|
937
|
+
const meta = getMeta(entity);
|
|
938
|
+
const { joins } = resolveGroupJoins(meta, q);
|
|
939
|
+
const exact = new Set(parseGroupMap(q.$group, q.$select)
|
|
940
|
+
.filter((entry) => aggregateColumnField(meta, joins, entry)?.field?.type === BigInt)
|
|
941
|
+
.map((entry) => entry.alias));
|
|
942
|
+
for (const row of rows) {
|
|
943
|
+
decodeBigIntsExcept(row, (key) => exact.has(key));
|
|
944
|
+
}
|
|
945
|
+
return rows;
|
|
946
|
+
}
|
|
906
947
|
/**
|
|
907
948
|
* A key as MongoDB stores it: a 24-hex string as an `ObjectId`, so a write matches the filter looking for
|
|
908
949
|
* it, and anything else as given. Only 24-hex, not any 12-byte string. Arrays convert element-wise.
|
|
@@ -2,7 +2,7 @@ import { AGGREGATE_VALUE_ALIAS } from '../dialect/aliases.js';
|
|
|
2
2
|
import { hasRequiredJoin } from '../dialect/queryJoins.js';
|
|
3
3
|
import { fieldOf, getMeta, namesKey, soleIdOf } from '../entity/index.js';
|
|
4
4
|
import { AbstractQuerier, enrichError } from '../querier/index.js';
|
|
5
|
-
import { clone, getKeys, getSoftDeleteValue, hasKeys, populatesRelations, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
5
|
+
import { clone, getKeys, getSoftDeleteValue, hasKeys, populatesRelations, textSortOf, throwNoPendingTransaction, throwPendingTransaction, vectorCandidates, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
6
6
|
/**
|
|
7
7
|
* `$limit: 0` asks for no rows, the way it does on every SQL dialect - but MongoDB reads `limit(0)`
|
|
8
8
|
* as *unlimited*, so a read that passed it straight to the driver came back with the whole
|
|
@@ -77,7 +77,8 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
77
77
|
populatesRelations(getMeta(entity), q.$populate) ||
|
|
78
78
|
this.dialect.constrainsRelations(entity, q.$where) ||
|
|
79
79
|
this.dialect.sortsRelations(entity, q.$sort) ||
|
|
80
|
-
this.dialect.readsAggregates(entity, q)
|
|
80
|
+
this.dialect.readsAggregates(entity, q) ||
|
|
81
|
+
textSortOf(q.$sort) !== undefined);
|
|
81
82
|
}
|
|
82
83
|
buildScalarProjection(entity, q) {
|
|
83
84
|
return this.dialect.select(entity, q.$select, q.$exclude);
|
|
@@ -114,20 +115,18 @@ export class MongodbQuerier extends AbstractQuerier {
|
|
|
114
115
|
const scoreAlias = vectorSort.vectorSearch.$project;
|
|
115
116
|
return [
|
|
116
117
|
this.dialect.buildVectorSearchStage(entity, vectorSort.vectorKey, vectorSort.vectorSearch, q.$where, q.$limit ?? 10, opts, vectorCandidates(q)),
|
|
117
|
-
// The score becomes a real field before anything reads it, so the lookups and the projection
|
|
118
|
-
// that follow treat it like any other - and a query with no projection keeps its own columns.
|
|
119
|
-
...(scoreAlias ? [{ $addFields: { [scoreAlias]: { $meta: 'vectorSearchScore' } } }] : []),
|
|
120
118
|
// `$vectorSearch` has already applied `$limit`, so the pager is its own.
|
|
121
119
|
...this.dialect.readStages(entity, q, {
|
|
122
120
|
sort: this.dialect.sort(entity, { ...q, $sort: vectorSort.regularSort }),
|
|
123
|
-
|
|
121
|
+
score: scoreAlias ? { field: scoreAlias, meta: 'vectorSearchScore' } : undefined,
|
|
124
122
|
}),
|
|
125
123
|
];
|
|
126
124
|
}
|
|
127
125
|
async internalAggregate(entity, q, opts) {
|
|
128
126
|
return this.timed('internalAggregate', undefined, async () => {
|
|
129
127
|
const pipeline = this.dialect.buildAggregateStages(entity, q, opts);
|
|
130
|
-
|
|
128
|
+
const rows = await this.execute((session) => this.collection(entity).aggregate(pipeline, { session }).toArray());
|
|
129
|
+
return this.dialect.normalizeAggregateRows(entity, q, rows);
|
|
131
130
|
});
|
|
132
131
|
}
|
|
133
132
|
/**
|
|
@@ -7,7 +7,10 @@ export class MongodbQuerierPool extends AbstractQuerierPool {
|
|
|
7
7
|
client;
|
|
8
8
|
constructor(uri, opts, extra) {
|
|
9
9
|
super(new MongoDialect(dialectOptionsFrom(extra)), extra);
|
|
10
|
-
|
|
10
|
+
// A 64-bit integer read as the exact `bigint` it is, where the driver would round it past 2^53 or
|
|
11
|
+
// hand back its own `Long`; each read then decodes it the way every SQL driver does. First, as the
|
|
12
|
+
// MySQL pool's `supportBigNumbers` is, so an explicit choice of the caller's wins.
|
|
13
|
+
this.client = new MongoClient(uri, { useBigInt64: true, ...opts });
|
|
11
14
|
}
|
|
12
15
|
async getQuerier() {
|
|
13
16
|
const conn = await this.client.connect();
|
|
@@ -2,9 +2,9 @@ import type { IndexNode } from './types.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* What an introspector reports about an index, and so all a diff may compare; apart from `IndexFeature`, what an engine emits.
|
|
4
4
|
* `vector` is whether it is a vector index at all, for an engine with one vector index whatever type declared it.
|
|
5
|
-
* `
|
|
5
|
+
* `textIndex` is a text index's weights and language, kept by an engine that lists its fields in no declared order.
|
|
6
6
|
*/
|
|
7
|
-
export type IndexFacet = 'order' | 'nulls' | 'opsClass' | 'accessMethod' | 'include' | 'vector' | '
|
|
7
|
+
export type IndexFacet = 'order' | 'nulls' | 'opsClass' | 'accessMethod' | 'include' | 'vector' | 'textIndex';
|
|
8
8
|
/**
|
|
9
9
|
* Whether the table has this index already, by shape rather than name, uniqueness included. An index
|
|
10
10
|
* over an expression, whose text the engine reprints, falls back to its name.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { isVectorIndexType } from '../type/vector.js';
|
|
2
|
+
import { fulltextConfig } from '../util/dialect.util.js';
|
|
2
3
|
/**
|
|
3
4
|
* Whether the table has this index already, by shape rather than name, uniqueness included. An index
|
|
4
5
|
* over an expression, whose text the engine reprints, falls back to its name.
|
|
@@ -40,6 +41,12 @@ export function describeIndexDifferences(source, target, facets) {
|
|
|
40
41
|
differences.push(`columns: (${targetColumns}) -> (${sourceColumns})`);
|
|
41
42
|
}
|
|
42
43
|
}
|
|
44
|
+
if (facets.has('textIndex') && source.type === 'fulltext' && target.type === 'fulltext') {
|
|
45
|
+
const [expected, actual] = [fulltextConfig(source), fulltextConfig(target)];
|
|
46
|
+
if (expected !== actual) {
|
|
47
|
+
differences.push(`config: ${actual} -> ${expected}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
if (source.unique !== target.unique) {
|
|
44
51
|
differences.push(`unique: ${target.unique} -> ${source.unique}`);
|
|
45
52
|
}
|
|
@@ -61,7 +68,7 @@ export function describeIndexDifferences(source, target, facets) {
|
|
|
61
68
|
}
|
|
62
69
|
/** A text index's fields as a set where the engine keeps its weights: MongoDB lists them alphabetically. */
|
|
63
70
|
function textFieldOrder(index, facets, entries) {
|
|
64
|
-
return facets.has('
|
|
71
|
+
return facets.has('textIndex') && index.type === 'fulltext' ? entries.toSorted() : entries;
|
|
65
72
|
}
|
|
66
73
|
function entrySignature(entry, facets) {
|
|
67
74
|
const parts = [entry.column];
|
|
@@ -76,7 +83,7 @@ function entrySignature(entry, facets) {
|
|
|
76
83
|
if (facets.has('opsClass') && entry.opsClass) {
|
|
77
84
|
parts.push(entry.opsClass);
|
|
78
85
|
}
|
|
79
|
-
if (facets.has('
|
|
86
|
+
if (facets.has('textIndex') && (entry.weight ?? 1) !== 1) {
|
|
80
87
|
parts.push(`weight ${entry.weight}`);
|
|
81
88
|
}
|
|
82
89
|
return parts.join(' ');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractSqlDialect, type DerivedRelation, type HydrateKind, type RelationRows } from '../dialect/abstractSqlDialect.js';
|
|
2
2
|
import { type JsonAccessMode, type JsonSlot } from '../dialect/jsonSql.js';
|
|
3
|
-
import { type EntityMeta, type FieldOptions, type Query, type QueryContext, type QueryPager, type QueryTextSearchOptions, type QueryWhere, type SqlDialectFeatures, type
|
|
3
|
+
import { type EntityMeta, type FieldOptions, type Query, type QueryContext, type QueryPager, type QueryTextSearchOptions, type QueryWhere, type SqlDialectFeatures, type VectorDistance, type VectorMetric } from '../type/index.js';
|
|
4
4
|
/** What SQLite and the engines derived from it have. */
|
|
5
5
|
export declare const SQLITE_FEATURES: SqlDialectFeatures;
|
|
6
6
|
export declare class SqliteDialect extends AbstractSqlDialect {
|
|
@@ -63,10 +63,10 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
63
63
|
/** A date reads back as SQLite stored it, a number or text, which JSON carries unchanged. */
|
|
64
64
|
protected hydrateKind(field: FieldOptions | undefined): HydrateKind | undefined;
|
|
65
65
|
/**
|
|
66
|
-
* FTS5 matches the table itself
|
|
67
|
-
*
|
|
66
|
+
* FTS5 matches the table itself, so this works only where the table *is* an FTS5 virtual table (UQL does
|
|
67
|
+
* not create those; declare it outside your entities). The whole query is bound, column filter and all.
|
|
68
68
|
*/
|
|
69
|
-
protected appendTextSearch<E>(ctx: QueryContext,
|
|
69
|
+
protected appendTextSearch<E>(ctx: QueryContext, meta: EntityMeta<E>, search: QueryTextSearchOptions<E>): void;
|
|
70
70
|
/** FTS5's `BM25` of the match, lower for a better one, so negated to rank as every other engine does. */
|
|
71
71
|
protected appendTextScore<E>(ctx: QueryContext, meta: EntityMeta<E>): void;
|
|
72
72
|
protected jsonLength(slot: JsonSlot): string;
|
|
@@ -6,6 +6,15 @@ import { indexDistance, isVectorIndexType } from '../type/vector.js';
|
|
|
6
6
|
import { declaredIndexName } from '../util/ddlExpression.util.js';
|
|
7
7
|
import { findVectorIndex, findVectorSort, textSearchFields, vectorCandidates } from '../util/dialect.util.js';
|
|
8
8
|
import { columnFamily, isIntegerColumn } from '../util/field.util.js';
|
|
9
|
+
/**
|
|
10
|
+
* An FTS5 query over `columns` for what a person typed: each word a quoted string, which FTS5 reads as a
|
|
11
|
+
* term to match and never as syntax, and every one required, as the other engines read plain words.
|
|
12
|
+
*/
|
|
13
|
+
function ftsQuery(columns, value) {
|
|
14
|
+
const quote = (text) => `"${text.replaceAll('"', '""')}"`;
|
|
15
|
+
const words = value.split(/\s+/).filter(Boolean);
|
|
16
|
+
return `{${columns.map(quote).join(' ')}} : (${words.map(quote).join(' ') || '""'})`;
|
|
17
|
+
}
|
|
9
18
|
/** What SQLite and the engines derived from it have. */
|
|
10
19
|
export const SQLITE_FEATURES = {
|
|
11
20
|
ifNotExists: true,
|
|
@@ -156,13 +165,13 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
156
165
|
return columnFamily(field?.type) === 'date' ? undefined : super.hydrateKind(field);
|
|
157
166
|
}
|
|
158
167
|
/**
|
|
159
|
-
* FTS5 matches the table itself
|
|
160
|
-
*
|
|
168
|
+
* FTS5 matches the table itself, so this works only where the table *is* an FTS5 virtual table (UQL does
|
|
169
|
+
* not create those; declare it outside your entities). The whole query is bound, column filter and all.
|
|
161
170
|
*/
|
|
162
|
-
appendTextSearch(ctx,
|
|
163
|
-
const columns = textSearchFields(meta, search).map((key) => this.
|
|
164
|
-
ctx.append(`${this.escapedTableName(meta)} MATCH
|
|
165
|
-
ctx.addValue(search.$value);
|
|
171
|
+
appendTextSearch(ctx, meta, search) {
|
|
172
|
+
const columns = textSearchFields(meta, search).map((key) => this.resolveColumnName(key, meta.fields[key]));
|
|
173
|
+
ctx.append(`${this.escapedTableName(meta)} MATCH `);
|
|
174
|
+
ctx.addValue(ftsQuery(columns, search.$value));
|
|
166
175
|
}
|
|
167
176
|
/** FTS5's `BM25` of the match, lower for a better one, so negated to rank as every other engine does. */
|
|
168
177
|
appendTextScore(ctx, meta) {
|
package/dist/type/query.d.ts
CHANGED
|
@@ -130,12 +130,20 @@ export type QuerySortByCount = {
|
|
|
130
130
|
$count: QuerySortDirection;
|
|
131
131
|
};
|
|
132
132
|
/**
|
|
133
|
-
* Ordering by relevance to the `$text` at the root of `$where
|
|
134
|
-
*
|
|
133
|
+
* Ordering by relevance to the `$text` at the root of `$where`, in either direction as any key sorts. The
|
|
134
|
+
* object form also answers it under the name `$project` gives it, most relevant first unless `$order` says.
|
|
135
135
|
*/
|
|
136
136
|
export type QuerySortByText = {
|
|
137
|
-
$text?:
|
|
137
|
+
$text?: QuerySortDirection | {
|
|
138
|
+
readonly $project: string;
|
|
139
|
+
readonly $order?: QuerySortDirection;
|
|
140
|
+
};
|
|
138
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* A row with the relevance a `$sort: { $text: { $project } }` names, which is not inferred:
|
|
144
|
+
* `(await querier.findMany(Post, q)) as WithScore<Post, 'score'>[]`.
|
|
145
|
+
*/
|
|
146
|
+
export type WithScore<E, K extends string> = E & Record<K, number>;
|
|
139
147
|
/**
|
|
140
148
|
* A sort by fields, JSON paths, a to-one relation's fields, a to-many's `$count`, or a vector distance or
|
|
141
149
|
* `$text` relevance, which `Vector` confines to the queried entity. One mapped type over the key sets: an
|
|
@@ -16,8 +16,9 @@ export type QueryTextSearchOptions<E> = {
|
|
|
16
16
|
*/
|
|
17
17
|
$fields?: QuerySelect<E>;
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* The language the search is parsed in (e.g. `'english'`, or `'simple'` for no stemming), else that of
|
|
20
|
+
* the fulltext index over its fields: the Postgres family's text-search config, MongoDB's `$language`.
|
|
21
|
+
* MySQL and SQLite parse by their index alone.
|
|
21
22
|
*/
|
|
22
23
|
$config?: string;
|
|
23
24
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IndexType } from '../schema/types.js';
|
|
2
|
-
import { type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type FieldUpdateOp, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
|
|
2
|
+
import { type CascadeType, type EntityData, type EntityId, type EntityIndexMeta, type EntityMeta, type FieldKey, type FieldOptions, type FieldUpdateOp, type JsonUpdateOp, type OnFieldCallback, type Query, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySearch, type QuerySelect, type QuerySelectValue, type QuerySortDirection, type QuerySizeComparisonOps, type QuerySortMap, type QueryTextSearchOptions, type QueryVectorSearch, type QueryWhere, type RelationKey, type UpdatePayload } from '../type/index.js';
|
|
3
3
|
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
|
|
4
4
|
/** The keys of `payload` a write persists as columns. */
|
|
5
5
|
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E> | UpdatePayload<E>, callbackKey: CallbackKey): FieldKey<E>[];
|
|
@@ -192,6 +192,11 @@ export declare function textWeightSteps(weights: readonly number[]): {
|
|
|
192
192
|
};
|
|
193
193
|
/** The fulltext index over exactly `fields`, in order, which a search of them is served by. */
|
|
194
194
|
export declare function fulltextIndexOver<E>(meta: EntityMeta<E>, fields: readonly string[]): EntityIndexMeta<E> | undefined;
|
|
195
|
+
/** How a `$sort` orders by `$text`: its direction, and the name it answers the relevance under, if any. */
|
|
196
|
+
export declare function textSortOf<E>(sort: QuerySortMap<E> | undefined): {
|
|
197
|
+
readonly order: QuerySortDirection;
|
|
198
|
+
readonly project?: string;
|
|
199
|
+
} | undefined;
|
|
195
200
|
/**
|
|
196
201
|
* The search a `$sort` by `$text` ranks by: the one at the root of the same query's `$where`. A nested or
|
|
197
202
|
* negated one has no score to order by, and MongoDB scores only the one `$text` it allows.
|
|
@@ -491,6 +491,14 @@ export function fulltextIndexOver(meta, fields) {
|
|
|
491
491
|
index.columns.length === fields.length &&
|
|
492
492
|
index.columns.every((entry, at) => entry.column === fields[at]));
|
|
493
493
|
}
|
|
494
|
+
/** How a `$sort` orders by `$text`: its direction, and the name it answers the relevance under, if any. */
|
|
495
|
+
export function textSortOf(sort) {
|
|
496
|
+
const text = sort?.$text;
|
|
497
|
+
if (text === undefined) {
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
return isRecord(text) ? { order: text.$order ?? 'desc', project: text.$project } : { order: text };
|
|
501
|
+
}
|
|
494
502
|
/**
|
|
495
503
|
* The search a `$sort` by `$text` ranks by: the one at the root of the same query's `$where`. A nested or
|
|
496
504
|
* negated one has no score to order by, and MongoDB scores only the one `$text` it allows.
|
|
@@ -7,8 +7,10 @@ import type { RawRow } from '../type/index.js';
|
|
|
7
7
|
*/
|
|
8
8
|
export declare function decodeWideNumber(value: string | bigint): number | string;
|
|
9
9
|
/**
|
|
10
|
-
* {@link decodeWideNumber} over every `bigint` cell of a row, for the drivers that hand a BIGINT back
|
|
11
|
-
*
|
|
12
|
-
* object, and a copy per row cost more than the decode it carried.
|
|
10
|
+
* {@link decodeWideNumber} over every `bigint` cell of a row, for the drivers that hand a BIGINT back as
|
|
11
|
+
* one (`bun:sql`, `mariadb`, and every SQLite driver but D1). In place: the row is the driver's fresh
|
|
12
|
+
* object, and a copy per row cost more than the decode it carried. One argument, so it maps rows as is.
|
|
13
13
|
*/
|
|
14
14
|
export declare function decodeBigInts(row: RawRow): RawRow;
|
|
15
|
+
/** {@link decodeBigInts}, keeping the cells `exact` names: what MongoDB reads a `BigInt` field into. */
|
|
16
|
+
export declare function decodeBigIntsExcept(row: RawRow, exact: (key: string) => boolean): RawRow;
|