@travetto/model-sql 5.0.0-rc.8 → 5.0.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/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 +29 -51
- package/src/internal/util.ts +19 -32
- package/src/service.ts +21 -16
- 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
|
|
3
|
+
"version": "5.0.0",
|
|
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
|
|
31
|
-
"@travetto/context": "^5.0.0
|
|
32
|
-
"@travetto/model": "^5.0.0
|
|
33
|
-
"@travetto/model-query": "^5.0.0
|
|
30
|
+
"@travetto/config": "^5.0.0",
|
|
31
|
+
"@travetto/context": "^5.0.0",
|
|
32
|
+
"@travetto/model": "^5.0.0",
|
|
33
|
+
"@travetto/model-query": "^5.0.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/command": "^5.0.0
|
|
37
|
-
"@travetto/test": "^5.0.0
|
|
36
|
+
"@travetto/command": "^5.0.0",
|
|
37
|
+
"@travetto/test": "^5.0.0"
|
|
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/runtime';
|
|
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,5 +1,6 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/indent */
|
|
1
2
|
import { DataUtil, SchemaRegistry, FieldConfig, Schema } from '@travetto/schema';
|
|
2
|
-
import { Class, AppError, TypedObject, describeFunction } from '@travetto/runtime';
|
|
3
|
+
import { Class, AppError, TypedObject, describeFunction, TimeUtil, castTo, castKey } from '@travetto/runtime';
|
|
3
4
|
import { SelectClause, Query, SortClause, WhereClause, RetainFields } from '@travetto/model-query';
|
|
4
5
|
import { BulkResponse, IndexConfig } from '@travetto/model';
|
|
5
6
|
import { PointImpl } from '@travetto/model-query/src/internal/model/point';
|
|
@@ -10,13 +11,6 @@ import { SQLUtil, VisitStack } from '../internal/util';
|
|
|
10
11
|
import { DeleteWrapper, InsertWrapper, DialectState } from '../internal/types';
|
|
11
12
|
import { Connection } from '../connection/base';
|
|
12
13
|
|
|
13
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
14
|
-
const has$And = (o: unknown): o is ({ $and: WhereClause<unknown>[] }) => !!o && '$and' in (o as object);
|
|
15
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
16
|
-
const has$Or = (o: unknown): o is ({ $or: WhereClause<unknown>[] }) => !!o && '$or' in (o as object);
|
|
17
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
18
|
-
const has$Not = (o: unknown): o is ({ $not: WhereClause<unknown> }) => !!o && '$not' in (o as object);
|
|
19
|
-
|
|
20
14
|
interface Alias {
|
|
21
15
|
alias: string;
|
|
22
16
|
path: VisitStack[];
|
|
@@ -195,16 +189,18 @@ export abstract class SQLDialect implements DialectState {
|
|
|
195
189
|
const src = DataUtil.toRegex(value).source.replace(/\\b/g, this.regexWordBoundary);
|
|
196
190
|
return this.quote(src);
|
|
197
191
|
} else {
|
|
198
|
-
|
|
199
|
-
return this.quote(value as string);
|
|
192
|
+
return this.quote(castTo(value));
|
|
200
193
|
}
|
|
201
194
|
} else if (conf.type === Boolean) {
|
|
202
195
|
return `${value ? 'TRUE' : 'FALSE'}`;
|
|
203
196
|
} else if (conf.type === Number) {
|
|
204
197
|
return `${value}`;
|
|
205
198
|
} else if (conf.type === Date) {
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
204
|
} else if (conf.type === PointImpl && Array.isArray(value)) {
|
|
209
205
|
return `point(${value[0]},${value[1]})`;
|
|
210
206
|
} else if (conf.type === Object) {
|
|
@@ -275,8 +271,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
275
271
|
* Delete query and return count removed
|
|
276
272
|
*/
|
|
277
273
|
async deleteAndGetCount<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
278
|
-
|
|
279
|
-
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));
|
|
280
275
|
return count;
|
|
281
276
|
}
|
|
282
277
|
|
|
@@ -284,7 +279,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
284
279
|
* Get the count for a given query
|
|
285
280
|
*/
|
|
286
281
|
async getCountForQuery<T>(cls: Class<T>, query: Query<T>): Promise<number> {
|
|
287
|
-
const { records } = await this.executeSQL<{ total: number }>(this.getQueryCountSQL(cls, query));
|
|
282
|
+
const { records } = await this.executeSQL<{ total: number }>(this.getQueryCountSQL(cls, query.where));
|
|
288
283
|
const [record] = records;
|
|
289
284
|
return Total.from(record).total;
|
|
290
285
|
}
|
|
@@ -301,9 +296,8 @@ export abstract class SQLDialect implements DialectState {
|
|
|
301
296
|
* Add a sql column
|
|
302
297
|
*/
|
|
303
298
|
getAddColumnSQL(stack: VisitStack[]): string {
|
|
304
|
-
const field = stack[stack.length - 1];
|
|
305
|
-
|
|
306
|
-
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)};`;
|
|
307
301
|
}
|
|
308
302
|
|
|
309
303
|
/**
|
|
@@ -455,8 +449,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
455
449
|
break;
|
|
456
450
|
}
|
|
457
451
|
case '$regex': {
|
|
458
|
-
|
|
459
|
-
const re = DataUtil.toRegex(v as string);
|
|
452
|
+
const re = DataUtil.toRegex(castTo(v));
|
|
460
453
|
const src = re.source;
|
|
461
454
|
const ins = re.flags && re.flags.includes('i');
|
|
462
455
|
|
|
@@ -505,8 +498,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
505
498
|
break;
|
|
506
499
|
}
|
|
507
500
|
case '$lt': case '$gt': case '$gte': case '$lte': {
|
|
508
|
-
|
|
509
|
-
const subItems = TypedObject.keys(top as typeof SQL_OPS)
|
|
501
|
+
const subItems = TypedObject.keys(castTo<typeof SQL_OPS>(top))
|
|
510
502
|
.map(ssk => `${sPath} ${SQL_OPS[ssk]} ${resolve(top[ssk])}`);
|
|
511
503
|
items.push(subItems.length > 1 ? `(${subItems.join(` ${SQL_OPS.$and} `)})` : subItems[0]);
|
|
512
504
|
break;
|
|
@@ -536,11 +528,11 @@ export abstract class SQLDialect implements DialectState {
|
|
|
536
528
|
getWhereGroupingSQL<T>(cls: Class<T>, o: WhereClause<T>): string {
|
|
537
529
|
const SQL_OPS = this.SQL_OPS;
|
|
538
530
|
|
|
539
|
-
if (has$And(o)) {
|
|
531
|
+
if (ModelQueryUtil.has$And(o)) {
|
|
540
532
|
return `(${o.$and.map(x => this.getWhereGroupingSQL<T>(cls, x)).join(` ${SQL_OPS.$and} `)})`;
|
|
541
|
-
} else if (has$Or(o)) {
|
|
533
|
+
} else if (ModelQueryUtil.has$Or(o)) {
|
|
542
534
|
return `(${o.$or.map(x => this.getWhereGroupingSQL<T>(cls, x)).join(` ${SQL_OPS.$or} `)})`;
|
|
543
|
-
} else if (has$Not(o)) {
|
|
535
|
+
} else if (ModelQueryUtil.has$Not(o)) {
|
|
544
536
|
return `${SQL_OPS.$not} (${this.getWhereGroupingSQL<T>(cls, o.$not)})`;
|
|
545
537
|
} else {
|
|
546
538
|
return this.getWhereFieldSQL(SQLUtil.classToStack(cls), o);
|
|
@@ -553,8 +545,7 @@ export abstract class SQLDialect implements DialectState {
|
|
|
553
545
|
getWhereSQL<T>(cls: Class<T>, where?: WhereClause<T>): string {
|
|
554
546
|
return !where || !Object.keys(where).length ?
|
|
555
547
|
'' :
|
|
556
|
-
|
|
557
|
-
`WHERE ${this.getWhereGroupingSQL(cls, where as WhereClause<T>)}`;
|
|
548
|
+
`WHERE ${this.getWhereGroupingSQL(cls, castTo(where))}`;
|
|
558
549
|
}
|
|
559
550
|
|
|
560
551
|
/**
|
|
@@ -623,15 +614,13 @@ LEFT OUTER JOIN ${from} ON
|
|
|
623
614
|
/**
|
|
624
615
|
* Generate full query
|
|
625
616
|
*/
|
|
626
|
-
getQuerySQL<T>(cls: Class<T>, query: Query<T>): string {
|
|
617
|
+
getQuerySQL<T>(cls: Class<T>, query: Query<T>, where?: WhereClause<T>): string {
|
|
627
618
|
const sortFields = !query.sort ?
|
|
628
619
|
'' :
|
|
629
620
|
SQLUtil.orderBy(cls, query.sort)
|
|
630
621
|
.map(x => this.resolveName(x.stack))
|
|
631
622
|
.join(', ');
|
|
632
623
|
|
|
633
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
634
|
-
const where = query.where as WhereClause<T>;
|
|
635
624
|
return `
|
|
636
625
|
${this.getSelectSQL(cls, query.select)}
|
|
637
626
|
${this.getFromSQL(cls)}
|
|
@@ -652,8 +641,7 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
652
641
|
|
|
653
642
|
const fields = SchemaRegistry.has(config.type) ?
|
|
654
643
|
[...SQLUtil.getFieldsByLocation(stack).local] :
|
|
655
|
-
|
|
656
|
-
(array ? [config as FieldConfig] : []);
|
|
644
|
+
(array ? [castTo<FieldConfig>(config)] : []);
|
|
657
645
|
|
|
658
646
|
if (!parent) {
|
|
659
647
|
let idField = fields.find(x => x.name === this.idField.name);
|
|
@@ -673,7 +661,6 @@ ${this.getLimitSQL(cls, query)}`;
|
|
|
673
661
|
.filter(x => !!x.trim())
|
|
674
662
|
.join(',\n ');
|
|
675
663
|
|
|
676
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
677
664
|
const out = `
|
|
678
665
|
CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
679
666
|
${fieldSql}${fieldSql.length ? ',' : ''}
|
|
@@ -733,8 +720,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
733
720
|
if (DataUtil.isPlainObject(val)) {
|
|
734
721
|
throw new Error('Unable to supported nested fields for indices');
|
|
735
722
|
}
|
|
736
|
-
|
|
737
|
-
return [key as string, typeof val === 'number' ? val === 1 : (!!val)];
|
|
723
|
+
return [castTo(key), typeof val === 'number' ? val === 1 : (!!val)];
|
|
738
724
|
});
|
|
739
725
|
const constraint = `idx_${table}_${fields.map(([f]) => f).join('_')}`;
|
|
740
726
|
return `CREATE ${idx.type === 'unique' ? 'UNIQUE ' : ''}INDEX ${constraint} ON ${this.ident(table)} (${fields
|
|
@@ -808,8 +794,7 @@ CREATE TABLE IF NOT EXISTS ${this.table(stack)} (
|
|
|
808
794
|
return;
|
|
809
795
|
}
|
|
810
796
|
|
|
811
|
-
|
|
812
|
-
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])));
|
|
813
798
|
|
|
814
799
|
columnNames.push(this.pathField.name);
|
|
815
800
|
if (hasParent) {
|
|
@@ -899,13 +884,11 @@ ${orderBy};`;
|
|
|
899
884
|
/**
|
|
900
885
|
* Get COUNT(1) query
|
|
901
886
|
*/
|
|
902
|
-
getQueryCountSQL<T>(cls: Class<T>,
|
|
903
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
904
|
-
const where = query.where as WhereClause<T>;
|
|
887
|
+
getQueryCountSQL<T>(cls: Class<T>, where?: WhereClause<T>): string {
|
|
905
888
|
return `
|
|
906
889
|
SELECT COUNT(DISTINCT ${this.rootAlias}.id) as total
|
|
907
890
|
${this.getFromSQL(cls)}
|
|
908
|
-
${this.getWhereSQL(cls, where)}`;
|
|
891
|
+
${this.getWhereSQL(cls, where!)}`;
|
|
909
892
|
}
|
|
910
893
|
|
|
911
894
|
async fetchDependents<T>(cls: Class<T>, items: T[], select?: SelectClause<T>): Promise<T[]> {
|
|
@@ -926,15 +909,12 @@ ${this.getWhereSQL(cls, where)}`;
|
|
|
926
909
|
const top = stack[stack.length - 1];
|
|
927
910
|
const ids = Object.keys(top);
|
|
928
911
|
const selectTop = selectStack[selectStack.length - 1];
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
932
|
-
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]);
|
|
933
914
|
|
|
934
915
|
// See if a selection exists at all
|
|
935
916
|
const sel: FieldConfig[] = subSelectTop ? fields
|
|
936
|
-
|
|
937
|
-
.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)
|
|
938
918
|
: [];
|
|
939
919
|
|
|
940
920
|
if (sel.length) {
|
|
@@ -1017,12 +997,10 @@ ${this.getWhereSQL(cls, where)}`;
|
|
|
1017
997
|
|
|
1018
998
|
await Promise.all([
|
|
1019
999
|
...upserts.filter(x => x.stack.length === 1).map(i =>
|
|
1020
|
-
|
|
1021
|
-
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]))
|
|
1022
1001
|
),
|
|
1023
1002
|
...updates.filter(x => x.stack.length === 1).map(i =>
|
|
1024
|
-
|
|
1025
|
-
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]))
|
|
1026
1004
|
),
|
|
1027
1005
|
]);
|
|
1028
1006
|
}
|
package/src/internal/util.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Class, TypedObject } from '@travetto/runtime';
|
|
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/runtime';
|
|
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/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
|
|