@memberjunction/codegen-lib 0.9.2

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.
Files changed (50) hide show
  1. package/dist/angular_client_codegen.d.ts +2 -0
  2. package/dist/angular_client_codegen.js +360 -0
  3. package/dist/angular_client_codegen.js.map +1 -0
  4. package/dist/config.d.ts +101 -0
  5. package/dist/config.js +55 -0
  6. package/dist/config.js.map +1 -0
  7. package/dist/db.d.ts +3 -0
  8. package/dist/db.js +19 -0
  9. package/dist/db.js.map +1 -0
  10. package/dist/dbSchema.d.ts +3 -0
  11. package/dist/dbSchema.js +143 -0
  12. package/dist/dbSchema.js.map +1 -0
  13. package/dist/entity_subclasses_codegen.d.ts +4 -0
  14. package/dist/entity_subclasses_codegen.js +84 -0
  15. package/dist/entity_subclasses_codegen.js.map +1 -0
  16. package/dist/graphql_client_codegen.d.ts +4 -0
  17. package/dist/graphql_client_codegen.js +161 -0
  18. package/dist/graphql_client_codegen.js.map +1 -0
  19. package/dist/graphql_server_codegen.d.ts +5 -0
  20. package/dist/graphql_server_codegen.js +509 -0
  21. package/dist/graphql_server_codegen.js.map +1 -0
  22. package/dist/index.d.ts +15 -0
  23. package/dist/index.js +32 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/logging.d.ts +2 -0
  26. package/dist/logging.js +36 -0
  27. package/dist/logging.js.map +1 -0
  28. package/dist/manageMetadata.d.ts +9 -0
  29. package/dist/manageMetadata.js +603 -0
  30. package/dist/manageMetadata.js.map +1 -0
  31. package/dist/react_client_codegen.d.ts +4 -0
  32. package/dist/react_client_codegen.js +147 -0
  33. package/dist/react_client_codegen.js.map +1 -0
  34. package/dist/runCodeGen.d.ts +1 -0
  35. package/dist/runCodeGen.js +213 -0
  36. package/dist/runCodeGen.js.map +1 -0
  37. package/dist/runCommand.d.ts +9 -0
  38. package/dist/runCommand.js +111 -0
  39. package/dist/runCommand.js.map +1 -0
  40. package/dist/sql.d.ts +6 -0
  41. package/dist/sql.js +78 -0
  42. package/dist/sql.js.map +1 -0
  43. package/dist/sql_codegen.d.ts +11 -0
  44. package/dist/sql_codegen.js +753 -0
  45. package/dist/sql_codegen.js.map +1 -0
  46. package/dist/util.d.ts +4 -0
  47. package/dist/util.js +36 -0
  48. package/dist/util.js.map +1 -0
  49. package/package.json +28 -0
  50. package/readme.md +3 -0
