@memberjunction/codegen-lib 6.2.0-edge.0 → 6.2.0-edge.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Angular/angular-codegen.d.ts +6 -0
- package/dist/Angular/angular-codegen.d.ts.map +1 -1
- package/dist/Angular/angular-codegen.js +26 -14
- package/dist/Angular/angular-codegen.js.map +1 -1
- package/dist/Config/config.d.ts +45 -2
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +116 -39
- package/dist/Config/config.js.map +1 -1
- package/dist/Config/db-connection.d.ts +4 -0
- package/dist/Config/db-connection.d.ts.map +1 -1
- package/dist/Config/db-connection.js +11 -3
- package/dist/Config/db-connection.js.map +1 -1
- package/dist/Config/pg-connection.d.ts +2 -0
- package/dist/Config/pg-connection.d.ts.map +1 -1
- package/dist/Config/pg-connection.js +5 -1
- package/dist/Config/pg-connection.js.map +1 -1
- package/dist/Config/schema-output.d.ts +8 -0
- package/dist/Config/schema-output.d.ts.map +1 -1
- package/dist/Config/schema-output.js +19 -7
- package/dist/Config/schema-output.js.map +1 -1
- package/dist/Database/__tests__/exclude-schema-permissions.test.js +3 -3
- package/dist/Database/codeGenDatabaseProvider.d.ts +16 -1
- package/dist/Database/codeGenDatabaseProvider.d.ts.map +1 -1
- package/dist/Database/codeGenDatabaseProvider.js +37 -9
- package/dist/Database/codeGenDatabaseProvider.js.map +1 -1
- package/dist/Database/dbSchema.d.ts +4 -0
- package/dist/Database/dbSchema.d.ts.map +1 -1
- package/dist/Database/dbSchema.js +17 -9
- package/dist/Database/dbSchema.js.map +1 -1
- package/dist/Database/entity-field-change-tracking.d.ts +4 -0
- package/dist/Database/entity-field-change-tracking.d.ts.map +1 -1
- package/dist/Database/entity-field-change-tracking.js +12 -4
- package/dist/Database/entity-field-change-tracking.js.map +1 -1
- package/dist/Database/exclude-tables.d.ts +2 -0
- package/dist/Database/exclude-tables.d.ts.map +1 -1
- package/dist/Database/exclude-tables.js +5 -1
- package/dist/Database/exclude-tables.js.map +1 -1
- package/dist/Database/field-metadata-lock.d.ts +8 -6
- package/dist/Database/field-metadata-lock.d.ts.map +1 -1
- package/dist/Database/field-metadata-lock.js +29 -25
- package/dist/Database/field-metadata-lock.js.map +1 -1
- package/dist/Database/heal-schema-params.d.ts +14 -2
- package/dist/Database/heal-schema-params.d.ts.map +1 -1
- package/dist/Database/heal-schema-params.js +21 -5
- package/dist/Database/heal-schema-params.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +119 -3
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +529 -364
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/materializationAnalysis.d.ts +61 -2
- package/dist/Database/materializationAnalysis.d.ts.map +1 -1
- package/dist/Database/materializationAnalysis.js +43 -23
- package/dist/Database/materializationAnalysis.js.map +1 -1
- package/dist/Database/materializationBroadRender.d.ts +5 -3
- package/dist/Database/materializationBroadRender.d.ts.map +1 -1
- package/dist/Database/materializationBroadRender.js +17 -13
- package/dist/Database/materializationBroadRender.js.map +1 -1
- package/dist/Database/materializationDrift.d.ts +2 -0
- package/dist/Database/materializationDrift.d.ts.map +1 -1
- package/dist/Database/materializationDrift.js +5 -1
- package/dist/Database/materializationDrift.js.map +1 -1
- package/dist/Database/materializationParamClassifier.d.ts +17 -2
- package/dist/Database/materializationParamClassifier.d.ts.map +1 -1
- package/dist/Database/materializationParamClassifier.js +22 -10
- package/dist/Database/materializationParamClassifier.js.map +1 -1
- package/dist/Database/materializationParamVerifier.d.ts +2 -0
- package/dist/Database/materializationParamVerifier.d.ts.map +1 -1
- package/dist/Database/materializationParamVerifier.js +18 -14
- package/dist/Database/materializationParamVerifier.js.map +1 -1
- package/dist/Database/materializationSqlAst.d.ts +19 -1
- package/dist/Database/materializationSqlAst.d.ts.map +1 -1
- package/dist/Database/materializationSqlAst.js +53 -17
- package/dist/Database/materializationSqlAst.js.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js +30 -30
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/postgresql/metadataSupportObjects.d.ts +2 -0
- package/dist/Database/providers/postgresql/metadataSupportObjects.d.ts.map +1 -1
- package/dist/Database/providers/postgresql/metadataSupportObjects.js +5 -1
- package/dist/Database/providers/postgresql/metadataSupportObjects.js.map +1 -1
- package/dist/Database/providers/postgresql/viewDependencyCapture.d.ts +10 -0
- package/dist/Database/providers/postgresql/viewDependencyCapture.d.ts.map +1 -1
- package/dist/Database/providers/postgresql/viewDependencyCapture.js +25 -5
- package/dist/Database/providers/postgresql/viewDependencyCapture.js.map +1 -1
- package/dist/Database/providers/postgresql/viewFallback.d.ts +11 -7
- package/dist/Database/providers/postgresql/viewFallback.d.ts.map +1 -1
- package/dist/Database/providers/postgresql/viewFallback.js +18 -12
- package/dist/Database/providers/postgresql/viewFallback.js.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js +13 -13
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js.map +1 -1
- package/dist/Database/reconcileFieldLevelSecurity.d.ts +2 -0
- package/dist/Database/reconcileFieldLevelSecurity.d.ts.map +1 -1
- package/dist/Database/reconcileFieldLevelSecurity.js +5 -1
- package/dist/Database/reconcileFieldLevelSecurity.js.map +1 -1
- package/dist/Database/reorder-columns.d.ts +2 -0
- package/dist/Database/reorder-columns.d.ts.map +1 -1
- package/dist/Database/reorder-columns.js +5 -1
- package/dist/Database/reorder-columns.js.map +1 -1
- package/dist/Database/schema-filters.d.ts +6 -0
- package/dist/Database/schema-filters.d.ts.map +1 -1
- package/dist/Database/schema-filters.js +17 -5
- package/dist/Database/schema-filters.js.map +1 -1
- package/dist/Database/schema-scope.d.ts +4 -0
- package/dist/Database/schema-scope.d.ts.map +1 -1
- package/dist/Database/schema-scope.js +13 -5
- package/dist/Database/schema-scope.js.map +1 -1
- package/dist/Database/search-guardrails.d.ts +42 -0
- package/dist/Database/search-guardrails.d.ts.map +1 -1
- package/dist/Database/search-guardrails.js +57 -17
- package/dist/Database/search-guardrails.js.map +1 -1
- package/dist/Database/sql.d.ts +20 -0
- package/dist/Database/sql.d.ts.map +1 -1
- package/dist/Database/sql.js +72 -32
- package/dist/Database/sql.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +92 -0
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +197 -113
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/EntityNameScanner/EntityNameScanner.d.ts +16 -0
- package/dist/EntityNameScanner/EntityNameScanner.d.ts.map +1 -1
- package/dist/EntityNameScanner/EntityNameScanner.js +47 -15
- package/dist/EntityNameScanner/EntityNameScanner.js.map +1 -1
- package/dist/EntityNameScanner/HtmlEntityNameScanner.d.ts +6 -0
- package/dist/EntityNameScanner/HtmlEntityNameScanner.d.ts.map +1 -1
- package/dist/EntityNameScanner/HtmlEntityNameScanner.js +19 -7
- package/dist/EntityNameScanner/HtmlEntityNameScanner.js.map +1 -1
- package/dist/EntityNameScanner/MetadataNameScanner.d.ts +6 -0
- package/dist/EntityNameScanner/MetadataNameScanner.d.ts.map +1 -1
- package/dist/EntityNameScanner/MetadataNameScanner.js +19 -7
- package/dist/EntityNameScanner/MetadataNameScanner.js.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +6 -0
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.js +16 -4
- package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -1
- package/dist/Misc/action_subclasses_codegen.d.ts +4 -0
- package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/action_subclasses_codegen.js +11 -3
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +48 -2
- package/dist/Misc/advanced_generation.d.ts.map +1 -1
- package/dist/Misc/advanced_generation.js +53 -15
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/codegen-reporter.d.ts +53 -0
- package/dist/Misc/codegen-reporter.d.ts.map +1 -1
- package/dist/Misc/codegen-reporter.js +106 -22
- package/dist/Misc/codegen-reporter.js.map +1 -1
- package/dist/Misc/createNewUser.d.ts +6 -0
- package/dist/Misc/createNewUser.d.ts.map +1 -1
- package/dist/Misc/createNewUser.js +5 -1
- package/dist/Misc/createNewUser.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +8 -0
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +52 -36
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/externalTypeMapping.d.ts +2 -0
- package/dist/Misc/externalTypeMapping.d.ts.map +1 -1
- package/dist/Misc/externalTypeMapping.js +5 -1
- package/dist/Misc/externalTypeMapping.js.map +1 -1
- package/dist/Misc/file-write.d.ts +2 -0
- package/dist/Misc/file-write.d.ts.map +1 -1
- package/dist/Misc/file-write.js +5 -1
- package/dist/Misc/file-write.js.map +1 -1
- package/dist/Misc/graphql_server_codegen.d.ts +10 -0
- package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +46 -26
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/dist/Misc/remote_operations_codegen.d.ts +12 -0
- package/dist/Misc/remote_operations_codegen.d.ts.map +1 -1
- package/dist/Misc/remote_operations_codegen.js +21 -5
- package/dist/Misc/remote_operations_codegen.js.map +1 -1
- package/dist/Misc/runCommand.d.ts +6 -0
- package/dist/Misc/runCommand.d.ts.map +1 -1
- package/dist/Misc/runCommand.js +16 -4
- package/dist/Misc/runCommand.js.map +1 -1
- package/dist/Misc/schema-emit.d.ts +28 -0
- package/dist/Misc/schema-emit.d.ts.map +1 -1
- package/dist/Misc/schema-emit.js +75 -23
- package/dist/Misc/schema-emit.js.map +1 -1
- package/dist/Misc/sql_logging.d.ts +31 -9
- package/dist/Misc/sql_logging.d.ts.map +1 -1
- package/dist/Misc/sql_logging.js +95 -51
- package/dist/Misc/sql_logging.js.map +1 -1
- package/dist/Misc/sql_text.d.ts +4 -0
- package/dist/Misc/sql_text.d.ts.map +1 -1
- package/dist/Misc/sql_text.js +10 -2
- package/dist/Misc/sql_text.js.map +1 -1
- package/dist/Misc/status_logging.d.ts +36 -0
- package/dist/Misc/status_logging.d.ts.map +1 -1
- package/dist/Misc/status_logging.js +95 -23
- package/dist/Misc/status_logging.js.map +1 -1
- package/dist/Misc/system_integrity.js +4 -4
- package/dist/Misc/system_integrity.js.map +1 -1
- package/dist/Misc/temp_batch_file.d.ts +12 -0
- package/dist/Misc/temp_batch_file.d.ts.map +1 -1
- package/dist/Misc/temp_batch_file.js +35 -11
- package/dist/Misc/temp_batch_file.js.map +1 -1
- package/dist/Misc/util.d.ts +33 -0
- package/dist/Misc/util.d.ts.map +1 -1
- package/dist/Misc/util.js +53 -13
- package/dist/Misc/util.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +11 -1
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +9 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/runCodeGen.d.ts +6 -0
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +150 -138
- package/dist/runCodeGen.js.map +1 -1
- package/package.json +29 -29
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { EntityInfo, Metadata, ShouldJoinRecordGeoCodes, HasNativeLatLngFields, NativeLatitudeField, NativeLongitudeField, ListEmbeddedGeoSpecs } from '@memberjunction/core';
|
|
2
|
-
import { logError, logStatus,
|
|
2
|
+
import { logError, logStatus, LogWarning, StartSpinner, UpdateSpinner, SucceedSpinner, FailSpinner } from '../Misc/status_logging.js';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { SQLUtilityBase } from './sql.js';
|
|
6
6
|
import { CodeGenDatabaseProvider } from './codeGenDatabaseProvider.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { AutoIndexForeignKeys, AutoIndexSoftPrimaryKeys, configInfo, CustomSqlScripts, mjCoreSchema } from '../Config/config.js';
|
|
8
|
+
import { EntityInCustomBaseViewRefreshScope, ShouldEmitCascadeForRelatedEntity } from './schema-filters.js';
|
|
9
9
|
import { ManageMetadataBase } from './manage-metadata.js';
|
|
10
10
|
import { UserCache } from '@memberjunction/generic-database-provider';
|
|
11
|
-
import {
|
|
11
|
+
import { CombineFiles, LogIf } from '../Misc/util.js';
|
|
12
12
|
import { MJGlobal, UUIDsEqual } from '@memberjunction/global';
|
|
13
13
|
import { SQLLogging } from '../Misc/sql_logging.js';
|
|
14
14
|
import { TempBatchFile } from '../Misc/temp_batch_file.js';
|
|
@@ -23,10 +23,14 @@ export const SPType = {
|
|
|
23
23
|
* This is the CodeGen run's in-scope set: SQL objects and GRANTs are
|
|
24
24
|
* generated and executed only for these. excludeSchemas means "not this run".
|
|
25
25
|
*/
|
|
26
|
-
export function
|
|
26
|
+
export function EntitiesNotInExcludedSchemas(entities, excludeSchemas) {
|
|
27
27
|
const exclude = new Set(excludeSchemas.map((s) => s.toLowerCase()));
|
|
28
28
|
return entities.filter((e) => !exclude.has(e.SchemaName.toLowerCase()));
|
|
29
29
|
}
|
|
30
|
+
/** @deprecated Use {@link EntitiesNotInExcludedSchemas}. */
|
|
31
|
+
export function entitiesNotInExcludedSchemas(entities, excludeSchemas) {
|
|
32
|
+
return EntitiesNotInExcludedSchemas(entities, excludeSchemas);
|
|
33
|
+
}
|
|
30
34
|
/**
|
|
31
35
|
* This class is responsible for generating database level objects like views, stored procedures, permissions, etc. You can sub-class this class to create your own SQL generation logic or implement support for other
|
|
32
36
|
* databases. The base class implements support for SQL Server. In future versions of MJ, we will break out an abstract base class that has the skeleton of the logic and then the SQL Server version will be a sub-class
|
|
@@ -85,7 +89,7 @@ export class SQLCodeGenBase {
|
|
|
85
89
|
set DBProvider(value) {
|
|
86
90
|
this._dbProvider = value;
|
|
87
91
|
}
|
|
88
|
-
async
|
|
92
|
+
async ManageSQLScriptsAndExecution(pool, entities, directory, currentUser) {
|
|
89
93
|
try {
|
|
90
94
|
this.existingRoutines = null;
|
|
91
95
|
// Build list of entities qualified for forced regeneration if entityWhereClause is provided
|
|
@@ -116,31 +120,31 @@ export class SQLCodeGenBase {
|
|
|
116
120
|
// we have custom base views, need to have them defined before we do
|
|
117
121
|
// the rest as the generated stuff might use custom base views in compiled
|
|
118
122
|
// objects like spCreate for a given entity might reference the vw for that entity
|
|
119
|
-
|
|
123
|
+
StartSpinner('Running custom SQL scripts...');
|
|
120
124
|
const startTime = new Date();
|
|
121
|
-
if (!await this.
|
|
122
|
-
|
|
125
|
+
if (!await this.RunCustomSQLScripts(pool, 'before-sql')) {
|
|
126
|
+
FailSpinner('Failed to run custom SQL scripts');
|
|
123
127
|
return false;
|
|
124
128
|
}
|
|
125
|
-
|
|
129
|
+
SucceedSpinner(`Custom SQL scripts completed (${(new Date().getTime() - startTime.getTime()) / 1000}s)`);
|
|
126
130
|
await this.loadExistingRoutines(pool, entities);
|
|
127
131
|
// ALWAYS use the first filter where we only include entities that have IncludeInAPI = 1
|
|
128
132
|
// Entities are already sorted by name in PostProcessEntityMetadata (see providerBase.ts)
|
|
129
133
|
const baselineEntities = entities.filter(e => e.IncludeInAPI);
|
|
130
134
|
// OPTIMIZATION: Use a Set for O(1) lookups instead of O(n) array finds to improve performance
|
|
131
|
-
const includedEntities =
|
|
135
|
+
const includedEntities = EntitiesNotInExcludedSchemas(baselineEntities, configInfo.excludeSchemas);
|
|
132
136
|
// Initialize temp batch files for each schema
|
|
133
137
|
// These will be populated as SQL is generated and will be used for actual execution
|
|
134
138
|
const schemas = Array.from(new Set(baselineEntities.map(e => e.SchemaName))).sort();
|
|
135
139
|
TempBatchFile.initialize(directory, schemas);
|
|
136
140
|
// STEP 1.5 - Check for cascade delete dependencies that require regeneration
|
|
137
|
-
|
|
141
|
+
StartSpinner('Analyzing cascade delete dependencies...');
|
|
138
142
|
await this.markEntitiesForCascadeDeleteRegeneration(pool, includedEntities);
|
|
139
|
-
|
|
143
|
+
SucceedSpinner('Cascade delete dependency analysis completed');
|
|
140
144
|
// STEP 2(a) - clean out all *.generated.sql and *.permissions.generated.sql files from the directory
|
|
141
|
-
|
|
142
|
-
this.
|
|
143
|
-
|
|
145
|
+
StartSpinner('Cleaning generated files...');
|
|
146
|
+
this.DeleteGeneratedEntityFiles(directory, baselineEntities);
|
|
147
|
+
SucceedSpinner('Cleaned generated files');
|
|
144
148
|
// STEP 2(a.5) - field-level security / permission-reconciliation run context.
|
|
145
149
|
// One async pass over the live catalog (permission state + service-login role
|
|
146
150
|
// memberships) so the synchronous permission emitters can (1) REVOKE-and-reassert
|
|
@@ -149,7 +153,7 @@ export class SQLCodeGenBase {
|
|
|
149
153
|
// only: PostgreSQL has no DENY primitive, so per decision D2 it emits nothing.
|
|
150
154
|
await this.prepareFieldSecurityRunContext(pool);
|
|
151
155
|
// STEP 2(b) - generate all the SQL files and execute them
|
|
152
|
-
|
|
156
|
+
StartSpinner(`Generating SQL for ${includedEntities.length} entities...`);
|
|
153
157
|
const step2StartTime = new Date();
|
|
154
158
|
// When the provider offers a phased executor (PG today), run per-entity
|
|
155
159
|
// execution DURING file generation. This gives us view → CRUD fns →
|
|
@@ -207,7 +211,7 @@ export class SQLCodeGenBase {
|
|
|
207
211
|
const configuredBatch = configInfo.fileEmit?.sqlEntityBatchSize ?? 8;
|
|
208
212
|
const perEntityBatchSize = configInfo.dbPlatform === 'postgresql' ? 1 : configuredBatch;
|
|
209
213
|
// Generate SQL for entities that don't need cascade delete regeneration
|
|
210
|
-
const genResult = await this.
|
|
214
|
+
const genResult = await this.GenerateAndExecuteEntitySQLToSeparateFiles({
|
|
211
215
|
pool,
|
|
212
216
|
entities: entitiesWithoutCascadeRegeneration,
|
|
213
217
|
directory,
|
|
@@ -226,8 +230,8 @@ export class SQLCodeGenBase {
|
|
|
226
230
|
}
|
|
227
231
|
// Generate SQL for cascade delete regenerations in dependency order (sequentially)
|
|
228
232
|
if (entitiesForCascadeRegeneration.length > 0) {
|
|
229
|
-
|
|
230
|
-
const cascadeGenResult = await this.
|
|
233
|
+
UpdateSpinner(`Regenerating ${entitiesForCascadeRegeneration.length} delete SPs in dependency order...`);
|
|
234
|
+
const cascadeGenResult = await this.GenerateAndExecuteEntitySQLToSeparateFiles({
|
|
231
235
|
pool,
|
|
232
236
|
entities: entitiesForCascadeRegeneration,
|
|
233
237
|
directory,
|
|
@@ -258,18 +262,18 @@ export class SQLCodeGenBase {
|
|
|
258
262
|
//
|
|
259
263
|
// Permissions are applied in STEP 4 for includedEntities only.
|
|
260
264
|
if (entityGenSuccess) {
|
|
261
|
-
|
|
265
|
+
SucceedSpinner(`Entity generation completed (${(new Date().getTime() - step2StartTime.getTime()) / 1000}s)`);
|
|
262
266
|
}
|
|
263
267
|
else {
|
|
264
|
-
|
|
268
|
+
FailSpinner(`Entity generation completed with batch failures (${(new Date().getTime() - step2StartTime.getTime()) / 1000}s) — see post-run CRUD validator for the authoritative gap report`);
|
|
265
269
|
}
|
|
266
270
|
// STEP 2(d) now that we've generated the SQL, let's create a combined file in each schema sub-directory for convenience for a DBA
|
|
267
|
-
|
|
268
|
-
const allEntityFiles = this.
|
|
269
|
-
|
|
271
|
+
StartSpinner('Creating combined SQL files...');
|
|
272
|
+
const allEntityFiles = this.CreateCombinedEntitySQLFiles(directory, includedEntities);
|
|
273
|
+
SucceedSpinner(`Created combined SQL files for ${allEntityFiles.length} file(s)`);
|
|
270
274
|
// STEP 2(e) ---- FINALLY, we execute SQL in proper dependency order
|
|
271
275
|
// Use temp batch files (which maintain CodeGen log order) if available, otherwise fall back to combined files
|
|
272
|
-
|
|
276
|
+
StartSpinner('Executing entity SQL files...');
|
|
273
277
|
const step2eStartTime = new Date();
|
|
274
278
|
let executionSuccess = false;
|
|
275
279
|
if (useProviderPhasedExecution) {
|
|
@@ -279,26 +283,26 @@ export class SQLCodeGenBase {
|
|
|
279
283
|
// undo the work the phased executor just did.
|
|
280
284
|
TempBatchFile.cleanup();
|
|
281
285
|
executionSuccess = true;
|
|
282
|
-
|
|
286
|
+
LogIf(configInfo?.verboseOutput ?? false, 'Skipping bulk SQL file execution — per-entity phased execution already ran');
|
|
283
287
|
}
|
|
284
288
|
else if (TempBatchFile.hasContent()) {
|
|
285
289
|
// Execute temp batch files in dependency order (matches CodeGen run log)
|
|
286
290
|
const tempFiles = TempBatchFile.getTempFilePaths();
|
|
287
|
-
|
|
291
|
+
LogIf(configInfo?.verboseOutput ?? false, `Executing ${tempFiles.length} temp batch file(s) in dependency order`);
|
|
288
292
|
executionSuccess = await this.SQLUtilityObject.executeSQLFiles(tempFiles, configInfo?.verboseOutput ?? false);
|
|
289
293
|
// Clean up temp files after execution
|
|
290
294
|
TempBatchFile.cleanup();
|
|
291
295
|
}
|
|
292
296
|
else {
|
|
293
297
|
// Fall back to combined files (for backward compatibility or if temp files weren't created)
|
|
294
|
-
|
|
298
|
+
LogIf(configInfo?.verboseOutput ?? false, `Executing ${allEntityFiles.length} combined file(s)`);
|
|
295
299
|
executionSuccess = await this.SQLUtilityObject.executeSQLFiles(allEntityFiles, configInfo?.verboseOutput ?? false);
|
|
296
300
|
}
|
|
297
301
|
// Initialize from entityGenSuccess so any per-batch failures from
|
|
298
302
|
// STEP 2(b)/(c) carry forward instead of being lost. Bug 2 fix.
|
|
299
303
|
let overallSuccess = entityGenSuccess;
|
|
300
304
|
if (!executionSuccess) {
|
|
301
|
-
|
|
305
|
+
FailSpinner('Failed to execute entity SQL files');
|
|
302
306
|
TempBatchFile.cleanup(); // Cleanup on error
|
|
303
307
|
overallSuccess = false;
|
|
304
308
|
// Continue to manage entity fields metadata even after SQL execution failure.
|
|
@@ -307,7 +311,7 @@ export class SQLCodeGenBase {
|
|
|
307
311
|
}
|
|
308
312
|
else {
|
|
309
313
|
const step2eEndTime = new Date();
|
|
310
|
-
|
|
314
|
+
SucceedSpinner(`SQL execution completed (${(step2eEndTime.getTime() - step2eStartTime.getTime()) / 1000}s)`);
|
|
311
315
|
}
|
|
312
316
|
const manageMD = MJGlobal.Instance.ClassFactory.CreateInstance(ManageMetadataBase);
|
|
313
317
|
// 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
|
|
@@ -324,13 +328,13 @@ export class SQLCodeGenBase {
|
|
|
324
328
|
...ManageMetadataBase.newEntityList,
|
|
325
329
|
...ManageMetadataBase.modifiedEntityList,
|
|
326
330
|
])];
|
|
327
|
-
|
|
331
|
+
StartSpinner('Managing entity fields metadata...');
|
|
328
332
|
if (!await manageMD.manageEntityFields(pool, configInfo.excludeSchemas, true, true, currentUser, false, false, pass2EntityFilter)) {
|
|
329
|
-
|
|
333
|
+
FailSpinner('Failed to manage entity fields');
|
|
330
334
|
overallSuccess = false;
|
|
331
335
|
}
|
|
332
336
|
else {
|
|
333
|
-
|
|
337
|
+
SucceedSpinner('Entity fields metadata updated');
|
|
334
338
|
}
|
|
335
339
|
// no logStatus/timer for this because manageEntityFields() has its own internal logging for this including the total, so it is redundant to log it here
|
|
336
340
|
// STEP 3.5 - Late-phase view regeneration
|
|
@@ -340,7 +344,7 @@ export class SQLCodeGenBase {
|
|
|
340
344
|
const regenList = ManageMetadataBase.EntitiesRequiringViewRegen;
|
|
341
345
|
if (regenList.length > 0) {
|
|
342
346
|
const uniqueEntityNames = [...new Set(regenList.map(e => e.EntityName))];
|
|
343
|
-
|
|
347
|
+
StartSpinner(`Regenerating ${uniqueEntityNames.length} entities with late-phase view changes...`);
|
|
344
348
|
// Refresh metadata to pick up DB changes from Step 3 (e.g., SupportsGeoCoding = 1)
|
|
345
349
|
const regenMd = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
|
|
346
350
|
await regenMd.Refresh();
|
|
@@ -355,7 +359,7 @@ export class SQLCodeGenBase {
|
|
|
355
359
|
.filter((e) => e !== null);
|
|
356
360
|
if (entitiesToRegen.length > 0) {
|
|
357
361
|
// Regenerate views + SPs for affected entities only
|
|
358
|
-
const regenResult = await this.
|
|
362
|
+
const regenResult = await this.GenerateAndExecuteEntitySQLToSeparateFiles({
|
|
359
363
|
pool,
|
|
360
364
|
entities: entitiesToRegen,
|
|
361
365
|
directory,
|
|
@@ -373,45 +377,45 @@ export class SQLCodeGenBase {
|
|
|
373
377
|
await manageMD.manageEntityFields(pool, configInfo.excludeSchemas, true, true, currentUser, true, false, lateRegenFilter);
|
|
374
378
|
// Apply reason-specific fixups
|
|
375
379
|
await this.applyLatePhaseFixups(pool, regenList, entitiesToRegen);
|
|
376
|
-
|
|
380
|
+
SucceedSpinner(`Regenerated ${entitiesToRegen.length} late-phase entities`);
|
|
377
381
|
}
|
|
378
382
|
else {
|
|
379
|
-
|
|
383
|
+
FailSpinner('Late-phase view regeneration failed');
|
|
380
384
|
overallSuccess = false;
|
|
381
385
|
}
|
|
382
386
|
}
|
|
383
387
|
else {
|
|
384
|
-
|
|
388
|
+
SucceedSpinner('No valid entities to regenerate');
|
|
385
389
|
}
|
|
386
390
|
}
|
|
387
391
|
// STEP 4- Apply permissions for THIS run's schemas only. Excluded-schema
|
|
388
392
|
// GRANT files are not generated (see STEP 2(c) note) and must not be
|
|
389
393
|
// executed: a missing sibling object would fail CodeGen with
|
|
390
394
|
// "Cannot find the object 'vw…'" even though this app's work succeeded.
|
|
391
|
-
|
|
395
|
+
StartSpinner('Applying permissions...');
|
|
392
396
|
const step4StartTime = new Date();
|
|
393
|
-
if (!await this.
|
|
394
|
-
|
|
397
|
+
if (!await this.ApplyPermissions(pool, directory, includedEntities)) {
|
|
398
|
+
FailSpinner('Failed to apply permissions');
|
|
395
399
|
overallSuccess = false;
|
|
396
400
|
}
|
|
397
401
|
else {
|
|
398
|
-
|
|
402
|
+
SucceedSpinner(`Permissions applied (${(new Date().getTime() - step4StartTime.getTime()) / 1000}s)`);
|
|
399
403
|
}
|
|
400
404
|
// STEP 4.5 - Emit sp_refreshview for modified custom base view entities into the
|
|
401
405
|
// migration log file so other environments pick up schema changes automatically.
|
|
402
406
|
this.emitCustomBaseViewRefreshes();
|
|
403
407
|
// STEP 5 - execute any custom SQL scripts that should run afterwards
|
|
404
|
-
|
|
408
|
+
StartSpinner('Running post-generation SQL scripts...');
|
|
405
409
|
const step5StartTime = new Date();
|
|
406
|
-
if (!await this.
|
|
407
|
-
|
|
410
|
+
if (!await this.RunCustomSQLScripts(pool, 'after-sql')) {
|
|
411
|
+
FailSpinner('Failed to run post-generation SQL scripts');
|
|
408
412
|
overallSuccess = false;
|
|
409
413
|
}
|
|
410
414
|
else {
|
|
411
|
-
|
|
415
|
+
SucceedSpinner(`Post-generation scripts completed (${(new Date().getTime() - step5StartTime.getTime()) / 1000}s)`);
|
|
412
416
|
}
|
|
413
417
|
if (overallSuccess) {
|
|
414
|
-
|
|
418
|
+
SucceedSpinner(`SQL CodeGen completed successfully (${((new Date().getTime() - startTime.getTime()) / 1000)}s total)`);
|
|
415
419
|
}
|
|
416
420
|
// now - we need to tell our metadata object to refresh itself
|
|
417
421
|
const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
|
|
@@ -425,6 +429,10 @@ export class SQLCodeGenBase {
|
|
|
425
429
|
return false;
|
|
426
430
|
}
|
|
427
431
|
}
|
|
432
|
+
/** @deprecated Use {@link ManageSQLScriptsAndExecution}. */
|
|
433
|
+
async manageSQLScriptsAndExecution(pool, entities, directory, currentUser) {
|
|
434
|
+
return this.ManageSQLScriptsAndExecution(pool, entities, directory, currentUser);
|
|
435
|
+
}
|
|
428
436
|
/**
|
|
429
437
|
* Apply reason-specific fixups after late-phase view regeneration.
|
|
430
438
|
* Each regen reason may require additional SQL to be executed and logged
|
|
@@ -446,9 +454,9 @@ export class SQLCodeGenBase {
|
|
|
446
454
|
await pool.query(lngSQL);
|
|
447
455
|
}
|
|
448
456
|
}
|
|
449
|
-
async
|
|
457
|
+
async RunCustomSQLScripts(pool, when) {
|
|
450
458
|
try {
|
|
451
|
-
const scripts =
|
|
459
|
+
const scripts = CustomSqlScripts(when);
|
|
452
460
|
let bSuccess = true;
|
|
453
461
|
if (scripts) {
|
|
454
462
|
for (let i = 0; i < scripts.length; ++i) {
|
|
@@ -466,6 +474,10 @@ export class SQLCodeGenBase {
|
|
|
466
474
|
return false;
|
|
467
475
|
}
|
|
468
476
|
}
|
|
477
|
+
/** @deprecated Use {@link RunCustomSQLScripts}. */
|
|
478
|
+
async runCustomSQLScripts(pool, when) {
|
|
479
|
+
return this.RunCustomSQLScripts(pool, when);
|
|
480
|
+
}
|
|
469
481
|
/**
|
|
470
482
|
* Builds the once-per-run {@link FieldSecurityRunContext} and hands it to the dialect
|
|
471
483
|
* provider (whose permission emitters are synchronous string builders and cannot query).
|
|
@@ -553,19 +565,19 @@ export class SQLCodeGenBase {
|
|
|
553
565
|
logStatus(` Field-security run context ready: ${roleSQLNameByID.size} custom role(s), ${serviceProtected.size} service-protected role(s), ${catalog.size} object(s) with managed permissions`);
|
|
554
566
|
}
|
|
555
567
|
catch (e) {
|
|
556
|
-
|
|
568
|
+
LogWarning(` ⚠️ Could not build the field-security run context (${e instanceof Error ? e.message : String(e)}). ` +
|
|
557
569
|
`Permission emission degrades to grants-only for this run: no field-security DENYs and no reconciliation REVOKEs.`);
|
|
558
570
|
this._dbProvider.SetFieldSecurityRunContext(null);
|
|
559
571
|
}
|
|
560
572
|
}
|
|
561
|
-
async
|
|
573
|
+
async ApplyPermissions(pool, directory, entities, batchSize = 5) {
|
|
562
574
|
try {
|
|
563
575
|
let bSuccess = true;
|
|
564
576
|
for (let i = 0; i < entities.length; i += batchSize) {
|
|
565
577
|
const batch = entities.slice(i, i + batchSize);
|
|
566
578
|
const promises = batch.map(async (e) => {
|
|
567
579
|
// generate the file names for the entity
|
|
568
|
-
const files = this.
|
|
580
|
+
const files = this.GetEntityPermissionFileNames(e);
|
|
569
581
|
let innerSuccess = true;
|
|
570
582
|
for (const f of files) {
|
|
571
583
|
const fullPath = path.join(directory, f);
|
|
@@ -608,6 +620,10 @@ export class SQLCodeGenBase {
|
|
|
608
620
|
return false;
|
|
609
621
|
}
|
|
610
622
|
}
|
|
623
|
+
/** @deprecated Use {@link ApplyPermissions}. */
|
|
624
|
+
async applyPermissions(pool, directory, entities, batchSize = 5) {
|
|
625
|
+
return this.ApplyPermissions(pool, directory, entities, batchSize);
|
|
626
|
+
}
|
|
611
627
|
/**
|
|
612
628
|
* This function will handle the process of creating all of the generated objects like base view, stored procedures, etc. for all entities in the entities array. It will generate the SQL for each entity and then execute it.
|
|
613
629
|
* @param ds The DataSource object to use to execute the SQL
|
|
@@ -615,7 +631,7 @@ export class SQLCodeGenBase {
|
|
|
615
631
|
* @param directory The directory to save the generated SQL files to
|
|
616
632
|
* @param onlyPermissions If true, only the permissions files will be generated and executed, not the actual SQL files. Use this if you are simply setting permission changes but no actual changes to the entities have occured.
|
|
617
633
|
*/
|
|
618
|
-
async
|
|
634
|
+
async GenerateAndExecuteEntitySQLToSeparateFiles(options) {
|
|
619
635
|
if (!options.batchSize)
|
|
620
636
|
options.batchSize = 5; // default to 5 if not specified
|
|
621
637
|
const files = [];
|
|
@@ -637,7 +653,7 @@ export class SQLCodeGenBase {
|
|
|
637
653
|
logError(`SKIPPING SQL GENERATION: Entity ${e.Name} has no primary key field in metadata. If using soft primary keys, ensure metadata was refreshed after applySoftPKFKConfig().`);
|
|
638
654
|
return { entity: e, result: { Success: false, Files: [] } };
|
|
639
655
|
}
|
|
640
|
-
const result = await this.
|
|
656
|
+
const result = await this.GenerateAndExecuteSingleEntitySQLToSeparateFiles({
|
|
641
657
|
pool: options.pool,
|
|
642
658
|
entity: e,
|
|
643
659
|
directory: options.directory,
|
|
@@ -673,7 +689,11 @@ export class SQLCodeGenBase {
|
|
|
673
689
|
return { Success: false, Files: files };
|
|
674
690
|
}
|
|
675
691
|
}
|
|
676
|
-
|
|
692
|
+
/** @deprecated Use {@link GenerateAndExecuteEntitySQLToSeparateFiles}. */
|
|
693
|
+
async generateAndExecuteEntitySQLToSeparateFiles(options) {
|
|
694
|
+
return this.GenerateAndExecuteEntitySQLToSeparateFiles(options);
|
|
695
|
+
}
|
|
696
|
+
DeleteGeneratedEntityFiles(directory, entities) {
|
|
677
697
|
try {
|
|
678
698
|
// for the schemas associated with the specified entities, clean out all the generated files
|
|
679
699
|
const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index).sort();
|
|
@@ -702,7 +722,11 @@ export class SQLCodeGenBase {
|
|
|
702
722
|
logError(e);
|
|
703
723
|
}
|
|
704
724
|
}
|
|
705
|
-
|
|
725
|
+
/** @deprecated Use {@link DeleteGeneratedEntityFiles}. */
|
|
726
|
+
deleteGeneratedEntityFiles(directory, entities) {
|
|
727
|
+
return this.DeleteGeneratedEntityFiles(directory, entities);
|
|
728
|
+
}
|
|
729
|
+
CreateCombinedEntitySQLFiles(directory, entities) {
|
|
706
730
|
// first, get a disinct list of schemanames from the entities
|
|
707
731
|
const files = [];
|
|
708
732
|
const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index).sort();
|
|
@@ -710,17 +734,21 @@ export class SQLCodeGenBase {
|
|
|
710
734
|
// generate the all-entities.sql file and all-entities.permissions.sql file in each schema folder
|
|
711
735
|
const fullPath = path.join(directory, s);
|
|
712
736
|
if (fs.statSync(fullPath).isDirectory()) {
|
|
713
|
-
|
|
737
|
+
CombineFiles(fullPath, '_all_entities.sql', '*.generated.sql', true);
|
|
714
738
|
files.push(path.join(fullPath, '_all_entities.sql'));
|
|
715
|
-
|
|
739
|
+
CombineFiles(fullPath, '_all_entities.permissions.sql', '*.permissions.generated.sql', true);
|
|
716
740
|
files.push(path.join(fullPath, '_all_entities.permissions.sql'));
|
|
717
741
|
}
|
|
718
742
|
}
|
|
719
743
|
return files;
|
|
720
744
|
}
|
|
721
|
-
|
|
745
|
+
/** @deprecated Use {@link CreateCombinedEntitySQLFiles}. */
|
|
746
|
+
createCombinedEntitySQLFiles(directory, entities) {
|
|
747
|
+
return this.CreateCombinedEntitySQLFiles(directory, entities);
|
|
748
|
+
}
|
|
749
|
+
async GenerateAndExecuteSingleEntitySQLToSeparateFiles(options) {
|
|
722
750
|
try {
|
|
723
|
-
const { sql, permissionsSQL, files } = await this.
|
|
751
|
+
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
|
|
724
752
|
if (options.skipExecution) {
|
|
725
753
|
return { Success: true, Files: files };
|
|
726
754
|
}
|
|
@@ -731,7 +759,7 @@ export class SQLCodeGenBase {
|
|
|
731
759
|
// is explicitly gated on phase 1 success so we never create fn_*
|
|
732
760
|
// against a missing or stale view rowtype.
|
|
733
761
|
if (!options.onlyPermissions && this._dbProvider.executeEntityPhased) {
|
|
734
|
-
const phaseResult = await this.
|
|
762
|
+
const phaseResult = await this.ExecuteEntityInPhases(options.pool, options.entity, options.willRegenerate);
|
|
735
763
|
if (!phaseResult.success) {
|
|
736
764
|
const where = phaseResult.phase ?? 'unknown';
|
|
737
765
|
logError(`Phased execution for ${options.entity.SchemaName}.${options.entity.Name} failed in phase "${where}": ${phaseResult.error?.message ?? '(no message)'}`);
|
|
@@ -751,13 +779,17 @@ export class SQLCodeGenBase {
|
|
|
751
779
|
return { Success: false, Files: [] };
|
|
752
780
|
}
|
|
753
781
|
}
|
|
782
|
+
/** @deprecated Use {@link GenerateAndExecuteSingleEntitySQLToSeparateFiles}. */
|
|
783
|
+
async generateAndExecuteSingleEntitySQLToSeparateFiles(options) {
|
|
784
|
+
return this.GenerateAndExecuteSingleEntitySQLToSeparateFiles(options);
|
|
785
|
+
}
|
|
754
786
|
/**
|
|
755
787
|
* Generates the entity's per-phase SQL pieces (view, CRUD functions, view
|
|
756
788
|
* permissions) and dispatches them to the provider's phased executor. Lives
|
|
757
789
|
* here because it needs access to the internal generators; the provider
|
|
758
790
|
* doesn't own context building for the view.
|
|
759
791
|
*/
|
|
760
|
-
async
|
|
792
|
+
async ExecuteEntityInPhases(pool, entity, willRegenerate) {
|
|
761
793
|
// External-data-source entities have no physical MJ table/view/sproc — their data is
|
|
762
794
|
// proxied live. Skip all DDL generation/execution (mirrors the early-return in
|
|
763
795
|
// generateSingleEntitySQLToSeparateFiles). Without this, the view phase would
|
|
@@ -784,7 +816,7 @@ export class SQLCodeGenBase {
|
|
|
784
816
|
const generatesView = (entity.BaseViewGenerated || entity.HasLayeredBaseView) && !entity.VirtualEntity;
|
|
785
817
|
const tvfSQL = generatesView ? this.generateRecursiveFKTVFs(entity) : '';
|
|
786
818
|
const viewPieces = generatesView
|
|
787
|
-
? await this.
|
|
819
|
+
? await this.GenerateBaseViewPieces(pool, entity)
|
|
788
820
|
: { viewSQL: '', viewPermSQL: '' };
|
|
789
821
|
const crudCreateSQL = entity.AllowCreateAPI && entity.spCreateGenerated && !entity.VirtualEntity
|
|
790
822
|
? this.generateSPCreate(entity)
|
|
@@ -806,6 +838,10 @@ export class SQLCodeGenBase {
|
|
|
806
838
|
willRegenerate,
|
|
807
839
|
});
|
|
808
840
|
}
|
|
841
|
+
/** @deprecated Use {@link ExecuteEntityInPhases}. */
|
|
842
|
+
async executeEntityInPhases(pool, entity, willRegenerate) {
|
|
843
|
+
return this.ExecuteEntityInPhases(pool, entity, willRegenerate);
|
|
844
|
+
}
|
|
809
845
|
/**
|
|
810
846
|
* Concatenates the hierarchy TVF SQL for every recursive ParentID-style FK
|
|
811
847
|
* on this entity. Mirrors the inline TVF generation in
|
|
@@ -885,7 +921,7 @@ export class SQLCodeGenBase {
|
|
|
885
921
|
}
|
|
886
922
|
catch (e) {
|
|
887
923
|
// Non-fatal — if we can't compare, the existing logic still runs
|
|
888
|
-
|
|
924
|
+
LogIf(configInfo.verboseOutput, `Could not compare base view for ${entity.Name}: ${e}`);
|
|
889
925
|
return false;
|
|
890
926
|
}
|
|
891
927
|
}
|
|
@@ -1004,7 +1040,7 @@ export class SQLCodeGenBase {
|
|
|
1004
1040
|
SQLLogging.appendToSQLLogFile(sql, description);
|
|
1005
1041
|
TempBatchFile.appendToTempBatchFile(sql, entity.SchemaName);
|
|
1006
1042
|
}
|
|
1007
|
-
|
|
1043
|
+
LogIf(configInfo.verboseOutput, `SQL Generated for ${entity.Name}: ${description}`);
|
|
1008
1044
|
}
|
|
1009
1045
|
/**
|
|
1010
1046
|
* Emits sp_refreshview statements into the migration log file for modified custom base view
|
|
@@ -1037,7 +1073,7 @@ export class SQLCodeGenBase {
|
|
|
1037
1073
|
!e.BaseViewGenerated &&
|
|
1038
1074
|
e.IncludeInAPI &&
|
|
1039
1075
|
!e.VirtualEntity &&
|
|
1040
|
-
|
|
1076
|
+
EntityInCustomBaseViewRefreshScope(e.SchemaName, configInfo.excludeSchemas ?? [], configInfo.includeSchemas));
|
|
1041
1077
|
}
|
|
1042
1078
|
/**
|
|
1043
1079
|
* Builds a combined SQL string of sp_refreshview statements for the given entities.
|
|
@@ -1074,7 +1110,7 @@ export class SQLCodeGenBase {
|
|
|
1074
1110
|
})
|
|
1075
1111
|
.join('\n');
|
|
1076
1112
|
}
|
|
1077
|
-
async
|
|
1113
|
+
async GenerateSingleEntitySQLToSeparateFiles(options) {
|
|
1078
1114
|
const files = [];
|
|
1079
1115
|
try {
|
|
1080
1116
|
// External-data-source entities are backed by a remote system (Snowflake/MongoDB/
|
|
@@ -1101,18 +1137,18 @@ export class SQLCodeGenBase {
|
|
|
1101
1137
|
// Indexes for Fkeys for the table (skip for virtual entities — views can't have indexes)
|
|
1102
1138
|
if (!options.onlyPermissions && !options.entity.VirtualEntity) {
|
|
1103
1139
|
const forceIndexes = !!(configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.indexes);
|
|
1104
|
-
const shouldGenerateIndexes =
|
|
1105
|
-
const indexSQL = shouldGenerateIndexes ? this.
|
|
1140
|
+
const shouldGenerateIndexes = AutoIndexForeignKeys() || forceIndexes;
|
|
1141
|
+
const indexSQL = shouldGenerateIndexes ? this.GenerateIndexesForForeignKeys(options.pool, options.entity) : ''; // generate indexes if auto-indexing is on OR force regeneration is enabled
|
|
1106
1142
|
// The soft-PK index rides in the same generated file (and therefore the same
|
|
1107
1143
|
// migration log), but on its own setting — see generateIndexForSoftPrimaryKey.
|
|
1108
1144
|
// Appended rather than prepended so the file's existing content stays
|
|
1109
1145
|
// byte-identical for every entity that has no soft PK, which is nearly all of
|
|
1110
1146
|
// them: writeFileIfChanged compares content, and reordering would rewrite the
|
|
1111
1147
|
// whole tree for no functional gain.
|
|
1112
|
-
const softPKSQL = (
|
|
1113
|
-
? this.
|
|
1148
|
+
const softPKSQL = (AutoIndexSoftPrimaryKeys() || forceIndexes)
|
|
1149
|
+
? this.GenerateIndexForSoftPrimaryKey(options.entity)
|
|
1114
1150
|
: '';
|
|
1115
|
-
const s = this.
|
|
1151
|
+
const s = this.GenerateSingleEntitySQLFileHeader(options.entity, 'Index for Foreign Keys')
|
|
1116
1152
|
+ indexSQL
|
|
1117
1153
|
+ (softPKSQL ? (indexSQL ? '\n\n' : '') + softPKSQL : '');
|
|
1118
1154
|
if (options.writeFiles) {
|
|
@@ -1153,7 +1189,7 @@ export class SQLCodeGenBase {
|
|
|
1153
1189
|
}
|
|
1154
1190
|
}
|
|
1155
1191
|
// Generate the base view (which may reference the TVFs created above)
|
|
1156
|
-
const s = this.
|
|
1192
|
+
const s = this.GenerateSingleEntitySQLFileHeader(options.entity, options.entity.GeneratedViewName) + await this.GenerateBaseView(options.pool, options.entity);
|
|
1157
1193
|
// Compare generated view SQL against what's currently in the database.
|
|
1158
1194
|
// If the SELECT body differs, force-log just this base view via the forceLog flag
|
|
1159
1195
|
// so logging stays centralized in logSQLForNewOrModifiedEntity.
|
|
@@ -1176,7 +1212,7 @@ export class SQLCodeGenBase {
|
|
|
1176
1212
|
sRet += s + '\n' + this._dbProvider.BatchSeparator + '\n';
|
|
1177
1213
|
}
|
|
1178
1214
|
// always generate permissions for the base view
|
|
1179
|
-
const permHeader = this.
|
|
1215
|
+
const permHeader = this.GenerateSingleEntitySQLFileHeader(options.entity, 'Permissions for ' + options.entity.BaseView);
|
|
1180
1216
|
const rawPermBody = this.generateViewPermissions(options.entity);
|
|
1181
1217
|
// Guarded for the same reason as in generateBaseViewPieces: these GRANTs target the
|
|
1182
1218
|
// application-owned BaseView, which does not exist during a layered entity's bootstrap
|
|
@@ -1202,12 +1238,12 @@ export class SQLCodeGenBase {
|
|
|
1202
1238
|
}
|
|
1203
1239
|
// CREATE SP
|
|
1204
1240
|
if (options.entity.AllowCreateAPI && !options.entity.VirtualEntity) {
|
|
1205
|
-
const spName = this.
|
|
1241
|
+
const spName = this.GetSPName(options.entity, SPType.Create);
|
|
1206
1242
|
// Only generate if spCreateGenerated is true (respects custom SPs where it's false)
|
|
1207
1243
|
// forceRegeneration only forces regeneration of SPs where spCreateGenerated=true
|
|
1208
1244
|
if (!options.onlyPermissions && options.entity.spCreateGenerated) {
|
|
1209
1245
|
// generate the create SP
|
|
1210
|
-
const s = this.
|
|
1246
|
+
const s = this.GenerateSingleEntitySQLFileHeader(options.entity, spName) + this.generateSPCreate(options.entity);
|
|
1211
1247
|
if (options.writeFiles) {
|
|
1212
1248
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
|
|
1213
1249
|
this.writeFileIfChanged(filePath, s);
|
|
@@ -1232,12 +1268,12 @@ export class SQLCodeGenBase {
|
|
|
1232
1268
|
}
|
|
1233
1269
|
// UPDATE SP
|
|
1234
1270
|
if (options.entity.AllowUpdateAPI && !options.entity.VirtualEntity) {
|
|
1235
|
-
const spName = this.
|
|
1271
|
+
const spName = this.GetSPName(options.entity, SPType.Update);
|
|
1236
1272
|
// Only generate if spUpdateGenerated is true (respects custom SPs where it's false)
|
|
1237
1273
|
// forceRegeneration only forces regeneration of SPs where spUpdateGenerated=true
|
|
1238
1274
|
if (!options.onlyPermissions && options.entity.spUpdateGenerated) {
|
|
1239
1275
|
// generate the update SP
|
|
1240
|
-
const s = this.
|
|
1276
|
+
const s = this.GenerateSingleEntitySQLFileHeader(options.entity, spName) + this.generateSPUpdate(options.entity);
|
|
1241
1277
|
if (options.writeFiles) {
|
|
1242
1278
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
|
|
1243
1279
|
this.writeFileIfChanged(filePath, s);
|
|
@@ -1262,7 +1298,7 @@ export class SQLCodeGenBase {
|
|
|
1262
1298
|
}
|
|
1263
1299
|
// DELETE SP
|
|
1264
1300
|
if (options.entity.AllowDeleteAPI && !options.entity.VirtualEntity) {
|
|
1265
|
-
const spName = this.
|
|
1301
|
+
const spName = this.GetSPName(options.entity, SPType.Delete);
|
|
1266
1302
|
// Only generate if spDeleteGenerated is true (respects custom SPs where it's false)
|
|
1267
1303
|
// OR if this entity has cascade delete dependencies that require regeneration
|
|
1268
1304
|
// forceRegeneration only forces regeneration of SPs where spDeleteGenerated=true
|
|
@@ -1273,7 +1309,7 @@ export class SQLCodeGenBase {
|
|
|
1273
1309
|
if (this.entitiesNeedingDeleteSPRegeneration.has(options.entity.ID)) {
|
|
1274
1310
|
logStatus(` Regenerating ${spName} due to cascade dependency changes`);
|
|
1275
1311
|
}
|
|
1276
|
-
const s = this.
|
|
1312
|
+
const s = this.GenerateSingleEntitySQLFileHeader(options.entity, spName) + await this.generateSPDelete(options.entity, options.pool);
|
|
1277
1313
|
if (options.writeFiles) {
|
|
1278
1314
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
|
|
1279
1315
|
this.writeFileIfChanged(filePath, s);
|
|
@@ -1299,7 +1335,7 @@ export class SQLCodeGenBase {
|
|
|
1299
1335
|
// check to see if the options.entity supports full text search or not
|
|
1300
1336
|
if (options.entity.FullTextSearchEnabled || (configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.fullTextSearch)) {
|
|
1301
1337
|
// always generate the code so we can get the function name from the below function call
|
|
1302
|
-
const ft = await this.
|
|
1338
|
+
const ft = await this.GenerateEntityFullTextSearchSQL(options.pool, options.entity);
|
|
1303
1339
|
if (!options.onlyPermissions) {
|
|
1304
1340
|
// only write the actual sql out if we're not only generating permissions
|
|
1305
1341
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', options.entity.SchemaName, options.entity.BaseTable, false, true));
|
|
@@ -1331,9 +1367,17 @@ export class SQLCodeGenBase {
|
|
|
1331
1367
|
return null;
|
|
1332
1368
|
}
|
|
1333
1369
|
}
|
|
1334
|
-
|
|
1370
|
+
/** @deprecated Use {@link GenerateSingleEntitySQLToSeparateFiles}. */
|
|
1371
|
+
async generateSingleEntitySQLToSeparateFiles(options) {
|
|
1372
|
+
return this.GenerateSingleEntitySQLToSeparateFiles(options);
|
|
1373
|
+
}
|
|
1374
|
+
GetSPName(entity, type) {
|
|
1335
1375
|
return this._dbProvider.getCRUDRoutineName(entity, type);
|
|
1336
1376
|
}
|
|
1377
|
+
/** @deprecated Use {@link GetSPName}. */
|
|
1378
|
+
getSPName(entity, type) {
|
|
1379
|
+
return this.GetSPName(entity, type);
|
|
1380
|
+
}
|
|
1337
1381
|
/**
|
|
1338
1382
|
* Snapshot every CRUD routine currently in the database (one query) so
|
|
1339
1383
|
* generate-and-log can force-emit CREATE PROC for objects that vanished
|
|
@@ -1357,7 +1401,7 @@ export class SQLCodeGenBase {
|
|
|
1357
1401
|
}
|
|
1358
1402
|
}
|
|
1359
1403
|
catch (e) {
|
|
1360
|
-
|
|
1404
|
+
LogWarning(`Could not load existing CRUD routines (missing-proc self-heal disabled this run): ${e instanceof Error ? e.message : String(e)}`);
|
|
1361
1405
|
this.existingRoutines = null;
|
|
1362
1406
|
}
|
|
1363
1407
|
}
|
|
@@ -1370,7 +1414,7 @@ export class SQLCodeGenBase {
|
|
|
1370
1414
|
forceLogForRoutine(entity, routineName, baseViewChanged) {
|
|
1371
1415
|
return baseViewChanged || this.isRoutineMissing(entity.SchemaName, routineName);
|
|
1372
1416
|
}
|
|
1373
|
-
|
|
1417
|
+
GetEntityPermissionFileNames(entity) {
|
|
1374
1418
|
const files = [];
|
|
1375
1419
|
// all entities have a base view - and we always generate permissions for the base view even if not generated base view
|
|
1376
1420
|
files.push(this.SQLUtilityObject.getDBObjectFileName('view', entity.SchemaName, entity.BaseView, true, true));
|
|
@@ -1378,16 +1422,20 @@ export class SQLCodeGenBase {
|
|
|
1378
1422
|
if (!entity.VirtualEntity) {
|
|
1379
1423
|
// only add each SP file if the Allow flags are set to true, doesn't matter if the SPs are generated or not, we always generate permissions
|
|
1380
1424
|
if (entity.AllowCreateAPI)
|
|
1381
|
-
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.
|
|
1425
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.GetSPName(entity, SPType.Create), true, true));
|
|
1382
1426
|
if (entity.AllowUpdateAPI)
|
|
1383
|
-
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.
|
|
1427
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.GetSPName(entity, SPType.Update), true, true));
|
|
1384
1428
|
if (entity.AllowDeleteAPI)
|
|
1385
|
-
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.
|
|
1429
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.GetSPName(entity, SPType.Delete), true, true));
|
|
1386
1430
|
}
|
|
1387
1431
|
if (entity.FullTextSearchEnabled)
|
|
1388
1432
|
files.push(this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', entity.SchemaName, entity.BaseTable, true, true));
|
|
1389
1433
|
return files;
|
|
1390
1434
|
}
|
|
1435
|
+
/** @deprecated Use {@link GetEntityPermissionFileNames}. */
|
|
1436
|
+
getEntityPermissionFileNames(entity) {
|
|
1437
|
+
return this.GetEntityPermissionFileNames(entity);
|
|
1438
|
+
}
|
|
1391
1439
|
/**
|
|
1392
1440
|
* Deprecated - do not use
|
|
1393
1441
|
* @deprecated
|
|
@@ -1399,7 +1447,7 @@ export class SQLCodeGenBase {
|
|
|
1399
1447
|
let sOutput = '';
|
|
1400
1448
|
if (entity.BaseViewGenerated && !entity.VirtualEntity)
|
|
1401
1449
|
// generated the base view (will include permissions)
|
|
1402
|
-
sOutput += await this.
|
|
1450
|
+
sOutput += await this.GenerateBaseView(pool, entity) + '\n\n';
|
|
1403
1451
|
else
|
|
1404
1452
|
// still generate the permissions for the view even if a custom view
|
|
1405
1453
|
sOutput += this.generateViewPermissions(entity) + '\n\n';
|
|
@@ -1429,11 +1477,11 @@ export class SQLCodeGenBase {
|
|
|
1429
1477
|
}
|
|
1430
1478
|
// check to see if the entity supports full text search or not
|
|
1431
1479
|
if (entity.FullTextSearchEnabled) {
|
|
1432
|
-
sOutput += await this.
|
|
1480
|
+
sOutput += await this.GenerateEntityFullTextSearchSQL(pool, entity) + '\n\n';
|
|
1433
1481
|
}
|
|
1434
1482
|
return sOutput;
|
|
1435
1483
|
}
|
|
1436
|
-
async
|
|
1484
|
+
async GenerateEntityFullTextSearchSQL(pool, entity) {
|
|
1437
1485
|
const searchFields = entity.Fields.filter(f => f.FullTextSearchEnabled);
|
|
1438
1486
|
// Validate search fields exist when needed
|
|
1439
1487
|
if (entity.FullTextIndexGenerated && searchFields.length === 0)
|
|
@@ -1443,7 +1491,7 @@ export class SQLCodeGenBase {
|
|
|
1443
1491
|
// Get the primary key index name (needed for full text index)
|
|
1444
1492
|
let primaryKeyIndexName = '';
|
|
1445
1493
|
if (entity.FullTextIndexGenerated) {
|
|
1446
|
-
primaryKeyIndexName = await this.
|
|
1494
|
+
primaryKeyIndexName = await this.GetEntityPrimaryKeyIndexName(pool, entity);
|
|
1447
1495
|
}
|
|
1448
1496
|
// If the function name is not set, save it to the DB
|
|
1449
1497
|
const functionName = entity.FullTextSearchFunction && entity.FullTextSearchFunction.length > 0
|
|
@@ -1468,7 +1516,11 @@ export class SQLCodeGenBase {
|
|
|
1468
1516
|
}
|
|
1469
1517
|
return result;
|
|
1470
1518
|
}
|
|
1471
|
-
|
|
1519
|
+
/** @deprecated Use {@link GenerateEntityFullTextSearchSQL}. */
|
|
1520
|
+
async generateEntityFullTextSearchSQL(pool, entity) {
|
|
1521
|
+
return this.GenerateEntityFullTextSearchSQL(pool, entity);
|
|
1522
|
+
}
|
|
1523
|
+
async GetEntityPrimaryKeyIndexName(pool, entity) {
|
|
1472
1524
|
const sSQL = this._dbProvider.getPrimaryKeyIndexNameSQL(entity.SchemaName, entity.BaseTable);
|
|
1473
1525
|
const resultResult = await pool.query(sSQL);
|
|
1474
1526
|
const result = resultResult.recordset;
|
|
@@ -1477,20 +1529,36 @@ export class SQLCodeGenBase {
|
|
|
1477
1529
|
else
|
|
1478
1530
|
throw new Error(`Could not find primary key index for entity ${entity.Name}`);
|
|
1479
1531
|
}
|
|
1480
|
-
|
|
1532
|
+
/** @deprecated Use {@link GetEntityPrimaryKeyIndexName}. */
|
|
1533
|
+
async getEntityPrimaryKeyIndexName(pool, entity) {
|
|
1534
|
+
return this.GetEntityPrimaryKeyIndexName(pool, entity);
|
|
1535
|
+
}
|
|
1536
|
+
GenerateAllEntitiesSQLFileHeader() {
|
|
1481
1537
|
return this._dbProvider.generateAllEntitiesSQLFileHeader();
|
|
1482
1538
|
}
|
|
1483
|
-
|
|
1539
|
+
/** @deprecated Use {@link GenerateAllEntitiesSQLFileHeader}. */
|
|
1540
|
+
generateAllEntitiesSQLFileHeader() {
|
|
1541
|
+
return this.GenerateAllEntitiesSQLFileHeader();
|
|
1542
|
+
}
|
|
1543
|
+
GenerateSingleEntitySQLFileHeader(entity, itemName) {
|
|
1484
1544
|
return this._dbProvider.generateSQLFileHeader(entity, itemName);
|
|
1485
1545
|
}
|
|
1546
|
+
/** @deprecated Use {@link GenerateSingleEntitySQLFileHeader}. */
|
|
1547
|
+
generateSingleEntitySQLFileHeader(entity, itemName) {
|
|
1548
|
+
return this.GenerateSingleEntitySQLFileHeader(entity, itemName);
|
|
1549
|
+
}
|
|
1486
1550
|
/**
|
|
1487
1551
|
* Generates the SQL for creating indexes for the foreign keys in the entity.
|
|
1488
1552
|
* Delegates to the database provider for platform-specific index DDL.
|
|
1489
1553
|
*/
|
|
1490
|
-
|
|
1554
|
+
GenerateIndexesForForeignKeys(pool, entity) {
|
|
1491
1555
|
const indexStatements = this._dbProvider.generateForeignKeyIndexes(entity);
|
|
1492
1556
|
return indexStatements.join('\n\n');
|
|
1493
1557
|
}
|
|
1558
|
+
/** @deprecated Use {@link GenerateIndexesForForeignKeys}. */
|
|
1559
|
+
generateIndexesForForeignKeys(pool, entity) {
|
|
1560
|
+
return this.GenerateIndexesForForeignKeys(pool, entity);
|
|
1561
|
+
}
|
|
1494
1562
|
/**
|
|
1495
1563
|
* Generates the composite index covering the entity's SOFT primary key. Empty for every
|
|
1496
1564
|
* entity with a real `PRIMARY KEY` constraint, which is nearly all of them.
|
|
@@ -1500,10 +1568,14 @@ export class SQLCodeGenBase {
|
|
|
1500
1568
|
* different concern from indexing foreign keys for joins and filters, and an operator's
|
|
1501
1569
|
* opinion about one is not an opinion about the other.
|
|
1502
1570
|
*/
|
|
1503
|
-
|
|
1571
|
+
GenerateIndexForSoftPrimaryKey(entity) {
|
|
1504
1572
|
const indexStatements = this._dbProvider.generateSoftPrimaryKeyIndex(entity);
|
|
1505
1573
|
return indexStatements.join('\n\n');
|
|
1506
1574
|
}
|
|
1575
|
+
/** @deprecated Use {@link GenerateIndexForSoftPrimaryKey}. */
|
|
1576
|
+
generateIndexForSoftPrimaryKey(entity) {
|
|
1577
|
+
return this.GenerateIndexForSoftPrimaryKey(entity);
|
|
1578
|
+
}
|
|
1507
1579
|
/**
|
|
1508
1580
|
* Detects self-referential foreign keys in an entity (e.g., ParentTaskID pointing back to Task table)
|
|
1509
1581
|
* Returns array of field info objects representing recursive relationships
|
|
@@ -1517,7 +1589,7 @@ export class SQLCodeGenBase {
|
|
|
1517
1589
|
return [];
|
|
1518
1590
|
}
|
|
1519
1591
|
if (entity.PrimaryKeys.length !== 1) {
|
|
1520
|
-
|
|
1592
|
+
LogWarning(`[Hierarchy] Entity '${entity.Name}' has ${hierarchyFKs.length} hierarchy foreign key(s) ` +
|
|
1521
1593
|
`(${hierarchyFKs.map(f => f.Name).join(', ')}), but has ${entity.PrimaryKeys.length} primary key fields. ` +
|
|
1522
1594
|
`MemberJunction hierarchy TVF generation and traversal require a single-column primary key. Skipping hierarchy generation for this entity.`);
|
|
1523
1595
|
return [];
|
|
@@ -1535,25 +1607,25 @@ export class SQLCodeGenBase {
|
|
|
1535
1607
|
return [
|
|
1536
1608
|
{
|
|
1537
1609
|
functionName: metaFnName,
|
|
1538
|
-
sql: this.
|
|
1610
|
+
sql: this.GenerateSingleEntitySQLFileHeader(entity, metaFnName) + this._dbProvider.generateHierarchyMetaFunction(entity, field),
|
|
1539
1611
|
description: `Hierarchy Metadata Function SQL for ${entity.Name}.${field.Name}`,
|
|
1540
1612
|
fieldName: field.Name
|
|
1541
1613
|
},
|
|
1542
1614
|
{
|
|
1543
1615
|
functionName: descFnName,
|
|
1544
|
-
sql: this.
|
|
1616
|
+
sql: this.GenerateSingleEntitySQLFileHeader(entity, descFnName) + this._dbProvider.generateDescendantsFunction(entity, field),
|
|
1545
1617
|
description: `Descendants Traversal Function SQL for ${entity.Name}.${field.Name}`,
|
|
1546
1618
|
fieldName: field.Name
|
|
1547
1619
|
},
|
|
1548
1620
|
{
|
|
1549
1621
|
functionName: ancFnName,
|
|
1550
|
-
sql: this.
|
|
1622
|
+
sql: this.GenerateSingleEntitySQLFileHeader(entity, ancFnName) + this._dbProvider.generateAncestorsFunction(entity, field),
|
|
1551
1623
|
description: `Ancestors Traversal Function SQL for ${entity.Name}.${field.Name}`,
|
|
1552
1624
|
fieldName: field.Name
|
|
1553
1625
|
},
|
|
1554
1626
|
{
|
|
1555
1627
|
functionName: rootFnName,
|
|
1556
|
-
sql: this.
|
|
1628
|
+
sql: this.GenerateSingleEntitySQLFileHeader(entity, rootFnName) + this._dbProvider.generateRootIDFunction(entity, field),
|
|
1557
1629
|
description: `Root ID Function SQL for ${entity.Name}.${field.Name}`,
|
|
1558
1630
|
fieldName: field.Name
|
|
1559
1631
|
}
|
|
@@ -1681,10 +1753,14 @@ export class SQLCodeGenBase {
|
|
|
1681
1753
|
}
|
|
1682
1754
|
return joins.join('\n');
|
|
1683
1755
|
}
|
|
1684
|
-
async
|
|
1685
|
-
const { viewSQL, viewPermSQL } = await this.
|
|
1756
|
+
async GenerateBaseView(pool, entity) {
|
|
1757
|
+
const { viewSQL, viewPermSQL } = await this.GenerateBaseViewPieces(pool, entity);
|
|
1686
1758
|
return viewSQL + viewPermSQL;
|
|
1687
1759
|
}
|
|
1760
|
+
/** @deprecated Use {@link GenerateBaseView}. */
|
|
1761
|
+
async generateBaseView(pool, entity) {
|
|
1762
|
+
return this.GenerateBaseView(pool, entity);
|
|
1763
|
+
}
|
|
1688
1764
|
/**
|
|
1689
1765
|
* Same as `generateBaseView`, but returns the view DDL and the GRANT/permissions
|
|
1690
1766
|
* DDL as separate strings rather than concatenating. Used by phased per-entity
|
|
@@ -1693,9 +1769,9 @@ export class SQLCodeGenBase {
|
|
|
1693
1769
|
* phase — concatenating them would prevent the executor from detecting where
|
|
1694
1770
|
* a failure actually occurred.
|
|
1695
1771
|
*/
|
|
1696
|
-
async
|
|
1772
|
+
async GenerateBaseViewPieces(pool, entity) {
|
|
1697
1773
|
const classNameFirstChar = entity.BaseTableCodeName.charAt(0).toLowerCase();
|
|
1698
|
-
let relatedFieldsString = await this.
|
|
1774
|
+
let relatedFieldsString = await this.GenerateBaseViewRelatedFieldsString(pool, entity.Fields);
|
|
1699
1775
|
const relatedFieldsJoinString = this.generateBaseViewJoins(entity, entity.Fields);
|
|
1700
1776
|
// Geo WRITE source: native lat/lng aliases, or RecordGeoCode JOIN when the entity
|
|
1701
1777
|
// has writable Geo* fields and no native coords. Display-only entities (Person/Org
|
|
@@ -1750,6 +1826,10 @@ export class SQLCodeGenBase {
|
|
|
1750
1826
|
viewPermSQL: permissions,
|
|
1751
1827
|
};
|
|
1752
1828
|
}
|
|
1829
|
+
/** @deprecated Use {@link GenerateBaseViewPieces}. */
|
|
1830
|
+
async generateBaseViewPieces(pool, entity) {
|
|
1831
|
+
return this.GenerateBaseViewPieces(pool, entity);
|
|
1832
|
+
}
|
|
1753
1833
|
generateViewPermissions(entity) {
|
|
1754
1834
|
return this._dbProvider.generateViewPermissions(entity);
|
|
1755
1835
|
}
|
|
@@ -1922,7 +2002,7 @@ export class SQLCodeGenBase {
|
|
|
1922
2002
|
generateRecordGeoCodeFields(qi) {
|
|
1923
2003
|
return ` __mj_rgc.${qi('Latitude')} AS ${qi('__mj_Latitude')},\n __mj_rgc.${qi('Longitude')} AS ${qi('__mj_Longitude')}`;
|
|
1924
2004
|
}
|
|
1925
|
-
async
|
|
2005
|
+
async GenerateBaseViewRelatedFieldsString(pool, entityFields) {
|
|
1926
2006
|
let sOutput = '';
|
|
1927
2007
|
let fieldCount = 0;
|
|
1928
2008
|
const manageMD = MJGlobal.Instance.ClassFactory.CreateInstance(ManageMetadataBase);
|
|
@@ -2084,6 +2164,10 @@ export class SQLCodeGenBase {
|
|
|
2084
2164
|
}
|
|
2085
2165
|
return sOutput;
|
|
2086
2166
|
}
|
|
2167
|
+
/** @deprecated Use {@link GenerateBaseViewRelatedFieldsString}. */
|
|
2168
|
+
async generateBaseViewRelatedFieldsString(pool, entityFields) {
|
|
2169
|
+
return this.GenerateBaseViewRelatedFieldsString(pool, entityFields);
|
|
2170
|
+
}
|
|
2087
2171
|
getIsNameFieldForSingleEntity(entityName) {
|
|
2088
2172
|
const md = new Metadata(); // use the full metadata entity list, not the filtered version that we receive // global-provider-ok: codegen runs offline against a single provider
|
|
2089
2173
|
const e = md.EntityByName(entityName);
|
|
@@ -2172,7 +2256,7 @@ export class SQLCodeGenBase {
|
|
|
2172
2256
|
const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
|
|
2173
2257
|
// Find all fields in other entities that are foreign keys to this entity
|
|
2174
2258
|
for (const e of md.Entities) {
|
|
2175
|
-
if (!
|
|
2259
|
+
if (!ShouldEmitCascadeForRelatedEntity(entity.SchemaName, e.SchemaName, configInfo.allowCrossSchemaCascadeDeletes === true)) {
|
|
2176
2260
|
continue;
|
|
2177
2261
|
}
|
|
2178
2262
|
for (const ef of e.Fields) {
|
|
@@ -2203,7 +2287,7 @@ export class SQLCodeGenBase {
|
|
|
2203
2287
|
else {
|
|
2204
2288
|
// Can't delete and can't safely update - warn
|
|
2205
2289
|
const msg = `${relatedEntity.Name}.${fkField.Name} is part of composite unique constraint but entity doesn't allow delete API`;
|
|
2206
|
-
|
|
2290
|
+
LogWarning(`WARNING in spDelete${parentEntity.BaseTableCodeName}: ${msg}`);
|
|
2207
2291
|
return `
|
|
2208
2292
|
-- WARNING: ${msg} - cascade operation may fail due to unique constraint violation`;
|
|
2209
2293
|
}
|
|
@@ -2221,7 +2305,7 @@ export class SQLCodeGenBase {
|
|
|
2221
2305
|
// Nullable FK but no update API - this is a configuration error
|
|
2222
2306
|
const sqlComment = `WARNING: ${relatedEntity.BaseTable} has nullable FK to ${parentEntity.BaseTable} but doesn't allow update API - cascade operation will fail`;
|
|
2223
2307
|
const consoleMsg = `WARNING in spDelete${parentEntity.BaseTableCodeName} generation: ${relatedEntity.BaseTable} has nullable FK to ${parentEntity.BaseTable} but doesn't allow update API - cascade operation will fail`;
|
|
2224
|
-
|
|
2308
|
+
LogWarning(consoleMsg);
|
|
2225
2309
|
return `
|
|
2226
2310
|
-- ${sqlComment}
|
|
2227
2311
|
-- This will cause the delete operation to fail due to referential integrity`;
|
|
@@ -2230,7 +2314,7 @@ export class SQLCodeGenBase {
|
|
|
2230
2314
|
// Entity doesn't allow delete API, so we can't cascade delete
|
|
2231
2315
|
const sqlComment = `WARNING: ${relatedEntity.BaseTable} has non-nullable FK to ${parentEntity.BaseTable} but doesn't allow delete API - cascade operation will fail`;
|
|
2232
2316
|
const consoleMsg = `WARNING in spDelete${parentEntity.BaseTableCodeName} generation: ${relatedEntity.BaseTable} has non-nullable FK to ${parentEntity.BaseTable} but doesn't allow delete API - cascade operation will fail`;
|
|
2233
|
-
|
|
2317
|
+
LogWarning(consoleMsg);
|
|
2234
2318
|
return `
|
|
2235
2319
|
-- ${sqlComment}
|
|
2236
2320
|
-- This will cause a referential integrity violation`;
|
|
@@ -2272,7 +2356,7 @@ export class SQLCodeGenBase {
|
|
|
2272
2356
|
return result.recordset.length > 0;
|
|
2273
2357
|
}
|
|
2274
2358
|
catch (error) {
|
|
2275
|
-
|
|
2359
|
+
LogWarning(`Failed to check composite unique constraint for ${schemaName}.${tableName}.${columnName}: ${error}`);
|
|
2276
2360
|
return false; // Fallback to existing UPDATE behavior
|
|
2277
2361
|
}
|
|
2278
2362
|
}
|
|
@@ -2285,7 +2369,7 @@ export class SQLCodeGenBase {
|
|
|
2285
2369
|
const md = new Metadata(); // global-provider-ok: codegen runs offline against a single provider
|
|
2286
2370
|
// Find all fields in other entities that are foreign keys to this entity
|
|
2287
2371
|
for (const e of md.Entities) {
|
|
2288
|
-
if (!
|
|
2372
|
+
if (!ShouldEmitCascadeForRelatedEntity(entity.SchemaName, e.SchemaName, configInfo.allowCrossSchemaCascadeDeletes === true)) {
|
|
2289
2373
|
continue;
|
|
2290
2374
|
}
|
|
2291
2375
|
for (const ef of e.Fields) {
|