@travetto/model-sql 5.0.0-rc.1 → 5.0.0-rc.10
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/package.json +7 -7
- package/src/config.ts +2 -2
- package/src/connection/base.ts +4 -5
- package/src/connection/decorator.ts +23 -19
- package/src/dialect/base.ts +30 -53
- package/src/internal/util.ts +19 -32
- package/src/service.ts +21 -16
- package/src/table-manager.ts +1 -1
- package/support/test/query.ts +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-sql",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.10",
|
|
4
4
|
"description": "SQL backing for the travetto model module, with real-time modeling support for SQL schemas.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sql",
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
"directory": "module/model-sql"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@travetto/config": "^5.0.0-rc.
|
|
31
|
-
"@travetto/context": "^5.0.0-rc.
|
|
32
|
-
"@travetto/model": "^5.0.0-rc.
|
|
33
|
-
"@travetto/model-query": "^5.0.0-rc.
|
|
30
|
+
"@travetto/config": "^5.0.0-rc.10",
|
|
31
|
+
"@travetto/context": "^5.0.0-rc.9",
|
|
32
|
+
"@travetto/model": "^5.0.0-rc.10",
|
|
33
|
+
"@travetto/model-query": "^5.0.0-rc.10"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/command": "^5.0.0-rc.
|
|
37
|
-
"@travetto/test": "^5.0.0-rc.
|
|
36
|
+
"@travetto/command": "^5.0.0-rc.9",
|
|
37
|
+
"@travetto/test": "^5.0.0-rc.9"
|
|
38
38
|
},
|
|
39
39
|
"peerDependenciesMeta": {
|
|
40
40
|
"@travetto/command": {
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Config } from '@travetto/config';
|
|
2
|
+
import { asFull } from '@travetto/runtime';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* SQL Model Config
|
|
@@ -40,6 +41,5 @@ export class SQLModelConfig<T extends {} = {}> {
|
|
|
40
41
|
/**
|
|
41
42
|
* Raw client options
|
|
42
43
|
*/
|
|
43
|
-
|
|
44
|
-
options: T = {} as T;
|
|
44
|
+
options: T = asFull({});
|
|
45
45
|
}
|
package/src/connection/base.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Util } from '@travetto/
|
|
1
|
+
import { castTo, Util } from '@travetto/runtime';
|
|
2
2
|
import { AsyncContext } from '@travetto/context';
|
|
3
3
|
|
|
4
4
|
const ContextActiveⲐ: unique symbol = Symbol.for('@travetto/model:sql-active');
|
|
@@ -95,13 +95,12 @@ export abstract class Connection<C = unknown> {
|
|
|
95
95
|
* @param op
|
|
96
96
|
* @param args
|
|
97
97
|
*/
|
|
98
|
-
async * iterateWithActive<R>(op: () =>
|
|
98
|
+
async * iterateWithActive<R>(op: () => AsyncIterable<R>): AsyncIterable<R> {
|
|
99
99
|
if (this.active) {
|
|
100
100
|
yield* op();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
const self = this;
|
|
103
|
+
const self = castTo<Connection>(this);
|
|
105
104
|
yield* this.context.iterate(async function* () {
|
|
106
105
|
try {
|
|
107
106
|
self.context.set(ContextActiveⲐ, await self.acquire());
|
|
@@ -117,7 +116,7 @@ export abstract class Connection<C = unknown> {
|
|
|
117
116
|
/**
|
|
118
117
|
* Run a function within a valid sql transaction. Relies on @travetto/context.
|
|
119
118
|
*/
|
|
120
|
-
async runWithTransaction<R>(mode: TransactionType, op: () => R): Promise<R> {
|
|
119
|
+
async runWithTransaction<R>(mode: TransactionType, op: () => Promise<R>): Promise<R> {
|
|
121
120
|
if (this.activeTx) {
|
|
122
121
|
if (mode === 'isolated' || mode === 'force') {
|
|
123
122
|
const txId = mode === 'isolated' ? `tx${Util.uuid()}` : undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncItrMethodDescriptor, AsyncMethodDescriptor } from '@travetto/runtime';
|
|
1
2
|
import { Connection, TransactionType } from './base';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -10,38 +11,41 @@ export interface ConnectionAware<C = unknown> {
|
|
|
10
11
|
/**
|
|
11
12
|
* Decorator to ensure a method runs with a valid connection
|
|
12
13
|
*/
|
|
13
|
-
export function Connected
|
|
14
|
-
return function
|
|
14
|
+
export function Connected() {
|
|
15
|
+
return function <T extends { conn?: Connection }>(
|
|
16
|
+
target: T, prop: string | symbol, desc: AsyncMethodDescriptor<T>
|
|
17
|
+
): void {
|
|
15
18
|
const og = desc.value!;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} as typeof og;
|
|
19
|
+
desc.value = function (...args: unknown[]): ReturnType<typeof og> {
|
|
20
|
+
return this.conn!.runWithActive(() => og.call(this, ...args));
|
|
21
|
+
};
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
26
|
* Decorator to ensure a method runs with a valid connection
|
|
25
27
|
*/
|
|
26
|
-
export function ConnectedIterator
|
|
27
|
-
return function
|
|
28
|
+
export function ConnectedIterator() {
|
|
29
|
+
return function <T extends { conn?: Connection }>(
|
|
30
|
+
target: T, prop: string | symbol, desc: AsyncItrMethodDescriptor<T>
|
|
31
|
+
): void {
|
|
28
32
|
const og = desc.value!;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} as typeof og;
|
|
33
|
+
desc.value = async function* (...args: unknown[]): ReturnType<typeof og> {
|
|
34
|
+
yield* this.conn!.iterateWithActive(() => og.call(this, ...args));
|
|
35
|
+
};
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
/**
|
|
37
40
|
* Decorator to ensure a method runs with a valid transaction
|
|
38
41
|
*/
|
|
39
|
-
export function Transactional
|
|
40
|
-
return function
|
|
42
|
+
export function Transactional(mode: TransactionType = 'required') {
|
|
43
|
+
return function <T extends { conn?: Connection }>(
|
|
44
|
+
target: unknown, prop: string | symbol, desc: AsyncMethodDescriptor<T>
|
|
45
|
+
): void {
|
|
41
46
|
const og = desc.value!;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
} as typeof og;
|
|
47
|
+
desc.value = function (...args: unknown[]): ReturnType<typeof og> {
|
|
48
|
+
return this.conn!.runWithTransaction(mode, () => og.call(this, ...args));
|
|
49
|
+
};
|
|
46
50
|
};
|
|
47
|
-
}
|
|
51
|
+
}
|
package/src/dialect/base.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable @stylistic/indent */
|
|
2
2
|
import { DataUtil, SchemaRegistry, FieldConfig, Schema } from '@travetto/schema';
|
|
3
|
-
import { Class, AppError, TypedObject } from '@travetto/
|
|
3
|
+
import { Class, AppError, TypedObject, describeFunction, TimeUtil, castTo, castKey } from '@travetto/runtime';
|
|
4
4
|
import { SelectClause, Query, SortClause, WhereClause, RetainFields } from '@travetto/model-query';
|
|
5
5
|
import { BulkResponse, IndexConfig } from '@travetto/model';
|
|
6
6
|
import { PointImpl } from '@travetto/model-query/src/internal/model/point';
|
|
@@ -11,13 +11,6 @@ import { SQLUtil, VisitStack } from '../internal/util';
|
|
|
11
11
|
import { DeleteWrapper, InsertWrapper, DialectState } from '../internal/types';
|
|
12
12
|
import { Connection } from '../connection/base';
|
|
13
13
|
|
|
14
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
15
|
-
const has$And = (o: unknown): o is ({ $and: WhereClause<unknown>[] }) => !!o && '$and' in (o as object);
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
17
|
-
const has$Or = (o: unknown): o is ({ $or: WhereClause<unknown>[] }) => !!o && '$or' in (o as object);
|
|
18
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
19
|
-
const has$Not = (o: unknown): o is ({ $not: WhereClause<unknown> }) => !!o && '$not' in (o as object);
|
|
20
|
-
|
|
21
14
|
interface Alias {
|
|
22
15
|
alias: string;
|
|
23
16
|
path: VisitStack[];
|
|
@@ -196,16 +189,18 @@ export abstract class SQLDialect implements DialectState {
|
|
|
196
189
|
const src = DataUtil.toRegex(value).source.replace(/\\b/g, this.regexWordBoundary);
|
|
197
190
|
return this.quote(src);
|
|
198
191
|
} else {
|
|
199
|
-
|
|
200
|
-
return this.quote(value as string);
|
|
192
|
+
return this.quote(castTo(value));
|
|
201
193
|
}
|
|
202
194
|
} else if (conf.type === Boolean) {
|
|
203
195
|
return `${value ? 'TRUE' : 'FALSE'}`;
|
|
204
196
|
} else if (conf.type === Number) {
|
|
205
197
|
return `${value}`;
|
|
206
198
|
} else if (conf.type === Date) {
|
|
207
|
-
|
|
208
|
-
|
|
199
|
+
if (typeof value === 'string' && TimeUtil.isTimeSpan(value)) {
|
|
200
|
+
return this.resolveDateValue(TimeUtil.fromNow(value));
|
|
201
|
+
} else {
|
|
202
|
+
return this.resolveDateValue(DataUtil.coerceType(value, Date, true));
|
|
203
|
+
}
|
|
209
204
|
} else if (conf.type === PointImpl && Array.isArray(value)) {
|
|
210
205
|
return `point(${value[0]},${value[1]})`;
|
|
211
206
|
} else if (conf.type === Object) {
|
|
@@ -276,8 +271,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
276
271
|
* Delete query and return count removed
|
|
277
272
|
*/
|
|
278
273
|
async deleteAndGetCount<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
279
|
-
|
|
280
|
-
const { count } = await this.executeSQL(this.getDeleteSQL(SQLUtil.classToStack(cls), query.where as WhereClause<T>));
|
|
274
|
+
const { count } = await this.executeSQL<T>(this.getDeleteSQL(SQLUtil.classToStack(cls), query.where));
|
|
281
275
|
return count;
|
|
282
276
|
}
|
|
283
277
|
|
|
@@ -285,7 +279,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
285
279
|
* Get the count for a given query
|
|
286
280
|
*/
|
|
287
281
|
async getCountForQuery<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
288
|
-
const { records } = await this.executeSQL<{ total: number }>(this.getQueryCountSQL(cls, query));
|
|
282
|
+
const { records } = await this.executeSQL<{ total: number }>(this.getQueryCountSQL(cls, query.where));
|
|
289
283
|
const [record] = records;
|
|
290
284
|
return Total.from(record).total;
|
|
291
285
|
}
|
|
@@ -302,9 +296,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
302
296
|
* Add a sql column
|
|
303
297
|
*/
|
|
304
298
|
getAddColumnSQL(stack: VisitStack[]): string {
|
|
305
|
-
const field = stack[stack.length - 1];
|
|
306
|
-
|
|
307
|
-
return `ALTER TABLE ${this.parentTable(stack)} ADD COLUMN ${this.getColumnDefinition(field as FieldConfig)};`;
|
|
299
|
+
const field: FieldConfig = castTo(stack[stack.length - 1]);
|
|
300
|
+
return `ALTER TABLE ${this.parentTable(stack)} ADD COLUMN ${this.getColumnDefinition(field)};`;
|
|
308
301
|
}
|
|
309
302
|
|
|
310
303
|
/**
|
|
@@ -456,8 +449,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
456
449
|
break;
|
|
457
450
|
}
|
|
458
451
|
case '$regex': {
|
|
459
|
-
|
|
460
|
-
const re = DataUtil.toRegex(v as string);
|
|
452
|
+
const re = DataUtil.toRegex(castTo(v));
|
|
461
453
|
const src = re.source;
|
|
462
454
|
const ins = re.flags && re.flags.includes('i');
|
|
463
455
|
|
|
@@ -506,8 +498,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
506
498
|
break;
|
|
507
499
|
}
|
|
508
500
|
case '$lt': case '$gt': case '$gte': case '$lte': {
|
|
509
|
-
|
|
510
|
-
const subItems = TypedObject.keys(top as typeof SQL_OPS)
|
|
501
|
+
const subItems = TypedObject.keys(castTo<typeof SQL_OPS>(top))
|
|
511
502
|
.map(ssk => `${sPath} ${SQL_OPS[ssk]} ${resolve(top[ssk])}`);
|
|
512
503
|
items.push(subItems.length > 1 ? `(${subItems.join(` ${SQL_OPS.$and} `)})` : subItems[0]);
|
|
513
504
|
break;
|
|
@@ -537,11 +528,11 @@ export abstract class SQLDialect implements DialectState {
|
|
|
537
528
|
getWhereGroupingSQL<T>(cls: Class<T>, o: WhereClause<T>): string {
|
|
538
529
|
const SQL_OPS = this.SQL_OPS;
|
|
539
530
|
|
|
540
|
-
if (has$And(o)) {
|
|
531
|
+
if (ModelQueryUtil.has$And(o)) {
|
|
541
532
|
return `(${o.$and.map(x => this.getWhereGroupingSQL<T>(cls, x)).join(` ${SQL_OPS.$and} `)})`;
|
|
542
|
-
} else if (has$Or(o)) {
|
|
533
|
+
} else if (ModelQueryUtil.has$Or(o)) {
|
|
543
534
|
return `(${o.$or.map(x => this.getWhereGroupingSQL<T>(cls, x)).join(` ${SQL_OPS.$or} `)})`;
|
|
544
|
-
} else if (has$Not(o)) {
|
|
535
|
+
} else if (ModelQueryUtil.has$Not(o)) {
|
|
545
536
|
return `${SQL_OPS.$not} (${this.getWhereGroupingSQL<T>(cls, o.$not)})`;
|
|
546
537
|
} else {
|
|
547
538
|
return this.getWhereFieldSQL(SQLUtil.classToStack(cls), o);
|
|
@@ -554,8 +545,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
554
545
|
getWhereSQL<T>(cls: Class<T>, where?: WhereClause<T>): string {
|
|
555
546
|
return !where || !Object.keys(where).length ?
|
|
556
547
|
'' :
|
|
557
|
-
|
|
558
|
-
`WHERE ${this.getWhereGroupingSQL(cls, where as WhereClause<T>)}`;
|
|
548
|
+
`WHERE ${this.getWhereGroupingSQL(cls, castTo(where))}`;
|
|
559
549
|
}
|
|
560
550
|
|
|
561
551
|
/**
|
|
@@ -624,15 +614,13 @@ LEFT OUTER JOIN ${from} ON
|
|
|
624
614
|
/**
|
|
625
615
|
* Generate full query
|
|
626
616
|
*/
|
|
627
|
-
getQuerySQL<T>(cls: Class<T>, query: Query<T>): string {
|
|
617
|
+
getQuerySQL<T>(cls: Class<T>, query: Query<T>, where?: WhereClause<T>): string {
|
|
628
618
|
const sortFields = !query.sort ?
|
|
629
619
|
'' :
|
|
630
620
|
SQLUtil.orderBy(cls, query.sort)
|
|
631
621
|
.map(x => this.resolveName(x.stack))
|
|
632
622
|
.join(', ');
|
|
633
623
|
|
|
634
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
635
|
-
const where = query.where as WhereClause<T>;
|
|
636
624
|
return `
|
|
637
625
|
${this.getSelectSQL(cls, query.select)}
|
|
638
626
|
${this.getFromSQL(cls)}
|
|
@@ -647,14 +635,13 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
647
635
|
const parent = stack.length > 1;
|
|
648
636
|
const array = parent && config.array;
|
|
649
637
|
|
|
650
|
-
if (config.type &&
|
|
638
|
+
if (config.type && describeFunction(config.type)?.synthetic) {
|
|
651
639
|
throw new AppError(`Cannot create SQL tables for synthetic types, please convert ${SQLUtil.buildPath(stack)} to a concrete class`);
|
|
652
640
|
}
|
|
653
641
|
|
|
654
642
|
const fields = SchemaRegistry.has(config.type) ?
|
|
655
643
|
[...SQLUtil.getFieldsByLocation(stack).local] :
|
|
656
|
-
|
|
657
|
-
(array ? [config as FieldConfig] : []);
|
|
644
|
+
(array ? [castTo<FieldConfig>(config)] : []);
|
|
658
645
|
|
|
659
646
|
if (!parent) {
|
|
660
647
|
let idField = fields.find(x => x.name === this.idField.name);
|
|
@@ -674,7 +661,6 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
674
661
|
.filter(x => !!x.trim())
|
|
675
662
|
.join(',\n ');
|
|
676
663
|
|
|
677
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
678
664
|
const out = `
|
|
679
665
|
CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
680
666
|
${fieldSql}${fieldSql.length ? ',' : ''}
|
|
@@ -734,8 +720,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
734
720
|
if (DataUtil.isPlainObject(val)) {
|
|
735
721
|
throw new Error('Unable to supported nested fields for indices');
|
|
736
722
|
}
|
|
737
|
-
|
|
738
|
-
return [key as string, typeof val === 'number' ? val === 1 : (!!val)];
|
|
723
|
+
return [castTo(key), typeof val === 'number' ? val === 1 : (!!val)];
|
|
739
724
|
});
|
|
740
725
|
const constraint = `idx_${table}_${fields.map(([f]) => f).join('_')}`;
|
|
741
726
|
return `CREATE ${idx.type === 'unique' ? 'UNIQUE ' : ''}INDEX ${constraint} ON ${this.ident(table)} (${fields
|
|
@@ -809,8 +794,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
809
794
|
return;
|
|
810
795
|
}
|
|
811
796
|
|
|
812
|
-
|
|
813
|
-
const matrix = instances.map(inst => columns.map(c => this.resolveValue(c, (inst.value as Record<string, string>)[c.name])));
|
|
797
|
+
const matrix = instances.map(inst => columns.map(c => this.resolveValue(c, castTo<Record<string, unknown>>(inst.value)[c.name])));
|
|
814
798
|
|
|
815
799
|
columnNames.push(this.pathField.name);
|
|
816
800
|
if (hasParent) {
|
|
@@ -900,13 +884,11 @@ ${orderBy};`;
|
|
|
900
884
|
/**
|
|
901
885
|
* Get COUNT(1) query
|
|
902
886
|
*/
|
|
903
|
-
getQueryCountSQL<T>(cls: Class<T>,
|
|
904
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
905
|
-
const where = query.where as WhereClause<T>;
|
|
887
|
+
getQueryCountSQL<T>(cls: Class<T>, where?: WhereClause<T>): string {
|
|
906
888
|
return `
|
|
907
889
|
SELECT COUNT(DISTINCT ${this.rootAlias}.id) as total
|
|
908
890
|
${this.getFromSQL(cls)}
|
|
909
|
-
${this.getWhereSQL(cls, where)}`;
|
|
891
|
+
${this.getWhereSQL(cls, where!)}`;
|
|
910
892
|
}
|
|
911
893
|
|
|
912
894
|
async fetchDependents<T>(cls: Class<T>, items: T[], select?: SelectClause<T>): Promise<T[]> {
|
|
@@ -927,15 +909,12 @@ ${this.getWhereSQL(cls, where)}`;
|
|
|
927
909
|
const top = stack[stack.length - 1];
|
|
928
910
|
const ids = Object.keys(top);
|
|
929
911
|
const selectTop = selectStack[selectStack.length - 1];
|
|
930
|
-
|
|
931
|
-
const
|
|
932
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
933
|
-
const subSelectTop: SelectClause<T> | undefined = selectTop?.[fieldKey] as SelectClause<T> | undefined;
|
|
912
|
+
const fieldKey = castKey<RetainFields<T>>(config.name);
|
|
913
|
+
const subSelectTop: SelectClause<T> | undefined = castTo(selectTop?.[fieldKey]);
|
|
934
914
|
|
|
935
915
|
// See if a selection exists at all
|
|
936
916
|
const sel: FieldConfig[] = subSelectTop ? fields
|
|
937
|
-
|
|
938
|
-
.filter(f => typeof subSelectTop === 'object' && subSelectTop[f.name as typeof fieldKey] === 1)
|
|
917
|
+
.filter(f => typeof subSelectTop === 'object' && subSelectTop[castTo<typeof fieldKey>(f.name)] === 1)
|
|
939
918
|
: [];
|
|
940
919
|
|
|
941
920
|
if (sel.length) {
|
|
@@ -1018,12 +997,10 @@ ${this.getWhereSQL(cls, where)}`;
|
|
|
1018
997
|
|
|
1019
998
|
await Promise.all([
|
|
1020
999
|
...upserts.filter(x => x.stack.length === 1).map(i =>
|
|
1021
|
-
|
|
1022
|
-
this.deleteByIds(i.stack, i.records.map(v => (v.value as Record<string, string>)[idx]))
|
|
1000
|
+
this.deleteByIds(i.stack, i.records.map(v => castTo<Record<string, string>>(v.value)[idx]))
|
|
1023
1001
|
),
|
|
1024
1002
|
...updates.filter(x => x.stack.length === 1).map(i =>
|
|
1025
|
-
|
|
1026
|
-
this.deleteByIds(i.stack, i.records.map(v => (v.value as Record<string, string>)[idx]))
|
|
1003
|
+
this.deleteByIds(i.stack, i.records.map(v => castTo<Record<string, string>>(v.value)[idx]))
|
|
1027
1004
|
),
|
|
1028
1005
|
]);
|
|
1029
1006
|
}
|
package/src/internal/util.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class, TypedObject } from '@travetto/
|
|
1
|
+
import { castKey, castTo, Class, TypedObject } from '@travetto/runtime';
|
|
2
2
|
import { SelectClause, SortClause } from '@travetto/model-query';
|
|
3
3
|
import { ModelRegistry, ModelType, OptionalId } from '@travetto/model';
|
|
4
4
|
import { SchemaRegistry, ClassConfig, FieldConfig, DataUtil } from '@travetto/schema';
|
|
@@ -53,11 +53,9 @@ export class SQLUtil {
|
|
|
53
53
|
o[k] = this.cleanResults(dct, o[k]);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
return { ...o } as unknown as U[];
|
|
56
|
+
return castTo({ ...o });
|
|
58
57
|
} else {
|
|
59
|
-
|
|
60
|
-
return o as unknown as U;
|
|
58
|
+
return castTo(o);
|
|
61
59
|
}
|
|
62
60
|
}
|
|
63
61
|
|
|
@@ -73,8 +71,7 @@ export class SQLUtil {
|
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
if (!cls) { // If a simple type, it is it's own field
|
|
76
|
-
|
|
77
|
-
const field = { ...top } as FieldConfig;
|
|
74
|
+
const field: FieldConfig = castTo({ ...top });
|
|
78
75
|
return {
|
|
79
76
|
local: [field], localMap: { [field.name]: field },
|
|
80
77
|
foreign: [], foreignMap: {}
|
|
@@ -184,8 +181,7 @@ export class SQLUtil {
|
|
|
184
181
|
},
|
|
185
182
|
onSub: (config) => {
|
|
186
183
|
const { config: field } = config;
|
|
187
|
-
|
|
188
|
-
const topObj = pathObj[pathObj.length - 1] as Record<string, unknown>;
|
|
184
|
+
const topObj: Record<string, unknown> = castTo(pathObj[pathObj.length - 1]);
|
|
189
185
|
const top = config.path[config.path.length - 1];
|
|
190
186
|
|
|
191
187
|
if (field.name in topObj) {
|
|
@@ -213,8 +209,7 @@ export class SQLUtil {
|
|
|
213
209
|
},
|
|
214
210
|
onSimple: (config) => {
|
|
215
211
|
const { config: field } = config;
|
|
216
|
-
|
|
217
|
-
const topObj = pathObj[pathObj.length - 1] as Record<string, unknown>;
|
|
212
|
+
const topObj: Record<string, unknown> = castTo(pathObj[pathObj.length - 1]);
|
|
218
213
|
const value = topObj[field.name];
|
|
219
214
|
return handler.onSimple({ ...config, value });
|
|
220
215
|
}
|
|
@@ -234,16 +229,14 @@ export class SQLUtil {
|
|
|
234
229
|
let toGet = new Set<string>();
|
|
235
230
|
|
|
236
231
|
for (const [k, v] of TypedObject.entries(select)) {
|
|
237
|
-
|
|
238
|
-
const sk = k as string;
|
|
239
|
-
if (!DataUtil.isPlainObject(select[k]) && localMap[sk]) {
|
|
232
|
+
if (typeof k === 'string' && !DataUtil.isPlainObject(select[k]) && localMap[k]) {
|
|
240
233
|
if (!v) {
|
|
241
234
|
if (toGet.size === 0) {
|
|
242
235
|
toGet = new Set(SchemaRegistry.get(cls).views[AllViewⲐ].fields);
|
|
243
236
|
}
|
|
244
|
-
toGet.delete(
|
|
237
|
+
toGet.delete(k);
|
|
245
238
|
} else {
|
|
246
|
-
toGet.add(
|
|
239
|
+
toGet.add(k);
|
|
247
240
|
}
|
|
248
241
|
}
|
|
249
242
|
}
|
|
@@ -268,8 +261,7 @@ export class SQLUtil {
|
|
|
268
261
|
} else {
|
|
269
262
|
stack.push(field);
|
|
270
263
|
schema = SchemaRegistry.get(field.type);
|
|
271
|
-
|
|
272
|
-
cl = val as Record<string, unknown>;
|
|
264
|
+
cl = castTo(val);
|
|
273
265
|
}
|
|
274
266
|
}
|
|
275
267
|
return found;
|
|
@@ -283,29 +275,24 @@ export class SQLUtil {
|
|
|
283
275
|
if (field) {
|
|
284
276
|
const isSimple = SchemaRegistry.has(field.type);
|
|
285
277
|
for (const el of v) {
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
const root = (parent as Record<string, Record<string, unknown>>)[parentKey];
|
|
278
|
+
const parentKey: string = castTo(el[castKey<T>(dct.parentPathField.name)]);
|
|
279
|
+
const root = castTo<Record<string, Record<string, unknown>>>(parent)[parentKey];
|
|
280
|
+
const fieldKey = castKey<(typeof root) | T>(field.name);
|
|
290
281
|
if (field.array) {
|
|
291
|
-
if (!root[
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
296
|
-
(root[field.name] as unknown[]).push(isSimple ? el : el[field.name as keyof T]);
|
|
282
|
+
if (!root[fieldKey]) {
|
|
283
|
+
root[fieldKey] = [isSimple ? el : el[fieldKey]];
|
|
284
|
+
} else if (Array.isArray(root[fieldKey])) {
|
|
285
|
+
root[fieldKey].push(isSimple ? el : el[fieldKey]);
|
|
297
286
|
}
|
|
298
287
|
} else {
|
|
299
|
-
|
|
300
|
-
root[field.name] = isSimple ? el : el[field.name as keyof T];
|
|
288
|
+
root[fieldKey] = isSimple ? el : el[fieldKey];
|
|
301
289
|
}
|
|
302
290
|
}
|
|
303
291
|
}
|
|
304
292
|
|
|
305
293
|
const mapping: Record<string, T> = {};
|
|
306
294
|
for (const el of v) {
|
|
307
|
-
|
|
308
|
-
const key = el[dct.pathField.name as keyof T];
|
|
295
|
+
const key = el[castKey<T>(dct.pathField.name)];
|
|
309
296
|
if (typeof key === 'string') {
|
|
310
297
|
mapping[key] = el;
|
|
311
298
|
}
|
package/src/service.ts
CHANGED
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
NotFoundError, ModelRegistry, ExistsError, OptionalId,
|
|
5
5
|
ModelIdSource
|
|
6
6
|
} from '@travetto/model';
|
|
7
|
-
import { Class } from '@travetto/
|
|
7
|
+
import { castTo, Class } from '@travetto/runtime';
|
|
8
8
|
import { DataUtil, SchemaChange } from '@travetto/schema';
|
|
9
9
|
import { AsyncContext } from '@travetto/context';
|
|
10
10
|
import { Injectable } from '@travetto/di';
|
|
11
11
|
import {
|
|
12
12
|
ModelQuery, ModelQueryCrudSupport, ModelQueryFacetSupport, ModelQuerySupport,
|
|
13
|
-
PageableModelQuery, ValidStringFields,
|
|
13
|
+
PageableModelQuery, ValidStringFields, WhereClauseRaw, QueryVerifier
|
|
14
14
|
} from '@travetto/model-query';
|
|
15
15
|
|
|
16
16
|
import { ModelQueryUtil } from '@travetto/model-query/src/internal/service/query';
|
|
@@ -95,8 +95,7 @@ export class SQLModelService implements
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
async #deleteRaw<T extends ModelType>(cls: Class<T>, id: string, where?: WhereClauseRaw<T>, checkExpiry = true): Promise<void> {
|
|
98
|
-
|
|
99
|
-
((where ??= {}) as WhereClauseRaw<ModelType>).id = id;
|
|
98
|
+
castTo<WhereClauseRaw<ModelType>>(where ??= {}).id = id;
|
|
100
99
|
|
|
101
100
|
const count = await this.#dialect.deleteAndGetCount<ModelType>(cls, {
|
|
102
101
|
where: ModelQueryUtil.getWhereClause(cls, where, checkExpiry)
|
|
@@ -191,8 +190,7 @@ export class SQLModelService implements
|
|
|
191
190
|
|
|
192
191
|
@Connected()
|
|
193
192
|
async get<T extends ModelType>(cls: Class<T>, id: string): Promise<T> {
|
|
194
|
-
const
|
|
195
|
-
const res = await this.query(cls, { where });
|
|
193
|
+
const res = await this.query(cls, { where: castTo({ id }) });
|
|
196
194
|
if (res.length === 1) {
|
|
197
195
|
return await ModelCrudUtil.load(cls, res[0]);
|
|
198
196
|
}
|
|
@@ -250,7 +248,8 @@ export class SQLModelService implements
|
|
|
250
248
|
|
|
251
249
|
@Connected()
|
|
252
250
|
async query<T extends ModelType>(cls: Class<T>, query: PageableModelQuery<T>): Promise<T[]> {
|
|
253
|
-
|
|
251
|
+
await QueryVerifier.verify(cls, query);
|
|
252
|
+
const { records: res } = await this.#exec<T>(this.#dialect.getQuerySQL(cls, query, ModelQueryUtil.getWhereClause(cls, query.where)));
|
|
254
253
|
if (ModelRegistry.has(cls)) {
|
|
255
254
|
await this.#dialect.fetchDependents(cls, res, query && query.select);
|
|
256
255
|
}
|
|
@@ -267,35 +266,40 @@ export class SQLModelService implements
|
|
|
267
266
|
|
|
268
267
|
@Connected()
|
|
269
268
|
async queryCount<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
|
|
270
|
-
|
|
269
|
+
await QueryVerifier.verify(cls, query);
|
|
270
|
+
const { records } = await this.#exec<{ total: string | number }>(this.#dialect.getQueryCountSQL(cls, ModelQueryUtil.getWhereClause(cls, query.where)));
|
|
271
271
|
return +records[0].total;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
@Connected()
|
|
275
275
|
@Transactional()
|
|
276
276
|
async updateOneWithQuery<T extends ModelType>(cls: Class<T>, item: T, query: ModelQuery<T>): Promise<T> {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
await QueryVerifier.verify(cls, query);
|
|
278
|
+
const where = ModelQueryUtil.getWhereClause(cls, query.where);
|
|
279
|
+
where.id = item.id;
|
|
280
|
+
await this.#deleteRaw(cls, item.id, where, true);
|
|
280
281
|
return await this.create(cls, item);
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
@Connected()
|
|
284
285
|
@Transactional()
|
|
285
286
|
async updateByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>, data: Partial<T>): Promise<number> {
|
|
286
|
-
|
|
287
|
+
await QueryVerifier.verify(cls, query);
|
|
288
|
+
const { count } = await this.#exec(this.#dialect.getUpdateSQL(SQLUtil.classToStack(cls), data, ModelQueryUtil.getWhereClause(cls, query.where)));
|
|
287
289
|
return count;
|
|
288
290
|
}
|
|
289
291
|
|
|
290
292
|
@Connected()
|
|
291
293
|
@Transactional()
|
|
292
294
|
async deleteByQuery<T extends ModelType>(cls: Class<T>, query: ModelQuery<T>): Promise<number> {
|
|
293
|
-
|
|
295
|
+
await QueryVerifier.verify(cls, query);
|
|
296
|
+
const { count } = await this.#exec(this.#dialect.getDeleteSQL(SQLUtil.classToStack(cls), ModelQueryUtil.getWhereClause(cls, query.where, false)));
|
|
294
297
|
return count;
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
@Connected()
|
|
298
301
|
async suggest<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<T[]> {
|
|
302
|
+
await QueryVerifier.verify(cls, query);
|
|
299
303
|
const q = ModelQuerySuggestUtil.getSuggestQuery<T>(cls, field, prefix, query);
|
|
300
304
|
const results = await this.query<T>(cls, q);
|
|
301
305
|
return ModelQuerySuggestUtil.combineSuggestResults(cls, field, prefix, results, (a, b) => b, query?.limit);
|
|
@@ -303,16 +307,17 @@ export class SQLModelService implements
|
|
|
303
307
|
|
|
304
308
|
@Connected()
|
|
305
309
|
async suggestValues<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, prefix?: string, query?: PageableModelQuery<T>): Promise<string[]> {
|
|
310
|
+
await QueryVerifier.verify(cls, query);
|
|
306
311
|
const q = ModelQuerySuggestUtil.getSuggestFieldQuery(cls, field, prefix, query);
|
|
307
312
|
const results = await this.query(cls, q);
|
|
308
313
|
|
|
309
|
-
|
|
310
|
-
const modelTypeField = field as ValidStringFields<ModelType>;
|
|
314
|
+
const modelTypeField: ValidStringFields<ModelType> = castTo(field);
|
|
311
315
|
return ModelQuerySuggestUtil.combineSuggestResults(cls, modelTypeField, prefix, results, x => x, query?.limit);
|
|
312
316
|
}
|
|
313
317
|
|
|
314
318
|
@Connected()
|
|
315
319
|
async facet<T extends ModelType>(cls: Class<T>, field: ValidStringFields<T>, query?: ModelQuery<T>): Promise<{ key: string, count: number }[]> {
|
|
320
|
+
await QueryVerifier.verify(cls, query);
|
|
316
321
|
const col = this.#dialect.ident(field);
|
|
317
322
|
const ttl = this.#dialect.ident('count');
|
|
318
323
|
const key = this.#dialect.ident('key');
|
|
@@ -321,7 +326,7 @@ export class SQLModelService implements
|
|
|
321
326
|
this.#dialect.getFromSQL(cls),
|
|
322
327
|
];
|
|
323
328
|
q.push(
|
|
324
|
-
this.#dialect.getWhereSQL(cls, ModelQueryUtil.
|
|
329
|
+
this.#dialect.getWhereSQL(cls, ModelQueryUtil.getWhereClause(cls, query?.where))
|
|
325
330
|
);
|
|
326
331
|
q.push(
|
|
327
332
|
`GROUP BY ${col}`,
|
package/src/table-manager.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
2
2
|
import { ModelRegistry } from '@travetto/model';
|
|
3
|
-
import { Class } from '@travetto/
|
|
3
|
+
import { Class } from '@travetto/runtime';
|
|
4
4
|
import { ChangeEvent } from '@travetto/registry';
|
|
5
5
|
import { SchemaChange } from '@travetto/schema';
|
|
6
6
|
|
package/support/test/query.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { BaseModelSuite } from '@travetto/model/support/test/base';
|
|
|
6
6
|
|
|
7
7
|
import { VisitStack } from '../../src/internal/util';
|
|
8
8
|
import { SQLModelService } from '../../src/service';
|
|
9
|
+
import { castTo } from '@travetto/runtime';
|
|
9
10
|
|
|
10
11
|
@Schema()
|
|
11
12
|
class User {
|
|
@@ -66,8 +67,8 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
66
67
|
|
|
67
68
|
const dct = await this.dialect;
|
|
68
69
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
69
|
-
const field = stack[stack.length - 1]
|
|
70
|
-
const parent = stack[stack.length - 2]
|
|
70
|
+
const field: FieldConfig = castTo(stack[stack.length - 1]);
|
|
71
|
+
const parent: FieldConfig = castTo(stack[stack.length - 2]);
|
|
71
72
|
return `${field.owner ? field.owner.name : parent.name}.${field.name}`;
|
|
72
73
|
};
|
|
73
74
|
|
|
@@ -79,7 +80,7 @@ export abstract class BaseSQLTest extends BaseModelSuite<SQLModelService> {
|
|
|
79
80
|
async testRegEx() {
|
|
80
81
|
const dct = await this.dialect;
|
|
81
82
|
dct.resolveName = (stack: VisitStack[]) => {
|
|
82
|
-
const field = stack[stack.length - 1]
|
|
83
|
+
const field: FieldConfig = castTo(stack[stack.length - 1]);
|
|
83
84
|
return `${field.owner?.name}.${field.name}`;
|
|
84
85
|
};
|
|
85
86
|
|