@@ -0,0 +1,753 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.generateSingleEntitySQLFileHeader = exports.generateAllEntitiesSQLFileHeader = exports.generateEntitySQL = exports.generateSingleEntitySQLToSeparateFiles = exports.generateAndExecuteSingleEntitySQLToSeparateFiles = exports.generateAndExecuteAllEntitiesSQLToSeparateFiles = exports.applyPermissions = exports.runCustomSQLScripts = exports.manageSQLScriptsAndExecution = void 0;
30
+ const core_1 = require("@memberjunction/core");
31
+ const logging_1 = require("./logging");
32
+ const fs = __importStar(require("fs"));
33
+ const path_1 = __importDefault(require("path"));
34
+ const sql_1 = require("./sql");
35
+ const config_1 = require("./config");
36
+ const manageMetadata_1 = require("./manageMetadata");
37
+ const sqlserver_dataprovider_1 = require("@memberjunction/sqlserver-dataprovider");
38
+ //import { LoadGeneratedEntities } from 'mj_generatedentities';
39
+ //LoadGeneratedEntities(); // make sure we have everything loaded up
40
+ async function manageSQLScriptsAndExecution(ds, entities, directory) {
41
+ try {
42
+ // STEP 1 - execute any custom SQL scripts for object creation that need to happen first - for example, if
43
+ // we have custom base views, need to have them defined before we do
44
+ // the rest as the generated stuff might use custom base views in compiled
45
+ // objects like spCreate for a given entity might reference the vw for that entity
46
+ const startTime = new Date();
47
+ if (!await runCustomSQLScripts(ds, 'before-sql'))
48
+ return false;
49
+ console.log(` Time to run custom SQL scripts: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
50
+ // STEP 2 - generate all the SQL files and execute them
51
+ const step2StartTime = new Date();
52
+ if (!await generateAndExecuteAllEntitiesSQLToSeparateFiles(ds, entities, directory)) {
53
+ (0, logging_1.logError)('Error generating and executing all entities SQL to separate files');
54
+ return false;
55
+ }
56
+ console.log(` Time to Generate/Execute Entity SQL: ${(new Date().getTime() - step2StartTime.getTime()) / 1000} seconds`);
57
+ // STEP 3 - re-run the process to manage entity fields since the Step 1 and 2 above might have resulted in differences in base view columns compared to what we had at first
58
+ if (!await (0, manageMetadata_1.manageEntityFields)(ds)) {
59
+ (0, logging_1.logError)('Error managing entity fields');
60
+ return false;
61
+ }
62
+ // no console.log/timer for this because manageEntityFields() has its own internal logging for this including the total, so it is redundant to log it here
63
+ // STEP 4- Apply permissions, executing all .permissions files
64
+ const step4StartTime = new Date();
65
+ if (!await applyPermissions(ds, entities, directory)) {
66
+ (0, logging_1.logError)('Error applying permissions');
67
+ return false;
68
+ }
69
+ console.log(` Time to Apply Permissions: ${(new Date().getTime() - step4StartTime.getTime()) / 1000} seconds`);
70
+ // STEP 5 - execute any custom SQL scripts that should run afterwards
71
+ const step5StartTime = new Date();
72
+ if (!await runCustomSQLScripts(ds, 'after-sql'))
73
+ return false;
74
+ console.log(` Time to run custom SQL scripts: ${(new Date().getTime() - step5StartTime.getTime()) / 1000} seconds`);
75
+ console.log(' Total time to run generate and execute SQL scripts: ' + ((new Date().getTime() - startTime.getTime()) / 1000) + ' seconds');
76
+ // now - we need to tell our metadata object to refresh itself
77
+ const md = new core_1.Metadata();
78
+ await md.Refresh();
79
+ return true;
80
+ }
81
+ catch (err) {
82
+ (0, logging_1.logError)(err);
83
+ return false;
84
+ }
85
+ }
86
+ exports.manageSQLScriptsAndExecution = manageSQLScriptsAndExecution;
87
+ async function runCustomSQLScripts(ds, when) {
88
+ try {
89
+ const scripts = (0, config_1.customSqlScripts)(when);
90
+ let bSuccess = true;
91
+ if (scripts) {
92
+ for (let i = 0; i < scripts.length; ++i) {
93
+ const s = scripts[i];
94
+ if (!await (0, sql_1.executeSQLFile)(ds, s.scriptFile, true)) {
95
+ (0, logging_1.logError)(`Error executing custom '${when}' SQL script ${s.scriptFile}`);
96
+ bSuccess = false; // keep going if we have more scripts, but make sure we return false
97
+ }
98
+ }
99
+ }
100
+ return bSuccess;
101
+ }
102
+ catch (e) {
103
+ (0, logging_1.logError)(e);
104
+ return false;
105
+ }
106
+ }
107
+ exports.runCustomSQLScripts = runCustomSQLScripts;
108
+ async function applyPermissions(ds, entities, directory, batchSize = 5) {
109
+ try {
110
+ let bSuccess = true;
111
+ const files = fs.readdirSync(directory);
112
+ const permissionsFiles = files.filter(file => file.includes('.permissions.'));
113
+ for (let i = 0; i < permissionsFiles.length; i += batchSize) {
114
+ const batch = permissionsFiles.slice(i, i + batchSize);
115
+ const promises = batch.map(async (f) => {
116
+ return (0, sql_1.executeSQLFile)(ds, path_1.default.join(directory, f), true);
117
+ });
118
+ const results = await Promise.all(promises);
119
+ if (results.includes(false)) {
120
+ (0, logging_1.logError)(`Error executing one or more permissions files in batch starting from index ${i}`);
121
+ bSuccess = false; // keep going, but will return false at the end
122
+ }
123
+ }
124
+ return bSuccess;
125
+ }
126
+ catch (err) {
127
+ (0, logging_1.logError)(err);
128
+ return false;
129
+ }
130
+ }
131
+ exports.applyPermissions = applyPermissions;
132
+ async function generateAndExecuteAllEntitiesSQLToSeparateFiles(ds, entities, directory, batchSize = 5) {
133
+ try {
134
+ let bFail = false;
135
+ const totalEntities = entities.length;
136
+ for (let i = 0; i < totalEntities; i += batchSize) {
137
+ const batch = entities.slice(i, i + batchSize);
138
+ const promises = batch.map(async (e) => {
139
+ const idField = e.Fields.find(f => f.Name.toLowerCase().trim() === 'id');
140
+ if (!idField) {
141
+ (0, logging_1.logError)(`SKIPPING ENTITY: Entity ${e.Name}, because it does not have an ID field defined which is required for a MemberJunction entity`);
142
+ return false;
143
+ }
144
+ return generateAndExecuteSingleEntitySQLToSeparateFiles(ds, e, entities, directory);
145
+ });
146
+ const results = await Promise.all(promises);
147
+ if (results.includes(false)) {
148
+ bFail = true; // keep going, but will return false at the end
149
+ }
150
+ }
151
+ return !bFail;
152
+ }
153
+ catch (err) {
154
+ (0, logging_1.logError)(err);
155
+ return false;
156
+ }
157
+ }
158
+ exports.generateAndExecuteAllEntitiesSQLToSeparateFiles = generateAndExecuteAllEntitiesSQLToSeparateFiles;
159
+ async function generateAndExecuteSingleEntitySQLToSeparateFiles(ds, entity, entities, directory) {
160
+ try {
161
+ const sSQL = await generateSingleEntitySQLToSeparateFiles(ds, entity, entities, directory); // this creates the files and returns a single string with all the SQL we can then execute
162
+ return await (0, sql_1.executeSQLScript)(ds, sSQL, true);
163
+ }
164
+ catch (err) {
165
+ (0, logging_1.logError)(err);
166
+ return false;
167
+ }
168
+ }
169
+ exports.generateAndExecuteSingleEntitySQLToSeparateFiles = generateAndExecuteSingleEntitySQLToSeparateFiles;
170
+ async function generateSingleEntitySQLToSeparateFiles(ds, entity, entities, directory) {
171
+ try {
172
+ if (!fs.existsSync(directory))
173
+ fs.mkdirSync(directory, { recursive: true }); // create the directory if it doesn't exist
174
+ let sRet = '';
175
+ // BASE VIEW
176
+ if (entity.BaseViewGenerated && !entity.VirtualEntity) {
177
+ // generate the base view
178
+ const s = generateSingleEntitySQLFileHeader(entity, entity.BaseView) + await generateBaseView(ds, entity, entities);
179
+ fs.writeFileSync(directory + `/${entity.BaseView}.generated.sql`, s);
180
+ sRet += s + '\nGO\n';
181
+ }
182
+ // always generate permissions for the base view
183
+ const s = generateSingleEntitySQLFileHeader(entity, 'Permissions for ' + entity.BaseView) + generateViewPermissions(entity);
184
+ fs.writeFileSync(directory + `/${entity.BaseView}.permissions.generated.sql`, s);
185
+ // now, append the permissions to the return string IF we did NOT generate the base view - because if we generated the base view, that
186
+ // means we already generated the permissions for it above and it is part of sRet already, but we always save it to a file, (per above line)
187
+ if (!entity.BaseViewGenerated)
188
+ sRet += s + '\nGO\n';
189
+ // CREATE SP
190
+ if (entity.AllowCreateAPI && !entity.VirtualEntity) {
191
+ const spName = entity.spCreate && entity.spCreate.length > 0 ? entity.spCreate : 'spCreate' + entity.ClassName;
192
+ if (entity.spCreateGenerated) {
193
+ // generate the create SP
194
+ const s = generateSingleEntitySQLFileHeader(entity, spName) + generateSPCreate(entity);
195
+ fs.writeFileSync(directory + `/${spName}.generated.sql`, s);
196
+ sRet += s + '\nGO\n';
197
+ }
198
+ const s = generateSPPermissions(entity, spName, SPType.Create) + '\n\n';
199
+ fs.writeFileSync(directory + `/${spName}.permissions.generated.sql`, s);
200
+ // now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
201
+ // means we already generated the permissions for it above and it is part of sRet already, but we always save it to a file, (per above line)
202
+ if (!entity.spCreateGenerated)
203
+ sRet += s + '\nGO\n';
204
+ }
205
+ // UPDATE SP
206
+ if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
207
+ const spName = entity.spUpdate && entity.spUpdate.length > 0 ? entity.spUpdate : 'spUpdate' + entity.ClassName;
208
+ if (entity.spUpdateGenerated) {
209
+ // generate the update SP
210
+ const s = generateSingleEntitySQLFileHeader(entity, spName) + generateSPUpdate(entity);
211
+ fs.writeFileSync(directory + `/${spName}.generated.sql`, s);
212
+ sRet += s + '\nGO\n';
213
+ }
214
+ const s = generateSPPermissions(entity, spName, SPType.Update) + '\n\n';
215
+ fs.writeFileSync(directory + `/${spName}.permissions.generated.sql`, s);
216
+ // now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
217
+ // means we already generated the permissions for it above and it is part of sRet already, but we always save it to a file, (per above line)
218
+ if (!entity.spUpdateGenerated)
219
+ sRet += s + '\nGO\n';
220
+ }
221
+ // DELETE SP
222
+ if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
223
+ const spName = entity.spDelete && entity.spDelete.length > 0 ? entity.spDelete : 'spDelete' + entity.ClassName;
224
+ if (entity.spDeleteGenerated) {
225
+ // generate the delete SP
226
+ const s = generateSingleEntitySQLFileHeader(entity, spName) + generateSPDelete(entity);
227
+ fs.writeFileSync(directory + `/${spName}.generated.sql`, s);
228
+ sRet += s + '\nGO\n';
229
+ }
230
+ const s = generateSPPermissions(entity, spName, SPType.Delete) + '\n\n';
231
+ fs.writeFileSync(directory + `/${spName}.permissions.generated.sql`, s);
232
+ // now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
233
+ // means we already generated the permissions for it above and it is part of sRet already, but we always save it to a file, (per above line)
234
+ if (!entity.spDeleteGenerated)
235
+ sRet += s + '\nGO\n';
236
+ }
237
+ // check to see if the entity supports full text search or not
238
+ if (entity.FullTextSearchEnabled) {
239
+ const ft = await generateEntityFullTextSearchSQL(ds, entity);
240
+ fs.writeFileSync(directory + `/${entity.BaseTable}.fulltext.generated.sql`, ft.sql);
241
+ sRet += ft.sql + '\nGO\n';
242
+ const sP = generateFullTextSearchFunctionPermissions(entity, ft.functionName) + '\n\n';
243
+ fs.writeFileSync(directory + `/${entity.BaseTable}.fulltext.permissions.generated.sql`, sP);
244
+ // now, append the permissions to the return string IF we did NOT generate the function - because if we generated the function, that
245
+ // means we already generated the permissions for it above and it is part of sRet already, but we always save it to a file, (per above line)
246
+ if (!entity.FullTextSearchFunctionGenerated)
247
+ sRet += sP + '\nGO\n';
248
+ }
249
+ return sRet;
250
+ }
251
+ catch (err) {
252
+ (0, logging_1.logError)(err);
253
+ return null;
254
+ }
255
+ }
256
+ exports.generateSingleEntitySQLToSeparateFiles = generateSingleEntitySQLToSeparateFiles;
257
+ async function generateEntitySQL(ds, entity, entities) {
258
+ let sOutput = '';
259
+ if (entity.BaseViewGenerated && !entity.VirtualEntity)
260
+ // generated the base view (will include permissions)
261
+ sOutput += await generateBaseView(ds, entity, entities) + '\n\n';
262
+ else
263
+ // still generate the permissions for the view even if a custom view
264
+ sOutput += generateViewPermissions(entity) + '\n\n';
265
+ if (entity.AllowCreateAPI && !entity.VirtualEntity) {
266
+ if (entity.spCreateGenerated)
267
+ // generated SP, will include permissions
268
+ sOutput += generateSPCreate(entity) + '\n\n';
269
+ else
270
+ // custom SP, still generate the permissions
271
+ sOutput += generateSPPermissions(entity, entity.spCreate, SPType.Create) + '\n\n';
272
+ }
273
+ if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
274
+ if (entity.spUpdateGenerated)
275
+ // generated SP, will include permissions
276
+ sOutput += generateSPUpdate(entity) + '\n\n';
277
+ else
278
+ // custom SP, still generate the permissions
279
+ sOutput += generateSPPermissions(entity, entity.spUpdate, SPType.Update) + '\n\n';
280
+ }
281
+ if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
282
+ if (entity.spDeleteGenerated)
283
+ // generated SP, will include permissions
284
+ sOutput += generateSPDelete(entity) + '\n\n';
285
+ else
286
+ // custom SP, still generate the permissions
287
+ sOutput += generateSPPermissions(entity, entity.spDelete, SPType.Delete) + '\n\n';
288
+ }
289
+ // check to see if the entity supports full text search or not
290
+ if (entity.FullTextSearchEnabled) {
291
+ sOutput += await generateEntityFullTextSearchSQL(ds, entity) + '\n\n';
292
+ }
293
+ return sOutput;
294
+ }
295
+ exports.generateEntitySQL = generateEntitySQL;
296
+ async function generateEntityFullTextSearchSQL(ds, entity) {
297
+ let sql = '';
298
+ const catalogName = entity.FullTextCatalog && entity.FullTextCatalog.length > 0 ? entity.FullTextCatalog : config_1.dbDatabase + '_FullTextCatalog';
299
+ if (entity.FullTextCatalogGenerated) {
300
+ // this situation means we have a generated catalog and the user has provided a name specific to THIS entity
301
+ sql += ` -- CREATE THE FULL TEXT CATALOG FOR THE ENTITY, IF NOT ALREADY CREATED
302
+ IF NOT EXISTS (
303
+ SELECT *
304
+ FROM sys.fulltext_catalogs
305
+ WHERE name = '${catalogName}'
306
+ )
307
+ CREATE FULLTEXT CATALOG ${catalogName};
308
+ GO
309
+ `;
310
+ }
311
+ if (entity.FullTextIndexGenerated) {
312
+ const fullTextFields = entity.Fields.filter(f => f.FullTextSearchEnabled).map(f => `${f.Name} LANGUAGE 'English'`).join(', ');
313
+ if (fullTextFields.length === 0)
314
+ throw new Error(`FullTextIndexGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
315
+ // drop and recreate the full text index
316
+ const entity_pk_name = await getEntityPrimaryKeyIndexName(ds, entity);
317
+ sql += ` -- DROP AND RECREATE THE FULL TEXT INDEX
318
+ IF EXISTS (
319
+ SELECT *
320
+ FROM sys.fulltext_indexes
321
+ WHERE object_id = OBJECT_ID('${entity.SchemaName}.${entity.BaseTable}')
322
+ )
323
+ BEGIN
324
+ DROP FULLTEXT INDEX ON ${entity.SchemaName}.${entity.BaseTable};
325
+ END
326
+ GO
327
+
328
+ IF NOT EXISTS (
329
+ SELECT *
330
+ FROM sys.fulltext_indexes
331
+ WHERE object_id = OBJECT_ID('${entity.SchemaName}.${entity.BaseTable}')
332
+ )
333
+ BEGIN
334
+ CREATE FULLTEXT INDEX ON ${entity.SchemaName}.${entity.BaseTable}
335
+ (
336
+ ${fullTextFields}
337
+ )
338
+ KEY INDEX ${entity_pk_name}
339
+ ON ${catalogName};
340
+ END
341
+ GO
342
+ `;
343
+ }
344
+ const functionName = entity.FullTextSearchFunction && entity.FullTextSearchFunction.length > 0 ? entity.FullTextSearchFunction : `fnSearch${entity.CodeName}`;
345
+ if (entity.FullTextSearchFunctionGenerated) {
346
+ const fullTextFieldsSimple = entity.Fields.filter(f => f.FullTextSearchEnabled).map(f => f.Name).join(',');
347
+ if (fullTextFieldsSimple.length === 0)
348
+ throw new Error(`FullTextSearchFunctionGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
349
+ if (!entity.FullTextSearchFunction || entity.FullTextSearchFunction.length === 0) {
350
+ // update this in the DB
351
+ const md = new core_1.Metadata();
352
+ const u = sqlserver_dataprovider_1.UserCache.Instance.Users[0];
353
+ if (!u)
354
+ throw new Error('Could not find the first user in the cache, cant generate the full text search function without a user');
355
+ const e = await md.GetEntityObject('Entities', u);
356
+ await e.Load(entity.ID);
357
+ e.Set('FullTextSearchFunction', functionName);
358
+ if (!await e.Save())
359
+ throw new Error(`Could not update the FullTextSearchFunction for entity ${entity.Name}`);
360
+ }
361
+ // drop and recreate the full text search function
362
+ sql += ` -- DROP AND RECREATE THE FULL TEXT SEARCH FUNCTION
363
+ -- Create an inline table-valued function to perform full-text search
364
+ -- Drop the function if it already exists
365
+ IF OBJECT_ID('${entity.SchemaName}.${functionName}', 'IF') IS NOT NULL
366
+ DROP FUNCTION ${entity.SchemaName}.${functionName};
367
+ GO
368
+ CREATE FUNCTION ${entity.SchemaName}.${functionName} (@searchTerm NVARCHAR(255))
369
+ RETURNS TABLE
370
+ AS
371
+ RETURN (
372
+ SELECT ID
373
+ FROM ${entity.SchemaName}.${entity.BaseTable}
374
+ WHERE CONTAINS((${fullTextFieldsSimple}), @searchTerm)
375
+ )
376
+ GO
377
+ `;
378
+ sql += generateFullTextSearchFunctionPermissions(entity, functionName) + '\n\nGO\n';
379
+ }
380
+ return { sql, functionName };
381
+ }
382
+ async function getEntityPrimaryKeyIndexName(ds, entity) {
383
+ const sSQL = ` SELECT
384
+ i.name AS IndexName
385
+ FROM
386
+ sys.indexes i
387
+ INNER JOIN
388
+ sys.objects o ON i.object_id = o.object_id
389
+ INNER JOIN
390
+ sys.key_constraints kc ON i.object_id = kc.parent_object_id AND
391
+ i.index_id = kc.unique_index_id
392
+ WHERE
393
+ o.name = '${entity.BaseTable}' AND
394
+ o.schema_id = SCHEMA_ID('${entity.SchemaName}') AND
395
+ kc.type = 'PK';
396
+ `;
397
+ const result = await ds.query(sSQL);
398
+ if (result && result.length > 0)
399
+ return result[0].IndexName;
400
+ else
401
+ throw new Error(`Could not find primary key index for entity ${entity.Name}`);
402
+ }
403
+ function generateAllEntitiesSQLFileHeader() {
404
+ return `-----------------------------------------------------------------
405
+ -- SQL Code Generation for Entities
406
+ -- Generated: ${new Date().toLocaleString()}
407
+ --
408
+ -- This file contains the SQL code for the entities in the database
409
+ -- that are included in the API and have generated SQL elements like views and
410
+ -- stored procedures.
411
+ --
412
+ -- It is generated by the Entity Generator.
413
+ -- It is not intended to be edited by hand.
414
+ -----------------------------------------------------------------
415
+ `;
416
+ }
417
+ exports.generateAllEntitiesSQLFileHeader = generateAllEntitiesSQLFileHeader;
418
+ function generateSingleEntitySQLFileHeader(entity, itemName) {
419
+ return `-----------------------------------------------------------------
420
+ -- SQL Code Generation
421
+ -- Entity: ${entity.Name}
422
+ -- Item: ${itemName}
423
+ -- Generated: ${new Date().toLocaleString()}
424
+ --
425
+ -- This was generated by the Entity Generator.
426
+ -- This file should NOT be edited by hand.
427
+ -----------------------------------------------------------------
428
+ `;
429
+ }
430
+ exports.generateSingleEntitySQLFileHeader = generateSingleEntitySQLFileHeader;
431
+ async function generateBaseView(ds, entity, entities) {
432
+ const viewName = entity.BaseView ? entity.BaseView : `vw${entity.CodeName}`;
433
+ const baseTableFirstChar = entity.BaseTable.charAt(0).toLowerCase();
434
+ const relatedFieldsString = await generateBaseViewRelatedFieldsString(ds, entity.Fields, entities);
435
+ const relatedFieldsJoinString = generateBaseViewJoins(entity.Fields);
436
+ const permissions = generateViewPermissions(entity);
437
+ return `
438
+ ------------------------------------------------------------
439
+ ----- BASE VIEW FOR ENTITY: ${entity.Name}
440
+ ----- BASE TABLE: ${entity.BaseTable}
441
+ ------------------------------------------------------------
442
+ IF OBJECT_ID('dbo.${viewName}', 'V') IS NOT NULL
443
+ DROP VIEW [dbo].[${viewName}]
444
+ GO
445
+
446
+ CREATE VIEW [dbo].[${viewName}]
447
+ AS
448
+ SELECT
449
+ ${baseTableFirstChar}.*${relatedFieldsString.length > 0 ? ',' : ''}${relatedFieldsString}
450
+ FROM
451
+ [${entity.SchemaName}].[${entity.BaseTable}] AS ${baseTableFirstChar}${relatedFieldsJoinString ? '\n' + relatedFieldsJoinString : ''}
452
+ GO${permissions}
453
+ `;
454
+ }
455
+ function generateViewPermissions(entity) {
456
+ let sOutput = '';
457
+ for (let i = 0; i < entity.Permissions.length; i++) {
458
+ const ep = entity.Permissions[i];
459
+ sOutput += (sOutput == '' ? `GRANT SELECT ON [dbo].[${entity.BaseView}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
460
+ }
461
+ return (sOutput == '' ? '' : '\n') + sOutput;
462
+ }
463
+ function generateBaseViewJoins(entityFields) {
464
+ let sOutput = '';
465
+ for (let i = 0; i < entityFields.length; i++) {
466
+ const ef = entityFields[i];
467
+ if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView && ef._RelatedEntityTableAlias) {
468
+ sOutput += sOutput == '' ? '' : '\n';
469
+ sOutput += `${ef.AllowsNull ? 'LEFT OUTER' : 'INNER'} JOIN\n ${ef._RelatedEntityNameFieldIsVirtual ? '' : '[' + ef.RelatedEntitySchemaName + '].'}[${ef._RelatedEntityNameFieldIsVirtual ? ef.RelatedEntityBaseView : ef.RelatedEntityBaseTable}] AS ${ef._RelatedEntityTableAlias}\n ON\n [${ef.Entity.charAt(0).toLowerCase()}].[${ef.Name}] = ${ef._RelatedEntityTableAlias}.[${ef.RelatedEntityFieldName}]`;
470
+ }
471
+ }
472
+ return sOutput;
473
+ }
474
+ async function generateBaseViewRelatedFieldsString(ds, entityFields, entities) {
475
+ let sOutput = '';
476
+ let fieldCount = 0;
477
+ for (let i = 0; i < entityFields.length; i++) {
478
+ const ef = entityFields[i];
479
+ if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView) {
480
+ const { nameField, nameFieldIsVirtual } = getIsNameFieldForSingleEntity(ef.RelatedEntity, entities);
481
+ if (nameField !== '') {
482
+ // only add to the output, if we found a name field for the related entity.
483
+ ef._RelatedEntityTableAlias = ef.RelatedEntityClassName + '_' + ef.Name;
484
+ ef._RelatedEntityNameFieldIsVirtual = nameFieldIsVirtual;
485
+ ef._RelatedEntityNameFieldMap = stripID(ef.Name);
486
+ sOutput += `${fieldCount == 0 ? '' : ','}\n ${ef._RelatedEntityTableAlias}.[${nameField}] AS [${ef._RelatedEntityNameFieldMap}]`;
487
+ // check to see if the database already knows about the RelatedEntityNameFieldMap or not
488
+ if (ef.RelatedEntityNameFieldMap === null ||
489
+ ef.RelatedEntityNameFieldMap === undefined ||
490
+ ef.RelatedEntityNameFieldMap.trim().length === 0) {
491
+ // the database doesn't yet know about this RelatedEntityNameFieldMap, so we need to update it
492
+ // first update the actul field in the metadata object so it can be used from this point forward
493
+ // and it also reflects what the DB will hold
494
+ ef.RelatedEntityNameFieldMap = ef._RelatedEntityNameFieldMap;
495
+ // then update the database itself
496
+ await (0, manageMetadata_1.updateEntityFieldRelatedEntityNameFieldMap)(ds, ef.ID, ef.RelatedEntityNameFieldMap);
497
+ }
498
+ fieldCount++;
499
+ }
500
+ }
501
+ }
502
+ return sOutput;
503
+ }
504
+ function getIsNameFieldForSingleEntity(entityName, entities) {
505
+ const e = entities.find(e => e.Name === entityName);
506
+ if (e) {
507
+ const ef = e.NameField;
508
+ if (e.NameField)
509
+ return { nameField: ef.Name, nameFieldIsVirtual: ef.IsVirtual };
510
+ }
511
+ else
512
+ console.log(`ERROR: Could not find entity with name ${entityName}`);
513
+ return { nameField: '', nameFieldIsVirtual: false };
514
+ }
515
+ function stripID(name) {
516
+ if (name.endsWith('ID'))
517
+ return name.substring(0, name.length - 2);
518
+ else
519
+ return name;
520
+ }
521
+ var SPType;
522
+ (function (SPType) {
523
+ SPType[SPType["Create"] = 0] = "Create";
524
+ SPType[SPType["Update"] = 1] = "Update";
525
+ SPType[SPType["Delete"] = 2] = "Delete";
526
+ })(SPType || (SPType = {}));
527
+ function generateSPPermissions(entity, spName, type) {
528
+ let sOutput = '';
529
+ for (let i = 0; i < entity.Permissions.length; i++) {
530
+ const ep = entity.Permissions[i];
531
+ if ((type == SPType.Create && ep.CanCreate) ||
532
+ (type == SPType.Update && ep.CanUpdate) ||
533
+ (type == SPType.Delete && ep.CanDelete))
534
+ sOutput += (sOutput == '' ? `GRANT EXECUTE ON [${spName}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
535
+ }
536
+ return (sOutput == '' ? '' : '\n') + sOutput;
537
+ }
538
+ function generateFullTextSearchFunctionPermissions(entity, functionName) {
539
+ let sOutput = '';
540
+ for (let i = 0; i < entity.Permissions.length; i++) {
541
+ const ep = entity.Permissions[i];
542
+ if (ep.CanRead)
543
+ sOutput += (sOutput == '' ? `GRANT SELECT ON [${entity.SchemaName}].[${functionName}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
544
+ }
545
+ return (sOutput == '' ? '' : '\n') + sOutput;
546
+ }
547
+ function generateSPCreate(entity) {
548
+ const spName = entity.spCreate ? entity.spCreate : `spCreate${entity.ClassName}`;
549
+ const efString = createEntityFieldsParamString(entity.Fields, false);
550
+ const permissions = generateSPPermissions(entity, spName, SPType.Create);
551
+ return `
552
+ ------------------------------------------------------------
553
+ ----- CREATE PROCEDURE FOR ${entity.BaseTable}
554
+ ------------------------------------------------------------
555
+ IF OBJECT_ID('dbo.${spName}', 'P') IS NOT NULL
556
+ DROP PROCEDURE [dbo].[${spName}]
557
+ GO
558
+
559
+ CREATE PROCEDURE [dbo].[${spName}]
560
+ ${efString}
561
+ AS
562
+ BEGIN
563
+ SET NOCOUNT ON;
564
+ INSERT INTO
565
+ [${entity.SchemaName}].[${entity.BaseTable}]
566
+ (
567
+ ${createEntityFieldsInsertString(entity.Fields, '')}
568
+ )
569
+ VALUES
570
+ (
571
+ ${createEntityFieldsInsertString(entity.Fields, '@')}
572
+ )
573
+
574
+ -- return the new record from the base view, which might have some calculated fields
575
+ SELECT * FROM ${entity.BaseView} WHERE ID = SCOPE_IDENTITY()
576
+ END
577
+ GO${permissions}
578
+ `;
579
+ }
580
+ function generateSPUpdate(entity) {
581
+ const spName = entity.spDelete ? entity.spDelete : `spUpdate${entity.ClassName}`;
582
+ const efParamString = createEntityFieldsParamString(entity.Fields, true);
583
+ const permissions = generateSPPermissions(entity, spName, SPType.Update);
584
+ return `
585
+ ------------------------------------------------------------
586
+ ----- UPDATE PROCEDURE FOR ${entity.BaseTable}
587
+ ------------------------------------------------------------
588
+ IF OBJECT_ID('dbo.${spName}', 'P') IS NOT NULL
589
+ DROP PROCEDURE [dbo].[${spName}]
590
+ GO
591
+
592
+ CREATE PROCEDURE [dbo].[${spName}]
593
+ ${efParamString}
594
+ AS
595
+ BEGIN
596
+ SET NOCOUNT ON;
597
+ UPDATE
598
+ [${entity.SchemaName}].[${entity.BaseTable}]
599
+ SET
600
+ ${createEntityFieldsUpdateString(entity.Fields)}
601
+ WHERE
602
+ [ID] = @ID
603
+
604
+ -- return the updated record so the caller can see the updated values and any calculated fields
605
+ SELECT * FROM ${entity.BaseView} WHERE ID = @ID
606
+ END
607
+ GO${permissions}
608
+ `;
609
+ }
610
+ function createEntityFieldsParamString(entityFields, includeID) {
611
+ let sOutput = '', isFirst = true;
612
+ for (let i = 0; i < entityFields.length; ++i) {
613
+ const ef = entityFields[i];
614
+ if ((ef.AllowUpdateAPI || (ef.Name == 'ID' && includeID)) &&
615
+ !ef.IsVirtual &&
616
+ (ef.Name !== 'ID' || includeID) &&
617
+ ef.Name.toLowerCase().trim() !== 'updatedat' &&
618
+ ef.Name.toLowerCase().trim() !== 'createdat' &&
619
+ ef.Type.toLowerCase().trim() !== 'uniqueidentifier') {
620
+ if (!isFirst)
621
+ sOutput += ',\n ';
622
+ else
623
+ isFirst = false;
624
+ sOutput += `@${ef.Name} ${ef.SQLFullType}`;
625
+ }
626
+ }
627
+ return sOutput;
628
+ }
629
+ function createEntityFieldsInsertString(entityFields, prefix) {
630
+ let sOutput = '', isFirst = true;
631
+ for (let i = 0; i < entityFields.length; ++i) {
632
+ const ef = entityFields[i];
633
+ if (ef.Name !== 'ID' &&
634
+ ef.IsVirtual === false &&
635
+ ef.AllowUpdateAPI &&
636
+ ef.AutoIncrement === false &&
637
+ ef.Type.trim().toLowerCase() !== 'uniqueidentifier') {
638
+ if (!isFirst)
639
+ sOutput += ',\n ';
640
+ else
641
+ isFirst = false;
642
+ if (prefix !== '' && (ef.Name.toLowerCase().trim() === 'updatedat' ||
643
+ ef.Name.toLowerCase().trim() === 'createdat'))
644
+ sOutput += `GETDATE()`;
645
+ else {
646
+ let sVal = prefix + ef.Name;
647
+ if (!prefix || prefix.length === 0)
648
+ sVal = '[' + sVal + ']'; // always put field names in brackets so that if reserved words are being used for field names in a table like "USER" and so on, they still work
649
+ sOutput += sVal;
650
+ }
651
+ }
652
+ }
653
+ return sOutput;
654
+ }
655
+ function createEntityFieldsUpdateString(entityFields) {
656
+ let sOutput = '', isFirst = true;
657
+ for (let i = 0; i < entityFields.length; ++i) {
658
+ const ef = entityFields[i];
659
+ if (ef.Name !== 'ID' &&
660
+ ef.IsVirtual === false &&
661
+ ef.AllowUpdateAPI &&
662
+ ef.AutoIncrement === false &&
663
+ ef.Name.toLowerCase().trim() !== 'createdat' &&
664
+ ef.Name.toLowerCase().trim() !== 'updatedat' &&
665
+ ef.Type.toLowerCase().trim() !== 'uniqueidentifier') {
666
+ if (!isFirst)
667
+ sOutput += ',\n ';
668
+ else
669
+ isFirst = false;
670
+ sOutput += `[${ef.Name}] = @${ef.Name}`;
671
+ }
672
+ else if (ef.Name.trim().toLowerCase() === 'updatedat') {
673
+ if (!isFirst)
674
+ sOutput += ',\n ';
675
+ else
676
+ isFirst = false;
677
+ sOutput += `${ef.Name} = GETDATE()`;
678
+ }
679
+ }
680
+ return sOutput;
681
+ }
682
+ function generateSPDelete(entity) {
683
+ const spName = entity.spDelete ? entity.spDelete : `spDelete${entity.ClassName}`;
684
+ const sCascadeDeletes = generateCascadeDeletes(entity);
685
+ const permissions = generateSPPermissions(entity, spName, SPType.Delete);
686
+ return `
687
+ ------------------------------------------------------------
688
+ ----- DELETE PROCEDURE FOR ${entity.BaseTable}
689
+ ------------------------------------------------------------
690
+ IF OBJECT_ID('dbo.${spName}', 'P') IS NOT NULL
691
+ DROP PROCEDURE [dbo].[${spName}]
692
+ GO
693
+
694
+ CREATE PROCEDURE [dbo].[${spName}]
695
+ @ID INT
696
+ AS
697
+ BEGIN
698
+ SET NOCOUNT ON;${sCascadeDeletes}
699
+ DELETE FROM
700
+ [${entity.SchemaName}].[${entity.BaseTable}]
701
+ WHERE
702
+ [ID] = @ID
703
+ SELECT @ID AS ID -- Return the ID to indicate we successfully deleted the record
704
+ END
705
+ GO${permissions}
706
+ `;
707
+ }
708
+ function generateCascadeDeletes(entity) {
709
+ let sOutput = '';
710
+ if (entity.CascadeDeletes) {
711
+ const md = new core_1.Metadata();
712
+ // we need to find all of the fields in other entities that are foreign keys to this entity
713
+ // and generate DELETE statements for those tables
714
+ for (let i = 0; i < md.Entities.length; ++i) {
715
+ const e = md.Entities[i];
716
+ for (let j = 0; j < e.Fields.length; ++j) {
717
+ const ef = e.Fields[j];
718
+ if (ef.RelatedEntityID === entity.ID && ef.IsVirtual === false) {
719
+ let sql = '';
720
+ if (ef.AllowsNull === false) {
721
+ // we have a non-virtual field that is a foreign key to this entity
722
+ // and only those that are non-null. If they allow null we want to UPDATE those rows to be null
723
+ // so we need to generate a DELETE statement for that table
724
+ sql = `
725
+ -- Cascade delete from ${e.BaseTable}
726
+ DELETE FROM
727
+ [${e.SchemaName}].[${e.BaseTable}]
728
+ WHERE
729
+ [${ef.Name}] = @ID`;
730
+ }
731
+ else {
732
+ // we have a non-virtual field that is a foreign key to this entity
733
+ // and this field ALLOWS nulls, which means we don't delete the rows, we just update them to be null
734
+ // so they don't have an orphaned foreign key
735
+ sql = `
736
+ -- Cascade update on ${e.BaseTable} - set FK to null before deleting rows in ${entity.BaseTable}
737
+ UPDATE
738
+ [${e.SchemaName}].[${e.BaseTable}]
739
+ SET
740
+ [${ef.Name}] = NULL
741
+ WHERE
742
+ [${ef.Name}] = @ID`;
743
+ }
744
+ if (sOutput !== '')
745
+ sOutput += '\n ';
746
+ sOutput += sql;
747
+ }
748
+ }
749
+ }
750
+ }
751
+ return sOutput === '' ? '' : `${sOutput}\n `;
752
+ }
753
+ //# sourceMappingURL=sql_codegen.js.map