@memberjunction/codegen-lib 2.112.0 → 2.113.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/Angular/angular-codegen.d.ts +1 -1
- package/dist/Angular/angular-codegen.d.ts.map +1 -1
- package/dist/Angular/angular-codegen.js +80 -119
- package/dist/Angular/angular-codegen.js.map +1 -1
- package/dist/Angular/join-grid-related-entity-component.d.ts +1 -1
- package/dist/Angular/join-grid-related-entity-component.d.ts.map +1 -1
- package/dist/Angular/join-grid-related-entity-component.js +12 -12
- package/dist/Angular/join-grid-related-entity-component.js.map +1 -1
- package/dist/Angular/related-entity-components.d.ts +1 -1
- package/dist/Angular/related-entity-components.d.ts.map +1 -1
- package/dist/Angular/related-entity-components.js +14 -14
- package/dist/Angular/related-entity-components.js.map +1 -1
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +3 -3
- package/dist/Config/config.js.map +1 -1
- package/dist/Database/dbSchema.d.ts +1 -1
- package/dist/Database/dbSchema.d.ts.map +1 -1
- package/dist/Database/dbSchema.js +10 -14
- package/dist/Database/dbSchema.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +3 -3
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +139 -167
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/sql.d.ts +1 -1
- package/dist/Database/sql.d.ts.map +1 -1
- package/dist/Database/sql.js +17 -17
- package/dist/Database/sql.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +2 -2
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +119 -128
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/action_subclasses_codegen.js +17 -17
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +2 -2
- package/dist/Misc/advanced_generation.d.ts.map +1 -1
- package/dist/Misc/advanced_generation.js +29 -29
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/createNewUser.d.ts +1 -1
- package/dist/Misc/createNewUser.d.ts.map +1 -1
- package/dist/Misc/createNewUser.js +30 -30
- package/dist/Misc/createNewUser.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +37 -56
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/graphql_server_codegen.d.ts +1 -1
- package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +17 -15
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/dist/Misc/status_logging.d.ts +2 -2
- package/dist/Misc/status_logging.d.ts.map +1 -1
- package/dist/Misc/status_logging.js +22 -22
- package/dist/Misc/status_logging.js.map +1 -1
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +4 -4
- package/dist/runCodeGen.js.map +1 -1
- package/package.json +10 -9
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.SQLCodeGenBase = exports.SPType = void 0;
|
|
30
|
-
const
|
|
30
|
+
const core_1 = require("@memberjunction/core");
|
|
31
31
|
const status_logging_1 = require("../Misc/status_logging");
|
|
32
32
|
const fs = __importStar(require("fs"));
|
|
33
33
|
const path_1 = __importDefault(require("path"));
|
|
@@ -36,7 +36,7 @@ const config_1 = require("../Config/config");
|
|
|
36
36
|
const manage_metadata_1 = require("./manage-metadata");
|
|
37
37
|
const sqlserver_dataprovider_1 = require("@memberjunction/sqlserver-dataprovider");
|
|
38
38
|
const util_1 = require("../Misc/util");
|
|
39
|
-
const
|
|
39
|
+
const global_1 = require("@memberjunction/global");
|
|
40
40
|
const sql_logging_1 = require("../Misc/sql_logging");
|
|
41
41
|
const temp_batch_file_1 = require("../Misc/temp_batch_file");
|
|
42
42
|
exports.SPType = {
|
|
@@ -50,7 +50,7 @@ exports.SPType = {
|
|
|
50
50
|
* of that abstract base class and other databases will be sub-classes of the abstract base class as well.
|
|
51
51
|
*/
|
|
52
52
|
class SQLCodeGenBase {
|
|
53
|
-
_sqlUtilityObject =
|
|
53
|
+
_sqlUtilityObject = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(sql_1.SQLUtilityBase);
|
|
54
54
|
get SQLUtilityObject() {
|
|
55
55
|
return this._sqlUtilityObject;
|
|
56
56
|
}
|
|
@@ -105,7 +105,7 @@ class SQLCodeGenBase {
|
|
|
105
105
|
// objects like spCreate for a given entity might reference the vw for that entity
|
|
106
106
|
(0, status_logging_1.startSpinner)('Running custom SQL scripts...');
|
|
107
107
|
const startTime = new Date();
|
|
108
|
-
if (!
|
|
108
|
+
if (!await this.runCustomSQLScripts(pool, 'before-sql')) {
|
|
109
109
|
(0, status_logging_1.failSpinner)('Failed to run custom SQL scripts');
|
|
110
110
|
return false;
|
|
111
111
|
}
|
|
@@ -113,12 +113,12 @@ class SQLCodeGenBase {
|
|
|
113
113
|
// ALWAYS use the first filter where we only include entities that have IncludeInAPI = 1
|
|
114
114
|
// Sort entities by name for deterministic processing order (workaround until MJCore fix in issue #1436)
|
|
115
115
|
const sortedEntities = entities.sort((a, b) => a.Name.localeCompare(b.Name));
|
|
116
|
-
const baselineEntities = sortedEntities.filter(
|
|
117
|
-
const includedEntities = baselineEntities.filter(
|
|
118
|
-
const excludedEntities = baselineEntities.filter(
|
|
116
|
+
const baselineEntities = sortedEntities.filter(e => e.IncludeInAPI);
|
|
117
|
+
const includedEntities = baselineEntities.filter(e => config_1.configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) === undefined); //only include entities that are NOT in the excludeSchemas list
|
|
118
|
+
const excludedEntities = baselineEntities.filter(e => config_1.configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) !== undefined); //only include entities that ARE in the excludeSchemas list in this array
|
|
119
119
|
// Initialize temp batch files for each schema
|
|
120
120
|
// These will be populated as SQL is generated and will be used for actual execution
|
|
121
|
-
const schemas = Array.from(new Set(baselineEntities.map(
|
|
121
|
+
const schemas = Array.from(new Set(baselineEntities.map(e => e.SchemaName)));
|
|
122
122
|
temp_batch_file_1.TempBatchFile.initialize(directory, schemas);
|
|
123
123
|
// STEP 1.5 - Check for cascade delete dependencies that require regeneration
|
|
124
124
|
(0, status_logging_1.startSpinner)('Analyzing cascade delete dependencies...');
|
|
@@ -132,10 +132,10 @@ class SQLCodeGenBase {
|
|
|
132
132
|
(0, status_logging_1.startSpinner)(`Generating SQL for ${includedEntities.length} entities...`);
|
|
133
133
|
const step2StartTime = new Date();
|
|
134
134
|
// First, separate entities that need cascade delete regeneration from others
|
|
135
|
-
const entitiesWithoutCascadeRegeneration = includedEntities.filter(
|
|
135
|
+
const entitiesWithoutCascadeRegeneration = includedEntities.filter(e => !this.entitiesNeedingDeleteSPRegeneration.has(e.ID));
|
|
136
136
|
const entitiesForCascadeRegeneration = this.orderedEntitiesForDeleteSPRegeneration
|
|
137
|
-
.map(
|
|
138
|
-
.filter(
|
|
137
|
+
.map(id => includedEntities.find(e => e.ID === id))
|
|
138
|
+
.filter(e => e !== undefined);
|
|
139
139
|
// Generate SQL for entities that don't need cascade delete regeneration
|
|
140
140
|
const genResult = await this.generateAndExecuteEntitySQLToSeparateFiles({
|
|
141
141
|
pool,
|
|
@@ -145,7 +145,7 @@ class SQLCodeGenBase {
|
|
|
145
145
|
skipExecution: true, // skip execution because we execute it all in a giant batch below
|
|
146
146
|
writeFiles: true,
|
|
147
147
|
batchSize: 5,
|
|
148
|
-
enableSQLLoggingForNewOrModifiedEntities: true
|
|
148
|
+
enableSQLLoggingForNewOrModifiedEntities: true
|
|
149
149
|
}); // enable sql logging for NEW entities....
|
|
150
150
|
if (!genResult.Success) {
|
|
151
151
|
(0, status_logging_1.failSpinner)('Failed to generate entity SQL files');
|
|
@@ -162,7 +162,7 @@ class SQLCodeGenBase {
|
|
|
162
162
|
skipExecution: true,
|
|
163
163
|
writeFiles: true,
|
|
164
164
|
batchSize: 1, // Process sequentially to maintain dependency order
|
|
165
|
-
enableSQLLoggingForNewOrModifiedEntities: true
|
|
165
|
+
enableSQLLoggingForNewOrModifiedEntities: true
|
|
166
166
|
});
|
|
167
167
|
if (!cascadeGenResult.Success) {
|
|
168
168
|
(0, status_logging_1.failSpinner)('Failed to regenerate cascade delete SPs');
|
|
@@ -180,7 +180,7 @@ class SQLCodeGenBase {
|
|
|
180
180
|
skipExecution: true, // skip execution because we execute it all in a giant batch below
|
|
181
181
|
batchSize: 5,
|
|
182
182
|
writeFiles: true,
|
|
183
|
-
enableSQLLoggingForNewOrModifiedEntities: false /*don't log this stuff, it is just permissions for excluded entities
|
|
183
|
+
enableSQLLoggingForNewOrModifiedEntities: false /*don't log this stuff, it is just permissions for excluded entities*/
|
|
184
184
|
});
|
|
185
185
|
if (!genResult2.Success) {
|
|
186
186
|
(0, status_logging_1.failSpinner)('Failed to generate permissions for excluded entities');
|
|
@@ -216,11 +216,11 @@ class SQLCodeGenBase {
|
|
|
216
216
|
}
|
|
217
217
|
const step2eEndTime = new Date();
|
|
218
218
|
(0, status_logging_1.succeedSpinner)(`SQL execution completed (${(step2eEndTime.getTime() - step2eStartTime.getTime()) / 1000}s)`);
|
|
219
|
-
const manageMD =
|
|
219
|
+
const manageMD = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(manage_metadata_1.ManageMetadataBase);
|
|
220
220
|
// 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
|
|
221
221
|
// we CAN skip the entity field values part because that wouldn't change from the first time we ran it
|
|
222
222
|
(0, status_logging_1.startSpinner)('Managing entity fields metadata...');
|
|
223
|
-
if (!
|
|
223
|
+
if (!await manageMD.manageEntityFields(pool, config_1.configInfo.excludeSchemas, true, true, currentUser)) {
|
|
224
224
|
(0, status_logging_1.failSpinner)('Failed to manage entity fields');
|
|
225
225
|
return false;
|
|
226
226
|
}
|
|
@@ -229,7 +229,7 @@ class SQLCodeGenBase {
|
|
|
229
229
|
// STEP 4- Apply permissions, executing all .permissions files
|
|
230
230
|
(0, status_logging_1.startSpinner)('Applying permissions...');
|
|
231
231
|
const step4StartTime = new Date();
|
|
232
|
-
if (!
|
|
232
|
+
if (!await this.applyPermissions(pool, directory, baselineEntities)) {
|
|
233
233
|
(0, status_logging_1.failSpinner)('Failed to apply permissions');
|
|
234
234
|
return false;
|
|
235
235
|
}
|
|
@@ -237,14 +237,14 @@ class SQLCodeGenBase {
|
|
|
237
237
|
// STEP 5 - execute any custom SQL scripts that should run afterwards
|
|
238
238
|
(0, status_logging_1.startSpinner)('Running post-generation SQL scripts...');
|
|
239
239
|
const step5StartTime = new Date();
|
|
240
|
-
if (!
|
|
240
|
+
if (!await this.runCustomSQLScripts(pool, 'after-sql')) {
|
|
241
241
|
(0, status_logging_1.failSpinner)('Failed to run post-generation SQL scripts');
|
|
242
242
|
return false;
|
|
243
243
|
}
|
|
244
244
|
(0, status_logging_1.succeedSpinner)(`Post-generation scripts completed (${(new Date().getTime() - step5StartTime.getTime()) / 1000}s)`);
|
|
245
|
-
(0, status_logging_1.succeedSpinner)(`CodeGen completed successfully (${(new Date().getTime() - startTime.getTime()) / 1000}s total)`);
|
|
245
|
+
(0, status_logging_1.succeedSpinner)(`CodeGen completed successfully (${((new Date().getTime() - startTime.getTime()) / 1000)}s total)`);
|
|
246
246
|
// now - we need to tell our metadata object to refresh itself
|
|
247
|
-
const md = new
|
|
247
|
+
const md = new core_1.Metadata();
|
|
248
248
|
await md.Refresh();
|
|
249
249
|
return true;
|
|
250
250
|
}
|
|
@@ -262,7 +262,7 @@ class SQLCodeGenBase {
|
|
|
262
262
|
if (scripts) {
|
|
263
263
|
for (let i = 0; i < scripts.length; ++i) {
|
|
264
264
|
const s = scripts[i];
|
|
265
|
-
if (!
|
|
265
|
+
if (!await this.SQLUtilityObject.executeSQLFile(s.scriptFile)) {
|
|
266
266
|
(0, status_logging_1.logError)(`Error executing custom '${when}' SQL script ${s.scriptFile}`);
|
|
267
267
|
bSuccess = false; // keep going if we have more scripts, but make sure we return false
|
|
268
268
|
}
|
|
@@ -335,7 +335,7 @@ class SQLCodeGenBase {
|
|
|
335
335
|
for (let i = 0; i < totalEntities; i += options.batchSize) {
|
|
336
336
|
const batch = options.entities.slice(i, i + options.batchSize);
|
|
337
337
|
const promises = batch.map(async (e) => {
|
|
338
|
-
const pkeyField = e.Fields.find(
|
|
338
|
+
const pkeyField = e.Fields.find(f => f.IsPrimaryKey);
|
|
339
339
|
if (!pkeyField) {
|
|
340
340
|
(0, status_logging_1.logError)(`SKIPPING ENTITY: Entity ${e.Name}, because it does not have a primary key field defined. A table must have a primary key defined to quality to be a MemberJunction entity`);
|
|
341
341
|
return { Success: false, Files: [] };
|
|
@@ -347,11 +347,11 @@ class SQLCodeGenBase {
|
|
|
347
347
|
onlyPermissions: options.onlyPermissions,
|
|
348
348
|
writeFiles: options.writeFiles,
|
|
349
349
|
skipExecution: options.skipExecution,
|
|
350
|
-
enableSQLLoggingForNewOrModifiedEntities: options.enableSQLLoggingForNewOrModifiedEntities
|
|
350
|
+
enableSQLLoggingForNewOrModifiedEntities: options.enableSQLLoggingForNewOrModifiedEntities
|
|
351
351
|
});
|
|
352
352
|
});
|
|
353
353
|
const results = await Promise.all(promises);
|
|
354
|
-
results.forEach(
|
|
354
|
+
results.forEach(r => {
|
|
355
355
|
if (!r.Success)
|
|
356
356
|
bFail = true; // keep going, but will return false at the end
|
|
357
357
|
files.push(...r.Files); // add the files to the main files array
|
|
@@ -367,7 +367,7 @@ class SQLCodeGenBase {
|
|
|
367
367
|
deleteGeneratedEntityFiles(directory, entities) {
|
|
368
368
|
try {
|
|
369
369
|
// for the schemas associated with the specified entities, clean out all the generated files
|
|
370
|
-
const schemaNames = entities.map(
|
|
370
|
+
const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index);
|
|
371
371
|
for (const s of schemaNames) {
|
|
372
372
|
const fullPath = path_1.default.join(directory, s);
|
|
373
373
|
// now, within each schema directory, clean out all the generated files
|
|
@@ -381,7 +381,7 @@ class SQLCodeGenBase {
|
|
|
381
381
|
//logMessage(` Directory '${fullPath}' does not exist so no need to delete previously generated SQL`, 'Info');
|
|
382
382
|
}
|
|
383
383
|
if (stats?.isDirectory()) {
|
|
384
|
-
const files = fs.readdirSync(fullPath).filter(
|
|
384
|
+
const files = fs.readdirSync(fullPath).filter(f => f.endsWith('.generated.sql') || f.endsWith('.permissions.generated.sql'));
|
|
385
385
|
for (const f of files) {
|
|
386
386
|
const filePath = path_1.default.join(fullPath, f);
|
|
387
387
|
fs.unlinkSync(filePath);
|
|
@@ -396,7 +396,7 @@ class SQLCodeGenBase {
|
|
|
396
396
|
createCombinedEntitySQLFiles(directory, entities) {
|
|
397
397
|
// first, get a disinct list of schemanames from the entities
|
|
398
398
|
const files = [];
|
|
399
|
-
const schemaNames = entities.map(
|
|
399
|
+
const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index);
|
|
400
400
|
for (const s of schemaNames) {
|
|
401
401
|
// generate the all-entities.sql file and all-entities.permissions.sql file in each schema folder
|
|
402
402
|
const fullPath = path_1.default.join(directory, s);
|
|
@@ -414,8 +414,8 @@ class SQLCodeGenBase {
|
|
|
414
414
|
const { sql, permissionsSQL, files } = await this.generateSingleEntitySQLToSeparateFiles(options); // this creates the files and returns a single string with all the SQL we can then execute
|
|
415
415
|
if (!options.skipExecution) {
|
|
416
416
|
return {
|
|
417
|
-
Success: await this.SQLUtilityObject.executeSQLScript(options.pool, sql +
|
|
418
|
-
Files: files
|
|
417
|
+
Success: await this.SQLUtilityObject.executeSQLScript(options.pool, sql + "\n\nGO\n\n" + permissionsSQL, true), // combine the SQL and permissions and execute it,
|
|
418
|
+
Files: files
|
|
419
419
|
};
|
|
420
420
|
}
|
|
421
421
|
else
|
|
@@ -431,23 +431,23 @@ class SQLCodeGenBase {
|
|
|
431
431
|
let shouldLog = false;
|
|
432
432
|
if (logSql) {
|
|
433
433
|
// Check if entity is in new or modified lists
|
|
434
|
-
const isNewOrModified = !!manage_metadata_1.ManageMetadataBase.newEntityList.find(
|
|
435
|
-
!!manage_metadata_1.ManageMetadataBase.modifiedEntityList.find(
|
|
434
|
+
const isNewOrModified = !!manage_metadata_1.ManageMetadataBase.newEntityList.find(e => e === entity.Name) ||
|
|
435
|
+
!!manage_metadata_1.ManageMetadataBase.modifiedEntityList.find(e => e === entity.Name);
|
|
436
436
|
// Check if entity is being regenerated due to cascade dependencies
|
|
437
|
-
const isCascadeDependencyRegeneration = description.toLowerCase().includes('spdelete') &&
|
|
437
|
+
const isCascadeDependencyRegeneration = description.toLowerCase().includes('spdelete') &&
|
|
438
|
+
this.entitiesNeedingDeleteSPRegeneration.has(entity.ID);
|
|
438
439
|
// Check if force regeneration is enabled for relevant SQL types
|
|
439
|
-
const isForceRegeneration = config_1.configInfo.forceRegeneration?.enabled &&
|
|
440
|
-
(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
(
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
description.toLowerCase().includes('spdelete'))));
|
|
440
|
+
const isForceRegeneration = config_1.configInfo.forceRegeneration?.enabled && ((description.toLowerCase().includes('base view') && config_1.configInfo.forceRegeneration.baseViews) ||
|
|
441
|
+
(description.toLowerCase().includes('root id function') && config_1.configInfo.forceRegeneration.baseViews) || // TVFs are part of base view infrastructure
|
|
442
|
+
(description.toLowerCase().includes('spcreate') && config_1.configInfo.forceRegeneration.spCreate) ||
|
|
443
|
+
(description.toLowerCase().includes('spupdate') && config_1.configInfo.forceRegeneration.spUpdate) ||
|
|
444
|
+
(description.toLowerCase().includes('spdelete') && config_1.configInfo.forceRegeneration.spDelete) ||
|
|
445
|
+
(description.toLowerCase().includes('index') && config_1.configInfo.forceRegeneration.indexes) ||
|
|
446
|
+
(description.toLowerCase().includes('full text search') && config_1.configInfo.forceRegeneration.fullTextSearch) ||
|
|
447
|
+
(config_1.configInfo.forceRegeneration.allStoredProcedures &&
|
|
448
|
+
(description.toLowerCase().includes('spcreate') ||
|
|
449
|
+
description.toLowerCase().includes('spupdate') ||
|
|
450
|
+
description.toLowerCase().includes('spdelete'))));
|
|
451
451
|
// Determine if we should log based on entity state and force regeneration settings
|
|
452
452
|
if (isNewOrModified) {
|
|
453
453
|
// Always log new or modified entities
|
|
@@ -458,7 +458,7 @@ class SQLCodeGenBase {
|
|
|
458
458
|
shouldLog = true;
|
|
459
459
|
}
|
|
460
460
|
else if (isForceRegeneration) {
|
|
461
|
-
// For force regeneration, the specific type flags (spCreate, baseViews, etc.)
|
|
461
|
+
// For force regeneration, the specific type flags (spCreate, baseViews, etc.)
|
|
462
462
|
// already filtered this - now we just need to check entity filtering
|
|
463
463
|
if (this.filterEntitiesQualifiedForRegeneration) {
|
|
464
464
|
// Only log if entity is in the qualified list
|
|
@@ -505,7 +505,9 @@ class SQLCodeGenBase {
|
|
|
505
505
|
// BASE VIEW AND RELATED TVFs
|
|
506
506
|
// Only generate if BaseViewGenerated is true (respects custom views where it's false)
|
|
507
507
|
// forceRegeneration.baseViews only forces regeneration of views where BaseViewGenerated=true
|
|
508
|
-
if (!options.onlyPermissions &&
|
|
508
|
+
if (!options.onlyPermissions &&
|
|
509
|
+
options.entity.BaseViewGenerated &&
|
|
510
|
+
!options.entity.VirtualEntity) {
|
|
509
511
|
// ROOT ID FUNCTIONS (TVFs)
|
|
510
512
|
// Generate inline Table Value Functions for recursive foreign keys
|
|
511
513
|
// These must be created BEFORE the view that references them
|
|
@@ -513,7 +515,8 @@ class SQLCodeGenBase {
|
|
|
513
515
|
if (recursiveFKs.length > 0) {
|
|
514
516
|
for (const field of recursiveFKs) {
|
|
515
517
|
const functionName = `fn${options.entity.BaseTable}${field.Name}_GetRootID`;
|
|
516
|
-
const s = this.generateSingleEntitySQLFileHeader(options.entity, functionName) +
|
|
518
|
+
const s = this.generateSingleEntitySQLFileHeader(options.entity, functionName) +
|
|
519
|
+
this.generateRootIDFunction(options.entity, field);
|
|
517
520
|
const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('function', options.entity.SchemaName, functionName, false, true));
|
|
518
521
|
if (options.writeFiles) {
|
|
519
522
|
this.logSQLForNewOrModifiedEntity(options.entity, s, `Root ID Function SQL for ${options.entity.Name}.${field.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
|
|
@@ -525,8 +528,7 @@ class SQLCodeGenBase {
|
|
|
525
528
|
}
|
|
526
529
|
}
|
|
527
530
|
// Generate the base view (which may reference the TVFs created above)
|
|
528
|
-
const s = this.generateSingleEntitySQLFileHeader(options.entity, options.entity.BaseView) +
|
|
529
|
-
(await this.generateBaseView(options.pool, options.entity));
|
|
531
|
+
const s = this.generateSingleEntitySQLFileHeader(options.entity, options.entity.BaseView) + await this.generateBaseView(options.pool, options.entity);
|
|
530
532
|
const filePath = path_1.default.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('view', options.entity.SchemaName, options.entity.BaseView, false, true));
|
|
531
533
|
if (options.writeFiles) {
|
|
532
534
|
this.logSQLForNewOrModifiedEntity(options.entity, s, `Base View SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities);
|
|
@@ -536,8 +538,7 @@ class SQLCodeGenBase {
|
|
|
536
538
|
sRet += s + '\nGO\n';
|
|
537
539
|
}
|
|
538
540
|
// always generate permissions for the base view
|
|
539
|
-
const s = this.generateSingleEntitySQLFileHeader(options.entity, 'Permissions for ' + options.entity.BaseView) +
|
|
540
|
-
this.generateViewPermissions(options.entity);
|
|
541
|
+
const s = this.generateSingleEntitySQLFileHeader(options.entity, 'Permissions for ' + options.entity.BaseView) + this.generateViewPermissions(options.entity);
|
|
541
542
|
if (s.length > 0)
|
|
542
543
|
permissionsSQL += s + '\nGO\n';
|
|
543
544
|
if (options.writeFiles) {
|
|
@@ -617,7 +618,8 @@ class SQLCodeGenBase {
|
|
|
617
618
|
// OR if this entity has cascade delete dependencies that require regeneration
|
|
618
619
|
// forceRegeneration only forces regeneration of SPs where spDeleteGenerated=true
|
|
619
620
|
if (!options.onlyPermissions &&
|
|
620
|
-
(options.entity.spDeleteGenerated ||
|
|
621
|
+
(options.entity.spDeleteGenerated ||
|
|
622
|
+
this.entitiesNeedingDeleteSPRegeneration.has(options.entity.ID))) {
|
|
621
623
|
// generate the delete SP
|
|
622
624
|
if (this.entitiesNeedingDeleteSPRegeneration.has(options.entity.ID)) {
|
|
623
625
|
(0, status_logging_1.logStatus)(` Regenerating ${spName} due to cascade dependency changes`);
|
|
@@ -719,37 +721,37 @@ class SQLCodeGenBase {
|
|
|
719
721
|
let sOutput = '';
|
|
720
722
|
if (entity.BaseViewGenerated && !entity.VirtualEntity)
|
|
721
723
|
// generated the base view (will include permissions)
|
|
722
|
-
sOutput +=
|
|
723
|
-
// still generate the permissions for the view even if a custom view
|
|
724
|
+
sOutput += await this.generateBaseView(pool, entity) + '\n\n';
|
|
724
725
|
else
|
|
726
|
+
// still generate the permissions for the view even if a custom view
|
|
725
727
|
sOutput += this.generateViewPermissions(entity) + '\n\n';
|
|
726
728
|
if (entity.AllowCreateAPI && !entity.VirtualEntity) {
|
|
727
729
|
if (entity.spCreateGenerated)
|
|
728
730
|
// generated SP, will include permissions
|
|
729
731
|
sOutput += this.generateSPCreate(entity) + '\n\n';
|
|
730
|
-
// custom SP, still generate the permissions
|
|
731
732
|
else
|
|
733
|
+
// custom SP, still generate the permissions
|
|
732
734
|
sOutput += this.generateSPPermissions(entity, entity.spCreate, exports.SPType.Create) + '\n\n';
|
|
733
735
|
}
|
|
734
736
|
if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
|
|
735
737
|
if (entity.spUpdateGenerated)
|
|
736
738
|
// generated SP, will include permissions
|
|
737
739
|
sOutput += this.generateSPUpdate(entity) + '\n\n';
|
|
738
|
-
// custom SP, still generate the permissions
|
|
739
740
|
else
|
|
741
|
+
// custom SP, still generate the permissions
|
|
740
742
|
sOutput += this.generateSPPermissions(entity, entity.spUpdate, exports.SPType.Update) + '\n\n';
|
|
741
743
|
}
|
|
742
744
|
if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
|
|
743
745
|
if (entity.spDeleteGenerated)
|
|
744
746
|
// generated SP, will include permissions
|
|
745
747
|
sOutput += this.generateSPDelete(entity) + '\n\n';
|
|
746
|
-
// custom SP, still generate the permissions
|
|
747
748
|
else
|
|
749
|
+
// custom SP, still generate the permissions
|
|
748
750
|
sOutput += this.generateSPPermissions(entity, entity.spDelete, exports.SPType.Delete) + '\n\n';
|
|
749
751
|
}
|
|
750
752
|
// check to see if the entity supports full text search or not
|
|
751
753
|
if (entity.FullTextSearchEnabled) {
|
|
752
|
-
sOutput +=
|
|
754
|
+
sOutput += await this.generateEntityFullTextSearchSQL(pool, entity) + '\n\n';
|
|
753
755
|
}
|
|
754
756
|
return sOutput;
|
|
755
757
|
}
|
|
@@ -769,9 +771,7 @@ class SQLCodeGenBase {
|
|
|
769
771
|
`;
|
|
770
772
|
}
|
|
771
773
|
if (entity.FullTextIndexGenerated) {
|
|
772
|
-
const fullTextFields = entity.Fields.filter(
|
|
773
|
-
.map((f) => `${f.Name} LANGUAGE 'English'`)
|
|
774
|
-
.join(', ');
|
|
774
|
+
const fullTextFields = entity.Fields.filter(f => f.FullTextSearchEnabled).map(f => `${f.Name} LANGUAGE 'English'`).join(', ');
|
|
775
775
|
if (fullTextFields.length === 0)
|
|
776
776
|
throw new Error(`FullTextIndexGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
|
|
777
777
|
// drop and recreate the full text index
|
|
@@ -803,28 +803,24 @@ class SQLCodeGenBase {
|
|
|
803
803
|
GO
|
|
804
804
|
`;
|
|
805
805
|
}
|
|
806
|
-
const functionName = entity.FullTextSearchFunction && entity.FullTextSearchFunction.length > 0
|
|
807
|
-
? entity.FullTextSearchFunction
|
|
808
|
-
: `fnSearch${entity.CodeName}`;
|
|
806
|
+
const functionName = entity.FullTextSearchFunction && entity.FullTextSearchFunction.length > 0 ? entity.FullTextSearchFunction : `fnSearch${entity.CodeName}`;
|
|
809
807
|
if (entity.FullTextSearchFunctionGenerated) {
|
|
810
|
-
const fullTextFieldsSimple = entity.Fields.filter(
|
|
811
|
-
.map((f) => '[' + f.Name + ']')
|
|
812
|
-
.join(', ');
|
|
808
|
+
const fullTextFieldsSimple = entity.Fields.filter(f => f.FullTextSearchEnabled).map(f => '[' + f.Name + ']').join(', ');
|
|
813
809
|
if (fullTextFieldsSimple.length === 0)
|
|
814
810
|
throw new Error(`FullTextSearchFunctionGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
|
|
815
811
|
if (!entity.FullTextSearchFunction || entity.FullTextSearchFunction.length === 0) {
|
|
816
812
|
// update this in the DB
|
|
817
|
-
const md = new
|
|
813
|
+
const md = new core_1.Metadata();
|
|
818
814
|
const u = sqlserver_dataprovider_1.UserCache.Instance.Users[0];
|
|
819
815
|
if (!u)
|
|
820
816
|
throw new Error('Could not find the first user in the cache, cant generate the full text search function without a user');
|
|
821
817
|
const e = await md.GetEntityObject('Entities', u);
|
|
822
818
|
await e.Load(entity.ID);
|
|
823
819
|
e.FullTextSearchFunction = functionName;
|
|
824
|
-
if (!
|
|
820
|
+
if (!await e.Save())
|
|
825
821
|
throw new Error(`Could not update the FullTextSearchFunction for entity ${entity.Name}`);
|
|
826
822
|
}
|
|
827
|
-
const pkeyList = entity.PrimaryKeys.map(
|
|
823
|
+
const pkeyList = entity.PrimaryKeys.map(pk => '[' + pk.Name + ']').join(', ');
|
|
828
824
|
// drop and recreate the full text search function
|
|
829
825
|
sql += ` -- DROP AND RECREATE THE FULL TEXT SEARCH FUNCTION
|
|
830
826
|
-- Create an inline table-valued function to perform full-text search
|
|
@@ -925,7 +921,8 @@ CREATE INDEX ${indexName} ON [${entity.SchemaName}].[${entity.BaseTable}] ([${f.
|
|
|
925
921
|
* Returns array of field info objects representing recursive relationships
|
|
926
922
|
*/
|
|
927
923
|
detectRecursiveForeignKeys(entity) {
|
|
928
|
-
return entity.Fields.filter(
|
|
924
|
+
return entity.Fields.filter(field => field.RelatedEntityID != null &&
|
|
925
|
+
field.RelatedEntityID === entity.ID);
|
|
929
926
|
}
|
|
930
927
|
/**
|
|
931
928
|
* Generates an inline Table Value Function for calculating root ID for a recursive FK field
|
|
@@ -1003,20 +1000,20 @@ GO
|
|
|
1003
1000
|
if (recursiveFKs.length === 0) {
|
|
1004
1001
|
return '';
|
|
1005
1002
|
}
|
|
1006
|
-
return recursiveFKs
|
|
1003
|
+
return recursiveFKs
|
|
1004
|
+
.map(field => this.generateRootIDFunction(entity, field))
|
|
1005
|
+
.join('\n');
|
|
1007
1006
|
}
|
|
1008
1007
|
/**
|
|
1009
1008
|
* Generates the SELECT clause additions for root fields from TVFs
|
|
1010
1009
|
* Example: , root_ParentID.RootID AS [RootParentID]
|
|
1011
1010
|
*/
|
|
1012
1011
|
generateRootFieldSelects(recursiveFKs, classNameFirstChar) {
|
|
1013
|
-
return recursiveFKs
|
|
1014
|
-
.map((field) => {
|
|
1012
|
+
return recursiveFKs.map(field => {
|
|
1015
1013
|
const alias = `root_${field.Name}`;
|
|
1016
1014
|
const columnName = `Root${field.Name}`;
|
|
1017
1015
|
return `,\n ${alias}.RootID AS [${columnName}]`;
|
|
1018
|
-
})
|
|
1019
|
-
.join('');
|
|
1016
|
+
}).join('');
|
|
1020
1017
|
}
|
|
1021
1018
|
/**
|
|
1022
1019
|
* Generates OUTER APPLY joins to inline Table Value Functions for root ID calculation
|
|
@@ -1029,13 +1026,11 @@ GO
|
|
|
1029
1026
|
const primaryKey = entity.FirstPrimaryKey.Name;
|
|
1030
1027
|
const schemaName = entity.SchemaName;
|
|
1031
1028
|
const tableName = entity.BaseTable;
|
|
1032
|
-
const joins = recursiveFKs
|
|
1033
|
-
.map((field) => {
|
|
1029
|
+
const joins = recursiveFKs.map(field => {
|
|
1034
1030
|
const functionName = `fn${tableName}${field.Name}_GetRootID`;
|
|
1035
1031
|
const alias = `root_${field.Name}`;
|
|
1036
1032
|
return `OUTER APPLY\n [${schemaName}].[${functionName}]([${classNameFirstChar}].[${primaryKey}], [${classNameFirstChar}].[${field.Name}]) AS ${alias}`;
|
|
1037
|
-
})
|
|
1038
|
-
.join('\n');
|
|
1033
|
+
}).join('\n');
|
|
1039
1034
|
return '\n' + joins;
|
|
1040
1035
|
}
|
|
1041
1036
|
/**
|
|
@@ -1051,11 +1046,9 @@ GO
|
|
|
1051
1046
|
const relatedFieldsString = await this.generateBaseViewRelatedFieldsString(pool, entity.Fields);
|
|
1052
1047
|
const relatedFieldsJoinString = this.generateBaseViewJoins(entity, entity.Fields);
|
|
1053
1048
|
const permissions = this.generateViewPermissions(entity);
|
|
1054
|
-
const whereClause = entity.DeleteType === 'Soft'
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
`
|
|
1058
|
-
: '';
|
|
1049
|
+
const whereClause = entity.DeleteType === 'Soft' ? `WHERE
|
|
1050
|
+
${classNameFirstChar}.[${core_1.EntityInfo.DeletedAtFieldName}] IS NULL
|
|
1051
|
+
` : '';
|
|
1059
1052
|
// Detect recursive foreign keys and generate TVF joins and root field selects
|
|
1060
1053
|
const recursiveFKs = this.detectRecursiveForeignKeys(entity);
|
|
1061
1054
|
const rootFields = recursiveFKs.length > 0 ? this.generateRootFieldSelects(recursiveFKs, classNameFirstChar) : '';
|
|
@@ -1065,7 +1058,7 @@ GO
|
|
|
1065
1058
|
----- BASE VIEW FOR ENTITY: ${entity.Name}
|
|
1066
1059
|
----- SCHEMA: ${entity.SchemaName}
|
|
1067
1060
|
----- BASE TABLE: ${entity.BaseTable}
|
|
1068
|
-
----- PRIMARY KEY: ${entity.PrimaryKeys.map(
|
|
1061
|
+
----- PRIMARY KEY: ${entity.PrimaryKeys.map(pk => pk.Name).join(', ')}
|
|
1069
1062
|
------------------------------------------------------------
|
|
1070
1063
|
IF OBJECT_ID('[${entity.SchemaName}].[${viewName}]', 'V') IS NOT NULL
|
|
1071
1064
|
DROP VIEW [${entity.SchemaName}].[${viewName}];
|
|
@@ -1105,9 +1098,9 @@ ${whereClause}GO${permissions}
|
|
|
1105
1098
|
async generateBaseViewRelatedFieldsString(pool, entityFields) {
|
|
1106
1099
|
let sOutput = '';
|
|
1107
1100
|
let fieldCount = 0;
|
|
1108
|
-
const manageMD =
|
|
1101
|
+
const manageMD = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(manage_metadata_1.ManageMetadataBase);
|
|
1109
1102
|
// next get the fields that are related entities and have the IncludeRelatedEntityNameFieldInBaseView flag set to true
|
|
1110
|
-
const qualifyingFields = entityFields.filter(
|
|
1103
|
+
const qualifyingFields = entityFields.filter(f => f.RelatedEntityID && f.IncludeRelatedEntityNameFieldInBaseView);
|
|
1111
1104
|
for (const ef of qualifyingFields) {
|
|
1112
1105
|
const { nameField, nameFieldIsVirtual } = this.getIsNameFieldForSingleEntity(ef.RelatedEntity);
|
|
1113
1106
|
if (nameField !== '') {
|
|
@@ -1118,8 +1111,7 @@ ${whereClause}GO${permissions}
|
|
|
1118
1111
|
const candidateName = this.stripID(ef.Name);
|
|
1119
1112
|
// check to make sure candidateName is not already a field name in the base table (other than a virtual field of course, as that is what we're creating)
|
|
1120
1113
|
// because if it is, we need to change it to something else
|
|
1121
|
-
const bFound = entityFields.find(
|
|
1122
|
-
undefined;
|
|
1114
|
+
const bFound = entityFields.find(f => f.IsVirtual === false && f.Name.trim().toLowerCase() === candidateName.trim().toLowerCase()) !== undefined;
|
|
1123
1115
|
if (bFound)
|
|
1124
1116
|
ef._RelatedEntityNameFieldMap = candidateName + '_Virtual';
|
|
1125
1117
|
else
|
|
@@ -1143,8 +1135,8 @@ ${whereClause}GO${permissions}
|
|
|
1143
1135
|
return sOutput;
|
|
1144
1136
|
}
|
|
1145
1137
|
getIsNameFieldForSingleEntity(entityName) {
|
|
1146
|
-
const md = new
|
|
1147
|
-
const e = md.Entities.find(
|
|
1138
|
+
const md = new core_1.Metadata(); // use the full metadata entity list, not the filtered version that we receive
|
|
1139
|
+
const e = md.Entities.find(e => e.Name === entityName);
|
|
1148
1140
|
if (e) {
|
|
1149
1141
|
const ef = e.NameField;
|
|
1150
1142
|
if (e.NameField)
|
|
@@ -1164,7 +1156,9 @@ ${whereClause}GO${permissions}
|
|
|
1164
1156
|
let sOutput = '';
|
|
1165
1157
|
for (let i = 0; i < entity.Permissions.length; i++) {
|
|
1166
1158
|
const ep = entity.Permissions[i];
|
|
1167
|
-
if ((type == exports.SPType.Create && ep.CanCreate) ||
|
|
1159
|
+
if ((type == exports.SPType.Create && ep.CanCreate) ||
|
|
1160
|
+
(type == exports.SPType.Update && ep.CanUpdate) ||
|
|
1161
|
+
(type == exports.SPType.Delete && ep.CanDelete)) {
|
|
1168
1162
|
if (ep.RoleSQLName && ep.RoleSQLName.length > 0) {
|
|
1169
1163
|
sOutput += (sOutput === '' ? `GRANT EXECUTE ON [${entity.SchemaName}].[${spName}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
|
|
1170
1164
|
}
|
|
@@ -1276,9 +1270,7 @@ CREATE PROCEDURE [${entity.SchemaName}].[${spName}]
|
|
|
1276
1270
|
AS
|
|
1277
1271
|
BEGIN
|
|
1278
1272
|
SET NOCOUNT ON;
|
|
1279
|
-
${preInsertCode}${preInsertCode.includes('INSERT INTO')
|
|
1280
|
-
? ''
|
|
1281
|
-
: `
|
|
1273
|
+
${preInsertCode}${preInsertCode.includes('INSERT INTO') ? '' : `
|
|
1282
1274
|
INSERT INTO
|
|
1283
1275
|
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
1284
1276
|
(
|
|
@@ -1295,12 +1287,12 @@ GO${permissions}
|
|
|
1295
1287
|
`;
|
|
1296
1288
|
}
|
|
1297
1289
|
generateUpdatedAtTrigger(entity) {
|
|
1298
|
-
const updatedAtField = entity.Fields.find(
|
|
1290
|
+
const updatedAtField = entity.Fields.find(f => f.Name.toLowerCase().trim() === core_1.EntityInfo.UpdatedAtFieldName.toLowerCase().trim());
|
|
1299
1291
|
if (!updatedAtField)
|
|
1300
1292
|
return '';
|
|
1301
1293
|
const triggerStatement = `
|
|
1302
1294
|
------------------------------------------------------------
|
|
1303
|
-
----- TRIGGER FOR ${
|
|
1295
|
+
----- TRIGGER FOR ${core_1.EntityInfo.UpdatedAtFieldName} field for the ${entity.BaseTable} table
|
|
1304
1296
|
------------------------------------------------------------
|
|
1305
1297
|
IF OBJECT_ID('[${entity.SchemaName}].[trgUpdate${entity.ClassName}]', 'TR') IS NOT NULL
|
|
1306
1298
|
DROP TRIGGER [${entity.SchemaName}].[trgUpdate${entity.ClassName}];
|
|
@@ -1314,12 +1306,12 @@ BEGIN
|
|
|
1314
1306
|
UPDATE
|
|
1315
1307
|
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
1316
1308
|
SET
|
|
1317
|
-
${
|
|
1309
|
+
${core_1.EntityInfo.UpdatedAtFieldName} = GETUTCDATE()
|
|
1318
1310
|
FROM
|
|
1319
1311
|
[${entity.SchemaName}].[${entity.BaseTable}] AS _organicTable
|
|
1320
1312
|
INNER JOIN
|
|
1321
1313
|
INSERTED AS I ON
|
|
1322
|
-
${entity.PrimaryKeys.map(
|
|
1314
|
+
${entity.PrimaryKeys.map(k => `_organicTable.[${k.Name}] = I.[${k.Name}]`).join(' AND ')};
|
|
1323
1315
|
END;
|
|
1324
1316
|
GO`;
|
|
1325
1317
|
return triggerStatement;
|
|
@@ -1328,14 +1320,14 @@ GO`;
|
|
|
1328
1320
|
const spName = entity.spUpdate ? entity.spUpdate : `spUpdate${entity.BaseTableCodeName}`;
|
|
1329
1321
|
const efParamString = this.createEntityFieldsParamString(entity.Fields, true);
|
|
1330
1322
|
const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Update);
|
|
1331
|
-
const hasUpdatedAtField = entity.Fields.find(
|
|
1323
|
+
const hasUpdatedAtField = entity.Fields.find(f => f.Name.toLowerCase().trim() === core_1.EntityInfo.UpdatedAtFieldName.trim().toLowerCase()) !== undefined;
|
|
1332
1324
|
const updatedAtTrigger = hasUpdatedAtField ? this.generateUpdatedAtTrigger(entity) : '';
|
|
1333
1325
|
let selectUpdatedRecord = `SELECT
|
|
1334
1326
|
*
|
|
1335
1327
|
FROM
|
|
1336
1328
|
[${entity.SchemaName}].[${entity.BaseView}]
|
|
1337
1329
|
WHERE
|
|
1338
|
-
${entity.PrimaryKeys.map(
|
|
1330
|
+
${entity.PrimaryKeys.map(k => `[${k.Name}] = @${k.CodeName}`).join(' AND ')}
|
|
1339
1331
|
`;
|
|
1340
1332
|
return `
|
|
1341
1333
|
------------------------------------------------------------
|
|
@@ -1355,7 +1347,7 @@ BEGIN
|
|
|
1355
1347
|
SET
|
|
1356
1348
|
${this.createEntityFieldsUpdateString(entity.Fields)}
|
|
1357
1349
|
WHERE
|
|
1358
|
-
${entity.PrimaryKeys.map(
|
|
1350
|
+
${entity.PrimaryKeys.map(k => `[${k.Name}] = @${k.CodeName}`).join(' AND ')}
|
|
1359
1351
|
|
|
1360
1352
|
-- Check if the update was successful
|
|
1361
1353
|
IF @@ROWCOUNT = 0
|
|
@@ -1394,7 +1386,7 @@ ${updatedAtTrigger}
|
|
|
1394
1386
|
'current_timestamp',
|
|
1395
1387
|
'user_name()',
|
|
1396
1388
|
'suser_name()',
|
|
1397
|
-
'system_user'
|
|
1389
|
+
'system_user'
|
|
1398
1390
|
];
|
|
1399
1391
|
// Check if this is a SQL function
|
|
1400
1392
|
for (const func of sqlFunctions) {
|
|
@@ -1454,11 +1446,7 @@ ${updatedAtTrigger}
|
|
|
1454
1446
|
const ef = entityFields[i];
|
|
1455
1447
|
// we only want fields that are (a) not primary keys, or if a pkey, not an auto-increment pkey and (b) not virtual fields and (c) updateable fields and (d) not auto-increment fields if they're not pkeys)
|
|
1456
1448
|
// ALSO: if excludePrimaryKey is true, skip all primary key fields
|
|
1457
|
-
if ((excludePrimaryKey && ef.IsPrimaryKey) ||
|
|
1458
|
-
(ef.IsPrimaryKey && autoGeneratedPrimaryKey) ||
|
|
1459
|
-
ef.IsVirtual ||
|
|
1460
|
-
!ef.AllowUpdateAPI ||
|
|
1461
|
-
ef.AutoIncrement) {
|
|
1449
|
+
if ((excludePrimaryKey && ef.IsPrimaryKey) || (ef.IsPrimaryKey && autoGeneratedPrimaryKey) || ef.IsVirtual || !ef.AllowUpdateAPI || ef.AutoIncrement) {
|
|
1462
1450
|
continue; // skip this field
|
|
1463
1451
|
}
|
|
1464
1452
|
if (!isFirst)
|
|
@@ -1471,7 +1459,7 @@ ${updatedAtTrigger}
|
|
|
1471
1459
|
else
|
|
1472
1460
|
sOutput += `NULL`; // we don't set the deleted at field on an insert, only on a delete
|
|
1473
1461
|
}
|
|
1474
|
-
else if (prefix && prefix !== '' && !ef.IsPrimaryKey && ef.IsUniqueIdentifier && ef.HasDefaultValue && !ef.AllowsNull) {
|
|
1462
|
+
else if ((prefix && prefix !== '') && !ef.IsPrimaryKey && ef.IsUniqueIdentifier && ef.HasDefaultValue && !ef.AllowsNull) {
|
|
1475
1463
|
// this is the VALUE side (prefix not null/blank), is NOT a primary key, and is a uniqueidentifier column with a default value and does NOT allow NULL
|
|
1476
1464
|
// We need to handle both NULL and the special value '00000000-0000-0000-0000-000000000000' for backward compatibility
|
|
1477
1465
|
// Existing code uses the special value to indicate "use the default", so we preserve that behavior
|
|
@@ -1509,7 +1497,11 @@ ${updatedAtTrigger}
|
|
|
1509
1497
|
let sOutput = '', isFirst = true;
|
|
1510
1498
|
for (let i = 0; i < entityFields.length; ++i) {
|
|
1511
1499
|
const ef = entityFields[i];
|
|
1512
|
-
if (!ef.IsPrimaryKey &&
|
|
1500
|
+
if (!ef.IsPrimaryKey &&
|
|
1501
|
+
!ef.IsVirtual &&
|
|
1502
|
+
ef.AllowUpdateAPI &&
|
|
1503
|
+
!ef.AutoIncrement &&
|
|
1504
|
+
!ef.IsSpecialDateField) {
|
|
1513
1505
|
if (!isFirst)
|
|
1514
1506
|
sOutput += ',\n ';
|
|
1515
1507
|
else
|
|
@@ -1536,7 +1528,7 @@ ${updatedAtTrigger}
|
|
|
1536
1528
|
// next up, create the delete code which is based on the type of delete the entity is set to
|
|
1537
1529
|
// start off by creating the where clause first and then prepend the delete or update statement to it
|
|
1538
1530
|
let deleteCode = ` WHERE
|
|
1539
|
-
${entity.PrimaryKeys.map(
|
|
1531
|
+
${entity.PrimaryKeys.map(k => `[${k.Name}] = @${k.CodeName}`).join(' AND ')}
|
|
1540
1532
|
`;
|
|
1541
1533
|
if (entity.DeleteType === 'Hard') {
|
|
1542
1534
|
deleteCode = ` DELETE FROM
|
|
@@ -1547,8 +1539,8 @@ ${deleteCode}`;
|
|
|
1547
1539
|
deleteCode = ` UPDATE
|
|
1548
1540
|
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
1549
1541
|
SET
|
|
1550
|
-
${
|
|
1551
|
-
${deleteCode} AND ${
|
|
1542
|
+
${core_1.EntityInfo.DeletedAtFieldName} = GETUTCDATE()
|
|
1543
|
+
${deleteCode} AND ${core_1.EntityInfo.DeletedAtFieldName} IS NULL -- don't update the record if it's already been deleted via a soft delete`;
|
|
1552
1544
|
}
|
|
1553
1545
|
// Build the NULL select statement for when no rows are affected
|
|
1554
1546
|
let sNullSelect = '';
|
|
@@ -1585,7 +1577,7 @@ GO${permissions}
|
|
|
1585
1577
|
generateCascadeDeletes(entity) {
|
|
1586
1578
|
let sOutput = '';
|
|
1587
1579
|
if (entity.CascadeDeletes) {
|
|
1588
|
-
const md = new
|
|
1580
|
+
const md = new core_1.Metadata();
|
|
1589
1581
|
// Find all fields in other entities that are foreign keys to this entity
|
|
1590
1582
|
for (const e of md.Entities) {
|
|
1591
1583
|
for (const ef of e.Fields) {
|
|
@@ -1727,10 +1719,7 @@ GO${permissions}
|
|
|
1727
1719
|
const pkComponents = this.buildPrimaryKeyComponents(entity, varPrefix);
|
|
1728
1720
|
// Add primary key declarations to the declarations string
|
|
1729
1721
|
// Need to add DECLARE keyword since buildPrimaryKeyComponents doesn't include it
|
|
1730
|
-
declarations = pkComponents.varDeclarations
|
|
1731
|
-
.split(', ')
|
|
1732
|
-
.map((decl) => `DECLARE ${decl}`)
|
|
1733
|
-
.join('\n ');
|
|
1722
|
+
declarations = pkComponents.varDeclarations.split(', ').map(decl => `DECLARE ${decl}`).join('\n ');
|
|
1734
1723
|
selectFields = pkComponents.selectFields;
|
|
1735
1724
|
fetchInto = pkComponents.fetchInto;
|
|
1736
1725
|
allParams = pkComponents.spParams;
|
|
@@ -1761,7 +1750,7 @@ GO${permissions}
|
|
|
1761
1750
|
*/
|
|
1762
1751
|
analyzeCascadeDeleteDependencies(entity) {
|
|
1763
1752
|
if (entity.CascadeDeletes) {
|
|
1764
|
-
const md = new
|
|
1753
|
+
const md = new core_1.Metadata();
|
|
1765
1754
|
// Find all fields in other entities that are foreign keys to this entity
|
|
1766
1755
|
for (const e of md.Entities) {
|
|
1767
1756
|
for (const ef of e.Fields) {
|
|
@@ -1823,9 +1812,9 @@ GO${permissions}
|
|
|
1823
1812
|
// Log the dependency map
|
|
1824
1813
|
(0, status_logging_1.logStatus)(`Cascade delete dependency map built:`);
|
|
1825
1814
|
for (const [dependedOnEntityId, dependentEntityIds] of this.cascadeDeleteDependencies) {
|
|
1826
|
-
const dependedOnEntity = entities.find(
|
|
1815
|
+
const dependedOnEntity = entities.find(e => e.ID === dependedOnEntityId);
|
|
1827
1816
|
const dependentNames = Array.from(dependentEntityIds)
|
|
1828
|
-
.map(
|
|
1817
|
+
.map(id => entities.find(e => e.ID === id)?.Name || id)
|
|
1829
1818
|
.join(', ');
|
|
1830
1819
|
(0, status_logging_1.logStatus)(` ${dependedOnEntity?.Name || dependedOnEntityId} is depended on by: ${dependentNames}`);
|
|
1831
1820
|
}
|
|
@@ -1841,13 +1830,15 @@ GO${permissions}
|
|
|
1841
1830
|
(0, status_logging_1.logStatus)(`Modified entities from metadata phase: ${modifiedEntityNames.join(', ')}`);
|
|
1842
1831
|
// Convert entity names to IDs and filter for those with update API
|
|
1843
1832
|
for (const entityName of modifiedEntityNames) {
|
|
1844
|
-
const entity = entities.find(
|
|
1833
|
+
const entity = entities.find(e => e.Name === entityName &&
|
|
1834
|
+
e.AllowUpdateAPI &&
|
|
1835
|
+
e.spUpdateGenerated);
|
|
1845
1836
|
if (entity) {
|
|
1846
1837
|
modifiedEntitiesMap.set(entity.Name, entity.ID);
|
|
1847
1838
|
(0, status_logging_1.logStatus)(` - ${entity.Name} (${entity.ID}) has update API and will be tracked`);
|
|
1848
1839
|
}
|
|
1849
1840
|
else {
|
|
1850
|
-
const nonUpdateEntity = entities.find(
|
|
1841
|
+
const nonUpdateEntity = entities.find(e => e.Name === entityName);
|
|
1851
1842
|
if (nonUpdateEntity) {
|
|
1852
1843
|
(0, status_logging_1.logStatus)(` - ${entityName} found but AllowUpdateAPI=${nonUpdateEntity.AllowUpdateAPI}, spUpdateGenerated=${nonUpdateEntity.spUpdateGenerated}`);
|
|
1853
1844
|
}
|
|
@@ -1895,7 +1886,7 @@ GO${permissions}
|
|
|
1895
1886
|
(0, status_logging_1.logStatus)(`Identified ${entitiesNeedingRegeneration.size} entities requiring delete SP regeneration due to cascade dependencies`);
|
|
1896
1887
|
// Store the entity IDs that need regeneration (only if spDeleteGenerated=true)
|
|
1897
1888
|
for (const entityId of entitiesNeedingRegeneration) {
|
|
1898
|
-
const entity = entities.find(
|
|
1889
|
+
const entity = entities.find(e => e.ID === entityId);
|
|
1899
1890
|
if (entity && entity.spDeleteGenerated) {
|
|
1900
1891
|
this.entitiesNeedingDeleteSPRegeneration.add(entityId);
|
|
1901
1892
|
(0, status_logging_1.logStatus)(` - Marked ${entity.Name} for delete SP regeneration (cascade dependency)`);
|
|
@@ -1909,7 +1900,7 @@ GO${permissions}
|
|
|
1909
1900
|
if (this.orderedEntitiesForDeleteSPRegeneration.length > 0) {
|
|
1910
1901
|
(0, status_logging_1.logStatus)(`Ordered entities for delete SP regeneration:`);
|
|
1911
1902
|
this.orderedEntitiesForDeleteSPRegeneration.forEach((entityId, index) => {
|
|
1912
|
-
const entity = entities.find(
|
|
1903
|
+
const entity = entities.find(e => e.ID === entityId);
|
|
1913
1904
|
(0, status_logging_1.logStatus)(` ${index + 1}. ${entity?.Name || entityId}`);
|
|
1914
1905
|
});
|
|
1915
1906
|
}
|
|
@@ -1957,7 +1948,7 @@ GO${permissions}
|
|
|
1957
1948
|
}
|
|
1958
1949
|
if (visiting.has(entityId)) {
|
|
1959
1950
|
// Circular dependency detected - mark it but don't fail
|
|
1960
|
-
const entity = entities.find(
|
|
1951
|
+
const entity = entities.find(e => e.ID === entityId);
|
|
1961
1952
|
(0, status_logging_1.logStatus)(`Warning: Circular cascade delete dependency detected involving ${entity?.Name || entityId}`);
|
|
1962
1953
|
circularDeps.add(entityId);
|
|
1963
1954
|
return false; // Signal circular dependency but continue processing
|
|
@@ -1984,7 +1975,7 @@ GO${permissions}
|
|
|
1984
1975
|
if (!success && circularDeps.has(entityId)) {
|
|
1985
1976
|
// Entity is part of circular dependency - add it anyway in arbitrary order
|
|
1986
1977
|
// The SQL will still be generated, just not in perfect dependency order
|
|
1987
|
-
(0, status_logging_1.logStatus)(` - Adding ${entities.find(
|
|
1978
|
+
(0, status_logging_1.logStatus)(` - Adding ${entities.find(e => e.ID === entityId)?.Name || entityId} despite circular dependency`);
|
|
1988
1979
|
visited.add(entityId);
|
|
1989
1980
|
ordered.push(entityId);
|
|
1990
1981
|
}
|