@ruiapp/rapid-core 0.5.11 → 0.5.13
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/CHANGELOG.md +7 -7
- package/dist/facilities/cache/CacheFacilityTypes.d.ts +4 -2
- package/dist/facilities/cache/MemoryCache.d.ts +3 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +131 -72
- package/dist/utilities/entityUtility.d.ts +1 -0
- package/dist/utilities/passwordUtility.d.ts +14 -0
- package/package.json +2 -2
- package/rollup.config.js +16 -16
- package/src/bootstrapApplicationConfig.ts +638 -638
- package/src/core/actionHandler.ts +22 -22
- package/src/core/eventManager.ts +20 -20
- package/src/core/facility.ts +7 -7
- package/src/core/http/formDataParser.ts +89 -89
- package/src/core/http-types.ts +4 -4
- package/src/core/pluginManager.ts +175 -175
- package/src/core/providers/runtimeProvider.ts +5 -5
- package/src/core/request.ts +95 -95
- package/src/core/response.ts +79 -79
- package/src/core/routeContext.ts +100 -100
- package/src/core/routesBuilder.ts +88 -88
- package/src/core/server.ts +145 -145
- package/src/dataAccess/columnTypeMapper.ts +22 -22
- package/src/dataAccess/dataAccessTypes.ts +163 -163
- package/src/dataAccess/dataAccessor.ts +135 -135
- package/src/dataAccess/entityManager.ts +1910 -1910
- package/src/dataAccess/entityMapper.ts +100 -100
- package/src/dataAccess/propertyMapper.ts +28 -28
- package/src/deno-std/assert/assert.ts +9 -9
- package/src/deno-std/assert/assertion_error.ts +7 -7
- package/src/deno-std/datetime/to_imf.ts +32 -32
- package/src/deno-std/encoding/base64.ts +141 -141
- package/src/deno-std/http/cookie.ts +372 -372
- package/src/facilities/cache/CacheFacilityTypes.ts +29 -27
- package/src/facilities/cache/CacheFactory.ts +31 -31
- package/src/facilities/cache/MemoryCache.ts +58 -42
- package/src/facilities/cache/MemoryCacheProvider.ts +15 -15
- package/src/facilities/log/LogFacility.ts +35 -35
- package/src/helpers/entityHelpers.ts +76 -76
- package/src/helpers/filterHelper.ts +148 -148
- package/src/helpers/inputHelper.ts +11 -11
- package/src/helpers/metaHelper.ts +104 -104
- package/src/helpers/runCollectionEntityActionHandler.ts +57 -57
- package/src/index.ts +67 -63
- package/src/plugins/auth/AuthPlugin.ts +93 -93
- package/src/plugins/auth/actionHandlers/changePassword.ts +60 -61
- package/src/plugins/auth/actionHandlers/createSession.ts +68 -68
- package/src/plugins/auth/actionHandlers/deleteSession.ts +18 -18
- package/src/plugins/auth/actionHandlers/getMyProfile.ts +35 -35
- package/src/plugins/auth/actionHandlers/index.ts +8 -8
- package/src/plugins/auth/actionHandlers/resetPassword.ts +44 -45
- package/src/plugins/auth/models/AccessToken.ts +56 -56
- package/src/plugins/auth/models/index.ts +3 -3
- package/src/plugins/auth/routes/changePassword.ts +15 -15
- package/src/plugins/auth/routes/getMyProfile.ts +15 -15
- package/src/plugins/auth/routes/index.ts +7 -7
- package/src/plugins/auth/routes/resetPassword.ts +15 -15
- package/src/plugins/auth/routes/signin.ts +15 -15
- package/src/plugins/auth/routes/signout.ts +15 -15
- package/src/plugins/auth/services/AuthService.ts +39 -39
- package/src/plugins/cronJob/CronJobPlugin.ts +112 -112
- package/src/plugins/cronJob/CronJobPluginTypes.ts +49 -49
- package/src/plugins/cronJob/actionHandlers/index.ts +4 -4
- package/src/plugins/cronJob/actionHandlers/runCronJob.ts +29 -29
- package/src/plugins/cronJob/routes/index.ts +3 -3
- package/src/plugins/cronJob/routes/runCronJob.ts +15 -15
- package/src/plugins/dataManage/DataManagePlugin.ts +163 -163
- package/src/plugins/dataManage/actionHandlers/addEntityRelations.ts +15 -15
- package/src/plugins/dataManage/actionHandlers/countCollectionEntities.ts +17 -17
- package/src/plugins/dataManage/actionHandlers/createCollectionEntitiesBatch.ts +81 -81
- package/src/plugins/dataManage/actionHandlers/createCollectionEntity.ts +20 -20
- package/src/plugins/dataManage/actionHandlers/deleteCollectionEntities.ts +45 -45
- package/src/plugins/dataManage/actionHandlers/deleteCollectionEntityById.ts +20 -20
- package/src/plugins/dataManage/actionHandlers/findCollectionEntities.ts +27 -27
- package/src/plugins/dataManage/actionHandlers/findCollectionEntityById.ts +30 -30
- package/src/plugins/dataManage/actionHandlers/queryDatabase.ts +22 -22
- package/src/plugins/dataManage/actionHandlers/removeEntityRelations.ts +15 -15
- package/src/plugins/dataManage/actionHandlers/updateCollectionEntityById.ts +38 -38
- package/src/plugins/entityAccessControl/EntityAccessControlPlugin.ts +146 -146
- package/src/plugins/fileManage/FileManagePlugin.ts +52 -52
- package/src/plugins/fileManage/actionHandlers/downloadDocument.ts +65 -65
- package/src/plugins/fileManage/actionHandlers/downloadFile.ts +44 -44
- package/src/plugins/fileManage/actionHandlers/uploadFile.ts +33 -33
- package/src/plugins/fileManage/routes/downloadDocument.ts +15 -15
- package/src/plugins/fileManage/routes/downloadFile.ts +15 -15
- package/src/plugins/fileManage/routes/index.ts +5 -5
- package/src/plugins/fileManage/routes/uploadFile.ts +15 -15
- package/src/plugins/license/LicensePlugin.ts +79 -79
- package/src/plugins/license/LicensePluginTypes.ts +95 -95
- package/src/plugins/license/LicenseService.ts +118 -118
- package/src/plugins/license/actionHandlers/getLicense.ts +18 -18
- package/src/plugins/license/actionHandlers/index.ts +4 -4
- package/src/plugins/license/helpers/certHelper.ts +21 -21
- package/src/plugins/license/helpers/cryptoHelper.ts +47 -47
- package/src/plugins/license/models/index.ts +1 -1
- package/src/plugins/license/routes/getLicense.ts +15 -15
- package/src/plugins/license/routes/index.ts +3 -3
- package/src/plugins/mail/MailPlugin.ts +74 -74
- package/src/plugins/mail/MailPluginTypes.ts +27 -27
- package/src/plugins/mail/MailService.ts +38 -38
- package/src/plugins/mail/actionHandlers/index.ts +3 -3
- package/src/plugins/mail/models/index.ts +1 -1
- package/src/plugins/mail/routes/index.ts +1 -1
- package/src/plugins/metaManage/MetaManagePlugin.ts +530 -530
- package/src/plugins/metaManage/actionHandlers/getMetaModelDetail.ts +10 -10
- package/src/plugins/metaManage/actionHandlers/listMetaModels.ts +9 -9
- package/src/plugins/metaManage/actionHandlers/listMetaRoutes.ts +9 -9
- package/src/plugins/notification/NotificationPlugin.ts +68 -68
- package/src/plugins/notification/NotificationPluginTypes.ts +13 -13
- package/src/plugins/notification/NotificationService.ts +25 -25
- package/src/plugins/notification/actionHandlers/index.ts +3 -3
- package/src/plugins/notification/models/Notification.ts +60 -60
- package/src/plugins/notification/models/index.ts +3 -3
- package/src/plugins/notification/routes/index.ts +1 -1
- package/src/plugins/routeManage/RouteManagePlugin.ts +62 -62
- package/src/plugins/routeManage/actionHandlers/httpProxy.ts +13 -13
- package/src/plugins/sequence/SequencePlugin.ts +146 -146
- package/src/plugins/sequence/SequencePluginTypes.ts +69 -69
- package/src/plugins/sequence/SequenceService.ts +92 -92
- package/src/plugins/sequence/actionHandlers/generateSn.ts +32 -32
- package/src/plugins/sequence/actionHandlers/index.ts +4 -4
- package/src/plugins/sequence/models/SequenceAutoIncrementRecord.ts +49 -49
- package/src/plugins/sequence/models/SequenceRule.ts +42 -42
- package/src/plugins/sequence/models/index.ts +4 -4
- package/src/plugins/sequence/routes/generateSn.ts +15 -15
- package/src/plugins/sequence/routes/index.ts +3 -3
- package/src/plugins/sequence/segment-utility.ts +11 -11
- package/src/plugins/sequence/segments/autoIncrement.ts +90 -90
- package/src/plugins/sequence/segments/dayOfMonth.ts +19 -19
- package/src/plugins/sequence/segments/index.ts +9 -9
- package/src/plugins/sequence/segments/literal.ts +16 -16
- package/src/plugins/sequence/segments/month.ts +19 -19
- package/src/plugins/sequence/segments/parameter.ts +20 -20
- package/src/plugins/sequence/segments/year.ts +19 -19
- package/src/plugins/serverOperation/ServerOperationPlugin.ts +91 -91
- package/src/plugins/serverOperation/ServerOperationPluginTypes.ts +15 -15
- package/src/plugins/serverOperation/actionHandlers/index.ts +4 -4
- package/src/plugins/serverOperation/actionHandlers/runServerOperation.ts +15 -15
- package/src/plugins/setting/SettingPlugin.ts +68 -68
- package/src/plugins/setting/SettingPluginTypes.ts +37 -37
- package/src/plugins/setting/SettingService.ts +213 -213
- package/src/plugins/setting/actionHandlers/getSystemSettingValues.ts +30 -30
- package/src/plugins/setting/actionHandlers/getUserSettingValues.ts +38 -38
- package/src/plugins/setting/actionHandlers/index.ts +6 -6
- package/src/plugins/setting/actionHandlers/setSystemSettingValues.ts +30 -30
- package/src/plugins/setting/models/SystemSettingGroupSetting.ts +57 -57
- package/src/plugins/setting/models/SystemSettingItem.ts +48 -48
- package/src/plugins/setting/models/SystemSettingItemSetting.ts +73 -73
- package/src/plugins/setting/models/UserSettingGroupSetting.ts +57 -57
- package/src/plugins/setting/models/UserSettingItem.ts +55 -55
- package/src/plugins/setting/models/UserSettingItemSetting.ts +73 -73
- package/src/plugins/setting/models/index.ts +8 -8
- package/src/plugins/setting/routes/getSystemSettingValues.ts +15 -15
- package/src/plugins/setting/routes/getUserSettingValues.ts +15 -15
- package/src/plugins/setting/routes/index.ts +5 -5
- package/src/plugins/setting/routes/setSystemSettingValues.ts +15 -15
- package/src/plugins/stateMachine/StateMachinePlugin.ts +196 -196
- package/src/plugins/stateMachine/StateMachinePluginTypes.ts +48 -48
- package/src/plugins/stateMachine/actionHandlers/index.ts +4 -4
- package/src/plugins/stateMachine/actionHandlers/sendStateMachineEvent.ts +54 -54
- package/src/plugins/stateMachine/models/StateMachine.ts +42 -42
- package/src/plugins/stateMachine/models/index.ts +3 -3
- package/src/plugins/stateMachine/routes/index.ts +3 -3
- package/src/plugins/stateMachine/routes/sendStateMachineEvent.ts +15 -15
- package/src/plugins/stateMachine/stateMachineHelper.ts +36 -36
- package/src/plugins/webhooks/WebhooksPlugin.ts +148 -148
- package/src/plugins/webhooks/pluginConfig.ts +75 -75
- package/src/polyfill.ts +5 -5
- package/src/proxy/mod.ts +38 -38
- package/src/proxy/types.ts +21 -21
- package/src/queryBuilder/index.ts +1 -1
- package/src/queryBuilder/queryBuilder.ts +668 -668
- package/src/server.ts +480 -480
- package/src/types.ts +722 -722
- package/src/utilities/accessControlUtility.ts +33 -33
- package/src/utilities/entityUtility.ts +18 -0
- package/src/utilities/errorUtility.ts +15 -15
- package/src/utilities/fsUtility.ts +61 -61
- package/src/utilities/httpUtility.ts +19 -19
- package/src/utilities/jwtUtility.ts +26 -26
- package/src/utilities/passwordUtility.ts +26 -0
- package/src/utilities/pathUtility.ts +14 -14
- package/src/utilities/timeUtility.ts +9 -9
- package/src/utilities/typeUtility.ts +15 -15
- package/tsconfig.json +19 -19
|
@@ -1,530 +1,530 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Meta manager plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
IQueryBuilder,
|
|
7
|
-
QuoteTableOptions,
|
|
8
|
-
RpdApplicationConfig,
|
|
9
|
-
RpdDataDictionary,
|
|
10
|
-
RpdDataModel,
|
|
11
|
-
RpdDataModelIndex,
|
|
12
|
-
RpdDataModelProperty,
|
|
13
|
-
RpdDataPropertyTypes,
|
|
14
|
-
RpdEntityCreateEventPayload,
|
|
15
|
-
RpdEntityDeleteEventPayload,
|
|
16
|
-
RpdEntityUpdateEventPayload,
|
|
17
|
-
} from "~/types";
|
|
18
|
-
import {
|
|
19
|
-
IRpdServer,
|
|
20
|
-
RapidPlugin,
|
|
21
|
-
RpdConfigurationItemOptions,
|
|
22
|
-
RpdServerPluginConfigurableTargetOptions,
|
|
23
|
-
RpdServerPluginExtendingAbilities,
|
|
24
|
-
} from "~/core/server";
|
|
25
|
-
|
|
26
|
-
import * as listMetaModels from "./actionHandlers/listMetaModels";
|
|
27
|
-
import * as listMetaRoutes from "./actionHandlers/listMetaRoutes";
|
|
28
|
-
import * as getMetaModelDetail from "./actionHandlers/getMetaModelDetail";
|
|
29
|
-
import { find, isString, map } from "lodash";
|
|
30
|
-
import { getEntityPropertiesIncludingBase, getEntityPropertyByCode, isOneRelationProperty, isRelationProperty } from "~/helpers/metaHelper";
|
|
31
|
-
import { DataAccessPgColumnTypes } from "~/dataAccess/dataAccessTypes";
|
|
32
|
-
import { pgPropertyTypeColumnMap } from "~/dataAccess/columnTypeMapper";
|
|
33
|
-
import { convertModelIndexConditionsToRowFilterOptions } from "~/helpers/filterHelper";
|
|
34
|
-
import { RouteContext } from "~/core/routeContext";
|
|
35
|
-
|
|
36
|
-
class MetaManager implements RapidPlugin {
|
|
37
|
-
get code(): string {
|
|
38
|
-
return "metaManager";
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
get description(): string {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
get extendingAbilities(): RpdServerPluginExtendingAbilities[] {
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
get configurableTargets(): RpdServerPluginConfigurableTargetOptions[] {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
get configurations(): RpdConfigurationItemOptions[] {
|
|
54
|
-
return [];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async registerActionHandlers(server: IRpdServer): Promise<any> {
|
|
58
|
-
server.registerActionHandler(this, listMetaModels);
|
|
59
|
-
server.registerActionHandler(this, listMetaRoutes);
|
|
60
|
-
server.registerActionHandler(this, getMetaModelDetail);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async registerEventHandlers(server: IRpdServer): Promise<any> {
|
|
64
|
-
server.registerEventHandler("entity.create", handleEntityCreateEvent.bind(this, server));
|
|
65
|
-
server.registerEventHandler("entity.update", handleEntityUpdateEvent.bind(this, server));
|
|
66
|
-
server.registerEventHandler("entity.delete", handleEntityDeleteEvent.bind(this, server));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
70
|
-
const logger = server.getLogger();
|
|
71
|
-
try {
|
|
72
|
-
logger.info("Loading meta of models...");
|
|
73
|
-
const models: RpdDataModel[] = await listDataModels(server);
|
|
74
|
-
const dataDictionaries: RpdDataDictionary[] = await listDataDictionaries(server);
|
|
75
|
-
server.appendApplicationConfig({ models, dataDictionaries });
|
|
76
|
-
} catch (error) {
|
|
77
|
-
logger.crit("Failed to load meta of models.", { error });
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
82
|
-
await syncDatabaseSchema(server, applicationConfig);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default MetaManager;
|
|
87
|
-
|
|
88
|
-
async function handleEntityCreateEvent(server: IRpdServer, sender: RapidPlugin, payload: RpdEntityCreateEventPayload) {
|
|
89
|
-
if (sender === this) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (payload.namespace === "meta" && payload.modelSingularCode === "model") {
|
|
94
|
-
return;
|
|
95
|
-
const { queryBuilder } = server;
|
|
96
|
-
const model: Partial<RpdDataModel> = payload.after;
|
|
97
|
-
if (model.tableName) {
|
|
98
|
-
const model: RpdDataModel = payload.after;
|
|
99
|
-
await server.queryDatabaseObject(`CREATE TABLE ${queryBuilder.quoteTable(model)} ();`, []);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function handleEntityUpdateEvent(server: IRpdServer, sender: RapidPlugin, payload: RpdEntityUpdateEventPayload) {
|
|
105
|
-
if (sender === this) {
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (payload.namespace === "meta" && payload.modelSingularCode === "model") {
|
|
110
|
-
return;
|
|
111
|
-
const { queryBuilder } = server;
|
|
112
|
-
const modelChanges: Partial<RpdDataModel> = payload.changes;
|
|
113
|
-
if (modelChanges.tableName) {
|
|
114
|
-
const modelBefore: RpdDataModel = payload.before;
|
|
115
|
-
await server.queryDatabaseObject(
|
|
116
|
-
`ALTER TABLE ${queryBuilder.quoteTable(modelBefore)} RENAME TO ${queryBuilder.quoteTable(modelChanges as QuoteTableOptions)}`,
|
|
117
|
-
[],
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function handleEntityDeleteEvent(server: IRpdServer, sender: RapidPlugin, payload: RpdEntityDeleteEventPayload, routeContext?: RouteContext) {
|
|
124
|
-
if (sender === this) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (payload.namespace !== "meta") {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const { queryBuilder } = server;
|
|
133
|
-
|
|
134
|
-
if (payload.modelSingularCode === "model") {
|
|
135
|
-
const deletedModel: RpdDataModel = payload.before;
|
|
136
|
-
await server.queryDatabaseObject(`DROP TABLE ${queryBuilder.quoteTable(deletedModel)}`, []);
|
|
137
|
-
} else if (payload.modelSingularCode === "property") {
|
|
138
|
-
const deletedProperty: RpdDataModelProperty = payload.before;
|
|
139
|
-
|
|
140
|
-
let columnNameToDrop = deletedProperty.columnName || deletedProperty.code;
|
|
141
|
-
if (isRelationProperty(deletedProperty)) {
|
|
142
|
-
if (deletedProperty.relation === "one") {
|
|
143
|
-
columnNameToDrop = deletedProperty.targetIdColumnName || "";
|
|
144
|
-
} else {
|
|
145
|
-
// many relation
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const dataAccessor = server.getDataAccessor<RpdDataModel>({
|
|
151
|
-
namespace: "meta",
|
|
152
|
-
singularCode: "model",
|
|
153
|
-
});
|
|
154
|
-
const model = await dataAccessor.findById((deletedProperty as any).modelId, routeContext?.getDbTransactionClient());
|
|
155
|
-
if (model) {
|
|
156
|
-
await server.queryDatabaseObject(`ALTER TABLE ${queryBuilder.quoteTable(model)} DROP COLUMN ${queryBuilder.quoteObject(columnNameToDrop)}`, []);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function listDataModels(server: IRpdServer) {
|
|
162
|
-
const entityManager = server.getEntityManager("model");
|
|
163
|
-
const model = entityManager.getModel();
|
|
164
|
-
|
|
165
|
-
const properties = getEntityPropertiesIncludingBase(server, model);
|
|
166
|
-
return entityManager.findEntities({
|
|
167
|
-
properties: properties.map((item) => item.code),
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function listDataDictionaries(server: IRpdServer) {
|
|
172
|
-
const dataDictionaryManager = server.getEntityManager("data_dictionary");
|
|
173
|
-
const model = dataDictionaryManager.getModel();
|
|
174
|
-
|
|
175
|
-
const properties = getEntityPropertiesIncludingBase(server, model);
|
|
176
|
-
return dataDictionaryManager.findEntities({
|
|
177
|
-
properties: properties.map((item) => item.code),
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
type TableInformation = {
|
|
182
|
-
table_schema: string;
|
|
183
|
-
table_name: string;
|
|
184
|
-
table_description: string;
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
type ColumnInformation = {
|
|
188
|
-
table_schema: string;
|
|
189
|
-
table_name: string;
|
|
190
|
-
column_name: string;
|
|
191
|
-
ordinal_position: number;
|
|
192
|
-
description?: string;
|
|
193
|
-
data_type: string;
|
|
194
|
-
udt_name: string;
|
|
195
|
-
is_nullable: "YES" | "NO";
|
|
196
|
-
column_default: string;
|
|
197
|
-
character_maximum_length: number;
|
|
198
|
-
numeric_precision: number;
|
|
199
|
-
numeric_scale: number;
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
type ConstraintInformation = {
|
|
203
|
-
table_schema: string;
|
|
204
|
-
table_name: string;
|
|
205
|
-
constraint_type: string;
|
|
206
|
-
constraint_name: string;
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdApplicationConfig) {
|
|
210
|
-
const logger = server.getLogger();
|
|
211
|
-
logger.info("Synchronizing database schema...");
|
|
212
|
-
const sqlQueryTableInformations = `SELECT table_schema, table_name, obj_description((table_schema||'.'||quote_ident(table_name))::regclass) as table_description FROM information_schema.tables`;
|
|
213
|
-
const tablesInDb: TableInformation[] = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
214
|
-
const { queryBuilder } = server;
|
|
215
|
-
|
|
216
|
-
for (const model of applicationConfig.models) {
|
|
217
|
-
logger.debug(`Checking data table for '${model.namespace}.${model.singularCode}'...`);
|
|
218
|
-
|
|
219
|
-
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
220
|
-
const expectedTableName = model.tableName;
|
|
221
|
-
const tableInDb = find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName });
|
|
222
|
-
if (!tableInDb) {
|
|
223
|
-
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
224
|
-
}
|
|
225
|
-
if (!tableInDb || tableInDb.table_description != model.name) {
|
|
226
|
-
await server.queryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
231
|
-
FROM information_schema.columns c
|
|
232
|
-
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
233
|
-
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
234
|
-
const columnsInDb: ColumnInformation[] = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
235
|
-
|
|
236
|
-
for (const model of applicationConfig.models) {
|
|
237
|
-
logger.debug(`Checking data columns for '${model.namespace}.${model.singularCode}'...`);
|
|
238
|
-
|
|
239
|
-
for (const property of model.properties) {
|
|
240
|
-
let columnDDL = "";
|
|
241
|
-
if (isRelationProperty(property)) {
|
|
242
|
-
if (property.relation === "one") {
|
|
243
|
-
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
244
|
-
if (!targetModel) {
|
|
245
|
-
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
const columnInDb: ColumnInformation | undefined = find(columnsInDb, {
|
|
249
|
-
table_schema: model.schema || "public",
|
|
250
|
-
table_name: model.tableName,
|
|
251
|
-
column_name: property.targetIdColumnName!,
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
if (!columnInDb) {
|
|
255
|
-
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
256
|
-
schema: model.schema,
|
|
257
|
-
tableName: model.tableName,
|
|
258
|
-
name: property.targetIdColumnName!,
|
|
259
|
-
type: "integer",
|
|
260
|
-
autoIncrement: false,
|
|
261
|
-
notNull: property.required,
|
|
262
|
-
});
|
|
263
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (!columnInDb || columnInDb.description != property.name) {
|
|
267
|
-
await server.queryDatabaseObject(
|
|
268
|
-
`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(
|
|
269
|
-
property.targetIdColumnName,
|
|
270
|
-
)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`,
|
|
271
|
-
[],
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
} else if (property.relation === "many") {
|
|
275
|
-
if (property.linkTableName) {
|
|
276
|
-
const tableInDb = find(tablesInDb, {
|
|
277
|
-
table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema,
|
|
278
|
-
table_name: property.linkTableName,
|
|
279
|
-
});
|
|
280
|
-
if (!tableInDb) {
|
|
281
|
-
columnDDL = generateLinkTableDDL(queryBuilder, {
|
|
282
|
-
linkSchema: property.linkSchema,
|
|
283
|
-
linkTableName: property.linkTableName,
|
|
284
|
-
targetIdColumnName: property.targetIdColumnName!,
|
|
285
|
-
selfIdColumnName: property.selfIdColumnName!,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const contraintName = `${property.linkTableName}_pk`;
|
|
290
|
-
columnDDL += `ALTER TABLE ${queryBuilder.quoteTable({
|
|
291
|
-
schema: property.linkSchema,
|
|
292
|
-
tableName: property.linkTableName,
|
|
293
|
-
})} ADD CONSTRAINT ${queryBuilder.quoteObject(contraintName)} PRIMARY KEY (id);`;
|
|
294
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
295
|
-
} else {
|
|
296
|
-
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
297
|
-
if (!targetModel) {
|
|
298
|
-
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const columnInDb: ColumnInformation | undefined = find(columnsInDb, {
|
|
303
|
-
table_schema: targetModel.schema || "public",
|
|
304
|
-
table_name: targetModel.tableName,
|
|
305
|
-
column_name: property.selfIdColumnName!,
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
if (!columnInDb) {
|
|
309
|
-
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
310
|
-
schema: targetModel.schema,
|
|
311
|
-
tableName: targetModel.tableName,
|
|
312
|
-
name: property.selfIdColumnName || "",
|
|
313
|
-
type: "integer",
|
|
314
|
-
autoIncrement: false,
|
|
315
|
-
notNull: property.required,
|
|
316
|
-
});
|
|
317
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
continue;
|
|
322
|
-
}
|
|
323
|
-
} else {
|
|
324
|
-
const columnName = property.columnName || property.code;
|
|
325
|
-
const columnInDb: ColumnInformation | undefined = find(columnsInDb, {
|
|
326
|
-
table_schema: model.schema || "public",
|
|
327
|
-
table_name: model.tableName,
|
|
328
|
-
column_name: columnName,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
if (!columnInDb) {
|
|
332
|
-
// create column if not exists
|
|
333
|
-
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
334
|
-
schema: model.schema,
|
|
335
|
-
tableName: model.tableName,
|
|
336
|
-
name: columnName,
|
|
337
|
-
type: property.type,
|
|
338
|
-
autoIncrement: property.autoIncrement,
|
|
339
|
-
notNull: property.required,
|
|
340
|
-
defaultValue: property.defaultValue,
|
|
341
|
-
});
|
|
342
|
-
await server.tryQueryDatabaseObject(columnDDL);
|
|
343
|
-
} else {
|
|
344
|
-
const expectedColumnType = pgPropertyTypeColumnMap[property.type];
|
|
345
|
-
if (columnInDb.udt_name !== expectedColumnType) {
|
|
346
|
-
const sqlAlterColumnType = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(
|
|
347
|
-
columnName,
|
|
348
|
-
)} type ${expectedColumnType}`;
|
|
349
|
-
await server.tryQueryDatabaseObject(sqlAlterColumnType);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (property.defaultValue) {
|
|
353
|
-
if (!columnInDb.column_default) {
|
|
354
|
-
const sqlSetColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set default ${
|
|
355
|
-
property.defaultValue
|
|
356
|
-
}`;
|
|
357
|
-
await server.tryQueryDatabaseObject(sqlSetColumnDefault);
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
if (columnInDb.column_default && !property.autoIncrement) {
|
|
361
|
-
const sqlDropColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop default`;
|
|
362
|
-
await server.tryQueryDatabaseObject(sqlDropColumnDefault);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (property.required) {
|
|
367
|
-
if (columnInDb.is_nullable === "YES") {
|
|
368
|
-
const sqlSetColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set not null`;
|
|
369
|
-
await server.tryQueryDatabaseObject(sqlSetColumnNotNull);
|
|
370
|
-
}
|
|
371
|
-
} else {
|
|
372
|
-
if (columnInDb.is_nullable === "NO") {
|
|
373
|
-
const sqlDropColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop not null`;
|
|
374
|
-
await server.tryQueryDatabaseObject(sqlDropColumnNotNull);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
if (!columnInDb || columnInDb.description != property.name) {
|
|
380
|
-
await server.queryDatabaseObject(
|
|
381
|
-
`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(
|
|
382
|
-
property.columnName || property.code,
|
|
383
|
-
)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`,
|
|
384
|
-
[],
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
const sqlQueryConstraints = `SELECT table_schema, table_name, constraint_type, constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'PRIMARY KEY';`;
|
|
392
|
-
const constraintsInDb: ConstraintInformation[] = await server.queryDatabaseObject(sqlQueryConstraints);
|
|
393
|
-
for (const model of applicationConfig.models) {
|
|
394
|
-
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
395
|
-
const expectedTableName = model.tableName;
|
|
396
|
-
const expectedContraintName = `${expectedTableName}_pk`;
|
|
397
|
-
logger.debug(`Checking pk for '${expectedTableSchema}.${expectedTableName}'...`);
|
|
398
|
-
const constraintInDb = find(constraintsInDb, {
|
|
399
|
-
table_schema: expectedTableSchema,
|
|
400
|
-
table_name: expectedTableName,
|
|
401
|
-
constraint_type: "PRIMARY KEY",
|
|
402
|
-
constraint_name: expectedContraintName,
|
|
403
|
-
});
|
|
404
|
-
if (!constraintInDb) {
|
|
405
|
-
await server.queryDatabaseObject(
|
|
406
|
-
`ALTER TABLE ${queryBuilder.quoteTable(model)} ADD CONSTRAINT ${queryBuilder.quoteObject(expectedContraintName)} PRIMARY KEY (id);`,
|
|
407
|
-
[],
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// generate indexes
|
|
413
|
-
for (const model of applicationConfig.models) {
|
|
414
|
-
if (!model.indexes || !model.indexes.length) {
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
logger.debug(`Creating indexes of table ${queryBuilder.quoteTable(model)}`);
|
|
419
|
-
for (const index of model.indexes) {
|
|
420
|
-
const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
|
|
421
|
-
await server.tryQueryDatabaseObject(sqlCreateIndex, []);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function generateCreateColumnDDL(
|
|
427
|
-
queryBuilder: IQueryBuilder,
|
|
428
|
-
options: {
|
|
429
|
-
schema?: string;
|
|
430
|
-
tableName: string;
|
|
431
|
-
name: string;
|
|
432
|
-
type: RpdDataPropertyTypes;
|
|
433
|
-
autoIncrement?: boolean;
|
|
434
|
-
notNull?: boolean;
|
|
435
|
-
defaultValue?: string;
|
|
436
|
-
},
|
|
437
|
-
) {
|
|
438
|
-
let columnDDL = `ALTER TABLE ${queryBuilder.quoteTable(options)} ADD`;
|
|
439
|
-
columnDDL += ` ${queryBuilder.quoteObject(options.name)}`;
|
|
440
|
-
if (options.type === "integer" && options.autoIncrement) {
|
|
441
|
-
columnDDL += ` serial`;
|
|
442
|
-
} else {
|
|
443
|
-
const columnType = pgPropertyTypeColumnMap[options.type];
|
|
444
|
-
if (!columnType) {
|
|
445
|
-
throw new Error(`Property type "${options.type}" is not supported.`);
|
|
446
|
-
}
|
|
447
|
-
columnDDL += ` ${columnType}`;
|
|
448
|
-
}
|
|
449
|
-
if (options.notNull) {
|
|
450
|
-
columnDDL += " NOT NULL";
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
if (options.defaultValue) {
|
|
454
|
-
columnDDL += ` DEFAULT ${options.defaultValue}`;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return columnDDL;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function generateLinkTableDDL(
|
|
461
|
-
queryBuilder: IQueryBuilder,
|
|
462
|
-
options: {
|
|
463
|
-
linkSchema?: string;
|
|
464
|
-
linkTableName: string;
|
|
465
|
-
targetIdColumnName: string;
|
|
466
|
-
selfIdColumnName: string;
|
|
467
|
-
},
|
|
468
|
-
) {
|
|
469
|
-
let columnDDL = `CREATE TABLE ${queryBuilder.quoteTable({
|
|
470
|
-
schema: options.linkSchema,
|
|
471
|
-
tableName: options.linkTableName,
|
|
472
|
-
})} (`;
|
|
473
|
-
columnDDL += `id serial not null,`;
|
|
474
|
-
columnDDL += `${queryBuilder.quoteObject(options.selfIdColumnName)} integer not null,`;
|
|
475
|
-
columnDDL += `${queryBuilder.quoteObject(options.targetIdColumnName)} integer not null);`;
|
|
476
|
-
|
|
477
|
-
return columnDDL;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function generateTableIndexDDL(queryBuilder: IQueryBuilder, server: IRpdServer, model: RpdDataModel, index: RpdDataModelIndex) {
|
|
481
|
-
let indexName = index.name;
|
|
482
|
-
if (!indexName) {
|
|
483
|
-
indexName = model.tableName;
|
|
484
|
-
for (const indexProp of index.properties) {
|
|
485
|
-
const propCode = isString(indexProp) ? indexProp : indexProp.code;
|
|
486
|
-
const property = getEntityPropertyByCode(server, model, propCode);
|
|
487
|
-
if (!isRelationProperty(property)) {
|
|
488
|
-
indexName += "_" + property.columnName;
|
|
489
|
-
} else if (isOneRelationProperty(property)) {
|
|
490
|
-
indexName += "_" + property.targetIdColumnName;
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
indexName += index.unique ? "_uindex" : "_index";
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const indexColumns = map(index.properties, (indexProp) => {
|
|
497
|
-
let columnName: string;
|
|
498
|
-
const propCode = isString(indexProp) ? indexProp : indexProp.code;
|
|
499
|
-
const property = getEntityPropertyByCode(server, model, propCode);
|
|
500
|
-
if (!isRelationProperty(property)) {
|
|
501
|
-
columnName = property.columnName;
|
|
502
|
-
} else if (isOneRelationProperty(property)) {
|
|
503
|
-
columnName = property.targetIdColumnName;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (isString(indexProp)) {
|
|
507
|
-
return columnName;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (indexProp.order === "desc") {
|
|
511
|
-
return `${columnName} desc`;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
return columnName;
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
let ddl = `CREATE ${index.unique ? "UNIQUE" : ""} INDEX ${indexName} `;
|
|
518
|
-
ddl += `ON ${queryBuilder.quoteTable({
|
|
519
|
-
schema: model.schema,
|
|
520
|
-
tableName: model.tableName,
|
|
521
|
-
})} (${indexColumns.join(", ")})`;
|
|
522
|
-
|
|
523
|
-
if (index.conditions) {
|
|
524
|
-
const logger = server.getLogger();
|
|
525
|
-
const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(logger, model, index.conditions);
|
|
526
|
-
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return ddl;
|
|
530
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Meta manager plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
IQueryBuilder,
|
|
7
|
+
QuoteTableOptions,
|
|
8
|
+
RpdApplicationConfig,
|
|
9
|
+
RpdDataDictionary,
|
|
10
|
+
RpdDataModel,
|
|
11
|
+
RpdDataModelIndex,
|
|
12
|
+
RpdDataModelProperty,
|
|
13
|
+
RpdDataPropertyTypes,
|
|
14
|
+
RpdEntityCreateEventPayload,
|
|
15
|
+
RpdEntityDeleteEventPayload,
|
|
16
|
+
RpdEntityUpdateEventPayload,
|
|
17
|
+
} from "~/types";
|
|
18
|
+
import {
|
|
19
|
+
IRpdServer,
|
|
20
|
+
RapidPlugin,
|
|
21
|
+
RpdConfigurationItemOptions,
|
|
22
|
+
RpdServerPluginConfigurableTargetOptions,
|
|
23
|
+
RpdServerPluginExtendingAbilities,
|
|
24
|
+
} from "~/core/server";
|
|
25
|
+
|
|
26
|
+
import * as listMetaModels from "./actionHandlers/listMetaModels";
|
|
27
|
+
import * as listMetaRoutes from "./actionHandlers/listMetaRoutes";
|
|
28
|
+
import * as getMetaModelDetail from "./actionHandlers/getMetaModelDetail";
|
|
29
|
+
import { find, isString, map } from "lodash";
|
|
30
|
+
import { getEntityPropertiesIncludingBase, getEntityPropertyByCode, isOneRelationProperty, isRelationProperty } from "~/helpers/metaHelper";
|
|
31
|
+
import { DataAccessPgColumnTypes } from "~/dataAccess/dataAccessTypes";
|
|
32
|
+
import { pgPropertyTypeColumnMap } from "~/dataAccess/columnTypeMapper";
|
|
33
|
+
import { convertModelIndexConditionsToRowFilterOptions } from "~/helpers/filterHelper";
|
|
34
|
+
import { RouteContext } from "~/core/routeContext";
|
|
35
|
+
|
|
36
|
+
class MetaManager implements RapidPlugin {
|
|
37
|
+
get code(): string {
|
|
38
|
+
return "metaManager";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get description(): string {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get extendingAbilities(): RpdServerPluginExtendingAbilities[] {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get configurableTargets(): RpdServerPluginConfigurableTargetOptions[] {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
get configurations(): RpdConfigurationItemOptions[] {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async registerActionHandlers(server: IRpdServer): Promise<any> {
|
|
58
|
+
server.registerActionHandler(this, listMetaModels);
|
|
59
|
+
server.registerActionHandler(this, listMetaRoutes);
|
|
60
|
+
server.registerActionHandler(this, getMetaModelDetail);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async registerEventHandlers(server: IRpdServer): Promise<any> {
|
|
64
|
+
server.registerEventHandler("entity.create", handleEntityCreateEvent.bind(this, server));
|
|
65
|
+
server.registerEventHandler("entity.update", handleEntityUpdateEvent.bind(this, server));
|
|
66
|
+
server.registerEventHandler("entity.delete", handleEntityDeleteEvent.bind(this, server));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async configureModels(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
70
|
+
const logger = server.getLogger();
|
|
71
|
+
try {
|
|
72
|
+
logger.info("Loading meta of models...");
|
|
73
|
+
const models: RpdDataModel[] = await listDataModels(server);
|
|
74
|
+
const dataDictionaries: RpdDataDictionary[] = await listDataDictionaries(server);
|
|
75
|
+
server.appendApplicationConfig({ models, dataDictionaries });
|
|
76
|
+
} catch (error) {
|
|
77
|
+
logger.crit("Failed to load meta of models.", { error });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async onApplicationLoaded(server: IRpdServer, applicationConfig: RpdApplicationConfig): Promise<any> {
|
|
82
|
+
await syncDatabaseSchema(server, applicationConfig);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export default MetaManager;
|
|
87
|
+
|
|
88
|
+
async function handleEntityCreateEvent(server: IRpdServer, sender: RapidPlugin, payload: RpdEntityCreateEventPayload) {
|
|
89
|
+
if (sender === this) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (payload.namespace === "meta" && payload.modelSingularCode === "model") {
|
|
94
|
+
return;
|
|
95
|
+
const { queryBuilder } = server;
|
|
96
|
+
const model: Partial<RpdDataModel> = payload.after;
|
|
97
|
+
if (model.tableName) {
|
|
98
|
+
const model: RpdDataModel = payload.after;
|
|
99
|
+
await server.queryDatabaseObject(`CREATE TABLE ${queryBuilder.quoteTable(model)} ();`, []);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function handleEntityUpdateEvent(server: IRpdServer, sender: RapidPlugin, payload: RpdEntityUpdateEventPayload) {
|
|
105
|
+
if (sender === this) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (payload.namespace === "meta" && payload.modelSingularCode === "model") {
|
|
110
|
+
return;
|
|
111
|
+
const { queryBuilder } = server;
|
|
112
|
+
const modelChanges: Partial<RpdDataModel> = payload.changes;
|
|
113
|
+
if (modelChanges.tableName) {
|
|
114
|
+
const modelBefore: RpdDataModel = payload.before;
|
|
115
|
+
await server.queryDatabaseObject(
|
|
116
|
+
`ALTER TABLE ${queryBuilder.quoteTable(modelBefore)} RENAME TO ${queryBuilder.quoteTable(modelChanges as QuoteTableOptions)}`,
|
|
117
|
+
[],
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function handleEntityDeleteEvent(server: IRpdServer, sender: RapidPlugin, payload: RpdEntityDeleteEventPayload, routeContext?: RouteContext) {
|
|
124
|
+
if (sender === this) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (payload.namespace !== "meta") {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { queryBuilder } = server;
|
|
133
|
+
|
|
134
|
+
if (payload.modelSingularCode === "model") {
|
|
135
|
+
const deletedModel: RpdDataModel = payload.before;
|
|
136
|
+
await server.queryDatabaseObject(`DROP TABLE ${queryBuilder.quoteTable(deletedModel)}`, []);
|
|
137
|
+
} else if (payload.modelSingularCode === "property") {
|
|
138
|
+
const deletedProperty: RpdDataModelProperty = payload.before;
|
|
139
|
+
|
|
140
|
+
let columnNameToDrop = deletedProperty.columnName || deletedProperty.code;
|
|
141
|
+
if (isRelationProperty(deletedProperty)) {
|
|
142
|
+
if (deletedProperty.relation === "one") {
|
|
143
|
+
columnNameToDrop = deletedProperty.targetIdColumnName || "";
|
|
144
|
+
} else {
|
|
145
|
+
// many relation
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const dataAccessor = server.getDataAccessor<RpdDataModel>({
|
|
151
|
+
namespace: "meta",
|
|
152
|
+
singularCode: "model",
|
|
153
|
+
});
|
|
154
|
+
const model = await dataAccessor.findById((deletedProperty as any).modelId, routeContext?.getDbTransactionClient());
|
|
155
|
+
if (model) {
|
|
156
|
+
await server.queryDatabaseObject(`ALTER TABLE ${queryBuilder.quoteTable(model)} DROP COLUMN ${queryBuilder.quoteObject(columnNameToDrop)}`, []);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function listDataModels(server: IRpdServer) {
|
|
162
|
+
const entityManager = server.getEntityManager("model");
|
|
163
|
+
const model = entityManager.getModel();
|
|
164
|
+
|
|
165
|
+
const properties = getEntityPropertiesIncludingBase(server, model);
|
|
166
|
+
return entityManager.findEntities({
|
|
167
|
+
properties: properties.map((item) => item.code),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function listDataDictionaries(server: IRpdServer) {
|
|
172
|
+
const dataDictionaryManager = server.getEntityManager("data_dictionary");
|
|
173
|
+
const model = dataDictionaryManager.getModel();
|
|
174
|
+
|
|
175
|
+
const properties = getEntityPropertiesIncludingBase(server, model);
|
|
176
|
+
return dataDictionaryManager.findEntities({
|
|
177
|
+
properties: properties.map((item) => item.code),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
type TableInformation = {
|
|
182
|
+
table_schema: string;
|
|
183
|
+
table_name: string;
|
|
184
|
+
table_description: string;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
type ColumnInformation = {
|
|
188
|
+
table_schema: string;
|
|
189
|
+
table_name: string;
|
|
190
|
+
column_name: string;
|
|
191
|
+
ordinal_position: number;
|
|
192
|
+
description?: string;
|
|
193
|
+
data_type: string;
|
|
194
|
+
udt_name: string;
|
|
195
|
+
is_nullable: "YES" | "NO";
|
|
196
|
+
column_default: string;
|
|
197
|
+
character_maximum_length: number;
|
|
198
|
+
numeric_precision: number;
|
|
199
|
+
numeric_scale: number;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
type ConstraintInformation = {
|
|
203
|
+
table_schema: string;
|
|
204
|
+
table_name: string;
|
|
205
|
+
constraint_type: string;
|
|
206
|
+
constraint_name: string;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
async function syncDatabaseSchema(server: IRpdServer, applicationConfig: RpdApplicationConfig) {
|
|
210
|
+
const logger = server.getLogger();
|
|
211
|
+
logger.info("Synchronizing database schema...");
|
|
212
|
+
const sqlQueryTableInformations = `SELECT table_schema, table_name, obj_description((table_schema||'.'||quote_ident(table_name))::regclass) as table_description FROM information_schema.tables`;
|
|
213
|
+
const tablesInDb: TableInformation[] = await server.queryDatabaseObject(sqlQueryTableInformations);
|
|
214
|
+
const { queryBuilder } = server;
|
|
215
|
+
|
|
216
|
+
for (const model of applicationConfig.models) {
|
|
217
|
+
logger.debug(`Checking data table for '${model.namespace}.${model.singularCode}'...`);
|
|
218
|
+
|
|
219
|
+
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
220
|
+
const expectedTableName = model.tableName;
|
|
221
|
+
const tableInDb = find(tablesInDb, { table_schema: expectedTableSchema, table_name: expectedTableName });
|
|
222
|
+
if (!tableInDb) {
|
|
223
|
+
await server.queryDatabaseObject(`CREATE TABLE IF NOT EXISTS ${queryBuilder.quoteTable(model)} ()`, []);
|
|
224
|
+
}
|
|
225
|
+
if (!tableInDb || tableInDb.table_description != model.name) {
|
|
226
|
+
await server.queryDatabaseObject(`COMMENT ON TABLE ${queryBuilder.quoteTable(model)} IS ${queryBuilder.formatValueToSqlLiteral(model.name)};`, []);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const sqlQueryColumnInformations = `SELECT c.table_schema, c.table_name, c.column_name, c.ordinal_position, d.description, c.data_type, c.udt_name, c.is_nullable, c.column_default, c.character_maximum_length, c.numeric_precision, c.numeric_scale
|
|
231
|
+
FROM information_schema.columns c
|
|
232
|
+
INNER JOIN pg_catalog.pg_statio_all_tables st ON (st.schemaname = c.table_schema and st.relname = c.table_name)
|
|
233
|
+
LEFT JOIN pg_catalog.pg_description d ON (d.objoid = st.relid and d.objsubid = c.ordinal_position);`;
|
|
234
|
+
const columnsInDb: ColumnInformation[] = await server.queryDatabaseObject(sqlQueryColumnInformations, []);
|
|
235
|
+
|
|
236
|
+
for (const model of applicationConfig.models) {
|
|
237
|
+
logger.debug(`Checking data columns for '${model.namespace}.${model.singularCode}'...`);
|
|
238
|
+
|
|
239
|
+
for (const property of model.properties) {
|
|
240
|
+
let columnDDL = "";
|
|
241
|
+
if (isRelationProperty(property)) {
|
|
242
|
+
if (property.relation === "one") {
|
|
243
|
+
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
244
|
+
if (!targetModel) {
|
|
245
|
+
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const columnInDb: ColumnInformation | undefined = find(columnsInDb, {
|
|
249
|
+
table_schema: model.schema || "public",
|
|
250
|
+
table_name: model.tableName,
|
|
251
|
+
column_name: property.targetIdColumnName!,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
if (!columnInDb) {
|
|
255
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
256
|
+
schema: model.schema,
|
|
257
|
+
tableName: model.tableName,
|
|
258
|
+
name: property.targetIdColumnName!,
|
|
259
|
+
type: "integer",
|
|
260
|
+
autoIncrement: false,
|
|
261
|
+
notNull: property.required,
|
|
262
|
+
});
|
|
263
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
267
|
+
await server.queryDatabaseObject(
|
|
268
|
+
`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(
|
|
269
|
+
property.targetIdColumnName,
|
|
270
|
+
)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`,
|
|
271
|
+
[],
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
} else if (property.relation === "many") {
|
|
275
|
+
if (property.linkTableName) {
|
|
276
|
+
const tableInDb = find(tablesInDb, {
|
|
277
|
+
table_schema: property.linkSchema || server.databaseConfig.dbDefaultSchema,
|
|
278
|
+
table_name: property.linkTableName,
|
|
279
|
+
});
|
|
280
|
+
if (!tableInDb) {
|
|
281
|
+
columnDDL = generateLinkTableDDL(queryBuilder, {
|
|
282
|
+
linkSchema: property.linkSchema,
|
|
283
|
+
linkTableName: property.linkTableName,
|
|
284
|
+
targetIdColumnName: property.targetIdColumnName!,
|
|
285
|
+
selfIdColumnName: property.selfIdColumnName!,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const contraintName = `${property.linkTableName}_pk`;
|
|
290
|
+
columnDDL += `ALTER TABLE ${queryBuilder.quoteTable({
|
|
291
|
+
schema: property.linkSchema,
|
|
292
|
+
tableName: property.linkTableName,
|
|
293
|
+
})} ADD CONSTRAINT ${queryBuilder.quoteObject(contraintName)} PRIMARY KEY (id);`;
|
|
294
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
295
|
+
} else {
|
|
296
|
+
const targetModel = applicationConfig.models.find((item) => item.singularCode === property.targetSingularCode);
|
|
297
|
+
if (!targetModel) {
|
|
298
|
+
logger.warn(`Cannot find target model with singular code "${property.targetSingularCode}".`);
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const columnInDb: ColumnInformation | undefined = find(columnsInDb, {
|
|
303
|
+
table_schema: targetModel.schema || "public",
|
|
304
|
+
table_name: targetModel.tableName,
|
|
305
|
+
column_name: property.selfIdColumnName!,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (!columnInDb) {
|
|
309
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
310
|
+
schema: targetModel.schema,
|
|
311
|
+
tableName: targetModel.tableName,
|
|
312
|
+
name: property.selfIdColumnName || "",
|
|
313
|
+
type: "integer",
|
|
314
|
+
autoIncrement: false,
|
|
315
|
+
notNull: property.required,
|
|
316
|
+
});
|
|
317
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
const columnName = property.columnName || property.code;
|
|
325
|
+
const columnInDb: ColumnInformation | undefined = find(columnsInDb, {
|
|
326
|
+
table_schema: model.schema || "public",
|
|
327
|
+
table_name: model.tableName,
|
|
328
|
+
column_name: columnName,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
if (!columnInDb) {
|
|
332
|
+
// create column if not exists
|
|
333
|
+
columnDDL = generateCreateColumnDDL(queryBuilder, {
|
|
334
|
+
schema: model.schema,
|
|
335
|
+
tableName: model.tableName,
|
|
336
|
+
name: columnName,
|
|
337
|
+
type: property.type,
|
|
338
|
+
autoIncrement: property.autoIncrement,
|
|
339
|
+
notNull: property.required,
|
|
340
|
+
defaultValue: property.defaultValue,
|
|
341
|
+
});
|
|
342
|
+
await server.tryQueryDatabaseObject(columnDDL);
|
|
343
|
+
} else {
|
|
344
|
+
const expectedColumnType = pgPropertyTypeColumnMap[property.type];
|
|
345
|
+
if (columnInDb.udt_name !== expectedColumnType) {
|
|
346
|
+
const sqlAlterColumnType = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(
|
|
347
|
+
columnName,
|
|
348
|
+
)} type ${expectedColumnType}`;
|
|
349
|
+
await server.tryQueryDatabaseObject(sqlAlterColumnType);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (property.defaultValue) {
|
|
353
|
+
if (!columnInDb.column_default) {
|
|
354
|
+
const sqlSetColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set default ${
|
|
355
|
+
property.defaultValue
|
|
356
|
+
}`;
|
|
357
|
+
await server.tryQueryDatabaseObject(sqlSetColumnDefault);
|
|
358
|
+
}
|
|
359
|
+
} else {
|
|
360
|
+
if (columnInDb.column_default && !property.autoIncrement) {
|
|
361
|
+
const sqlDropColumnDefault = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop default`;
|
|
362
|
+
await server.tryQueryDatabaseObject(sqlDropColumnDefault);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (property.required) {
|
|
367
|
+
if (columnInDb.is_nullable === "YES") {
|
|
368
|
+
const sqlSetColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} set not null`;
|
|
369
|
+
await server.tryQueryDatabaseObject(sqlSetColumnNotNull);
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
if (columnInDb.is_nullable === "NO") {
|
|
373
|
+
const sqlDropColumnNotNull = `alter table ${queryBuilder.quoteTable(model)} alter column ${queryBuilder.quoteObject(columnName)} drop not null`;
|
|
374
|
+
await server.tryQueryDatabaseObject(sqlDropColumnNotNull);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!columnInDb || columnInDb.description != property.name) {
|
|
380
|
+
await server.queryDatabaseObject(
|
|
381
|
+
`COMMENT ON COLUMN ${queryBuilder.quoteTable(model)}.${queryBuilder.quoteObject(
|
|
382
|
+
property.columnName || property.code,
|
|
383
|
+
)} IS ${queryBuilder.formatValueToSqlLiteral(property.name)};`,
|
|
384
|
+
[],
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const sqlQueryConstraints = `SELECT table_schema, table_name, constraint_type, constraint_name FROM information_schema.table_constraints WHERE constraint_type = 'PRIMARY KEY';`;
|
|
392
|
+
const constraintsInDb: ConstraintInformation[] = await server.queryDatabaseObject(sqlQueryConstraints);
|
|
393
|
+
for (const model of applicationConfig.models) {
|
|
394
|
+
const expectedTableSchema = model.schema || server.databaseConfig.dbDefaultSchema;
|
|
395
|
+
const expectedTableName = model.tableName;
|
|
396
|
+
const expectedContraintName = `${expectedTableName}_pk`;
|
|
397
|
+
logger.debug(`Checking pk for '${expectedTableSchema}.${expectedTableName}'...`);
|
|
398
|
+
const constraintInDb = find(constraintsInDb, {
|
|
399
|
+
table_schema: expectedTableSchema,
|
|
400
|
+
table_name: expectedTableName,
|
|
401
|
+
constraint_type: "PRIMARY KEY",
|
|
402
|
+
constraint_name: expectedContraintName,
|
|
403
|
+
});
|
|
404
|
+
if (!constraintInDb) {
|
|
405
|
+
await server.queryDatabaseObject(
|
|
406
|
+
`ALTER TABLE ${queryBuilder.quoteTable(model)} ADD CONSTRAINT ${queryBuilder.quoteObject(expectedContraintName)} PRIMARY KEY (id);`,
|
|
407
|
+
[],
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// generate indexes
|
|
413
|
+
for (const model of applicationConfig.models) {
|
|
414
|
+
if (!model.indexes || !model.indexes.length) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
logger.debug(`Creating indexes of table ${queryBuilder.quoteTable(model)}`);
|
|
419
|
+
for (const index of model.indexes) {
|
|
420
|
+
const sqlCreateIndex = generateTableIndexDDL(queryBuilder, server, model, index);
|
|
421
|
+
await server.tryQueryDatabaseObject(sqlCreateIndex, []);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function generateCreateColumnDDL(
|
|
427
|
+
queryBuilder: IQueryBuilder,
|
|
428
|
+
options: {
|
|
429
|
+
schema?: string;
|
|
430
|
+
tableName: string;
|
|
431
|
+
name: string;
|
|
432
|
+
type: RpdDataPropertyTypes;
|
|
433
|
+
autoIncrement?: boolean;
|
|
434
|
+
notNull?: boolean;
|
|
435
|
+
defaultValue?: string;
|
|
436
|
+
},
|
|
437
|
+
) {
|
|
438
|
+
let columnDDL = `ALTER TABLE ${queryBuilder.quoteTable(options)} ADD`;
|
|
439
|
+
columnDDL += ` ${queryBuilder.quoteObject(options.name)}`;
|
|
440
|
+
if (options.type === "integer" && options.autoIncrement) {
|
|
441
|
+
columnDDL += ` serial`;
|
|
442
|
+
} else {
|
|
443
|
+
const columnType = pgPropertyTypeColumnMap[options.type];
|
|
444
|
+
if (!columnType) {
|
|
445
|
+
throw new Error(`Property type "${options.type}" is not supported.`);
|
|
446
|
+
}
|
|
447
|
+
columnDDL += ` ${columnType}`;
|
|
448
|
+
}
|
|
449
|
+
if (options.notNull) {
|
|
450
|
+
columnDDL += " NOT NULL";
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (options.defaultValue) {
|
|
454
|
+
columnDDL += ` DEFAULT ${options.defaultValue}`;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return columnDDL;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function generateLinkTableDDL(
|
|
461
|
+
queryBuilder: IQueryBuilder,
|
|
462
|
+
options: {
|
|
463
|
+
linkSchema?: string;
|
|
464
|
+
linkTableName: string;
|
|
465
|
+
targetIdColumnName: string;
|
|
466
|
+
selfIdColumnName: string;
|
|
467
|
+
},
|
|
468
|
+
) {
|
|
469
|
+
let columnDDL = `CREATE TABLE ${queryBuilder.quoteTable({
|
|
470
|
+
schema: options.linkSchema,
|
|
471
|
+
tableName: options.linkTableName,
|
|
472
|
+
})} (`;
|
|
473
|
+
columnDDL += `id serial not null,`;
|
|
474
|
+
columnDDL += `${queryBuilder.quoteObject(options.selfIdColumnName)} integer not null,`;
|
|
475
|
+
columnDDL += `${queryBuilder.quoteObject(options.targetIdColumnName)} integer not null);`;
|
|
476
|
+
|
|
477
|
+
return columnDDL;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function generateTableIndexDDL(queryBuilder: IQueryBuilder, server: IRpdServer, model: RpdDataModel, index: RpdDataModelIndex) {
|
|
481
|
+
let indexName = index.name;
|
|
482
|
+
if (!indexName) {
|
|
483
|
+
indexName = model.tableName;
|
|
484
|
+
for (const indexProp of index.properties) {
|
|
485
|
+
const propCode = isString(indexProp) ? indexProp : indexProp.code;
|
|
486
|
+
const property = getEntityPropertyByCode(server, model, propCode);
|
|
487
|
+
if (!isRelationProperty(property)) {
|
|
488
|
+
indexName += "_" + property.columnName;
|
|
489
|
+
} else if (isOneRelationProperty(property)) {
|
|
490
|
+
indexName += "_" + property.targetIdColumnName;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
indexName += index.unique ? "_uindex" : "_index";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const indexColumns = map(index.properties, (indexProp) => {
|
|
497
|
+
let columnName: string;
|
|
498
|
+
const propCode = isString(indexProp) ? indexProp : indexProp.code;
|
|
499
|
+
const property = getEntityPropertyByCode(server, model, propCode);
|
|
500
|
+
if (!isRelationProperty(property)) {
|
|
501
|
+
columnName = property.columnName;
|
|
502
|
+
} else if (isOneRelationProperty(property)) {
|
|
503
|
+
columnName = property.targetIdColumnName;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (isString(indexProp)) {
|
|
507
|
+
return columnName;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (indexProp.order === "desc") {
|
|
511
|
+
return `${columnName} desc`;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return columnName;
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
let ddl = `CREATE ${index.unique ? "UNIQUE" : ""} INDEX ${indexName} `;
|
|
518
|
+
ddl += `ON ${queryBuilder.quoteTable({
|
|
519
|
+
schema: model.schema,
|
|
520
|
+
tableName: model.tableName,
|
|
521
|
+
})} (${indexColumns.join(", ")})`;
|
|
522
|
+
|
|
523
|
+
if (index.conditions) {
|
|
524
|
+
const logger = server.getLogger();
|
|
525
|
+
const rowFilterOptions = convertModelIndexConditionsToRowFilterOptions(logger, model, index.conditions);
|
|
526
|
+
ddl += ` WHERE ${queryBuilder.buildFiltersExpression(model, rowFilterOptions)}`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return ddl;
|
|
530
|
+
}
|