uql-orm 0.68.1 → 0.70.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/dist/browser/querier/httpQuerier.d.ts +7 -7
- package/dist/browser/type/clientQuerier.d.ts +5 -5
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +6 -6
- package/dist/cockroachdb/cockroachDialect.js +2 -2
- package/dist/dialect/abstractDialect.d.ts +8 -2
- package/dist/dialect/abstractDialect.js +17 -1
- package/dist/dialect/abstractSqlDialect.d.ts +42 -4
- package/dist/dialect/abstractSqlDialect.js +164 -48
- package/dist/dialect/aliases.d.ts +15 -4
- package/dist/dialect/aliases.js +15 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +3 -2
- package/dist/dialect/pgLikeSqlDialect.js +1 -0
- package/dist/dialect/queryJoins.d.ts +8 -1
- package/dist/dialect/queryJoins.js +33 -10
- package/dist/entity/decorator/members.d.ts +44 -2
- package/dist/entity/decorator/members.js +0 -5
- package/dist/entity/metadata/definition.d.ts +8 -3
- package/dist/entity/metadata/definition.js +10 -3
- package/dist/migrate/codegen/entityCodeGenerator.js +4 -2
- package/dist/migrate/introspection/postgresIntrospector.d.ts +6 -0
- package/dist/migrate/introspection/postgresIntrospector.js +7 -1
- package/dist/migrate/migrator.d.ts +2 -1
- package/dist/migrate/migrator.js +5 -3
- package/dist/mongo/mongoDialect.d.ts +49 -19
- package/dist/mongo/mongoDialect.js +238 -81
- package/dist/mongo/mongodbQuerier.d.ts +2 -4
- package/dist/mongo/mongodbQuerier.js +16 -14
- package/dist/mssql/mssqlDialect.js +3 -2
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +16 -11
- package/dist/querier/abstractQuerier.js +28 -8
- package/dist/querier/abstractQuerierPool.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.d.ts +1 -1
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/sqlite/sqliteDialect.js +1 -0
- package/dist/turso/tursoDialect.d.ts +1 -1
- package/dist/turso/tursoDialect.js +6 -2
- package/dist/type/dialect.d.ts +35 -2
- package/dist/type/entity.d.ts +120 -4
- package/dist/type/migration.d.ts +7 -0
- package/dist/type/query.d.ts +7 -10
- package/dist/type/queryAggregate.d.ts +77 -42
- package/dist/type/queryAggregate.js +4 -21
- package/dist/type/queryRaw.d.ts +19 -1
- package/dist/type/queryRaw.js +18 -0
- package/dist/type/universalQuerier.d.ts +9 -9
- package/dist/util/dialect.util.d.ts +6 -2
- package/dist/util/dialect.util.js +21 -7
- package/dist/util/field.util.d.ts +15 -1
- package/dist/util/field.util.js +18 -1
- package/dist/util/object.util.d.ts +1 -4
- package/dist/util/object.util.js +0 -3
- package/dist/util/raw.d.ts +2 -2
- package/dist/util/raw.js +29 -2
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SOFT_DELETE_FILTER } from '../../type/index.js';
|
|
1
|
+
import { RelationAggregate, SOFT_DELETE_FILTER } from '../../type/index.js';
|
|
2
2
|
import { isInlinedExpression } from '../../util/field.util.js';
|
|
3
3
|
import { entitySql, entityWhere, fieldOptionConflict, getKeys, hasKeys, isToManyRelation, memberRefs, normalizeIndexColumn, definedEntries, } from '../../util/index.js';
|
|
4
4
|
import { ownRegistrations } from '../decorator/bag.js';
|
|
@@ -17,6 +17,14 @@ function globalMap(key) {
|
|
|
17
17
|
const metas = globalMap('uql-orm/entity/metadata/v1');
|
|
18
18
|
export function defineField(entity, key, opts = {}) {
|
|
19
19
|
const meta = ensureWritableMeta(entity);
|
|
20
|
+
const { computed, ...rest } = opts;
|
|
21
|
+
const sql = computed === undefined ? undefined : entitySql(computed);
|
|
22
|
+
// A relation aggregate reads as a correlated subquery, which no engine accepts in a generated column:
|
|
23
|
+
// keeping one on the row takes the triggers a write fires, which are not built yet.
|
|
24
|
+
if (opts.stored && sql instanceof RelationAggregate) {
|
|
25
|
+
throw new TypeError(`'${entity.name}.${key}' cannot be 'stored': a relation aggregate reads as a subquery, which no ` +
|
|
26
|
+
"engine keeps in a generated column. Drop 'stored' to have it read on each query.");
|
|
27
|
+
}
|
|
20
28
|
// A stored computed column is a real column and still needs a type; only an inlined one is exempt,
|
|
21
29
|
// its expression being spliced in rather than declared.
|
|
22
30
|
if (!opts.type && !opts.references && !isInlinedExpression(opts)) {
|
|
@@ -31,13 +39,12 @@ export function defineField(entity, key, opts = {}) {
|
|
|
31
39
|
// Flagged when the author gave `references` but no `type`, so schema generation knows to resolve the
|
|
32
40
|
// column from the referenced primary key (picking up its `columnType`, length and chained keys)
|
|
33
41
|
// instead of treating whatever ends up in `type` as deliberate.
|
|
34
|
-
const { computed, ...rest } = opts;
|
|
35
42
|
const resolved = rest.type ? rest : { ...rest, typeFromReference: true };
|
|
36
43
|
meta.fields[fieldKey] = {
|
|
37
44
|
...meta.fields[fieldKey],
|
|
38
45
|
name: key,
|
|
39
46
|
...resolved,
|
|
40
|
-
...(
|
|
47
|
+
...(sql && { computed: sql }),
|
|
41
48
|
};
|
|
42
49
|
return meta;
|
|
43
50
|
}
|
|
@@ -156,9 +156,11 @@ export class EntityCodeGenerator {
|
|
|
156
156
|
const fieldOptions = this.buildFieldOptions(col, propertyName);
|
|
157
157
|
lines.push(` @Field(${fieldOptions})`);
|
|
158
158
|
}
|
|
159
|
-
// Property
|
|
159
|
+
// Property. A generated column is the database's to write, so it is `readonly`: a write payload
|
|
160
|
+
// leaves those out, and one naming it would be dropped rather than persisted.
|
|
160
161
|
const nullable = col.nullable ? '?' : '';
|
|
161
|
-
|
|
162
|
+
const written = col.generatedAs === undefined ? '' : 'readonly ';
|
|
163
|
+
lines.push(` ${written}${propertyName}${nullable}: ${tsType};`);
|
|
162
164
|
return lines.join('\n');
|
|
163
165
|
}
|
|
164
166
|
/**
|
|
@@ -14,6 +14,12 @@ export declare class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrosp
|
|
|
14
14
|
protected getTableNamesQuery(): string;
|
|
15
15
|
protected tableExistsQuery(): string;
|
|
16
16
|
protected parseTableExistsResult([row]: RawRow[]): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* The comment reads through `to_regclass` rather than a `::regclass` cast: a name resolves against
|
|
19
|
+
* the live catalogue while `information_schema` answers from this statement's snapshot, so a table
|
|
20
|
+
* another connection has just dropped is still listed here and the cast would raise on it. Whole
|
|
21
|
+
* database scans meet that table every time something else is migrating.
|
|
22
|
+
*/
|
|
17
23
|
protected getColumnsQuery(_tableName: string): string;
|
|
18
24
|
/**
|
|
19
25
|
* `attname` where the entry is a column, `pg_get_indexdef` for that one position where it is an
|
|
@@ -37,6 +37,12 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
37
37
|
parseTableExistsResult([row]) {
|
|
38
38
|
return row['exists'] === true;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The comment reads through `to_regclass` rather than a `::regclass` cast: a name resolves against
|
|
42
|
+
* the live catalogue while `information_schema` answers from this statement's snapshot, so a table
|
|
43
|
+
* another connection has just dropped is still listed here and the cast would raise on it. Whole
|
|
44
|
+
* database scans meet that table every time something else is migrating.
|
|
45
|
+
*/
|
|
40
46
|
getColumnsQuery(_tableName) {
|
|
41
47
|
return /*sql*/ `
|
|
42
48
|
SELECT
|
|
@@ -68,7 +74,7 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
68
74
|
HAVING COUNT(*) = 1 AND MIN(kcu.column_name) = c.column_name
|
|
69
75
|
) AS is_unique,
|
|
70
76
|
pg_catalog.col_description(
|
|
71
|
-
(quote_ident(c.table_schema) || '.' || quote_ident(c.table_name))
|
|
77
|
+
to_regclass(quote_ident(c.table_schema) || '.' || quote_ident(c.table_name)),
|
|
72
78
|
c.ordinal_position
|
|
73
79
|
) AS column_comment
|
|
74
80
|
FROM information_schema.columns c
|
|
@@ -49,7 +49,8 @@ export declare class Migrator {
|
|
|
49
49
|
/** Runs the list narrowed by `to`/`step`, stopping at the first failure: `up` over the pending, `down` over the executed reversed. */
|
|
50
50
|
private runInOrder;
|
|
51
51
|
/**
|
|
52
|
-
* Run a single migration,
|
|
52
|
+
* Run a single migration, in a transaction where the dialect has one for it and the migration has not
|
|
53
|
+
* declared `transaction: false` - the opt-out a statement an engine refuses inside one needs.
|
|
53
54
|
*/
|
|
54
55
|
runMigration(migration: Migration<Querier>, direction: 'up' | 'down'): Promise<MigrationResult>;
|
|
55
56
|
/**
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -112,14 +112,15 @@ export class Migrator {
|
|
|
112
112
|
return results;
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
* Run a single migration,
|
|
115
|
+
* Run a single migration, in a transaction where the dialect has one for it and the migration has not
|
|
116
|
+
* declared `transaction: false` - the opt-out a statement an engine refuses inside one needs.
|
|
116
117
|
*/
|
|
117
118
|
async runMigration(migration, direction) {
|
|
118
119
|
const startTime = Date.now();
|
|
119
120
|
return this.target.withSession(async ({ querier, transaction }) => {
|
|
120
121
|
try {
|
|
121
122
|
this.logger.logMigration(`${direction === 'up' ? 'Running' : 'Reverting'} migration: ${migration.name}`);
|
|
122
|
-
|
|
123
|
+
const work = async () => {
|
|
123
124
|
if (direction === 'up') {
|
|
124
125
|
await migration.up(querier);
|
|
125
126
|
await this.storage.logWithQuerier(querier, migration.name);
|
|
@@ -128,7 +129,8 @@ export class Migrator {
|
|
|
128
129
|
await migration.down(querier);
|
|
129
130
|
await this.storage.unlogWithQuerier(querier, migration.name);
|
|
130
131
|
}
|
|
131
|
-
}
|
|
132
|
+
};
|
|
133
|
+
await (migration.transaction === false ? work() : transaction(work));
|
|
132
134
|
const duration = Date.now() - startTime;
|
|
133
135
|
this.logger.logMigration(`Migration ${migration.name} ${direction === 'up' ? 'applied' : 'reverted'} in ${duration}ms`);
|
|
134
136
|
return {
|
|
@@ -33,20 +33,15 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
33
33
|
columnOf<E>(meta: EntityMeta<E>, key: string): string;
|
|
34
34
|
where<E extends Document>(entity: Type<E>, where?: QueryWhere<E>, opts?: QueryOptions): Filter<E>;
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* The stages every pipeline starts with: the `$lookup` each relation condition needs, into a temporary
|
|
37
|
+
* field so `$or` keeps its meaning, the `$match` reading them, and the temporaries taken back out. Each
|
|
38
|
+
* relation aggregate the `$where` or `named` reads is left on the document, once, under its column.
|
|
38
39
|
*/
|
|
39
|
-
|
|
40
|
-
readonly stages: MongoAggregationPipelineEntry<Document>[];
|
|
41
|
-
readonly filter: Filter<E>;
|
|
42
|
-
readonly unset: string[];
|
|
43
|
-
};
|
|
44
|
-
/** Whether a `$where` constrains any relation, and so needs the aggregation path rather than a cursor. */
|
|
45
|
-
constrainsRelations<E extends Document>(entity: Type<E>, where: QueryWhere<E> | undefined): boolean;
|
|
40
|
+
matchStages<E extends Document>(entity: Type<E>, where?: QueryWhere<E>, opts?: QueryOptions, named?: readonly string[]): MongoAggregationPipelineEntry<Document>[];
|
|
46
41
|
/**
|
|
47
42
|
* Renders a `$where` tree without applying entity filters (used for same-scope group-operator
|
|
48
|
-
* recursion).
|
|
49
|
-
* given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
43
|
+
* recursion). A relation, or a relation aggregate, needs `$lookup` stages, so it is only accepted
|
|
44
|
+
* when `lookups` is given - a plain `find`/`updateMany` filter has nowhere to put them.
|
|
50
45
|
*/
|
|
51
46
|
protected renderFilter<E extends Document>(entity: Type<E>, where?: QueryWhere<E>, lookups?: RelationLookups): Filter<E>;
|
|
52
47
|
/**
|
|
@@ -139,9 +134,29 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
139
134
|
readonly stages: MongoAggregationPipelineEntry<Document>[];
|
|
140
135
|
readonly fields: string[];
|
|
141
136
|
};
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
/** The
|
|
137
|
+
/** Whether a read answers with a relation aggregate, which only the pipeline can build. */
|
|
138
|
+
readsAggregates<E extends Document>(entity: Type<E>, q: Query<E>): boolean;
|
|
139
|
+
/** The relation aggregates a read projects or sorts by; its `$where` puts its own on the document. */
|
|
140
|
+
private aggregateKeys;
|
|
141
|
+
/**
|
|
142
|
+
* The relation aggregate `key` computes, put on the document under its column by the stages
|
|
143
|
+
* {@link aggregateStages} builds from the same spec SQL renders as a subquery. Once however many
|
|
144
|
+
* clauses read it; a key computing none adds nothing.
|
|
145
|
+
*/
|
|
146
|
+
private appendAggregateField;
|
|
147
|
+
/**
|
|
148
|
+
* One relation aggregate on the document under `field`: the correlated lookup that reads the related
|
|
149
|
+
* rows - narrowed, ordered and capped as the spec says - ending in the tally or total it wants, and
|
|
150
|
+
* the `$addFields` reading that back, `0` or `null` where the lookup matched nothing.
|
|
151
|
+
*
|
|
152
|
+
* Every aggregate MongoDB answers is built here: a `$count` a query asks for, an ordering by one, and
|
|
153
|
+
* a field a `computed` declares, which is the same spec the SQL dialects render as one subquery.
|
|
154
|
+
*/
|
|
155
|
+
private aggregateStages;
|
|
156
|
+
/**
|
|
157
|
+
* The value a lookup left in `temp`, which holds no row at all where nothing matched: `0` for the
|
|
158
|
+
* aggregates that count something, and `null` for the ones with no value to report.
|
|
159
|
+
*/
|
|
145
160
|
private tally;
|
|
146
161
|
/**
|
|
147
162
|
* The lookups reading each to-many a query populates, and the tally of each `$count`, onto the fields
|
|
@@ -232,13 +247,28 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
232
247
|
/**
|
|
233
248
|
* Build MongoDB aggregation pipeline stages from a QueryAggregate.
|
|
234
249
|
*/
|
|
235
|
-
buildAggregateStages<E extends Document, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions):
|
|
250
|
+
buildAggregateStages<E extends Document, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): MongoAggregationPipelineEntry<Document>[];
|
|
236
251
|
/**
|
|
237
|
-
* Resolve parsed group entries into the `_id` keys and accumulators of a `$group` stage
|
|
238
|
-
* `
|
|
239
|
-
*
|
|
252
|
+
* Resolve parsed group entries into the `_id` keys and accumulators of a `$group` stage, and the
|
|
253
|
+
* `columns` its `$project` reads each result from: a group key out of `_id`, a DISTINCT set by its
|
|
254
|
+
* size, and a `$sum` as null where it read no value, as SQL answers it. `named` is every field it reads,
|
|
255
|
+
* filters included, so each relation aggregate among them is put on the document first.
|
|
240
256
|
*/
|
|
241
257
|
private buildGroupSpec;
|
|
258
|
+
/** `1` where `ref` holds a value, `0` where it is null or missing, which an expression tells apart. */
|
|
259
|
+
private static countOf;
|
|
260
|
+
/** Whether `ref` is null or missing: an expression compares a missing field as neither. */
|
|
261
|
+
private static isNullExpr;
|
|
262
|
+
/** The field a grouped path reads, on the document or on the joined one its lookup unwound. */
|
|
263
|
+
private groupedPath;
|
|
264
|
+
/**
|
|
265
|
+
* An aggregate's own `$where` as the expression a `$cond` tests, which a query filter is not: the
|
|
266
|
+
* comparisons and the logical operators translate, anything else is refused by name. `named` gathers
|
|
267
|
+
* the fields it reads, so a relation aggregate among them is on the document first.
|
|
268
|
+
*/
|
|
269
|
+
private whereExpression;
|
|
270
|
+
/** One field's condition as an expression: a value it equals, a list it is in, or a map of comparisons. */
|
|
271
|
+
private fieldExpression;
|
|
242
272
|
private buildHavingFilter;
|
|
243
273
|
/**
|
|
244
274
|
* Separate vector sort entries from regular sort entries.
|
|
@@ -249,7 +279,7 @@ export declare class MongoDialect extends AbstractDialect {
|
|
|
249
279
|
* Build a `$vectorSearch` aggregation pipeline stage.
|
|
250
280
|
* Merges `$where` into `$vectorSearch.filter` for optimal pre-filtering.
|
|
251
281
|
*/
|
|
252
|
-
buildVectorSearchStage<E extends Document>(entity: Type<E>, key: string, search: QueryVectorSearch, where: QueryWhere<E> | undefined, limit: number, opts?: QueryOptions, candidates?: number):
|
|
282
|
+
buildVectorSearchStage<E extends Document>(entity: Type<E>, key: string, search: QueryVectorSearch, where: QueryWhere<E> | undefined, limit: number, opts?: QueryOptions, candidates?: number): MongoAggregationPipelineEntry<Document>;
|
|
253
283
|
}
|
|
254
284
|
export type MongoAggregationPipelineEntry<E extends Document> = {
|
|
255
285
|
$lookup?: MongoAggregationLookup;
|