@memberjunction/codegen-lib 1.0.4 → 1.0.7-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/advanced_generation.d.ts +3 -0
- package/dist/advanced_generation.d.ts.map +1 -1
- package/dist/advanced_generation.js +39 -26
- package/dist/advanced_generation.js.map +1 -1
- package/dist/angular_client_codegen.d.ts +45 -2
- package/dist/angular_client_codegen.d.ts.map +1 -1
- package/dist/angular_client_codegen.js +423 -399
- package/dist/angular_client_codegen.js.map +1 -1
- package/dist/createNewUser.d.ts +6 -1
- package/dist/createNewUser.d.ts.map +1 -1
- package/dist/createNewUser.js +68 -53
- package/dist/createNewUser.js.map +1 -1
- package/dist/dbSchema.d.ts +10 -3
- package/dist/dbSchema.d.ts.map +1 -1
- package/dist/dbSchema.js +144 -130
- package/dist/dbSchema.js.map +1 -1
- package/dist/entity_subclasses_codegen.d.ts +11 -6
- package/dist/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/entity_subclasses_codegen.js +144 -131
- package/dist/entity_subclasses_codegen.js.map +1 -1
- package/dist/graphql_server_codegen.d.ts +28 -5
- package/dist/graphql_server_codegen.d.ts.map +1 -1
- package/dist/graphql_server_codegen.js +552 -531
- package/dist/graphql_server_codegen.js.map +1 -1
- package/dist/logging.d.ts +20 -0
- package/dist/logging.d.ts.map +1 -1
- package/dist/logging.js +61 -26
- package/dist/logging.js.map +1 -1
- package/dist/manageMetadata.d.ts +128 -7
- package/dist/manageMetadata.d.ts.map +1 -1
- package/dist/manageMetadata.js +992 -898
- package/dist/manageMetadata.js.map +1 -1
- package/dist/runCodeGen.d.ts +16 -0
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +185 -140
- package/dist/runCodeGen.js.map +1 -1
- package/dist/runCommand.d.ts +7 -2
- package/dist/runCommand.d.ts.map +1 -1
- package/dist/runCommand.js +104 -90
- package/dist/runCommand.js.map +1 -1
- package/dist/sql.d.ts +21 -16
- package/dist/sql.d.ts.map +1 -1
- package/dist/sql.js +98 -87
- package/dist/sql.js.map +1 -1
- package/dist/sql_codegen.d.ts +56 -13
- package/dist/sql_codegen.d.ts.map +1 -1
- package/dist/sql_codegen.js +836 -795
- package/dist/sql_codegen.js.map +1 -1
- package/dist/util.js +0 -2
- package/dist/util.js.map +1 -1
- package/package.json +32 -32
- package/readme.md +2 -2
- package/dist/graphql_client_codegen.d.ts +0 -4
- package/dist/graphql_client_codegen.js +0 -161
- package/dist/graphql_client_codegen.js.map +0 -1
- package/dist/react_client_codegen.d.ts +0 -4
- package/dist/react_client_codegen.js +0 -147
- package/dist/react_client_codegen.js.map +0 -1
package/dist/manageMetadata.js
CHANGED
|
@@ -1,247 +1,805 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var ManageMetadataBase_1;
|
|
7
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.
|
|
10
|
+
exports.ManageMetadataBase = void 0;
|
|
9
11
|
const config_1 = require("./config");
|
|
10
12
|
const core_1 = require("@memberjunction/core");
|
|
11
13
|
const logging_1 = require("./logging");
|
|
12
14
|
const sql_1 = require("./sql");
|
|
13
15
|
const advanced_generation_1 = require("./advanced_generation");
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
// //
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
// }
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
16
|
+
const global_1 = require("@memberjunction/global");
|
|
17
|
+
/**
|
|
18
|
+
* Base class for managing metadata within the CodeGen system. This class can be sub-classed to extend/override base class functionality. Make sure to use the RegisterClass decorator from the @memberjunction/global package
|
|
19
|
+
* to properly register your subclass with a priority of 1+ to ensure it gets instantiated.
|
|
20
|
+
*/
|
|
21
|
+
let ManageMetadataBase = ManageMetadataBase_1 = class ManageMetadataBase {
|
|
22
|
+
static get newEntityList() {
|
|
23
|
+
return this._newEntityList;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Primary function to manage metadata within the CodeGen system. This function will call a series of sub-functions to manage the metadata.
|
|
27
|
+
* @param ds - the DataSource object to use for querying and updating the database
|
|
28
|
+
* @returns
|
|
29
|
+
*/
|
|
30
|
+
async manageMetadata(ds) {
|
|
31
|
+
const md = new core_1.Metadata();
|
|
32
|
+
const excludeSchemas = config_1.configInfo.excludeSchemas ? config_1.configInfo.excludeSchemas : [];
|
|
33
|
+
let bSuccess = true;
|
|
34
|
+
if (!await this.createNewEntities(ds)) {
|
|
35
|
+
(0, logging_1.logError)('Error creating new entities');
|
|
36
|
+
bSuccess = false;
|
|
37
|
+
}
|
|
38
|
+
if (!await this.updateExistingEntitiesFromSchema(ds, excludeSchemas)) {
|
|
39
|
+
(0, logging_1.logError)('Error updating existing entities');
|
|
40
|
+
bSuccess = false;
|
|
41
|
+
}
|
|
42
|
+
const sqlUtility = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(sql_1.SQLUtilityBase);
|
|
43
|
+
if (!await sqlUtility.recompileAllBaseViews(ds, excludeSchemas, true)) {
|
|
44
|
+
(0, logging_1.logError)('Warning: Non-Fatal error recompiling base views');
|
|
45
|
+
// many times the former versions of base views will NOT succesfully recompile, so don't consider that scenario to be a
|
|
46
|
+
// failure for this entire function
|
|
47
|
+
}
|
|
48
|
+
if (!await this.manageEntityFields(ds, excludeSchemas)) {
|
|
49
|
+
(0, logging_1.logError)('Error managing entity fields');
|
|
50
|
+
bSuccess = false;
|
|
51
|
+
}
|
|
52
|
+
if (!await this.manageEntityRelationships(ds, excludeSchemas, md)) {
|
|
53
|
+
(0, logging_1.logError)('Error managing entity relationships');
|
|
54
|
+
bSuccess = false;
|
|
55
|
+
}
|
|
56
|
+
if (ManageMetadataBase_1.newEntityList.length > 0) {
|
|
57
|
+
await this.generateNewEntityDescriptions(ds, md); // don't pass excludeSchemas becuase by definition this is the NEW entities we created
|
|
58
|
+
}
|
|
59
|
+
if (!await this.manageVirtualEntities(ds)) {
|
|
60
|
+
(0, logging_1.logError)('Error managing virtual entities');
|
|
61
|
+
bSuccess = false;
|
|
62
|
+
}
|
|
63
|
+
// now - we need to tell our metadata object to refresh itself
|
|
64
|
+
await md.Refresh();
|
|
65
|
+
return bSuccess;
|
|
66
|
+
}
|
|
67
|
+
// VIRTUAL ENTITY FUNCTIONALITY IS COMMENTED OUT FOR NOW - FUTURE FUNCTIONALITY...
|
|
68
|
+
// ------------------------------------------------
|
|
69
|
+
async manageVirtualEntities(ds) {
|
|
70
|
+
// let bSuccess = true;
|
|
71
|
+
// // virtual entities are records defined in the entity metadata and do NOT define a distinct base table
|
|
72
|
+
// // but they do specify a base view. We DO NOT generate a base view for a virtual entity, we simply use it to figure
|
|
73
|
+
// // out the fields that should be in the entity definition and add/update/delete the entity definition to match what's in the view when this runs
|
|
74
|
+
// const sql = 'SELECT * FROM vwEntities WHERE VirtualEntity = 1';
|
|
75
|
+
// const virtualEntities = await ds.query(sql);
|
|
76
|
+
// if (virtualEntities && virtualEntities.length > 0) {
|
|
77
|
+
// // we have 1+ virtual entities, now loop through them and process each one
|
|
78
|
+
// for (const ve of virtualEntities) {
|
|
79
|
+
// if (! await manageSingleVirtualEntity(ds, ve)) {
|
|
80
|
+
// logError(`Error managing virtual entity ${ve.Name}`);
|
|
81
|
+
// bSuccess = false;
|
|
82
|
+
// }
|
|
83
|
+
// }
|
|
84
|
+
// }
|
|
85
|
+
// return bSuccess;
|
|
86
|
+
return true; // temp until the above is implemented
|
|
87
|
+
}
|
|
88
|
+
async manageSingleVirtualEntity(ds, ve) {
|
|
89
|
+
// try {
|
|
90
|
+
// // for a given virtual entity, we need to loop through the fields that exist in the current SQL definition for the view
|
|
91
|
+
// // and add/update/delete the entity fields to match what's in the view
|
|
92
|
+
// let bSuccess = true;
|
|
93
|
+
// const sql = `SELECT * FROM vwSQLColumnsAndEntityFields WHERE EntityID = ${ve.ID}`;
|
|
94
|
+
// const veFields = await ds.query(sql);
|
|
95
|
+
// if (veFields && veFields.length > 0) {
|
|
96
|
+
// // we have 1+ fields, now loop through them and process each one
|
|
97
|
+
// // first though, remove any fields that are no longer in the view
|
|
98
|
+
// const md = new Metadata();
|
|
99
|
+
// const entity = md.Entities.find(e => e.Name === ve.Name);
|
|
100
|
+
// if (entity) {
|
|
101
|
+
// const removeList = [];
|
|
102
|
+
// const fieldsToRemove = entity.Fields.filter(f => !veFields.find(vf => vf.FieldName === f.Name));
|
|
103
|
+
// for (const f of fieldsToRemove) {
|
|
104
|
+
// removeList.push(f.ID);
|
|
105
|
+
// }
|
|
106
|
+
// const sqlRemove = `DELETE FROM [${mj_core_schema()}].EntityField WHERE ID IN (${removeList.join(',')})`;
|
|
107
|
+
// await ds.query(sqlRemove); // this removes the fields that shouldn't be there anymore
|
|
108
|
+
// }
|
|
109
|
+
// for (const vef of veFields) {
|
|
110
|
+
// if (! await manageSingleVirtualEntityField(ds, ve, vef)) {
|
|
111
|
+
// logError(`Error managing virtual entity field ${vef.FieldName} for virtual entity ${ve.Name}`);
|
|
112
|
+
// bSuccess = false;
|
|
113
|
+
// }
|
|
114
|
+
// }
|
|
115
|
+
// }
|
|
116
|
+
// // finally make sure we update the UpdatedAt field for the entity
|
|
117
|
+
// const sqlUpdate = `UPDATE [${mj_core_schema()}].Entity SET UpdatedAt=GETDATE() WHERE ID = ${ve.ID}`;
|
|
118
|
+
// await ds.query(sqlUpdate);
|
|
119
|
+
// return true;
|
|
120
|
+
// }
|
|
121
|
+
// catch (e) {
|
|
122
|
+
// logError(e);
|
|
123
|
+
// return false;
|
|
124
|
+
// }
|
|
125
|
+
return true; // temp until the above is implemented
|
|
126
|
+
}
|
|
127
|
+
async manageSingleVirtualEntityField(ds, ve, veField) {
|
|
128
|
+
// // this protected checks to see if the field exists in the entity definition, and if not, adds it
|
|
129
|
+
// // if it exist it updates the entity field to match the view's data type and nullability attributes
|
|
130
|
+
// // first, get the entity definition
|
|
131
|
+
// const md = new Metadata();
|
|
132
|
+
// const entity = md.Entities.find(e => e.Name === ve.Name);
|
|
133
|
+
// if (entity) {
|
|
134
|
+
// const field = entity.Fields.find(f => f.Name === veField.FieldName);
|
|
135
|
+
// if (field) {
|
|
136
|
+
// // have a match, so the field exists in the entity definition, now check to see if it needs to be updated
|
|
137
|
+
// if (field.Type !== veField.Type || field.AllowsNull !== veField.AllowsNull) {
|
|
138
|
+
// // the field needs to be updated, so update it
|
|
139
|
+
// const sqlUpdate = `UPDATE [${mj_core_schema()}].EntityField SET Type='${veField.Type}', AllowsNull=${veField.AllowsNull ? 1 : 0}, UpdatedAt=GETDATE() WHERE ID = ${field.ID}`;
|
|
140
|
+
// await ds.query(sqlUpdate);
|
|
141
|
+
// }
|
|
142
|
+
// }
|
|
143
|
+
// }
|
|
144
|
+
// return true;
|
|
145
|
+
return true; // temp until the above is implemented
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* This method creates and updates relationships in the metadata based on foreign key relationships in the database.
|
|
149
|
+
* @param ds
|
|
150
|
+
* @param excludeSchemas - specify any schemas to exclude here and any relationships to/from the specified schemas will be ignored
|
|
151
|
+
* @param md
|
|
152
|
+
* @returns
|
|
153
|
+
*/
|
|
154
|
+
async manageEntityRelationships(ds, excludeSchemas, md) {
|
|
155
|
+
let bResult = true;
|
|
156
|
+
bResult = bResult && await this.manageManyToManyEntityRelationships(ds, excludeSchemas);
|
|
157
|
+
bResult = bResult && await this.manageOneToManyEntityRelationships(ds, excludeSchemas, md);
|
|
158
|
+
return bResult;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Manages 1->M relationships between entities in the metadata based on foreign key relationships in the database.
|
|
162
|
+
* @param ds
|
|
163
|
+
* @param excludeSchemas - specify any schemas to exclude here and any relationships to/from the specified schemas will be ignored
|
|
164
|
+
* @param md
|
|
165
|
+
* @returns
|
|
166
|
+
*/
|
|
167
|
+
async manageOneToManyEntityRelationships(ds, excludeSchemas, md) {
|
|
168
|
+
// the way this works is that we look for entities in our catalog and we look for
|
|
169
|
+
// foreign keys in those entities. For example, if we saw an entity called Persons and that entity
|
|
170
|
+
// had a foreign key linking to an entity called Organizations via a field called OrganizationID, then we would create a relationship
|
|
171
|
+
// record in the EntityRelationship table for that relationships. In that example we would create the
|
|
172
|
+
// relationship record with the following values:
|
|
173
|
+
// EntityID = ID of Organizations entity
|
|
174
|
+
// RelatedEntityID = ID of Persons entity
|
|
175
|
+
// RelatedEntityJoinField = OrganizationID
|
|
176
|
+
// Type = "One To Many"
|
|
177
|
+
// BundleInAPI = 1
|
|
178
|
+
// DisplayInForm = 1
|
|
179
|
+
// DisplayName = Persons (name of the entity)
|
|
180
|
+
try {
|
|
181
|
+
// STEP 1 - search for all foreign keys in the vwEntityFields view, we use the RelatedEntityID field to determine our FKs
|
|
182
|
+
const sSQL = `SELECT *
|
|
183
|
+
FROM ${(0, config_1.mj_core_schema)()}.vwEntityFields
|
|
184
|
+
WHERE
|
|
185
|
+
RelatedEntityID IS NOT NULL AND
|
|
186
|
+
IsVirtual = 0 AND
|
|
187
|
+
EntityID NOT IN (SELECT ID FROM ${(0, config_1.mj_core_schema)()}.Entity WHERE SchemaName IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))
|
|
188
|
+
ORDER BY RelatedEntityID`;
|
|
189
|
+
const entityFields = await ds.query(sSQL);
|
|
190
|
+
// now loop through all of our fkey fields
|
|
191
|
+
for (const f of entityFields) {
|
|
192
|
+
// for each field determine if an existing relationship exists, if not, create it
|
|
193
|
+
const sSQLRelationship = `SELECT * FROM ${(0, config_1.mj_core_schema)()}.EntityRelationship WHERE EntityID = ${f.RelatedEntityID} AND RelatedEntityID = ${f.EntityID}`;
|
|
194
|
+
const relationships = await ds.query(sSQLRelationship);
|
|
195
|
+
if (relationships && relationships.length === 0) {
|
|
196
|
+
// no relationship exists, so create it
|
|
197
|
+
const e = md.Entities.find(e => e.ID === f.EntityID);
|
|
198
|
+
const sSQLInsert = `INSERT INTO ${(0, config_1.mj_core_schema)()}.EntityRelationship (EntityID, RelatedEntityID, RelatedEntityJoinField, Type, BundleInAPI, DisplayInForm, DisplayName)
|
|
199
|
+
VALUES (${f.RelatedEntityID}, ${f.EntityID}, '${f.Name}', 'One To Many', 1, 1, '${e.Name}')`;
|
|
200
|
+
await ds.query(sSQLInsert);
|
|
201
|
+
}
|
|
170
202
|
}
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
(0, logging_1.logError)(e);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Manages M->M relationships between entities in the metadata based on foreign key relationships in the database.
|
|
212
|
+
* NOT IMPLEMENTED IN CURRENT VERSION IN BASE CLASS. M->M relationships ARE supported fully, but they are not AUTO generated by this
|
|
213
|
+
* method, instead an administrator must manually create these relationships in the metadata.
|
|
214
|
+
* @param ds
|
|
215
|
+
* @param excludeSchemas
|
|
216
|
+
* @returns
|
|
217
|
+
*/
|
|
218
|
+
async manageManyToManyEntityRelationships(ds, excludeSchemas) {
|
|
219
|
+
return true; // not implemented for now
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Manages the creation, updating and deletion of entity field records in the metadata based on the database schema.
|
|
223
|
+
* @param ds
|
|
224
|
+
* @param excludeSchemas
|
|
225
|
+
* @returns
|
|
226
|
+
*/
|
|
227
|
+
async manageEntityFields(ds, excludeSchemas) {
|
|
228
|
+
let bSuccess = true;
|
|
229
|
+
const startTime = new Date();
|
|
230
|
+
if (!await this.deleteUnneededEntityFields(ds, excludeSchemas)) {
|
|
231
|
+
(0, logging_1.logError)('Error deleting unneeded entity fields');
|
|
232
|
+
bSuccess = false;
|
|
233
|
+
}
|
|
234
|
+
(0, logging_1.logStatus)(` Deleted unneeded entity fields in ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
|
|
235
|
+
const step2StartTime = new Date();
|
|
236
|
+
if (!await this.updateExistingEntityFieldsFromSchema(ds, excludeSchemas)) {
|
|
237
|
+
(0, logging_1.logError)('Error updating existing entity fields from schema');
|
|
238
|
+
bSuccess = false;
|
|
239
|
+
}
|
|
240
|
+
(0, logging_1.logStatus)(` Updated existing entity fields from schema in ${(new Date().getTime() - step2StartTime.getTime()) / 1000} seconds`);
|
|
241
|
+
const step3StartTime = new Date();
|
|
242
|
+
if (!await this.createNewEntityFieldsFromSchema(ds)) { // has its own internal filtering for exclude schema/table so don't pass in
|
|
243
|
+
(0, logging_1.logError)('Error creating new entity fields from schema');
|
|
244
|
+
bSuccess = false;
|
|
245
|
+
}
|
|
246
|
+
(0, logging_1.logStatus)(` Created new entity fields from schema in ${(new Date().getTime() - step3StartTime.getTime()) / 1000} seconds`);
|
|
247
|
+
const step4StartTime = new Date();
|
|
248
|
+
if (!await this.setDefaultColumnWidthWhereNeeded(ds, excludeSchemas)) {
|
|
249
|
+
(0, logging_1.logError)('Error setting default column width where needed');
|
|
250
|
+
bSuccess = false;
|
|
171
251
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
(0, logging_1.logStatus)(` Set default column width where needed in ${(new Date().getTime() - step4StartTime.getTime()) / 1000} seconds`);
|
|
253
|
+
const step5StartTime = new Date();
|
|
254
|
+
if (!await this.updateEntityFieldDisplayNameWhereNull(ds, excludeSchemas)) {
|
|
255
|
+
(0, logging_1.logError)('Error updating entity field display name where null');
|
|
256
|
+
bSuccess = false;
|
|
257
|
+
}
|
|
258
|
+
(0, logging_1.logStatus)(` Updated entity field display name where null in ${(new Date().getTime() - step5StartTime.getTime()) / 1000} seconds`);
|
|
259
|
+
const step6StartTime = new Date();
|
|
260
|
+
if (!await this.manageEntityFieldValues(ds, excludeSchemas)) {
|
|
261
|
+
(0, logging_1.logError)('Error managing entity field values');
|
|
262
|
+
bSuccess = false;
|
|
263
|
+
}
|
|
264
|
+
(0, logging_1.logStatus)(` Managed entity field values in ${(new Date().getTime() - step6StartTime.getTime()) / 1000} seconds`);
|
|
265
|
+
(0, logging_1.logStatus)(` Total time to manage entity fields: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
|
|
266
|
+
return bSuccess;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* This method generates descriptions for entities in teh system where there is no existing description. This is an experimental feature and is done using AI. In order for it
|
|
270
|
+
* to be invoked, the EntityDescriptions feature must be enabled in the Advanced Generation configuration.
|
|
271
|
+
* @param ds
|
|
272
|
+
* @param md
|
|
273
|
+
*/
|
|
274
|
+
async generateNewEntityDescriptions(ds, md) {
|
|
275
|
+
// for the list of new entities, go through and attempt to generate new entity descriptions
|
|
276
|
+
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
277
|
+
if (ag.featureEnabled('EntityDescriptions')) {
|
|
278
|
+
// we have the feature enabled, so let's loop through the new entities and generate descriptions for them
|
|
279
|
+
const llm = ag.LLM;
|
|
280
|
+
const prompt = ag.getPrompt('EntityDescriptions');
|
|
281
|
+
const systemPrompt = prompt.systemPrompt;
|
|
282
|
+
const userMessage = prompt.userMessage + '\n\n';
|
|
283
|
+
// now loop through the new entities and generate descriptions for them
|
|
284
|
+
for (let e of ManageMetadataBase_1.newEntityList) {
|
|
285
|
+
const data = await ds.query(`SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntities WHERE Name = '${e}'`);
|
|
286
|
+
const fields = await ds.query(`SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntityFields WHERE EntityID = ${data[0].ID}`);
|
|
287
|
+
const entityUserMessage = userMessage + `Entity Name: ${e},
|
|
288
|
+
Base Table: ${data[0].BaseTable},
|
|
289
|
+
Schema: ${data[0].SchemaName}.
|
|
290
|
+
Fields:
|
|
291
|
+
${fields.map(f => ` ${f.Name}: ${f.Type}`).join('\n')}`;
|
|
292
|
+
const result = await llm.ChatCompletion({
|
|
293
|
+
model: ag.AIModel,
|
|
294
|
+
messages: [
|
|
295
|
+
{
|
|
296
|
+
role: 'system',
|
|
297
|
+
content: systemPrompt
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
role: 'user',
|
|
301
|
+
content: entityUserMessage
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
});
|
|
305
|
+
if (result?.success) {
|
|
306
|
+
const resultText = result?.data.choices[0].message.content;
|
|
307
|
+
try {
|
|
308
|
+
const structuredResult = JSON.parse(resultText);
|
|
309
|
+
if (structuredResult?.entityDescription && structuredResult.entityDescription.length > 0) {
|
|
310
|
+
const ssql = `UPDATE [${(0, config_1.mj_core_schema)()}].Entity SET Description = '${structuredResult.entityDescription}' WHERE Name = '${e}'`;
|
|
311
|
+
await ds.query(ssql);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
console.warn(' >>> Advanced Generation Error: LLM returned a blank entity description, skipping entity description for entity ' + e);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, skipping entity description for entity ' + e + '. Result from LLM: ' + resultText, e);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
console.warn(' >>> Advanced Generation Error: LLM call failed, skipping entity description for entity ' + e);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* This method is responsible for generating a Display Name for each field where a display name is not already set. The approach in the base class
|
|
329
|
+
* uses a simple algorithm that looks for case changes in the field name and inserts spaces at those points. It also strips the trailing 'ID' from the field name if it exists.
|
|
330
|
+
* Override this method in a sub-class if you would like to implement a different approach for generating display names.
|
|
331
|
+
* @param ds
|
|
332
|
+
* @param excludeSchemas
|
|
333
|
+
* @returns
|
|
334
|
+
*/
|
|
335
|
+
async updateEntityFieldDisplayNameWhereNull(ds, excludeSchemas) {
|
|
336
|
+
try {
|
|
337
|
+
const sql = `SELECT
|
|
338
|
+
ef.ID, ef.Name
|
|
339
|
+
FROM
|
|
340
|
+
[${(0, config_1.mj_core_schema)()}].vwEntityFields ef
|
|
341
|
+
INNER JOIN
|
|
342
|
+
[${(0, config_1.mj_core_schema)()}].vwEntities e
|
|
343
|
+
ON
|
|
344
|
+
ef.EntityID = e.ID
|
|
345
|
+
WHERE
|
|
346
|
+
ef.DisplayName IS NULL AND
|
|
347
|
+
ef.DisplayName <> ef.Name AND
|
|
348
|
+
ef.Name <> \'ID\' AND
|
|
349
|
+
e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})
|
|
350
|
+
`;
|
|
351
|
+
const fields = await ds.query(sql);
|
|
352
|
+
if (fields && fields.length > 0)
|
|
353
|
+
for (const field of fields) {
|
|
354
|
+
const sDisplayName = this.stripTrailingChars(this.convertCamelCaseToHaveSpaces(field.Name), 'ID', true).trim();
|
|
355
|
+
if (sDisplayName.length > 0 && sDisplayName.toLowerCase().trim() !== field.Name.toLowerCase().trim()) {
|
|
356
|
+
const sSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].EntityField SET UpdatedAt=GETDATE(), DisplayName = '${sDisplayName}' WHERE ID = ${field.ID}`;
|
|
357
|
+
await ds.query(sSQL);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
catch (e) {
|
|
363
|
+
(0, logging_1.logError)(e);
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* This method updates the DefaultColumnWidth field in the EntityField metadata. The default logic uses a stored procedure called spSetDefaultColumnWidthWhereNeeded
|
|
369
|
+
* which is part of the MJ Core Schema. You can override this method to implement custom logic for setting default column widths. It is NOT recommended to
|
|
370
|
+
* modify the stored procedure in the MJ Core Schema because your changes will be overriden during a future upgrade.
|
|
371
|
+
* @param ds
|
|
372
|
+
* @param excludeSchemas
|
|
373
|
+
* @returns
|
|
374
|
+
*/
|
|
375
|
+
async setDefaultColumnWidthWhereNeeded(ds, excludeSchemas) {
|
|
376
|
+
try {
|
|
377
|
+
await ds.query(`EXEC ${(0, config_1.mj_core_schema)()}.spSetDefaultColumnWidthWhereNeeded @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
(0, logging_1.logError)(e);
|
|
382
|
+
return false;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Creates a SQL statement to retrieve all of the pending entity fields that need to be created in the metadata. This method looks for fields that exist in the underlying
|
|
387
|
+
* database but are NOT in the metadata.
|
|
388
|
+
*/
|
|
389
|
+
getPendingEntityFieldsSELECTSQL() {
|
|
390
|
+
const sSQL = `WITH NumberedRows AS (
|
|
391
|
+
SELECT
|
|
392
|
+
sf.EntityID,
|
|
393
|
+
sf.Sequence,
|
|
394
|
+
sf.FieldName,
|
|
395
|
+
sf.Description,
|
|
396
|
+
sf.Type,
|
|
397
|
+
sf.Length,
|
|
398
|
+
sf.Precision,
|
|
399
|
+
sf.Scale,
|
|
400
|
+
sf.AllowsNull,
|
|
401
|
+
sf.DefaultValue,
|
|
402
|
+
sf.AutoIncrement,
|
|
403
|
+
IIF(sf.IsVirtual = 1, 0, IIF(sf.FieldName = 'CreatedAt' OR sf.FieldName = 'UpdatedAt' OR sf.FieldName = 'ID', 0, 1)) AllowUpdateAPI,
|
|
404
|
+
sf.IsVirtual,
|
|
405
|
+
re.ID RelatedEntityID,
|
|
406
|
+
fk.referenced_column RelatedEntityFieldName,
|
|
407
|
+
IIF(sf.FieldName = 'Name', 1, 0) IsNameField,
|
|
408
|
+
IsPrimaryKey = CASE
|
|
409
|
+
WHEN pk.ColumnName IS NOT NULL THEN 1
|
|
410
|
+
ELSE 0
|
|
411
|
+
END,
|
|
412
|
+
IsUnique = CASE
|
|
413
|
+
WHEN pk.ColumnName IS NOT NULL THEN 1
|
|
414
|
+
ELSE
|
|
415
|
+
CASE
|
|
416
|
+
WHEN uk.ColumnName IS NOT NULL THEN 1
|
|
417
|
+
ELSE 0
|
|
418
|
+
END
|
|
419
|
+
END,
|
|
420
|
+
ROW_NUMBER() OVER (PARTITION BY sf.EntityID, sf.FieldName ORDER BY (SELECT NULL)) AS rn
|
|
421
|
+
FROM
|
|
422
|
+
[${(0, config_1.mj_core_schema)()}].vwSQLColumnsAndEntityFields sf
|
|
423
|
+
LEFT OUTER JOIN
|
|
424
|
+
[${(0, config_1.mj_core_schema)()}].Entity e
|
|
425
|
+
ON
|
|
426
|
+
sf.EntityID = e.ID
|
|
427
|
+
LEFT OUTER JOIN
|
|
428
|
+
[${(0, config_1.mj_core_schema)()}].vwForeignKeys fk
|
|
429
|
+
ON
|
|
430
|
+
sf.FieldName = fk.[column] AND
|
|
431
|
+
e.BaseTable = fk.[table] AND
|
|
432
|
+
e.SchemaName = fk.[schema_name]
|
|
433
|
+
LEFT OUTER JOIN
|
|
434
|
+
[${(0, config_1.mj_core_schema)()}].Entity re -- Related Entity
|
|
435
|
+
ON
|
|
436
|
+
re.BaseTable = fk.referenced_table AND
|
|
437
|
+
re.SchemaName = fk.[referenced_schema]
|
|
438
|
+
LEFT OUTER JOIN
|
|
439
|
+
[${(0, config_1.mj_core_schema)()}].vwTablePrimaryKeys pk
|
|
440
|
+
ON
|
|
441
|
+
e.BaseTable = pk.TableName AND
|
|
442
|
+
sf.FieldName = pk.ColumnName AND
|
|
443
|
+
e.SchemaName = pk.SchemaName
|
|
444
|
+
LEFT OUTER JOIN
|
|
445
|
+
[${(0, config_1.mj_core_schema)()}].vwTableUniqueKeys uk
|
|
446
|
+
ON
|
|
447
|
+
e.BaseTable = uk.TableName AND
|
|
448
|
+
sf.FieldName = uk.ColumnName AND
|
|
449
|
+
e.SchemaName = uk.SchemaName
|
|
450
|
+
WHERE
|
|
451
|
+
EntityFieldID IS NULL -- only where we have NOT YET CREATED EntityField records\n${this.createExcludeTablesAndSchemasFilter('sf.')}
|
|
452
|
+
)
|
|
453
|
+
SELECT
|
|
454
|
+
*
|
|
455
|
+
FROM
|
|
456
|
+
NumberedRows WHERE rn = 1 -- if someone has two foreign keys with same to/from table and field name this makes sure we only get the field info ONCE
|
|
457
|
+
ORDER BY EntityID, Sequence`;
|
|
458
|
+
return sSQL;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* This method builds a SQL Statement that will insert a row into the EntityField table with information about a new field.
|
|
462
|
+
* @param n - the new field
|
|
463
|
+
* @returns
|
|
464
|
+
*/
|
|
465
|
+
getPendingEntityFieldINSERTSQL(n) {
|
|
466
|
+
const bDefaultInView = (n.FieldName?.trim().toLowerCase() === 'id' ||
|
|
467
|
+
n.FieldName?.trim().toLowerCase() === 'name' ||
|
|
468
|
+
n.Sequence <= config_1.configInfo.newEntityDefaults?.IncludeFirstNFieldsAsDefaultInView ||
|
|
469
|
+
n.IsNameField ? true : false);
|
|
470
|
+
const escapedDescription = n.Description ? `'${n.Description.replace(/'/g, "''")}'` : 'NULL';
|
|
471
|
+
return `
|
|
472
|
+
INSERT INTO [${(0, config_1.mj_core_schema)()}].EntityField
|
|
473
|
+
(
|
|
474
|
+
EntityID,
|
|
475
|
+
Sequence,
|
|
476
|
+
Name,
|
|
477
|
+
DisplayName,
|
|
478
|
+
Description,
|
|
479
|
+
Type,
|
|
480
|
+
Length,
|
|
481
|
+
Precision,
|
|
482
|
+
Scale,
|
|
483
|
+
AllowsNull,
|
|
484
|
+
DefaultValue,
|
|
485
|
+
AutoIncrement,
|
|
486
|
+
AllowUpdateAPI,
|
|
487
|
+
IsVirtual,
|
|
488
|
+
RelatedEntityID,
|
|
489
|
+
RelatedEntityFieldName,
|
|
490
|
+
IsNameField,
|
|
491
|
+
IncludeInUserSearchAPI,
|
|
492
|
+
IncludeRelatedEntityNameFieldInBaseView,
|
|
493
|
+
DefaultInView,
|
|
494
|
+
IsPrimaryKey,
|
|
495
|
+
IsUnique
|
|
496
|
+
)
|
|
497
|
+
VALUES
|
|
498
|
+
(
|
|
499
|
+
${n.EntityID},
|
|
500
|
+
${n.Sequence},
|
|
501
|
+
'${n.FieldName}',
|
|
502
|
+
'${this.convertCamelCaseToHaveSpaces(n.FieldName).trim()}',
|
|
503
|
+
${escapedDescription},
|
|
504
|
+
'${n.Type}',
|
|
505
|
+
${n.Length},
|
|
506
|
+
${n.Precision},
|
|
507
|
+
${n.Scale},
|
|
508
|
+
${n.AllowsNull ? 1 : 0},
|
|
509
|
+
'${this.parseDefaultValue(n.DefaultValue)}',
|
|
510
|
+
${n.AutoIncrement ? 1 : 0},
|
|
511
|
+
${n.AllowUpdateAPI ? 1 : 0},
|
|
512
|
+
${n.IsVirtual ? 1 : 0},
|
|
513
|
+
${n.RelatedEntityID},
|
|
514
|
+
${n.RelatedEntityFieldName && n.RelatedEntityFieldName.length > 0 ? `'${n.RelatedEntityFieldName}'` : 'NULL'},
|
|
515
|
+
${n.IsNameField !== null ? n.IsNameField : 0},
|
|
516
|
+
${n.FieldName === 'ID' || n.IsNameField ? 1 : 0},
|
|
517
|
+
${n.RelatedEntityID && n.RelatedEntityID > 0 && n.Type.trim().toLowerCase() === 'int' ? 1 : 0},
|
|
518
|
+
${bDefaultInView ? 1 : 0},
|
|
519
|
+
${n.IsPrimaryKey},
|
|
520
|
+
${n.IsUnique}
|
|
521
|
+
)`;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* This method takes the stored DEFAULT CONSTRAINT value from the database and parses it to retrieve the actual default value. This is necessary because the default value is
|
|
525
|
+
* sometimes wrapped in parentheses and sometimes wrapped in single quotes. This method removes the wrapping characters and returns the actual default value. Some common raw values
|
|
526
|
+
* that exist in SQL Server include 'getdate()', '(getdate())', 'N''SomeValue''', etc. and this method will remove those wrapping characters to get the actual underlying default value.
|
|
527
|
+
* NOTE: For future versions of MemberJunction where multiple back-end providers could be used, this method will be moved to the Provider architecture so that database-specific versions
|
|
528
|
+
* can be implemented, along with many other aspects of this current codebase.
|
|
529
|
+
* @param sqlDefaultValue
|
|
530
|
+
* @returns
|
|
531
|
+
*/
|
|
532
|
+
parseDefaultValue(sqlDefaultValue) {
|
|
533
|
+
let sResult = null;
|
|
534
|
+
if (sqlDefaultValue !== null && sqlDefaultValue !== undefined) {
|
|
535
|
+
if (sqlDefaultValue.startsWith('(') && sqlDefaultValue.endsWith(')'))
|
|
536
|
+
sResult = sqlDefaultValue.substring(1, sqlDefaultValue.length - 1);
|
|
537
|
+
else
|
|
538
|
+
sResult = sqlDefaultValue;
|
|
539
|
+
if (sResult.toUpperCase().startsWith('N\'') && sResult.endsWith('\''))
|
|
540
|
+
sResult = sResult.substring(2, sResult.length - 1);
|
|
541
|
+
if (sResult.startsWith('\'') && sResult.endsWith('\''))
|
|
542
|
+
sResult = sResult.substring(1, sResult.length - 1);
|
|
543
|
+
}
|
|
544
|
+
return sResult;
|
|
545
|
+
}
|
|
546
|
+
async createNewEntityFieldsFromSchema(ds) {
|
|
547
|
+
try {
|
|
548
|
+
const sSQL = this.getPendingEntityFieldsSELECTSQL();
|
|
549
|
+
const newEntityFields = await ds.query(sSQL);
|
|
550
|
+
await ds.transaction(async () => {
|
|
551
|
+
// wrap in a transaction so we get all of it or none of it
|
|
552
|
+
for (let i = 0; i < newEntityFields.length; ++i) {
|
|
553
|
+
const n = newEntityFields[i];
|
|
554
|
+
if (n.EntityID !== null && n.EntityID !== undefined && n.EntityID > 0) {
|
|
555
|
+
// need to check for null entity id = that is because the above query can return candidate Entity Fields but the entities may not have been created if the entities
|
|
556
|
+
// that would have been created violate rules - such as not having an ID column, etc.
|
|
557
|
+
const sSQLInsert = this.getPendingEntityFieldINSERTSQL(n);
|
|
558
|
+
await ds.query(sSQLInsert);
|
|
559
|
+
// if we get here, we're okay, otherwise we have an exception, which we want as it blows up transaction
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
catch (e) {
|
|
566
|
+
(0, logging_1.logError)(e);
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* This method handles updating entity field related name field maps which is basically the process of finding the related entity field that is the "name" field for the related entity.
|
|
572
|
+
* @param ds
|
|
573
|
+
* @param entityFieldID
|
|
574
|
+
* @param relatedEntityNameFieldMap
|
|
575
|
+
* @returns
|
|
576
|
+
*/
|
|
577
|
+
async updateEntityFieldRelatedEntityNameFieldMap(ds, entityFieldID, relatedEntityNameFieldMap) {
|
|
578
|
+
try {
|
|
579
|
+
const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spUpdateEntityFieldRelatedEntityNameFieldMap
|
|
580
|
+
@EntityFieldID=${entityFieldID} ,
|
|
581
|
+
@RelatedEntityNameFieldMap='${relatedEntityNameFieldMap}'`;
|
|
582
|
+
await ds.query(sSQL);
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
(0, logging_1.logError)(e);
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async updateExistingEntitiesFromSchema(ds, excludeSchemas) {
|
|
591
|
+
try {
|
|
592
|
+
await ds.query(`EXEC [${(0, config_1.mj_core_schema)()}].spUpdateExistingEntitiesFromSchema @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
catch (e) {
|
|
596
|
+
(0, logging_1.logError)(e);
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async updateExistingEntityFieldsFromSchema(ds, excludeSchemas) {
|
|
601
|
+
try {
|
|
602
|
+
await ds.query(`EXEC [${(0, config_1.mj_core_schema)()}].spUpdateExistingEntityFieldsFromSchema @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
catch (e) {
|
|
606
|
+
(0, logging_1.logError)(e);
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
async deleteUnneededEntityFields(ds, excludeSchemas) {
|
|
611
|
+
try {
|
|
612
|
+
await ds.query(`EXEC [${(0, config_1.mj_core_schema)()}].spDeleteUnneededEntityFields @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
613
|
+
return true;
|
|
614
|
+
}
|
|
615
|
+
catch (e) {
|
|
616
|
+
(0, logging_1.logError)(e);
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async manageEntityFieldValues(ds, excludeSchemas) {
|
|
621
|
+
try {
|
|
622
|
+
// here we want to get all of the entity fields that have check constraints attached to them. For each field that has a check constraint, we want to
|
|
623
|
+
// evaluate it to see if it is a simple series of OR statements or not, if it is a simple series of OR statements, we can parse the possible values
|
|
624
|
+
// for the field and sync that up with the EntityFieldValue table. If it is not a simple series of OR statements, we will not be able to parse it and we'll
|
|
625
|
+
// just ignore it.
|
|
626
|
+
const filter = excludeSchemas && excludeSchemas.length > 0 ? ` WHERE SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})` : '';
|
|
627
|
+
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntityFieldsWithCheckConstraints${filter}`;
|
|
628
|
+
const result = await ds.query(sSQL);
|
|
629
|
+
// now, for each of the constraints we get back here, loop through and evaluate if they're simple and if they're simple, parse and sync with entity field values for that field
|
|
630
|
+
for (const r of result) {
|
|
631
|
+
if (r.ConstraintDefinition && r.ConstraintDefinition.length > 0) {
|
|
632
|
+
const parsedValues = this.parseCheckConstraintValues(r.ConstraintDefinition, r.ColumnName);
|
|
633
|
+
if (parsedValues) {
|
|
634
|
+
// we have parsed values from the check constraint, so sync them with the entity field values
|
|
635
|
+
await this.syncEntityFieldValues(ds, r.EntityID, r.ColumnName, parsedValues);
|
|
636
|
+
// finally, make sure the ValueListType column within the EntityField table is set to "List" because for check constraints we only allow the values specified in the list.
|
|
637
|
+
await ds.query(`UPDATE [${(0, config_1.mj_core_schema)()}].EntityField SET ValueListType='List' WHERE EntityID=${r.EntityID} AND Name='${r.ColumnName}'`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
catch (e) {
|
|
644
|
+
(0, logging_1.logError)(e);
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
async syncEntityFieldValues(ds, entityID, entityFieldName, possibleValues) {
|
|
649
|
+
try {
|
|
650
|
+
// first, get a list of all of the existing entity field values for the field already in the database
|
|
651
|
+
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].EntityFieldValue WHERE EntityID=${entityID} AND EntityFieldName = '${entityFieldName}'`;
|
|
652
|
+
const existingValues = await ds.query(sSQL);
|
|
653
|
+
// now, loop through the possible values and add any that are not already in the database
|
|
654
|
+
// Step 1: for any existing value that is NOT in the list of possible Values, delete it
|
|
655
|
+
let numRemoved = 0;
|
|
656
|
+
await ds.transaction(async () => {
|
|
657
|
+
for (const ev of existingValues) {
|
|
658
|
+
if (!possibleValues.find(v => v === ev.Value)) {
|
|
659
|
+
// delete the value from the database
|
|
660
|
+
const sSQLDelete = `DELETE FROM [${(0, config_1.mj_core_schema)()}].EntityFieldValue WHERE ID=${ev.ID}`;
|
|
661
|
+
await ds.query(sSQLDelete);
|
|
662
|
+
numRemoved++;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
// Step 2: for any possible value that is NOT in the list of existing values, add it
|
|
666
|
+
let numAdded = 0;
|
|
667
|
+
for (const v of possibleValues) {
|
|
668
|
+
if (!existingValues.find(ev => ev.Value === v)) {
|
|
669
|
+
// add the value to the database
|
|
670
|
+
const sSQLInsert = `INSERT INTO [${(0, config_1.mj_core_schema)()}].EntityFieldValue
|
|
671
|
+
(EntityID, EntityFieldName, Sequence, Value, Code)
|
|
672
|
+
VALUES
|
|
673
|
+
(${entityID}, '${entityFieldName}', ${1 + possibleValues.indexOf(v)}, '${v}', '${v}')`;
|
|
674
|
+
await ds.query(sSQLInsert);
|
|
675
|
+
numAdded++;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
// Step 3: finally, for the existing values that are in the list of possible values, update the sequence to match the order in the possible values list
|
|
679
|
+
let numUpdated = 0;
|
|
680
|
+
for (const v of possibleValues) {
|
|
681
|
+
const ev = existingValues.find(ev => ev.Value === v);
|
|
682
|
+
if (ev) {
|
|
683
|
+
// update the sequence to match the order in the possible values list
|
|
684
|
+
const sSQLUpdate = `UPDATE [${(0, config_1.mj_core_schema)()}].EntityFieldValue SET Sequence=${1 + possibleValues.indexOf(v)} WHERE ID=${ev.ID}`;
|
|
685
|
+
await ds.query(sSQLUpdate);
|
|
686
|
+
numUpdated++;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
});
|
|
690
|
+
return true;
|
|
691
|
+
}
|
|
692
|
+
catch (e) {
|
|
693
|
+
(0, logging_1.logError)(e);
|
|
694
|
+
return false;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
parseCheckConstraintValues(constraintDefinition, fieldName) {
|
|
698
|
+
// This regex checks for the overall structure including field name and 'OR' sequences
|
|
699
|
+
// an example of a valid constraint definition would be: ([FieldName]='Value1' OR [FieldName]='Value2' OR [FieldName]='Value3')
|
|
700
|
+
// like: ([AutoRunIntervalUnits]='Years' OR [AutoRunIntervalUnits]='Months' OR [AutoRunIntervalUnits]='Weeks' OR [AutoRunIntervalUnits]='Days' OR [AutoRunIntervalUnits]='Hours' OR [AutoRunIntervalUnits]='Minutes')
|
|
701
|
+
// Note: Assuming fieldName does not contain regex special characters; otherwise, it needs to be escaped as well.
|
|
702
|
+
const structureRegex = new RegExp(`^\\(\\[${fieldName}\\]='[^']+'(?: OR \\[${fieldName}\\]='[^']+?')+\\)$`);
|
|
703
|
+
if (!structureRegex.test(constraintDefinition)) {
|
|
704
|
+
console.log('Constraint does not match the simple OR condition pattern or field name does not match.');
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
// Regular expression to match the values within the single quotes specifically for the field
|
|
708
|
+
const valueRegex = new RegExp(`\\[${fieldName}\\]='([^']+)\'`, 'g');
|
|
709
|
+
let match;
|
|
710
|
+
const possibleValues = [];
|
|
711
|
+
// Use regex to find matches and extract the values
|
|
712
|
+
while ((match = valueRegex.exec(constraintDefinition)) !== null) {
|
|
713
|
+
// This is necessary to avoid infinite loops with zero-width matches
|
|
714
|
+
if (match.index === valueRegex.lastIndex) {
|
|
715
|
+
valueRegex.lastIndex++;
|
|
716
|
+
}
|
|
717
|
+
// The first captured group contains the value
|
|
718
|
+
if (match[1]) {
|
|
719
|
+
possibleValues.push(match[1]);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return possibleValues;
|
|
723
|
+
}
|
|
724
|
+
createExcludeTablesAndSchemasFilter(fieldPrefix) {
|
|
725
|
+
let sExcludeTables = '';
|
|
726
|
+
let sExcludeSchemas = '';
|
|
727
|
+
if (config_1.configInfo.excludeTables) {
|
|
728
|
+
for (let i = 0; i < config_1.configInfo.excludeTables.length; ++i) {
|
|
729
|
+
const t = config_1.configInfo.excludeTables[i];
|
|
730
|
+
sExcludeTables += (sExcludeTables.length > 0 ? ' AND ' : '') +
|
|
731
|
+
(t.schema.indexOf('%') > -1 ? ` NOT ( ${fieldPrefix}SchemaName LIKE '${t.schema}'` :
|
|
732
|
+
` NOT ( ${fieldPrefix}SchemaName = '${t.schema}'`);
|
|
733
|
+
sExcludeTables += (t.table.indexOf('%') > -1 ? ` AND ${fieldPrefix}TableName LIKE '${t.table}') ` :
|
|
734
|
+
` AND ${fieldPrefix}TableName = '${t.table}') `);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (config_1.configInfo.excludeSchemas) {
|
|
738
|
+
for (let i = 0; i < config_1.configInfo.excludeSchemas.length; ++i) {
|
|
739
|
+
const s = config_1.configInfo.excludeSchemas[i];
|
|
740
|
+
sExcludeSchemas += (sExcludeSchemas.length > 0 ? ' AND ' : '') +
|
|
741
|
+
(s.indexOf('%') > -1 ? `${fieldPrefix}SchemaName NOT LIKE '${s}'` : `${fieldPrefix}SchemaName <> '${s}'`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
const sWhere = (sExcludeTables.length > 0 || sExcludeSchemas.length > 0 ? ` AND ` : '') +
|
|
745
|
+
(sExcludeTables.length > 0 ? `(${sExcludeTables})` : '') +
|
|
746
|
+
(sExcludeSchemas.length > 0 ? (sExcludeTables.length > 0 ? ` AND ` : ``) + '(' + sExcludeSchemas + ')' : '');
|
|
747
|
+
return sWhere;
|
|
748
|
+
}
|
|
749
|
+
async createNewEntities(ds) {
|
|
750
|
+
try {
|
|
751
|
+
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwSQLTablesAndEntities WHERE EntityID IS NULL ` + this.createExcludeTablesAndSchemasFilter('');
|
|
752
|
+
const newEntities = await ds.query(sSQL);
|
|
753
|
+
if (newEntities && newEntities.length > 0) {
|
|
754
|
+
const md = new core_1.Metadata();
|
|
755
|
+
await ds.transaction(async () => {
|
|
756
|
+
// wrap in a transaction so we get all of it or none of it
|
|
757
|
+
for (let i = 0; i < newEntities.length; ++i) {
|
|
758
|
+
// process each of the new entities
|
|
759
|
+
await this.createNewEntity(ds, newEntities[i], md);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
if (ManageMetadataBase_1.newEntityList.length > 0) {
|
|
763
|
+
// only do this if we actually created new entities
|
|
764
|
+
(0, core_1.LogStatus)(` Done creating entities, refreshing metadata to reflect new entities...`);
|
|
765
|
+
await md.Refresh(); // refresh now since we've added some new entities
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return true; // if we get here, we succeeded
|
|
769
|
+
}
|
|
770
|
+
catch (e) {
|
|
771
|
+
(0, core_1.LogError)(e);
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
async shouldCreateNewEntity(ds, newEntity) {
|
|
776
|
+
// validate that the new entity meets our criteria for creation
|
|
777
|
+
// criteria:
|
|
778
|
+
// 1) entity has a field that is a primary key
|
|
779
|
+
// validate all of these factors by getting the sql from SQL Server and check the result, if failure, shouldCreate=false and generate validation message, otherwise return empty validation message and true for shouldCreate.
|
|
780
|
+
const query = `EXEC ${core_1.Metadata.Provider.ConfigData.MJCoreSchemaName}.spGetPrimaryKeyForTable @TableName='${newEntity.TableName}', @SchemaName='${newEntity.SchemaName}'`;
|
|
781
|
+
try {
|
|
782
|
+
const result = await ds.query(query);
|
|
783
|
+
if (result.length === 0) {
|
|
784
|
+
return { shouldCreate: false, validationMessage: "No primary key found" };
|
|
785
|
+
}
|
|
786
|
+
return { shouldCreate: true, validationMessage: '' };
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
const errorMsg = 'Error validating new entity for table:' + newEntity?.TableName;
|
|
790
|
+
console.error(errorMsg, error);
|
|
791
|
+
return { shouldCreate: false, validationMessage: errorMsg };
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
async createNewEntityName(newEntity) {
|
|
795
|
+
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
796
|
+
if (ag.featureEnabled('EntityNames')) {
|
|
797
|
+
// get the LLM for this entity
|
|
798
|
+
const chat = ag.LLM;
|
|
799
|
+
const prompt = ag.getPrompt('EntityNames');
|
|
800
|
+
const systemPrompt = ag.fillTemplate(prompt.systemPrompt, newEntity);
|
|
801
|
+
const userMessage = ag.fillTemplate(prompt.userMessage, newEntity);
|
|
802
|
+
const result = await chat.ChatCompletion({
|
|
245
803
|
model: ag.AIModel,
|
|
246
804
|
messages: [
|
|
247
805
|
{
|
|
@@ -250,7 +808,7 @@ async function generateNewEntityDescriptions(ds, md) {
|
|
|
250
808
|
},
|
|
251
809
|
{
|
|
252
810
|
role: 'user',
|
|
253
|
-
content:
|
|
811
|
+
content: userMessage
|
|
254
812
|
}
|
|
255
813
|
]
|
|
256
814
|
});
|
|
@@ -258,695 +816,231 @@ async function generateNewEntityDescriptions(ds, md) {
|
|
|
258
816
|
const resultText = result?.data.choices[0].message.content;
|
|
259
817
|
try {
|
|
260
818
|
const structuredResult = JSON.parse(resultText);
|
|
261
|
-
if (structuredResult?.
|
|
262
|
-
|
|
263
|
-
await ds.query(ssql);
|
|
819
|
+
if (structuredResult?.entityName) {
|
|
820
|
+
return structuredResult.entityName;
|
|
264
821
|
}
|
|
265
822
|
else {
|
|
266
|
-
console.warn(' >>> Advanced Generation Error: LLM returned a blank entity
|
|
823
|
+
console.warn(' >>> Advanced Generation Error: LLM returned a blank entity name, falling back to simple generated entity name');
|
|
824
|
+
return this.simpleNewEntityName(newEntity.TableName);
|
|
267
825
|
}
|
|
268
826
|
}
|
|
269
827
|
catch (e) {
|
|
270
|
-
console.warn(' >>> Advanced Generation Error: LLM returned invalid result,
|
|
828
|
+
console.warn(' >>> Advanced Generation Error: LLM returned invalid result, falling back to simple generated entity name. Result from LLM: ' + resultText, e);
|
|
829
|
+
return this.simpleNewEntityName(newEntity.TableName);
|
|
271
830
|
}
|
|
272
831
|
}
|
|
273
832
|
else {
|
|
274
|
-
console.warn(' >>> Advanced Generation Error: LLM call failed,
|
|
833
|
+
console.warn(' >>> Advanced Generation Error: LLM call failed, falling back to simple generated entity name.');
|
|
834
|
+
return this.simpleNewEntityName(newEntity.TableName);
|
|
275
835
|
}
|
|
276
836
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
async function updateEntityFieldDisplayNameWhereNull(ds, excludeSchemas) {
|
|
280
|
-
try {
|
|
281
|
-
const sql = `SELECT
|
|
282
|
-
ef.ID, ef.Name
|
|
283
|
-
FROM
|
|
284
|
-
[${(0, config_1.mj_core_schema)()}].vwEntityFields ef
|
|
285
|
-
INNER JOIN
|
|
286
|
-
[${(0, config_1.mj_core_schema)()}].vwEntities e
|
|
287
|
-
ON
|
|
288
|
-
ef.EntityID = e.ID
|
|
289
|
-
WHERE
|
|
290
|
-
ef.DisplayName IS NULL AND
|
|
291
|
-
ef.DisplayName <> ef.Name AND
|
|
292
|
-
ef.Name <> \'ID\' AND
|
|
293
|
-
e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})
|
|
294
|
-
`;
|
|
295
|
-
const fields = await ds.query(sql);
|
|
296
|
-
if (fields && fields.length > 0)
|
|
297
|
-
for (const field of fields) {
|
|
298
|
-
const sDisplayName = stripTrailingChars(convertCamelCaseToHaveSpaces(field.Name), 'ID', true).trim();
|
|
299
|
-
if (sDisplayName.length > 0 && sDisplayName.toLowerCase().trim() !== field.Name.toLowerCase().trim()) {
|
|
300
|
-
const sSQL = `UPDATE [${(0, config_1.mj_core_schema)()}].EntityField SET UpdatedAt=GETDATE(), DisplayName = '${sDisplayName}' WHERE ID = ${field.ID}`;
|
|
301
|
-
await ds.query(sSQL);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return true;
|
|
305
|
-
}
|
|
306
|
-
catch (e) {
|
|
307
|
-
(0, logging_1.logError)(e);
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
async function setDefaultColumnWidthWhereNeeded(ds, excludeSchemas) {
|
|
312
|
-
try {
|
|
313
|
-
await ds.query(`EXEC ${(0, config_1.mj_core_schema)()}.spSetDefaultColumnWidthWhereNeeded @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
catch (e) {
|
|
317
|
-
(0, logging_1.logError)(e);
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
function getPendingEntityFieldsSELECTSQL() {
|
|
322
|
-
const sSQL = `WITH NumberedRows AS (
|
|
323
|
-
SELECT
|
|
324
|
-
sf.EntityID,
|
|
325
|
-
sf.Sequence,
|
|
326
|
-
sf.FieldName,
|
|
327
|
-
sf.Description,
|
|
328
|
-
sf.Type,
|
|
329
|
-
sf.Length,
|
|
330
|
-
sf.Precision,
|
|
331
|
-
sf.Scale,
|
|
332
|
-
sf.AllowsNull,
|
|
333
|
-
sf.DefaultValue,
|
|
334
|
-
sf.AutoIncrement,
|
|
335
|
-
IIF(sf.IsVirtual = 1, 0, IIF(sf.FieldName = 'CreatedAt' OR sf.FieldName = 'UpdatedAt' OR sf.FieldName = 'ID', 0, 1)) AllowUpdateAPI,
|
|
336
|
-
sf.IsVirtual,
|
|
337
|
-
re.ID RelatedEntityID,
|
|
338
|
-
fk.referenced_column RelatedEntityFieldName,
|
|
339
|
-
IIF(sf.FieldName = 'Name', 1, 0) IsNameField,
|
|
340
|
-
IsPrimaryKey = CASE
|
|
341
|
-
WHEN pk.ColumnName IS NOT NULL THEN 1
|
|
342
|
-
ELSE 0
|
|
343
|
-
END,
|
|
344
|
-
IsUnique = CASE
|
|
345
|
-
WHEN pk.ColumnName IS NOT NULL THEN 1
|
|
346
|
-
ELSE
|
|
347
|
-
CASE
|
|
348
|
-
WHEN uk.ColumnName IS NOT NULL THEN 1
|
|
349
|
-
ELSE 0
|
|
350
|
-
END
|
|
351
|
-
END,
|
|
352
|
-
ROW_NUMBER() OVER (PARTITION BY sf.EntityID, sf.FieldName ORDER BY (SELECT NULL)) AS rn
|
|
353
|
-
FROM
|
|
354
|
-
[${(0, config_1.mj_core_schema)()}].vwSQLColumnsAndEntityFields sf
|
|
355
|
-
LEFT OUTER JOIN
|
|
356
|
-
[${(0, config_1.mj_core_schema)()}].Entity e
|
|
357
|
-
ON
|
|
358
|
-
sf.EntityID = e.ID
|
|
359
|
-
LEFT OUTER JOIN
|
|
360
|
-
[${(0, config_1.mj_core_schema)()}].vwForeignKeys fk
|
|
361
|
-
ON
|
|
362
|
-
sf.FieldName = fk.[column] AND
|
|
363
|
-
e.BaseTable = fk.[table] AND
|
|
364
|
-
e.SchemaName = fk.[schema_name]
|
|
365
|
-
LEFT OUTER JOIN
|
|
366
|
-
[${(0, config_1.mj_core_schema)()}].Entity re -- Related Entity
|
|
367
|
-
ON
|
|
368
|
-
re.BaseTable = fk.referenced_table AND
|
|
369
|
-
re.SchemaName = fk.[referenced_schema]
|
|
370
|
-
LEFT OUTER JOIN
|
|
371
|
-
[${(0, config_1.mj_core_schema)()}].vwTablePrimaryKeys pk
|
|
372
|
-
ON
|
|
373
|
-
e.BaseTable = pk.TableName AND
|
|
374
|
-
sf.FieldName = pk.ColumnName AND
|
|
375
|
-
e.SchemaName = pk.SchemaName
|
|
376
|
-
LEFT OUTER JOIN
|
|
377
|
-
[${(0, config_1.mj_core_schema)()}].vwTableUniqueKeys uk
|
|
378
|
-
ON
|
|
379
|
-
e.BaseTable = uk.TableName AND
|
|
380
|
-
sf.FieldName = uk.ColumnName AND
|
|
381
|
-
e.SchemaName = uk.SchemaName
|
|
382
|
-
WHERE
|
|
383
|
-
EntityFieldID IS NULL -- only where we have NOT YET CREATED EntityField records\n${createExcludeTablesAndSchemasFilter('sf.')}
|
|
384
|
-
)
|
|
385
|
-
SELECT
|
|
386
|
-
*
|
|
387
|
-
FROM
|
|
388
|
-
NumberedRows WHERE rn = 1 -- if someone has two foreign keys with same to/from table and field name this makes sure we only get the field info ONCE
|
|
389
|
-
ORDER BY EntityID, Sequence`;
|
|
390
|
-
return sSQL;
|
|
391
|
-
}
|
|
392
|
-
function getPendingEntityFieldINSERTSQL(n) {
|
|
393
|
-
const bDefaultInView = (n.FieldName?.trim().toLowerCase() === 'id' ||
|
|
394
|
-
n.FieldName?.trim().toLowerCase() === 'name' ||
|
|
395
|
-
n.Sequence <= config_1.configInfo.newEntityDefaults?.IncludeFirstNFieldsAsDefaultInView ||
|
|
396
|
-
n.IsNameField ? true : false);
|
|
397
|
-
const escapedDescription = n.Description ? `'${n.Description.replace(/'/g, "''")}'` : 'NULL';
|
|
398
|
-
return `
|
|
399
|
-
INSERT INTO [${(0, config_1.mj_core_schema)()}].EntityField
|
|
400
|
-
(
|
|
401
|
-
EntityID,
|
|
402
|
-
Sequence,
|
|
403
|
-
Name,
|
|
404
|
-
DisplayName,
|
|
405
|
-
Description,
|
|
406
|
-
Type,
|
|
407
|
-
Length,
|
|
408
|
-
Precision,
|
|
409
|
-
Scale,
|
|
410
|
-
AllowsNull,
|
|
411
|
-
DefaultValue,
|
|
412
|
-
AutoIncrement,
|
|
413
|
-
AllowUpdateAPI,
|
|
414
|
-
IsVirtual,
|
|
415
|
-
RelatedEntityID,
|
|
416
|
-
RelatedEntityFieldName,
|
|
417
|
-
IsNameField,
|
|
418
|
-
IncludeInUserSearchAPI,
|
|
419
|
-
IncludeRelatedEntityNameFieldInBaseView,
|
|
420
|
-
DefaultInView,
|
|
421
|
-
IsPrimaryKey,
|
|
422
|
-
IsUnique
|
|
423
|
-
)
|
|
424
|
-
VALUES
|
|
425
|
-
(
|
|
426
|
-
${n.EntityID},
|
|
427
|
-
${n.Sequence},
|
|
428
|
-
'${n.FieldName}',
|
|
429
|
-
'${convertCamelCaseToHaveSpaces(n.FieldName).trim()}',
|
|
430
|
-
${escapedDescription},
|
|
431
|
-
'${n.Type}',
|
|
432
|
-
${n.Length},
|
|
433
|
-
${n.Precision},
|
|
434
|
-
${n.Scale},
|
|
435
|
-
${n.AllowsNull ? 1 : 0},
|
|
436
|
-
'${parseDefaultValue(n.DefaultValue)}',
|
|
437
|
-
${n.AutoIncrement ? 1 : 0},
|
|
438
|
-
${n.AllowUpdateAPI ? 1 : 0},
|
|
439
|
-
${n.IsVirtual ? 1 : 0},
|
|
440
|
-
${n.RelatedEntityID},
|
|
441
|
-
${n.RelatedEntityFieldName && n.RelatedEntityFieldName.length > 0 ? `'${n.RelatedEntityFieldName}'` : 'NULL'},
|
|
442
|
-
${n.IsNameField !== null ? n.IsNameField : 0},
|
|
443
|
-
${n.FieldName === 'ID' || n.IsNameField ? 1 : 0},
|
|
444
|
-
${n.RelatedEntityID && n.RelatedEntityID > 0 && n.Type.trim().toLowerCase() === 'int' ? 1 : 0},
|
|
445
|
-
${bDefaultInView ? 1 : 0},
|
|
446
|
-
${n.IsPrimaryKey},
|
|
447
|
-
${n.IsUnique}
|
|
448
|
-
)`;
|
|
449
|
-
}
|
|
450
|
-
function parseDefaultValue(sqlDefaultValue) {
|
|
451
|
-
let sResult = null;
|
|
452
|
-
if (sqlDefaultValue !== null && sqlDefaultValue !== undefined) {
|
|
453
|
-
if (sqlDefaultValue.startsWith('(') && sqlDefaultValue.endsWith(')'))
|
|
454
|
-
sResult = sqlDefaultValue.substring(1, sqlDefaultValue.length - 1);
|
|
455
|
-
else
|
|
456
|
-
sResult = sqlDefaultValue;
|
|
457
|
-
if (sResult.toUpperCase().startsWith('N\'') && sResult.endsWith('\''))
|
|
458
|
-
sResult = sResult.substring(2, sResult.length - 1);
|
|
459
|
-
if (sResult.startsWith('\'') && sResult.endsWith('\''))
|
|
460
|
-
sResult = sResult.substring(1, sResult.length - 1);
|
|
461
|
-
}
|
|
462
|
-
return sResult;
|
|
463
|
-
}
|
|
464
|
-
async function createNewEntityFieldsFromSchema(ds) {
|
|
465
|
-
try {
|
|
466
|
-
const sSQL = getPendingEntityFieldsSELECTSQL();
|
|
467
|
-
const newEntityFields = await ds.query(sSQL);
|
|
468
|
-
await ds.transaction(async () => {
|
|
469
|
-
// wrap in a transaction so we get all of it or none of it
|
|
470
|
-
for (let i = 0; i < newEntityFields.length; ++i) {
|
|
471
|
-
const n = newEntityFields[i];
|
|
472
|
-
if (n.EntityID !== null && n.EntityID !== undefined && n.EntityID > 0) {
|
|
473
|
-
// need to check for null entity id = that is because the above query can return candidate Entity Fields but the entities may not have been created if the entities
|
|
474
|
-
// that would have been created violate rules - such as not having an ID column, etc.
|
|
475
|
-
const sSQLInsert = getPendingEntityFieldINSERTSQL(n);
|
|
476
|
-
await ds.query(sSQLInsert);
|
|
477
|
-
// if we get here, we're okay, otherwise we have an exception, which we want as it blows up transaction
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
return true;
|
|
482
|
-
}
|
|
483
|
-
catch (e) {
|
|
484
|
-
(0, logging_1.logError)(e);
|
|
485
|
-
return false;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
async function updateEntityFieldRelatedEntityNameFieldMap(ds, entityFieldID, relatedEntityNameFieldMap) {
|
|
489
|
-
try {
|
|
490
|
-
const sSQL = `EXEC [${(0, config_1.mj_core_schema)()}].spUpdateEntityFieldRelatedEntityNameFieldMap
|
|
491
|
-
@EntityFieldID=${entityFieldID} ,
|
|
492
|
-
@RelatedEntityNameFieldMap='${relatedEntityNameFieldMap}'`;
|
|
493
|
-
await ds.query(sSQL);
|
|
494
|
-
return true;
|
|
495
|
-
}
|
|
496
|
-
catch (e) {
|
|
497
|
-
(0, logging_1.logError)(e);
|
|
498
|
-
return false;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
exports.updateEntityFieldRelatedEntityNameFieldMap = updateEntityFieldRelatedEntityNameFieldMap;
|
|
502
|
-
async function updateExistingEntitiesFromSchema(ds, excludeSchemas) {
|
|
503
|
-
try {
|
|
504
|
-
await ds.query(`EXEC [${(0, config_1.mj_core_schema)()}].spUpdateExistingEntitiesFromSchema @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
catch (e) {
|
|
508
|
-
(0, logging_1.logError)(e);
|
|
509
|
-
return false;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
async function updateExistingEntityFieldsFromSchema(ds, excludeSchemas) {
|
|
513
|
-
try {
|
|
514
|
-
await ds.query(`EXEC [${(0, config_1.mj_core_schema)()}].spUpdateExistingEntityFieldsFromSchema @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
515
|
-
return true;
|
|
516
|
-
}
|
|
517
|
-
catch (e) {
|
|
518
|
-
(0, logging_1.logError)(e);
|
|
519
|
-
return false;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function deleteUnneededEntityFields(ds, excludeSchemas) {
|
|
523
|
-
try {
|
|
524
|
-
await ds.query(`EXEC [${(0, config_1.mj_core_schema)()}].spDeleteUnneededEntityFields @ExcludedSchemaNames='${excludeSchemas.join(',')}'`);
|
|
525
|
-
return true;
|
|
526
|
-
}
|
|
527
|
-
catch (e) {
|
|
528
|
-
(0, logging_1.logError)(e);
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
async function manageEntityFieldValues(ds, excludeSchemas) {
|
|
533
|
-
try {
|
|
534
|
-
// here we want to get all of the entity fields that have check constraints attached to them. For each field that has a check constraint, we want to
|
|
535
|
-
// evaluate it to see if it is a simple series of OR statements or not, if it is a simple series of OR statements, we can parse the possible values
|
|
536
|
-
// for the field and sync that up with the EntityFieldValue table. If it is not a simple series of OR statements, we will not be able to parse it and we'll
|
|
537
|
-
// just ignore it.
|
|
538
|
-
const filter = excludeSchemas && excludeSchemas.length > 0 ? ` WHERE SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})` : '';
|
|
539
|
-
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwEntityFieldsWithCheckConstraints${filter}`;
|
|
540
|
-
const result = await ds.query(sSQL);
|
|
541
|
-
// now, for each of the constraints we get back here, loop through and evaluate if they're simple and if they're simple, parse and sync with entity field values for that field
|
|
542
|
-
for (const r of result) {
|
|
543
|
-
if (r.ConstraintDefinition && r.ConstraintDefinition.length > 0) {
|
|
544
|
-
const parsedValues = parseCheckConstraintValues(r.ConstraintDefinition, r.ColumnName);
|
|
545
|
-
if (parsedValues) {
|
|
546
|
-
// we have parsed values from the check constraint, so sync them with the entity field values
|
|
547
|
-
await syncEntityFieldValues(ds, r.EntityID, r.ColumnName, parsedValues);
|
|
548
|
-
// finally, make sure the ValueListType column within the EntityField table is set to "List" because for check constraints we only allow the values specified in the list.
|
|
549
|
-
await ds.query(`UPDATE [${(0, config_1.mj_core_schema)()}].EntityField SET ValueListType='List' WHERE EntityID=${r.EntityID} AND Name='${r.ColumnName}'`);
|
|
550
|
-
}
|
|
551
|
-
}
|
|
837
|
+
else {
|
|
838
|
+
return this.simpleNewEntityName(newEntity.TableName);
|
|
552
839
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
const sSQLDelete = `DELETE FROM [${(0, config_1.mj_core_schema)()}].EntityFieldValue WHERE ID=${ev.ID}`;
|
|
573
|
-
await ds.query(sSQLDelete);
|
|
574
|
-
numRemoved++;
|
|
840
|
+
}
|
|
841
|
+
simpleNewEntityName(tableName) {
|
|
842
|
+
return this.convertCamelCaseToHaveSpaces(this.generatePluralName(tableName));
|
|
843
|
+
}
|
|
844
|
+
async createNewEntity(ds, newEntity, md) {
|
|
845
|
+
try {
|
|
846
|
+
const { shouldCreate, validationMessage } = await this.shouldCreateNewEntity(ds, newEntity);
|
|
847
|
+
if (shouldCreate) {
|
|
848
|
+
// process a single new entity
|
|
849
|
+
let newEntityName = await this.createNewEntityName(newEntity);
|
|
850
|
+
let suffix = '';
|
|
851
|
+
const existingEntity = md.Entities.find(e => e.Name === newEntityName);
|
|
852
|
+
const existingEntityInNewEntityList = ManageMetadataBase_1.newEntityList.find(e => e === newEntityName); // check the newly created entity list to make sure we didn't create the new entity name along the way in this RUN of CodeGen as it wouldn't yet be in our metadata above
|
|
853
|
+
if (existingEntity || existingEntityInNewEntityList) {
|
|
854
|
+
// the generated name is already in place, so we need another name
|
|
855
|
+
// use Entity Name __ SchemaName instead of just the table name as basis
|
|
856
|
+
suffix = '__' + newEntity.SchemaName;
|
|
857
|
+
newEntityName = newEntityName + suffix;
|
|
858
|
+
(0, core_1.LogError)(` >>>> WARNING: Entity name already exists, so using ${newEntityName} instead. If you did not intend for this, please rename the ${newEntity.SchemaName}.${newEntity.TableName} table in the database.`);
|
|
575
859
|
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const
|
|
583
|
-
|
|
584
|
-
VALUES
|
|
585
|
-
(${entityID}, '${entityFieldName}', ${1 + possibleValues.indexOf(v)}, '${v}', '${v}')`;
|
|
860
|
+
// get the next entity ID
|
|
861
|
+
const params = [newEntity.SchemaName];
|
|
862
|
+
const sSQLNewEntityID = `EXEC [${(0, config_1.mj_core_schema)()}].spGetNextEntityID @SchemaName=@0`;
|
|
863
|
+
const newEntityIDRaw = await ds.query(sSQLNewEntityID, params);
|
|
864
|
+
const newEntityID = newEntityIDRaw && newEntityIDRaw.length > 0 ? newEntityIDRaw[0].NextID : null;
|
|
865
|
+
if (newEntityID && newEntityID > 0) {
|
|
866
|
+
const isNewSchema = await this.isSchemaNew(ds, newEntity.SchemaName);
|
|
867
|
+
const sSQLInsert = this.createNewEntityInsertSQL(newEntityID, newEntityName, newEntity, suffix);
|
|
586
868
|
await ds.query(sSQLInsert);
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const possibleValues = [];
|
|
623
|
-
// Use regex to find matches and extract the values
|
|
624
|
-
while ((match = valueRegex.exec(constraintDefinition)) !== null) {
|
|
625
|
-
// This is necessary to avoid infinite loops with zero-width matches
|
|
626
|
-
if (match.index === valueRegex.lastIndex) {
|
|
627
|
-
valueRegex.lastIndex++;
|
|
628
|
-
}
|
|
629
|
-
// The first captured group contains the value
|
|
630
|
-
if (match[1]) {
|
|
631
|
-
possibleValues.push(match[1]);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return possibleValues;
|
|
635
|
-
}
|
|
636
|
-
function createExcludeTablesAndSchemasFilter(fieldPrefix) {
|
|
637
|
-
let sExcludeTables = '';
|
|
638
|
-
let sExcludeSchemas = '';
|
|
639
|
-
if (config_1.configInfo.excludeTables) {
|
|
640
|
-
for (let i = 0; i < config_1.configInfo.excludeTables.length; ++i) {
|
|
641
|
-
const t = config_1.configInfo.excludeTables[i];
|
|
642
|
-
sExcludeTables += (sExcludeTables.length > 0 ? ' AND ' : '') +
|
|
643
|
-
(t.schema.indexOf('%') > -1 ? ` NOT ( ${fieldPrefix}SchemaName LIKE '${t.schema}'` :
|
|
644
|
-
` NOT ( ${fieldPrefix}SchemaName = '${t.schema}'`);
|
|
645
|
-
sExcludeTables += (t.table.indexOf('%') > -1 ? ` AND ${fieldPrefix}TableName LIKE '${t.table}') ` :
|
|
646
|
-
` AND ${fieldPrefix}TableName = '${t.table}') `);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (config_1.configInfo.excludeSchemas) {
|
|
650
|
-
for (let i = 0; i < config_1.configInfo.excludeSchemas.length; ++i) {
|
|
651
|
-
const s = config_1.configInfo.excludeSchemas[i];
|
|
652
|
-
sExcludeSchemas += (sExcludeSchemas.length > 0 ? ' AND ' : '') +
|
|
653
|
-
(s.indexOf('%') > -1 ? `${fieldPrefix}SchemaName NOT LIKE '${s}'` : `${fieldPrefix}SchemaName <> '${s}'`);
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
const sWhere = (sExcludeTables.length > 0 || sExcludeSchemas.length > 0 ? ` AND ` : '') +
|
|
657
|
-
(sExcludeTables.length > 0 ? `(${sExcludeTables})` : '') +
|
|
658
|
-
(sExcludeSchemas.length > 0 ? (sExcludeTables.length > 0 ? ` AND ` : ``) + '(' + sExcludeSchemas + ')' : '');
|
|
659
|
-
return sWhere;
|
|
660
|
-
}
|
|
661
|
-
async function createNewEntities(ds) {
|
|
662
|
-
try {
|
|
663
|
-
const sSQL = `SELECT * FROM [${(0, config_1.mj_core_schema)()}].vwSQLTablesAndEntities WHERE EntityID IS NULL ` + createExcludeTablesAndSchemasFilter('');
|
|
664
|
-
const newEntities = await ds.query(sSQL);
|
|
665
|
-
if (newEntities && newEntities.length > 0) {
|
|
666
|
-
const md = new core_1.Metadata();
|
|
667
|
-
await ds.transaction(async () => {
|
|
668
|
-
// wrap in a transaction so we get all of it or none of it
|
|
669
|
-
for (let i = 0; i < newEntities.length; ++i) {
|
|
670
|
-
// process each of the new entities
|
|
671
|
-
await createNewEntity(ds, newEntities[i], md);
|
|
672
|
-
}
|
|
673
|
-
});
|
|
674
|
-
if (exports.newEntityList.length > 0) {
|
|
675
|
-
// only do this if we actually created new entities
|
|
676
|
-
(0, core_1.LogStatus)(` Done creating entities, refreshing metadata to reflect new entities...`);
|
|
677
|
-
await md.Refresh(); // refresh now since we've added some new entities
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
return true; // if we get here, we succeeded
|
|
681
|
-
}
|
|
682
|
-
catch (e) {
|
|
683
|
-
(0, core_1.LogError)(e);
|
|
684
|
-
return false;
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
async function shouldCreateNewEntity(ds, newEntity) {
|
|
688
|
-
// validate that the new entity meets our criteria for creation
|
|
689
|
-
// criteria:
|
|
690
|
-
// 1) entity has a field that is a primary key
|
|
691
|
-
// validate all of these factors by getting the sql from SQL Server and check the result, if failure, shouldCreate=false and generate validation message, otherwise return empty validation message and true for shouldCreate.
|
|
692
|
-
const query = `EXEC ${core_1.Metadata.Provider.ConfigData.MJCoreSchemaName}.spGetPrimaryKeyForTable @TableName='${newEntity.TableName}', @SchemaName='${newEntity.SchemaName}'`;
|
|
693
|
-
try {
|
|
694
|
-
const result = await ds.query(query);
|
|
695
|
-
if (result.length === 0) {
|
|
696
|
-
return { shouldCreate: false, validationMessage: "No primary key found" };
|
|
697
|
-
}
|
|
698
|
-
return { shouldCreate: true, validationMessage: '' };
|
|
699
|
-
}
|
|
700
|
-
catch (error) {
|
|
701
|
-
const errorMsg = 'Error validating new entity for table:' + newEntity?.TableName;
|
|
702
|
-
console.error(errorMsg, error);
|
|
703
|
-
return { shouldCreate: false, validationMessage: errorMsg };
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
async function createNewEntityName(newEntity) {
|
|
707
|
-
const ag = new advanced_generation_1.AdvancedGeneration();
|
|
708
|
-
if (ag.featureEnabled('EntityNames')) {
|
|
709
|
-
// get the LLM for this entity
|
|
710
|
-
const chat = ag.LLM;
|
|
711
|
-
const prompt = ag.getPrompt('EntityNames');
|
|
712
|
-
const systemPrompt = ag.fillTemplate(prompt.systemPrompt, newEntity);
|
|
713
|
-
const userMessage = ag.fillTemplate(prompt.userMessage, newEntity);
|
|
714
|
-
const result = await chat.ChatCompletion({
|
|
715
|
-
model: ag.AIModel,
|
|
716
|
-
messages: [
|
|
717
|
-
{
|
|
718
|
-
role: 'system',
|
|
719
|
-
content: systemPrompt
|
|
720
|
-
},
|
|
721
|
-
{
|
|
722
|
-
role: 'user',
|
|
723
|
-
content: userMessage
|
|
724
|
-
}
|
|
725
|
-
]
|
|
726
|
-
});
|
|
727
|
-
if (result?.success) {
|
|
728
|
-
const resultText = result?.data.choices[0].message.content;
|
|
729
|
-
try {
|
|
730
|
-
const structuredResult = JSON.parse(resultText);
|
|
731
|
-
if (structuredResult?.entityName) {
|
|
732
|
-
return structuredResult.entityName;
|
|
869
|
+
// if we get here we created a new entity safely, otherwise we get exception
|
|
870
|
+
// add it to the new entity list
|
|
871
|
+
ManageMetadataBase_1.newEntityList.push(newEntityName);
|
|
872
|
+
// next, check if this entity is in a schema that is new (e.g. no other entities have been added to this schema yet), if so and if
|
|
873
|
+
// our config option is set to create new applications from new schemas, then create a new application for this schema
|
|
874
|
+
if (isNewSchema && config_1.configInfo.newSchemaDefaults.CreateNewApplicationWithSchemaName) {
|
|
875
|
+
// new schema and config option is to create a new application from the schema name so do that
|
|
876
|
+
if (!await this.applicationExists(ds, newEntity.SchemaName))
|
|
877
|
+
await this.createNewApplication(ds, newEntity.SchemaName);
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
// not a new schema, attempt to look up the application for this schema
|
|
881
|
+
await this.getApplicationIDForSchema(ds, newEntity.SchemaName);
|
|
882
|
+
}
|
|
883
|
+
// now we have an application ID, but make sure that we are configured to add this new entity to an application at all
|
|
884
|
+
if (config_1.configInfo.newEntityDefaults.AddToApplicationWithSchemaName) {
|
|
885
|
+
// we should add this entity to the application
|
|
886
|
+
const appName = newEntity.SchemaName === (0, config_1.mj_core_schema)() ? 'Admin' : newEntity.SchemaName; // for the __mj schema or whatever it is installed as for mj_core - we want to drop stuff into the admin app
|
|
887
|
+
const sSQLInsertApplicationEntity = `INSERT INTO ${(0, config_1.mj_core_schema)()}.ApplicationEntity
|
|
888
|
+
(ApplicationName, EntityID, Sequence) VALUES
|
|
889
|
+
('${appName}', ${newEntityID}, (SELECT ISNULL(MAX(Sequence),0)+1 FROM ${(0, config_1.mj_core_schema)()}.ApplicationEntity WHERE ApplicationName = '${appName}'))`;
|
|
890
|
+
await ds.query(sSQLInsertApplicationEntity);
|
|
891
|
+
}
|
|
892
|
+
// next up, we need to check if we're configured to add permissions for new entities, and if so, add them
|
|
893
|
+
if (config_1.configInfo.newEntityDefaults.PermissionDefaults && config_1.configInfo.newEntityDefaults.PermissionDefaults.AutoAddPermissionsForNewEntities) {
|
|
894
|
+
// we are asked to add permissions for new entities, so do that by looping through the permissions and adding them
|
|
895
|
+
const permissions = config_1.configInfo.newEntityDefaults.PermissionDefaults.Permissions;
|
|
896
|
+
for (const p of permissions) {
|
|
897
|
+
const sSQLInsertPermission = `INSERT INTO ${(0, config_1.mj_core_schema)()}.EntityPermission
|
|
898
|
+
(EntityID, RoleName, CanRead, CanCreate, CanUpdate, CanDelete) VALUES
|
|
899
|
+
(${newEntityID}, '${p.RoleName}', ${p.CanRead ? 1 : 0}, ${p.CanCreate ? 1 : 0}, ${p.CanUpdate ? 1 : 0}, ${p.CanDelete ? 1 : 0})`;
|
|
900
|
+
await ds.query(sSQLInsertPermission);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
(0, core_1.LogStatus)(` Created new entity ${newEntityName} for table ${newEntity.SchemaName}.${newEntity.TableName}`);
|
|
733
904
|
}
|
|
734
905
|
else {
|
|
735
|
-
|
|
736
|
-
|
|
906
|
+
(0, core_1.LogError)(`ERROR: Unable to get next entity ID for ${newEntity.SchemaName}.${newEntity.TableName} - it is possible that the schema has reached its MAX Id,
|
|
907
|
+
check the Schema Info entity for this schema to see if all ID values have been allocated.`);
|
|
737
908
|
}
|
|
738
909
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
return
|
|
910
|
+
else {
|
|
911
|
+
(0, core_1.LogStatus)(` Skipping new entity ${newEntity.TableName} because it doesn't qualify to be created. Reason: ${validationMessage}`);
|
|
912
|
+
return;
|
|
742
913
|
}
|
|
743
914
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
915
|
+
catch (e) {
|
|
916
|
+
(0, core_1.LogError)(`Failed to create new entity ${newEntity?.TableName}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
async isSchemaNew(ds, schemaName) {
|
|
920
|
+
// check to see if there are any entities in the db with this schema name
|
|
921
|
+
const sSQL = `SELECT COUNT(*) AS Count FROM [${(0, config_1.mj_core_schema)()}].Entity WHERE SchemaName = '${schemaName}'`;
|
|
922
|
+
const result = await ds.query(sSQL);
|
|
923
|
+
return result && result.length > 0 ? result[0].Count === 0 : true;
|
|
924
|
+
}
|
|
925
|
+
async createNewApplication(ds, appName) {
|
|
926
|
+
const sSQL = "INSERT INTO [" + (0, config_1.mj_core_schema)() + "].Application (Name, Description) VALUES ('" + appName + "', 'Generated for Schema'); SELECT @@IDENTITY AS ID";
|
|
927
|
+
const result = await ds.query(sSQL);
|
|
928
|
+
return result && result.length > 0 ? result[0].ID : null;
|
|
929
|
+
}
|
|
930
|
+
async applicationExists(ds, applicationName) {
|
|
931
|
+
const sSQL = `SELECT ID FROM [${(0, config_1.mj_core_schema)()}].Application WHERE Name = '${applicationName}'`;
|
|
932
|
+
const result = await ds.query(sSQL);
|
|
933
|
+
return result && result.length > 0 ? result[0].ID > 0 : false;
|
|
934
|
+
}
|
|
935
|
+
async getApplicationIDForSchema(ds, schemaName) {
|
|
936
|
+
const sSQL = `SELECT ID FROM [${(0, config_1.mj_core_schema)()}].Application WHERE Name = '${schemaName}'`;
|
|
937
|
+
const result = await ds.query(sSQL);
|
|
938
|
+
return result && result.length > 0 ? result[0].ID : null;
|
|
939
|
+
}
|
|
940
|
+
createNewEntityInsertSQL(newEntityID, newEntityName, newEntity, newEntitySuffix) {
|
|
941
|
+
const newEntityDefaults = config_1.configInfo.newEntityDefaults;
|
|
942
|
+
const newEntityDescriptionEscaped = newEntity.Description ? `'${newEntity.Description.replace(/'/g, "''")}` : null;
|
|
943
|
+
const sSQLInsert = `INSERT INTO [${(0, config_1.mj_core_schema)()}].Entity (
|
|
944
|
+
ID,
|
|
945
|
+
Name,
|
|
946
|
+
Description,
|
|
947
|
+
NameSuffix,
|
|
948
|
+
BaseTable,
|
|
949
|
+
BaseView,
|
|
950
|
+
SchemaName,
|
|
951
|
+
IncludeInAPI,
|
|
952
|
+
AllowUserSearchAPI
|
|
953
|
+
${newEntityDefaults.TrackRecordChanges === undefined ? '' : ', TrackRecordChanges'}
|
|
954
|
+
${newEntityDefaults.AuditRecordAccess === undefined ? '' : ', AuditRecordAccess'}
|
|
955
|
+
${newEntityDefaults.AuditViewRuns === undefined ? '' : ', AuditViewRuns'}
|
|
956
|
+
${newEntityDefaults.AllowAllRowsAPI === undefined ? '' : ', AllowAllRowsAPI'}
|
|
957
|
+
${newEntityDefaults.AllowCreateAPI === undefined ? '' : ', AllowCreateAPI'}
|
|
958
|
+
${newEntityDefaults.AllowUpdateAPI === undefined ? '' : ', AllowUpdateAPI'}
|
|
959
|
+
${newEntityDefaults.AllowDeleteAPI === undefined ? '' : ', AllowDeleteAPI'}
|
|
960
|
+
${newEntityDefaults.UserViewMaxRows === undefined ? '' : ', UserViewMaxRows'}
|
|
961
|
+
)
|
|
962
|
+
VALUES (
|
|
963
|
+
${newEntityID},
|
|
964
|
+
'${newEntityName}',
|
|
965
|
+
${newEntityDescriptionEscaped ? newEntityDescriptionEscaped : 'NULL' /*if no description, then null*/},
|
|
966
|
+
${newEntitySuffix && newEntitySuffix.length > 0 ? `'${newEntitySuffix}'` : 'NULL'},
|
|
967
|
+
'${newEntity.TableName}',
|
|
968
|
+
'vw${this.generatePluralName(newEntity.TableName) + (newEntitySuffix && newEntitySuffix.length > 0 ? newEntitySuffix : '')}',
|
|
969
|
+
'${newEntity.SchemaName}',
|
|
970
|
+
1,
|
|
971
|
+
${newEntityDefaults.AllowUserSearchAPI === undefined ? 1 : newEntityDefaults.AllowUserSearchAPI ? 1 : 0}
|
|
972
|
+
${newEntityDefaults.TrackRecordChanges === undefined ? '' : ', ' + (newEntityDefaults.TrackRecordChanges ? '1' : '0')}
|
|
973
|
+
${newEntityDefaults.AuditRecordAccess === undefined ? '' : ', ' + (newEntityDefaults.AuditRecordAccess ? '1' : '0')}
|
|
974
|
+
${newEntityDefaults.AuditViewRuns === undefined ? '' : ', ' + (newEntityDefaults.AuditViewRuns ? '1' : '0')}
|
|
975
|
+
${newEntityDefaults.AllowAllRowsAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowAllRowsAPI ? '1' : '0')}
|
|
976
|
+
${newEntityDefaults.AllowCreateAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowCreateAPI ? '1' : '0')}
|
|
977
|
+
${newEntityDefaults.AllowUpdateAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowUpdateAPI ? '1' : '0')}
|
|
978
|
+
${newEntityDefaults.AllowDeleteAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowDeleteAPI ? '1' : '0')}
|
|
979
|
+
${newEntityDefaults.UserViewMaxRows === undefined ? '' : ', ' + (newEntityDefaults.UserViewMaxRows)}
|
|
980
|
+
)`;
|
|
981
|
+
return sSQLInsert;
|
|
982
|
+
}
|
|
983
|
+
stripTrailingChars(s, charsToStrip, skipIfExactMatch) {
|
|
984
|
+
if (s && charsToStrip) {
|
|
985
|
+
if (s.endsWith(charsToStrip) && (skipIfExactMatch ? s !== charsToStrip : true))
|
|
986
|
+
return s.substring(0, s.length - charsToStrip.length);
|
|
987
|
+
else
|
|
988
|
+
return s;
|
|
989
|
+
}
|
|
990
|
+
else
|
|
991
|
+
return s;
|
|
992
|
+
}
|
|
993
|
+
convertCamelCaseToHaveSpaces(s) {
|
|
994
|
+
let result = '';
|
|
995
|
+
for (let i = 0; i < s.length; ++i) {
|
|
996
|
+
if (s[i] === s[i].toUpperCase() && // current character is upper case
|
|
997
|
+
i > 0 && // not first character
|
|
998
|
+
s[i - 1] !== ' ' && // previous character is not a space - needed for strings like "Database Version" that already have spaces
|
|
999
|
+
(s[i - 1] !== s[i - 1].toUpperCase()) // previous character is not upper case handles not putting space between I and D in ID as an example of consecutive upper case
|
|
1000
|
+
) {
|
|
1001
|
+
result += ' ';
|
|
771
1002
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
//
|
|
785
|
-
|
|
786
|
-
if (isNewSchema && config_1.configInfo.newSchemaDefaults.CreateNewApplicationWithSchemaName) {
|
|
787
|
-
// new schema and config option is to create a new application from the schema name so do that
|
|
788
|
-
if (!await applicationExists(ds, newEntity.SchemaName))
|
|
789
|
-
await createNewApplication(ds, newEntity.SchemaName);
|
|
790
|
-
}
|
|
791
|
-
else {
|
|
792
|
-
// not a new schema, attempt to look up the application for this schema
|
|
793
|
-
await getApplicationIDForSchema(ds, newEntity.SchemaName);
|
|
794
|
-
}
|
|
795
|
-
// now we have an application ID, but make sure that we are configured to add this new entity to an application at all
|
|
796
|
-
if (config_1.configInfo.newEntityDefaults.AddToApplicationWithSchemaName) {
|
|
797
|
-
// we should add this entity to the application
|
|
798
|
-
const appName = newEntity.SchemaName === (0, config_1.mj_core_schema)() ? 'Admin' : newEntity.SchemaName; // for the __mj schema or whatever it is installed as for mj_core - we want to drop stuff into the admin app
|
|
799
|
-
const sSQLInsertApplicationEntity = `INSERT INTO ${(0, config_1.mj_core_schema)()}.ApplicationEntity
|
|
800
|
-
(ApplicationName, EntityID, Sequence) VALUES
|
|
801
|
-
('${appName}', ${newEntityID}, (SELECT ISNULL(MAX(Sequence),0)+1 FROM ${(0, config_1.mj_core_schema)()}.ApplicationEntity WHERE ApplicationName = '${appName}'))`;
|
|
802
|
-
await ds.query(sSQLInsertApplicationEntity);
|
|
803
|
-
}
|
|
804
|
-
// next up, we need to check if we're configured to add permissions for new entities, and if so, add them
|
|
805
|
-
if (config_1.configInfo.newEntityDefaults.PermissionDefaults && config_1.configInfo.newEntityDefaults.PermissionDefaults.AutoAddPermissionsForNewEntities) {
|
|
806
|
-
// we are asked to add permissions for new entities, so do that by looping through the permissions and adding them
|
|
807
|
-
const permissions = config_1.configInfo.newEntityDefaults.PermissionDefaults.Permissions;
|
|
808
|
-
for (const p of permissions) {
|
|
809
|
-
const sSQLInsertPermission = `INSERT INTO ${(0, config_1.mj_core_schema)()}.EntityPermission
|
|
810
|
-
(EntityID, RoleName, CanRead, CanCreate, CanUpdate, CanDelete) VALUES
|
|
811
|
-
(${newEntityID}, '${p.RoleName}', ${p.CanRead ? 1 : 0}, ${p.CanCreate ? 1 : 0}, ${p.CanUpdate ? 1 : 0}, ${p.CanDelete ? 1 : 0})`;
|
|
812
|
-
await ds.query(sSQLInsertPermission);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
(0, core_1.LogStatus)(` Created new entity ${newEntityName} for table ${newEntity.SchemaName}.${newEntity.TableName}`);
|
|
1003
|
+
result += s[i];
|
|
1004
|
+
}
|
|
1005
|
+
return result;
|
|
1006
|
+
}
|
|
1007
|
+
stripWhitespace(s) {
|
|
1008
|
+
return s.replace(/\s/g, '');
|
|
1009
|
+
}
|
|
1010
|
+
generatePluralName(singularName) {
|
|
1011
|
+
if (singularName.endsWith('y') && singularName.length > 1) {
|
|
1012
|
+
// Check if the letter before 'y' is a vowel
|
|
1013
|
+
const secondLastChar = singularName[singularName.length - 2].toLowerCase();
|
|
1014
|
+
if ('aeiou'.includes(secondLastChar)) {
|
|
1015
|
+
// If it's a vowel, just add 's', example "key/keys"
|
|
1016
|
+
return singularName + 's';
|
|
816
1017
|
}
|
|
817
1018
|
else {
|
|
818
|
-
|
|
819
|
-
|
|
1019
|
+
// If it's a consonant, replace 'y' with 'ies' - example "party/parties"
|
|
1020
|
+
return singularName.substring(0, singularName.length - 1) + 'ies';
|
|
820
1021
|
}
|
|
821
1022
|
}
|
|
822
|
-
else {
|
|
823
|
-
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
catch (e) {
|
|
828
|
-
(0, core_1.LogError)(`Failed to create new entity ${newEntity?.TableName}`);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
async function isSchemaNew(ds, schemaName) {
|
|
832
|
-
// check to see if there are any entities in the db with this schema name
|
|
833
|
-
const sSQL = `SELECT COUNT(*) AS Count FROM [${(0, config_1.mj_core_schema)()}].Entity WHERE SchemaName = '${schemaName}'`;
|
|
834
|
-
const result = await ds.query(sSQL);
|
|
835
|
-
return result && result.length > 0 ? result[0].Count === 0 : true;
|
|
836
|
-
}
|
|
837
|
-
async function createNewApplication(ds, appName) {
|
|
838
|
-
const sSQL = "INSERT INTO [" + (0, config_1.mj_core_schema)() + "].Application (Name, Description) VALUES ('" + appName + "', 'Generated for Schema'); SELECT @@IDENTITY AS ID";
|
|
839
|
-
const result = await ds.query(sSQL);
|
|
840
|
-
return result && result.length > 0 ? result[0].ID : null;
|
|
841
|
-
}
|
|
842
|
-
async function applicationExists(ds, applicationName) {
|
|
843
|
-
const sSQL = `SELECT ID FROM [${(0, config_1.mj_core_schema)()}].Application WHERE Name = '${applicationName}'`;
|
|
844
|
-
const result = await ds.query(sSQL);
|
|
845
|
-
return result && result.length > 0 ? result[0].ID > 0 : false;
|
|
846
|
-
}
|
|
847
|
-
async function getApplicationIDForSchema(ds, schemaName) {
|
|
848
|
-
const sSQL = `SELECT ID FROM [${(0, config_1.mj_core_schema)()}].Application WHERE Name = '${schemaName}'`;
|
|
849
|
-
const result = await ds.query(sSQL);
|
|
850
|
-
return result && result.length > 0 ? result[0].ID : null;
|
|
851
|
-
}
|
|
852
|
-
function createNewEntityInsertSQL(newEntityID, newEntityName, newEntity, newEntitySuffix) {
|
|
853
|
-
const newEntityDefaults = config_1.configInfo.newEntityDefaults;
|
|
854
|
-
const newEntityDescriptionEscaped = newEntity.Description ? `'${newEntity.Description.replace(/'/g, "''")}` : null;
|
|
855
|
-
const sSQLInsert = `INSERT INTO [${(0, config_1.mj_core_schema)()}].Entity (
|
|
856
|
-
ID,
|
|
857
|
-
Name,
|
|
858
|
-
Description,
|
|
859
|
-
NameSuffix,
|
|
860
|
-
BaseTable,
|
|
861
|
-
BaseView,
|
|
862
|
-
SchemaName,
|
|
863
|
-
IncludeInAPI,
|
|
864
|
-
AllowUserSearchAPI
|
|
865
|
-
${newEntityDefaults.TrackRecordChanges === undefined ? '' : ', TrackRecordChanges'}
|
|
866
|
-
${newEntityDefaults.AuditRecordAccess === undefined ? '' : ', AuditRecordAccess'}
|
|
867
|
-
${newEntityDefaults.AuditViewRuns === undefined ? '' : ', AuditViewRuns'}
|
|
868
|
-
${newEntityDefaults.AllowAllRowsAPI === undefined ? '' : ', AllowAllRowsAPI'}
|
|
869
|
-
${newEntityDefaults.AllowCreateAPI === undefined ? '' : ', AllowCreateAPI'}
|
|
870
|
-
${newEntityDefaults.AllowUpdateAPI === undefined ? '' : ', AllowUpdateAPI'}
|
|
871
|
-
${newEntityDefaults.AllowDeleteAPI === undefined ? '' : ', AllowDeleteAPI'}
|
|
872
|
-
${newEntityDefaults.UserViewMaxRows === undefined ? '' : ', UserViewMaxRows'}
|
|
873
|
-
)
|
|
874
|
-
VALUES (
|
|
875
|
-
${newEntityID},
|
|
876
|
-
'${newEntityName}',
|
|
877
|
-
${newEntityDescriptionEscaped ? newEntityDescriptionEscaped : 'NULL' /*if no description, then null*/},
|
|
878
|
-
${newEntitySuffix && newEntitySuffix.length > 0 ? `'${newEntitySuffix}'` : 'NULL'},
|
|
879
|
-
'${newEntity.TableName}',
|
|
880
|
-
'vw${generatePluralName(newEntity.TableName) + (newEntitySuffix && newEntitySuffix.length > 0 ? newEntitySuffix : '')}',
|
|
881
|
-
'${newEntity.SchemaName}',
|
|
882
|
-
1,
|
|
883
|
-
${newEntityDefaults.AllowUserSearchAPI === undefined ? 1 : newEntityDefaults.AllowUserSearchAPI ? 1 : 0}
|
|
884
|
-
${newEntityDefaults.TrackRecordChanges === undefined ? '' : ', ' + (newEntityDefaults.TrackRecordChanges ? '1' : '0')}
|
|
885
|
-
${newEntityDefaults.AuditRecordAccess === undefined ? '' : ', ' + (newEntityDefaults.AuditRecordAccess ? '1' : '0')}
|
|
886
|
-
${newEntityDefaults.AuditViewRuns === undefined ? '' : ', ' + (newEntityDefaults.AuditViewRuns ? '1' : '0')}
|
|
887
|
-
${newEntityDefaults.AllowAllRowsAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowAllRowsAPI ? '1' : '0')}
|
|
888
|
-
${newEntityDefaults.AllowCreateAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowCreateAPI ? '1' : '0')}
|
|
889
|
-
${newEntityDefaults.AllowUpdateAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowUpdateAPI ? '1' : '0')}
|
|
890
|
-
${newEntityDefaults.AllowDeleteAPI === undefined ? '' : ', ' + (newEntityDefaults.AllowDeleteAPI ? '1' : '0')}
|
|
891
|
-
${newEntityDefaults.UserViewMaxRows === undefined ? '' : ', ' + (newEntityDefaults.UserViewMaxRows)}
|
|
892
|
-
)`;
|
|
893
|
-
return sSQLInsert;
|
|
894
|
-
}
|
|
895
|
-
function stripTrailingChars(s, charsToStrip, skipIfExactMatch) {
|
|
896
|
-
if (s && charsToStrip) {
|
|
897
|
-
if (s.endsWith(charsToStrip) && (skipIfExactMatch ? s !== charsToStrip : true))
|
|
898
|
-
return s.substring(0, s.length - charsToStrip.length);
|
|
899
|
-
else
|
|
900
|
-
return s;
|
|
901
|
-
}
|
|
902
|
-
else
|
|
903
|
-
return s;
|
|
904
|
-
}
|
|
905
|
-
function convertCamelCaseToHaveSpaces(s) {
|
|
906
|
-
let result = '';
|
|
907
|
-
for (let i = 0; i < s.length; ++i) {
|
|
908
|
-
if (s[i] === s[i].toUpperCase() && // current character is upper case
|
|
909
|
-
i > 0 && // not first character
|
|
910
|
-
s[i - 1] !== ' ' && // previous character is not a space - needed for strings like "Database Version" that already have spaces
|
|
911
|
-
(s[i - 1] !== s[i - 1].toUpperCase()) // previous character is not upper case handles not putting space between I and D in ID as an example of consecutive upper case
|
|
912
|
-
) {
|
|
913
|
-
result += ' ';
|
|
914
|
-
}
|
|
915
|
-
result += s[i];
|
|
916
|
-
}
|
|
917
|
-
return result;
|
|
918
|
-
}
|
|
919
|
-
function stripWhitespace(s) {
|
|
920
|
-
return s.replace(/\s/g, '');
|
|
921
|
-
}
|
|
922
|
-
function generatePluralName(singularName) {
|
|
923
|
-
if (singularName.endsWith('y') && singularName.length > 1) {
|
|
924
|
-
// Check if the letter before 'y' is a vowel
|
|
925
|
-
const secondLastChar = singularName[singularName.length - 2].toLowerCase();
|
|
926
|
-
if ('aeiou'.includes(secondLastChar)) {
|
|
927
|
-
// If it's a vowel, just add 's', example "key/keys"
|
|
1023
|
+
else if (singularName.endsWith('y')) {
|
|
1024
|
+
// If the string is just 'y', treat it like a vowel and just add 's'
|
|
928
1025
|
return singularName + 's';
|
|
929
1026
|
}
|
|
1027
|
+
else if (singularName.endsWith('s')) {
|
|
1028
|
+
// Singular name already ends with 's', so just return it
|
|
1029
|
+
return singularName;
|
|
1030
|
+
}
|
|
1031
|
+
else if (singularName.endsWith('ch') || singularName.endsWith('sh') || singularName.endsWith('x') || singularName.endsWith('z')) {
|
|
1032
|
+
// If the singular name ends with 'ch', 'sh', 'x', or 'z', add 'es' - example "box/boxes", "index/indexes", "church/churches", "dish/dishes", "buzz/buzzes"
|
|
1033
|
+
return singularName + 'es';
|
|
1034
|
+
}
|
|
930
1035
|
else {
|
|
931
|
-
//
|
|
932
|
-
return singularName
|
|
1036
|
+
// For other cases, just add 's'
|
|
1037
|
+
return singularName + 's';
|
|
933
1038
|
}
|
|
934
1039
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
return singularName;
|
|
942
|
-
}
|
|
943
|
-
else if (singularName.endsWith('ch') || singularName.endsWith('sh') || singularName.endsWith('x') || singularName.endsWith('z')) {
|
|
944
|
-
// If the singular name ends with 'ch', 'sh', 'x', or 'z', add 'es' - example "box/boxes", "index/indexes", "church/churches", "dish/dishes", "buzz/buzzes"
|
|
945
|
-
return singularName + 'es';
|
|
946
|
-
}
|
|
947
|
-
else {
|
|
948
|
-
// For other cases, just add 's'
|
|
949
|
-
return singularName + 's';
|
|
950
|
-
}
|
|
951
|
-
}
|
|
1040
|
+
};
|
|
1041
|
+
exports.ManageMetadataBase = ManageMetadataBase;
|
|
1042
|
+
ManageMetadataBase._newEntityList = [];
|
|
1043
|
+
exports.ManageMetadataBase = ManageMetadataBase = ManageMetadataBase_1 = __decorate([
|
|
1044
|
+
(0, global_1.RegisterClass)(ManageMetadataBase)
|
|
1045
|
+
], ManageMetadataBase);
|
|
952
1046
|
//# sourceMappingURL=manageMetadata.js.map
|