@travetto/model-sql 8.0.0-alpha.9 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -9
- package/__index__.ts +4 -6
- package/package.json +19 -18
- package/src/connection.ts +218 -0
- package/src/dialect.ts +847 -0
- package/src/schema.ts +61 -0
- package/src/service.ts +749 -240
- package/src/types.ts +23 -9
- package/support/test/dialect.ts +125 -0
- package/support/test/query.ts +115 -72
- package/src/config.ts +0 -45
- package/src/connection/base.ts +0 -188
- package/src/connection/decorator.ts +0 -54
- package/src/dialect/base.ts +0 -1102
- package/src/internal/types.ts +0 -64
- package/src/table-manager.ts +0 -162
- package/src/util.ts +0 -331
package/src/internal/types.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type { SchemaFieldConfig, SchemaClassConfig } from '@travetto/schema';
|
|
2
|
-
|
|
3
|
-
import type { VisitStack } from '../types.ts';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Insertion wrapper
|
|
7
|
-
*/
|
|
8
|
-
export interface InsertWrapper {
|
|
9
|
-
stack: VisitStack[];
|
|
10
|
-
records: { stack: VisitStack[], value: unknown }[];
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Dialect wrapper
|
|
15
|
-
*/
|
|
16
|
-
export interface DeleteWrapper {
|
|
17
|
-
stack: VisitStack[];
|
|
18
|
-
ids: string[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Dialect state
|
|
23
|
-
*/
|
|
24
|
-
export interface DialectState {
|
|
25
|
-
pathField: SchemaFieldConfig;
|
|
26
|
-
parentPathField: SchemaFieldConfig;
|
|
27
|
-
idField: SchemaFieldConfig;
|
|
28
|
-
idxField: SchemaFieldConfig;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type VisitState = { path: VisitStack[] };
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Visited node
|
|
35
|
-
*/
|
|
36
|
-
export interface VisitNode<R> {
|
|
37
|
-
path: VisitStack[];
|
|
38
|
-
fields: SchemaFieldConfig[];
|
|
39
|
-
descend: () => R;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Order by state
|
|
44
|
-
*/
|
|
45
|
-
export interface OrderBy {
|
|
46
|
-
stack: VisitStack[];
|
|
47
|
-
asc: boolean;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Visitation instance
|
|
52
|
-
*/
|
|
53
|
-
export interface VisitInstanceNode<R> extends VisitNode<R> {
|
|
54
|
-
value: unknown;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Visit handler
|
|
59
|
-
*/
|
|
60
|
-
export interface VisitHandler<R, U extends VisitNode<R> = VisitNode<R>> {
|
|
61
|
-
onRoot(config: U & { config: SchemaClassConfig }): R;
|
|
62
|
-
onSub(config: U & { config: SchemaFieldConfig }): R;
|
|
63
|
-
onSimple(config: Omit<U, 'descend'> & { config: SchemaFieldConfig }): R;
|
|
64
|
-
}
|
package/src/table-manager.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { type AsyncContext, WithAsyncContext } from '@travetto/context';
|
|
2
|
-
import { ModelRegistryIndex } from '@travetto/model';
|
|
3
|
-
import { type Class } from '@travetto/runtime';
|
|
4
|
-
import { SchemaRegistryIndex, type SchemaFieldConfig } from '@travetto/schema';
|
|
5
|
-
|
|
6
|
-
import { Connected, Transactional } from './connection/decorator.ts';
|
|
7
|
-
import type { SQLDialect } from './dialect/base.ts';
|
|
8
|
-
import { SQLModelUtil } from './util.ts';
|
|
9
|
-
import type { Connection } from './connection/base.ts';
|
|
10
|
-
import type { VisitStack } from './types.ts';
|
|
11
|
-
|
|
12
|
-
type UpsertStructure = { dropIndex: string[], createIndex: string[], table: string[] };
|
|
13
|
-
const isSimpleField = (input: VisitStack | undefined): input is SchemaFieldConfig =>
|
|
14
|
-
!!input && (!('type' in input) || (input.type && !SchemaRegistryIndex.has(input.type)));
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Manage creation/updating of all tables
|
|
18
|
-
*/
|
|
19
|
-
export class TableManager {
|
|
20
|
-
|
|
21
|
-
#dialect: SQLDialect;
|
|
22
|
-
context: AsyncContext;
|
|
23
|
-
|
|
24
|
-
constructor(context: AsyncContext, dialect: SQLDialect) {
|
|
25
|
-
this.#dialect = dialect;
|
|
26
|
-
this.context = context;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
#exec<T = unknown>(sql: string): Promise<{ records: T[], count: number }> {
|
|
30
|
-
return this.#dialect.executeSQL<T>(sql);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get a valid connection
|
|
35
|
-
*/
|
|
36
|
-
get connection(): Connection {
|
|
37
|
-
return this.#dialect.connection;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create all needed tables for a given class
|
|
42
|
-
*/
|
|
43
|
-
async exportTables(cls: Class): Promise<string[]> {
|
|
44
|
-
const out: string[] = [];
|
|
45
|
-
for (const command of this.#dialect.getCreateAllTablesSQL(cls)) {
|
|
46
|
-
out.push(command);
|
|
47
|
-
}
|
|
48
|
-
const indices = ModelRegistryIndex.getConfig(cls).indices;
|
|
49
|
-
if (indices) {
|
|
50
|
-
for (const command of this.#dialect.getCreateAllIndicesSQL(cls, indices)) {
|
|
51
|
-
out.push(command);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
@WithAsyncContext()
|
|
58
|
-
@Connected()
|
|
59
|
-
async getUpsertTablesSQL(cls: Class): Promise<UpsertStructure> {
|
|
60
|
-
const sqlCommands: UpsertStructure = { dropIndex: [], createIndex: [], table: [] };
|
|
61
|
-
|
|
62
|
-
const onVisit = async (type: Class, fields: SchemaFieldConfig[], path: VisitStack[]): Promise<void> => {
|
|
63
|
-
const found = await this.#dialect.describeTable(this.#dialect.namespace(path));
|
|
64
|
-
const existingFields = new Map(found?.columns.map(column => [column.name, column]) ?? []);
|
|
65
|
-
const existingIndices = new Map(found?.indices.map(index => [index.name, index]) ?? []);
|
|
66
|
-
const model = path.length === 1 ? ModelRegistryIndex.getConfig(type) : undefined;
|
|
67
|
-
const requestedIndices = new Map((model?.indices ?? []).map(index => [this.#dialect.getIndexName(type, index), index]) ?? []);
|
|
68
|
-
|
|
69
|
-
// Manage fields
|
|
70
|
-
if (!existingFields.size) {
|
|
71
|
-
sqlCommands.table.push(this.#dialect.getCreateTableSQL(path));
|
|
72
|
-
} else { // Existing
|
|
73
|
-
// Fields
|
|
74
|
-
const requestedFields = new Map(fields.map(field => [field.name, field]));
|
|
75
|
-
const top = path.at(-1);
|
|
76
|
-
|
|
77
|
-
if (isSimpleField(top)) {
|
|
78
|
-
requestedFields.set(top.name, top);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
for (const [column, field] of requestedFields.entries()) {
|
|
82
|
-
if (!existingFields.has(column)) {
|
|
83
|
-
sqlCommands.table.push(this.#dialect.getAddColumnSQL([...path, field]));
|
|
84
|
-
} else if (this.#dialect.isColumnChanged(field, existingFields.get(column)!)) {
|
|
85
|
-
sqlCommands.table.push(this.#dialect.getModifyColumnSQL([...path, field]));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// TODO: Handle dropping tables that are FK'd when no longer in use
|
|
90
|
-
|
|
91
|
-
for (const column of existingFields.keys()) {
|
|
92
|
-
if (!requestedFields.has(column)) {
|
|
93
|
-
sqlCommands.table.push(this.#dialect.getDropColumnSQL([...path, { name: column, type: undefined!, array: false }]));
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Manage indices
|
|
99
|
-
for (const index of requestedIndices.keys()) {
|
|
100
|
-
if (!existingIndices.has(index)) {
|
|
101
|
-
sqlCommands.createIndex.push(this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!));
|
|
102
|
-
} else if (this.#dialect.isIndexChanged(requestedIndices.get(index)!, existingIndices.get(index)!)) {
|
|
103
|
-
sqlCommands.dropIndex.push(this.#dialect.getDropIndexSQL(type, existingIndices.get(index)!.name));
|
|
104
|
-
sqlCommands.createIndex.push(this.#dialect.getCreateIndexSQL(type, requestedIndices.get(index)!));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
for (const index of existingIndices.keys()) {
|
|
109
|
-
if (!requestedIndices.has(index)) {
|
|
110
|
-
sqlCommands.dropIndex.push(this.#dialect.getDropIndexSQL(type, existingIndices.get(index)!.name));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const schema = SchemaRegistryIndex.getConfig(cls);
|
|
116
|
-
await SQLModelUtil.visitSchema(schema, {
|
|
117
|
-
onRoot: async ({ config, path, fields, descend }) => { await onVisit(config.class, fields, path); return descend(); },
|
|
118
|
-
onSub: async ({ config, path, fields, descend }) => { await onVisit(config.type, fields, path); return descend(); },
|
|
119
|
-
onSimple: async ({ config, path, fields }) => { await onVisit(config.type, fields, path); }
|
|
120
|
-
});
|
|
121
|
-
return sqlCommands;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
@WithAsyncContext()
|
|
125
|
-
@Connected()
|
|
126
|
-
@Transactional()
|
|
127
|
-
async upsertTables(cls: Class): Promise<void> {
|
|
128
|
-
// Enforce id length
|
|
129
|
-
this.#dialect.enforceIdLength(cls);
|
|
130
|
-
|
|
131
|
-
const sqlCommands = await this.getUpsertTablesSQL(cls);
|
|
132
|
-
for (const key of ['dropIndex', 'table', 'createIndex'] as const) {
|
|
133
|
-
for (const command of sqlCommands[key]) {
|
|
134
|
-
await this.#exec(command);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Drop all tables for a given class
|
|
141
|
-
*/
|
|
142
|
-
@WithAsyncContext()
|
|
143
|
-
@Connected()
|
|
144
|
-
@Transactional()
|
|
145
|
-
async dropTables(cls: Class): Promise<void> {
|
|
146
|
-
for (const command of this.#dialect.getDropAllTablesSQL(cls)) {
|
|
147
|
-
await this.#exec(command);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Drop all tables for a given class
|
|
153
|
-
*/
|
|
154
|
-
@WithAsyncContext()
|
|
155
|
-
@Connected()
|
|
156
|
-
@Transactional()
|
|
157
|
-
async truncateTables(cls: Class): Promise<void> {
|
|
158
|
-
for (const command of this.#dialect.getTruncateAllTablesSQL(cls)) {
|
|
159
|
-
await this.#exec(command);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
package/src/util.ts
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import { castKey, castTo, type Class, TypedObject } from '@travetto/runtime';
|
|
2
|
-
import type { SelectClause, SortClause } from '@travetto/model-query';
|
|
3
|
-
import { ModelRegistryIndex, type ModelType, type OptionalId } from '@travetto/model';
|
|
4
|
-
import { type SchemaClassConfig, type SchemaFieldConfig, DataUtil, SchemaRegistryIndex } from '@travetto/schema';
|
|
5
|
-
|
|
6
|
-
import type { DialectState, InsertWrapper, VisitHandler, VisitState, VisitInstanceNode, OrderBy } from './internal/types.ts';
|
|
7
|
-
import { TableSymbol, type VisitStack } from './types.ts';
|
|
8
|
-
|
|
9
|
-
type FieldCacheEntry = {
|
|
10
|
-
local: SchemaFieldConfig[];
|
|
11
|
-
localMap: Record<string, SchemaFieldConfig>;
|
|
12
|
-
foreign: SchemaFieldConfig[];
|
|
13
|
-
foreignMap: Record<string, SchemaFieldConfig>;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Utilities for dealing with SQL operations
|
|
18
|
-
*/
|
|
19
|
-
export class SQLModelUtil {
|
|
20
|
-
|
|
21
|
-
static #schemaFieldsCache = new Map<Class, FieldCacheEntry>();
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Creates a new visitation stack with the class as the root
|
|
25
|
-
*/
|
|
26
|
-
static classToStack(type: Class): VisitStack[] {
|
|
27
|
-
return [{ type, name: type.name }];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Clean results from db, by dropping internal fields
|
|
32
|
-
*/
|
|
33
|
-
static cleanResults<T, U = T>(state: DialectState, item: T[]): U[];
|
|
34
|
-
static cleanResults<T, U = T>(state: DialectState, item: T): U;
|
|
35
|
-
static cleanResults<T, U = T>(state: DialectState, item: T | T[]): U | U[] {
|
|
36
|
-
if (Array.isArray(item)) {
|
|
37
|
-
return item.filter(value => value !== null && value !== undefined).map(value => this.cleanResults(state, value));
|
|
38
|
-
} else if (!DataUtil.isSimpleValue(item)) {
|
|
39
|
-
for (const key of TypedObject.keys(item)) {
|
|
40
|
-
if (item[key] === null || item[key] === undefined || key === state.parentPathField.name || key === state.pathField.name || key === state.idxField.name) {
|
|
41
|
-
delete item[key];
|
|
42
|
-
} else {
|
|
43
|
-
item[key] = this.cleanResults(state, item[key]);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return castTo({ ...item });
|
|
47
|
-
} else {
|
|
48
|
-
return castTo(item);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get all available fields at current stack path
|
|
54
|
-
*/
|
|
55
|
-
static getFieldsByLocation(stack: VisitStack[]): FieldCacheEntry {
|
|
56
|
-
const top = stack.at(-1)!;
|
|
57
|
-
const config = SchemaRegistryIndex.getOptional(top.type)?.get();
|
|
58
|
-
|
|
59
|
-
if (config && this.#schemaFieldsCache.has(config.class)) {
|
|
60
|
-
return this.#schemaFieldsCache.get(config.class)!;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!config) { // If a simple type, it is it's own field
|
|
64
|
-
const field: SchemaFieldConfig = castTo({ ...top });
|
|
65
|
-
return {
|
|
66
|
-
local: [field], localMap: { [field.name]: field },
|
|
67
|
-
foreign: [], foreignMap: {}
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const hasModel = ModelRegistryIndex.has(config.class)!;
|
|
72
|
-
const fields = Object.values(config.fields).map(field => ({ ...field }));
|
|
73
|
-
|
|
74
|
-
// Polymorphic
|
|
75
|
-
if (hasModel && config.discriminatedBase) {
|
|
76
|
-
const fieldMap = new Set(fields.map(field => field.name));
|
|
77
|
-
for (const type of SchemaRegistryIndex.getDiscriminatedClasses(config.class)) {
|
|
78
|
-
const typeConfig = SchemaRegistryIndex.getConfig(type);
|
|
79
|
-
for (const [fieldName, field] of Object.entries<SchemaFieldConfig>(typeConfig.fields)) {
|
|
80
|
-
if (!fieldMap.has(fieldName)) {
|
|
81
|
-
fieldMap.add(fieldName);
|
|
82
|
-
fields.push({ ...field, required: { active: false } });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const entry: FieldCacheEntry = {
|
|
89
|
-
localMap: {},
|
|
90
|
-
foreignMap: {},
|
|
91
|
-
local: fields.filter(field => !SchemaRegistryIndex.has(field.type) && !field.array),
|
|
92
|
-
foreign: fields.filter(field => SchemaRegistryIndex.has(field.type) || field.array)
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
entry.local.reduce((map, field) => (map[field.name] = field) && map, entry.localMap);
|
|
96
|
-
entry.foreign.reduce((map, field) => (map[field.name] = field) && map, entry.foreignMap);
|
|
97
|
-
|
|
98
|
-
this.#schemaFieldsCache.set(config.class, entry);
|
|
99
|
-
|
|
100
|
-
return entry;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Process a schema structure, synchronously
|
|
105
|
-
*/
|
|
106
|
-
static visitSchemaSync(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<void>, state: VisitState = { path: [] }): void {
|
|
107
|
-
const path = 'fields' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
108
|
-
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
109
|
-
|
|
110
|
-
const descend = (): void => {
|
|
111
|
-
for (const field of foreign) {
|
|
112
|
-
if (SchemaRegistryIndex.has(field.type)) {
|
|
113
|
-
this.visitSchemaSync(field, handler, { path });
|
|
114
|
-
} else {
|
|
115
|
-
handler.onSimple({
|
|
116
|
-
config: field,
|
|
117
|
-
fields: [],
|
|
118
|
-
path: [...path, field]
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
if ('fields' in config) {
|
|
125
|
-
return handler.onRoot({ config, fields, descend, path });
|
|
126
|
-
} else {
|
|
127
|
-
return handler.onSub({ config, fields, descend, path });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Visit a Schema structure
|
|
133
|
-
*/
|
|
134
|
-
static async visitSchema(config: SchemaClassConfig | SchemaFieldConfig, handler: VisitHandler<Promise<void>>, state: VisitState = { path: [] }): Promise<void> {
|
|
135
|
-
const path = 'fields' in config ? this.classToStack(config.class) : [...state.path, config];
|
|
136
|
-
const { local: fields, foreign } = this.getFieldsByLocation(path);
|
|
137
|
-
|
|
138
|
-
const descend = async (): Promise<void> => {
|
|
139
|
-
for (const field of foreign) {
|
|
140
|
-
if (SchemaRegistryIndex.has(field.type)) {
|
|
141
|
-
await this.visitSchema(field, handler, { path });
|
|
142
|
-
} else {
|
|
143
|
-
await handler.onSimple({
|
|
144
|
-
config: field,
|
|
145
|
-
fields: [],
|
|
146
|
-
path: [...path, field]
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
if ('fields' in config) {
|
|
153
|
-
return handler.onRoot({ config, fields, descend, path });
|
|
154
|
-
} else {
|
|
155
|
-
return handler.onSub({ config, fields, descend, path });
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Process a schema instance by visiting it synchronously. This is synchronous to prevent concurrent calls from breaking
|
|
161
|
-
*/
|
|
162
|
-
static visitSchemaInstance<T extends ModelType>(cls: Class<T>, instance: T | OptionalId<T>, handler: VisitHandler<unknown, VisitInstanceNode<unknown>>): void {
|
|
163
|
-
const pathStack: unknown[] = [instance];
|
|
164
|
-
this.visitSchemaSync(SchemaRegistryIndex.getConfig(cls), {
|
|
165
|
-
onRoot: (config) => {
|
|
166
|
-
const { path } = config;
|
|
167
|
-
path[0].name = instance.id!;
|
|
168
|
-
handler.onRoot({ ...config, value: instance });
|
|
169
|
-
return config.descend();
|
|
170
|
-
},
|
|
171
|
-
onSub: (config) => {
|
|
172
|
-
const { config: field } = config;
|
|
173
|
-
const topObject: Record<string, unknown> = castTo(pathStack.at(-1));
|
|
174
|
-
const top = config.path.at(-1)!;
|
|
175
|
-
|
|
176
|
-
if (field.name in topObject) {
|
|
177
|
-
const valuesInput = topObject[field.name];
|
|
178
|
-
const values = Array.isArray(valuesInput) ? valuesInput : [valuesInput];
|
|
179
|
-
|
|
180
|
-
let i = 0;
|
|
181
|
-
for (const value of values) {
|
|
182
|
-
try {
|
|
183
|
-
pathStack.push(value);
|
|
184
|
-
config.path[config.path.length - 1] = { ...top, index: i++ };
|
|
185
|
-
handler.onSub({ ...config, value });
|
|
186
|
-
if (!field.array) {
|
|
187
|
-
config.descend();
|
|
188
|
-
}
|
|
189
|
-
} finally {
|
|
190
|
-
pathStack.pop();
|
|
191
|
-
}
|
|
192
|
-
i += 1;
|
|
193
|
-
}
|
|
194
|
-
if (field.array) {
|
|
195
|
-
config.descend();
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
},
|
|
199
|
-
onSimple: (config) => {
|
|
200
|
-
const { config: field } = config;
|
|
201
|
-
const topObject: Record<string, unknown> = castTo(pathStack.at(-1));
|
|
202
|
-
const value = topObject[field.name];
|
|
203
|
-
return handler.onSimple({ ...config, value });
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Get list of selected fields
|
|
210
|
-
*/
|
|
211
|
-
static select<T>(cls: Class<T>, select?: SelectClause<T>): SchemaFieldConfig[] {
|
|
212
|
-
if (!select || Object.keys(select).length === 0) {
|
|
213
|
-
return [{ type: cls, name: '*', class: cls, array: false }];
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const { localMap } = this.getFieldsByLocation(this.classToStack(cls));
|
|
217
|
-
|
|
218
|
-
let toGet = new Set<string>();
|
|
219
|
-
|
|
220
|
-
for (const [key, value] of TypedObject.entries(select)) {
|
|
221
|
-
if (typeof key === 'string' && !DataUtil.isPlainObject(select[key]) && localMap[key]) {
|
|
222
|
-
if (!value) {
|
|
223
|
-
if (toGet.size === 0) {
|
|
224
|
-
toGet = new Set(Object.keys(SchemaRegistryIndex.getConfig(cls).fields));
|
|
225
|
-
}
|
|
226
|
-
toGet.delete(key);
|
|
227
|
-
} else {
|
|
228
|
-
toGet.add(key);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return [...toGet].map(field => localMap[field]);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Get list of Order By clauses
|
|
237
|
-
*/
|
|
238
|
-
static orderBy<T>(cls: Class<T>, sort: SortClause<T>[]): OrderBy[] {
|
|
239
|
-
return sort.map((cl: Record<string, unknown>) => {
|
|
240
|
-
let schema: SchemaClassConfig = SchemaRegistryIndex.getConfig(cls);
|
|
241
|
-
const stack = this.classToStack(cls);
|
|
242
|
-
let found: OrderBy | undefined;
|
|
243
|
-
while (!found) {
|
|
244
|
-
const key = Object.keys(cl)[0];
|
|
245
|
-
const value = cl[key];
|
|
246
|
-
const field = { ...schema.fields[key] };
|
|
247
|
-
if (DataUtil.isPrimitive(value)) {
|
|
248
|
-
stack.push(field);
|
|
249
|
-
found = { stack, asc: value === 1 };
|
|
250
|
-
} else {
|
|
251
|
-
stack.push(field);
|
|
252
|
-
schema = SchemaRegistryIndex.getConfig(field.type);
|
|
253
|
-
cl = castTo(value);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
return found;
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Find all dependent fields via child tables
|
|
262
|
-
*/
|
|
263
|
-
static collectDependents<T>(state: DialectState, parent: unknown, items: T[], field?: SchemaFieldConfig): Record<string, T> {
|
|
264
|
-
if (field) {
|
|
265
|
-
const isSimple = SchemaRegistryIndex.has(field.type);
|
|
266
|
-
for (const item of items) {
|
|
267
|
-
const parentKey: string = castTo(item[castKey<T>(state.parentPathField.name)]);
|
|
268
|
-
const root = castTo<Record<string, Record<string, unknown>>>(parent)[parentKey];
|
|
269
|
-
const fieldKey = castKey<(typeof root) | T>(field.name);
|
|
270
|
-
if (field.array) {
|
|
271
|
-
if (!root[fieldKey]) {
|
|
272
|
-
root[fieldKey] = [isSimple ? item : item[fieldKey]];
|
|
273
|
-
} else if (Array.isArray(root[fieldKey])) {
|
|
274
|
-
root[fieldKey].push(isSimple ? item : item[fieldKey]);
|
|
275
|
-
}
|
|
276
|
-
} else {
|
|
277
|
-
root[fieldKey] = isSimple ? item : item[fieldKey];
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const mapping: Record<string, T> = {};
|
|
283
|
-
for (const item of items) {
|
|
284
|
-
const key = item[castKey<T>(state.pathField.name)];
|
|
285
|
-
if (typeof key === 'string') {
|
|
286
|
-
mapping[key] = item;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return mapping;
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Build table name via stack path
|
|
294
|
-
*/
|
|
295
|
-
static buildTable(list: VisitStack[]): string {
|
|
296
|
-
const top = list.at(-1)!;
|
|
297
|
-
if (!top[TableSymbol]) {
|
|
298
|
-
top[TableSymbol] = list.map((item, i) => i === 0 ? ModelRegistryIndex.getStoreName(item.type) : item.name).join('_');
|
|
299
|
-
}
|
|
300
|
-
return top[TableSymbol]!;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Build property path for a table/field given the current stack
|
|
305
|
-
*/
|
|
306
|
-
static buildPath(list: VisitStack[]): string {
|
|
307
|
-
return list.map((item) => `${item.name}${item.index ? `[${item.index}]` : ''}`).join('.');
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Get insert statements for a given class, and its child tables
|
|
312
|
-
*/
|
|
313
|
-
static async getInserts<T extends ModelType>(cls: Class<T>, items: (T | OptionalId<T>)[]): Promise<InsertWrapper[]> {
|
|
314
|
-
const wrappers: Record<string, InsertWrapper> = {};
|
|
315
|
-
|
|
316
|
-
const track = (stack: VisitStack[], value: unknown): void => {
|
|
317
|
-
const key = this.buildTable(stack);
|
|
318
|
-
(wrappers[key] ??= { stack, records: [] }).records.push({ stack, value });
|
|
319
|
-
};
|
|
320
|
-
|
|
321
|
-
items.map(item =>
|
|
322
|
-
this.visitSchemaInstance(cls, item, {
|
|
323
|
-
onRoot: ({ path, value }) => track(path, value),
|
|
324
|
-
onSub: ({ path, value }) => track(path, value),
|
|
325
|
-
onSimple: ({ path, value }) => track(path, value)
|
|
326
|
-
}));
|
|
327
|
-
|
|
328
|
-
const result = [...Object.values(wrappers)].toSorted((a, b) => a.stack.length - b.stack.length);
|
|
329
|
-
return result;
|
|
330
|
-
}
|
|
331
|
-
}
|