@memberjunction/codegen-lib 6.1.0-edge.4 → 6.1.0-edge.6
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/README.md +20 -0
- package/dist/Angular/angular-codegen.d.ts +9 -6
- package/dist/Angular/angular-codegen.d.ts.map +1 -1
- package/dist/Angular/angular-codegen.js +80 -51
- package/dist/Angular/angular-codegen.js.map +1 -1
- package/dist/Angular/join-grid-related-entity-component.d.ts.map +1 -1
- package/dist/Angular/join-grid-related-entity-component.js +5 -2
- package/dist/Angular/join-grid-related-entity-component.js.map +1 -1
- package/dist/Angular/related-entity-components.d.ts +11 -0
- package/dist/Angular/related-entity-components.d.ts.map +1 -1
- package/dist/Angular/related-entity-components.js +23 -0
- package/dist/Angular/related-entity-components.js.map +1 -1
- package/dist/Angular/timeline-related-entity-component.d.ts.map +1 -1
- package/dist/Angular/timeline-related-entity-component.js +3 -2
- package/dist/Angular/timeline-related-entity-component.js.map +1 -1
- package/dist/Config/config.d.ts +216 -12
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +88 -6
- package/dist/Config/config.js.map +1 -1
- package/dist/Config/schema-output.d.ts +33 -0
- package/dist/Config/schema-output.d.ts.map +1 -0
- package/dist/Config/schema-output.js +52 -0
- package/dist/Config/schema-output.js.map +1 -0
- package/dist/Database/codeGenDatabaseProvider.d.ts +68 -1
- package/dist/Database/codeGenDatabaseProvider.d.ts.map +1 -1
- package/dist/Database/codeGenDatabaseProvider.js +142 -3
- package/dist/Database/codeGenDatabaseProvider.js.map +1 -1
- package/dist/Database/dbSchema.js +2 -1
- package/dist/Database/dbSchema.js.map +1 -1
- package/dist/Database/decision-metadata-format.d.ts +124 -0
- package/dist/Database/decision-metadata-format.d.ts.map +1 -0
- package/dist/Database/decision-metadata-format.js +279 -0
- package/dist/Database/decision-metadata-format.js.map +1 -0
- package/dist/Database/decision-metadata-writer.d.ts +48 -0
- package/dist/Database/decision-metadata-writer.d.ts.map +1 -0
- package/dist/Database/decision-metadata-writer.js +482 -0
- package/dist/Database/decision-metadata-writer.js.map +1 -0
- package/dist/Database/entity-field-change-tracking.d.ts +53 -0
- package/dist/Database/entity-field-change-tracking.d.ts.map +1 -0
- package/dist/Database/entity-field-change-tracking.js +120 -0
- package/dist/Database/entity-field-change-tracking.js.map +1 -0
- package/dist/Database/entity-level-tree-cycles.d.ts.map +1 -1
- package/dist/Database/entity-level-tree-cycles.js +3 -2
- package/dist/Database/entity-level-tree-cycles.js.map +1 -1
- package/dist/Database/exclude-tables.d.ts +26 -0
- package/dist/Database/exclude-tables.d.ts.map +1 -0
- package/dist/Database/exclude-tables.js +38 -0
- package/dist/Database/exclude-tables.js.map +1 -0
- package/dist/Database/field-metadata-lock.d.ts +47 -0
- package/dist/Database/field-metadata-lock.d.ts.map +1 -0
- package/dist/Database/field-metadata-lock.js +159 -0
- package/dist/Database/field-metadata-lock.js.map +1 -0
- package/dist/Database/manage-metadata.d.ts +142 -42
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +625 -287
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.d.ts +2 -2
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.d.ts.map +1 -1
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js +90 -41
- package/dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/postgresql/metadataSupportObjects.js +31 -8
- package/dist/Database/providers/postgresql/metadataSupportObjects.js.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.d.ts +3 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.d.ts.map +1 -1
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js +58 -23
- package/dist/Database/providers/sqlserver/SQLServerCodeGenProvider.js.map +1 -1
- package/dist/Database/providers/sqlserver/__tests__/BaseViewEmissionGoldenMaster.test.js +15 -2
- package/dist/Database/providers/sqlserver/__tests__/BaseViewEmissionGoldenMaster.test.js.map +1 -1
- package/dist/Database/providers/sqlserver/__tests__/SQLServerCodeGenProvider.test.js +17 -0
- package/dist/Database/providers/sqlserver/__tests__/SQLServerCodeGenProvider.test.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +24 -0
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +108 -29
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +22 -0
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.js +115 -7
- package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -1
- package/dist/Misc/action_subclasses_codegen.js +2 -2
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +7 -5
- package/dist/Misc/advanced_generation.d.ts.map +1 -1
- package/dist/Misc/advanced_generation.js +34 -23
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/codegen-reporter.d.ts +5 -0
- package/dist/Misc/codegen-reporter.d.ts.map +1 -1
- package/dist/Misc/codegen-reporter.js +9 -2
- package/dist/Misc/codegen-reporter.js.map +1 -1
- package/dist/Misc/emit-stats.d.ts +28 -0
- package/dist/Misc/emit-stats.d.ts.map +1 -0
- package/dist/Misc/emit-stats.js +43 -0
- package/dist/Misc/emit-stats.js.map +1 -0
- package/dist/Misc/entity_subclasses_codegen.d.ts +20 -2
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +120 -51
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/file-write.d.ts +15 -0
- package/dist/Misc/file-write.d.ts.map +1 -0
- package/dist/Misc/file-write.js +33 -0
- package/dist/Misc/file-write.js.map +1 -0
- package/dist/Misc/graphql_server_codegen.d.ts +12 -2
- package/dist/Misc/graphql_server_codegen.d.ts.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +72 -16
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/dist/Misc/remote_operations_codegen.d.ts.map +1 -1
- package/dist/Misc/remote_operations_codegen.js +4 -3
- package/dist/Misc/remote_operations_codegen.js.map +1 -1
- package/dist/Misc/runCommand.d.ts +5 -0
- package/dist/Misc/runCommand.d.ts.map +1 -1
- package/dist/Misc/runCommand.js +43 -17
- package/dist/Misc/runCommand.js.map +1 -1
- package/dist/Misc/schema-emit.d.ts +123 -0
- package/dist/Misc/schema-emit.d.ts.map +1 -0
- package/dist/Misc/schema-emit.js +221 -0
- package/dist/Misc/schema-emit.js.map +1 -0
- package/dist/Misc/sql_logging.d.ts +63 -8
- package/dist/Misc/sql_logging.d.ts.map +1 -1
- package/dist/Misc/sql_logging.js +169 -42
- package/dist/Misc/sql_logging.js.map +1 -1
- package/dist/Misc/util.d.ts +12 -0
- package/dist/Misc/util.d.ts.map +1 -1
- package/dist/Misc/util.js +71 -8
- package/dist/Misc/util.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +28 -1
- package/dist/plugins/index.js.map +1 -1
- package/dist/runCodeGen.d.ts +20 -2
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +186 -33
- package/dist/runCodeGen.js.map +1 -1
- package/package.json +29 -29
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { EntityInfo, Metadata } from '@memberjunction/core';
|
|
1
|
+
import { EntityInfo, Metadata, ShouldJoinRecordGeoCodes, HasNativeLatLngFields, NativeLatitudeField, NativeLongitudeField, ListEmbeddedGeoSpecs } from '@memberjunction/core';
|
|
2
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';
|
|
@@ -12,6 +12,7 @@ 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';
|
|
15
|
+
import { writeFileIfChanged as writeFileIfChangedShared } from '../Misc/file-write.js';
|
|
15
16
|
export const SPType = {
|
|
16
17
|
Create: 'Create',
|
|
17
18
|
Update: 'Update',
|
|
@@ -67,6 +68,13 @@ export class SQLCodeGenBase {
|
|
|
67
68
|
* Ordered list of entity IDs for delete SP regeneration (dependency order)
|
|
68
69
|
*/
|
|
69
70
|
this.orderedEntitiesForDeleteSPRegeneration = [];
|
|
71
|
+
/**
|
|
72
|
+
* `schema.routine` keys (lowercased) present in the database at the start of
|
|
73
|
+
* this SQL-generation pass. Used to force-log CREATE PROC for routines that
|
|
74
|
+
* were dropped out-of-band (BigSchemaDemo recreate, a failed prior batch)
|
|
75
|
+
* while the entity itself is not in newEntityList/modifiedEntityList.
|
|
76
|
+
*/
|
|
77
|
+
this.existingRoutines = null;
|
|
70
78
|
}
|
|
71
79
|
get SQLUtilityObject() {
|
|
72
80
|
return this._sqlUtilityObject;
|
|
@@ -79,6 +87,7 @@ export class SQLCodeGenBase {
|
|
|
79
87
|
}
|
|
80
88
|
async manageSQLScriptsAndExecution(pool, entities, directory, currentUser) {
|
|
81
89
|
try {
|
|
90
|
+
this.existingRoutines = null;
|
|
82
91
|
// Build list of entities qualified for forced regeneration if entityWhereClause is provided
|
|
83
92
|
if (configInfo.forceRegeneration?.enabled && configInfo.forceRegeneration?.entityWhereClause) {
|
|
84
93
|
this.filterEntitiesQualifiedForRegeneration = true; // Enable filtering
|
|
@@ -114,6 +123,7 @@ export class SQLCodeGenBase {
|
|
|
114
123
|
return false;
|
|
115
124
|
}
|
|
116
125
|
succeedSpinner(`Custom SQL scripts completed (${(new Date().getTime() - startTime.getTime()) / 1000}s)`);
|
|
126
|
+
await this.loadExistingRoutines(pool, entities);
|
|
117
127
|
// ALWAYS use the first filter where we only include entities that have IncludeInAPI = 1
|
|
118
128
|
// Entities are already sorted by name in PostProcessEntityMetadata (see providerBase.ts)
|
|
119
129
|
const baselineEntities = entities.filter(e => e.IncludeInAPI);
|
|
@@ -187,7 +197,8 @@ export class SQLCodeGenBase {
|
|
|
187
197
|
// AI Engine init because vwAIModels is gone. SQL Server doesn't
|
|
188
198
|
// hit this because its execution path is bulk-monolithic, not
|
|
189
199
|
// phased per-entity.
|
|
190
|
-
const
|
|
200
|
+
const configuredBatch = configInfo.fileEmit?.sqlEntityBatchSize ?? 8;
|
|
201
|
+
const perEntityBatchSize = configInfo.dbPlatform === 'postgresql' ? 1 : configuredBatch;
|
|
191
202
|
// Generate SQL for entities that don't need cascade delete regeneration
|
|
192
203
|
const genResult = await this.generateAndExecuteEntitySQLToSeparateFiles({
|
|
193
204
|
pool,
|
|
@@ -669,6 +680,8 @@ export class SQLCodeGenBase {
|
|
|
669
680
|
// BaseView while CodeGen keeps writing the inner view underneath it. Gating on
|
|
670
681
|
// BaseViewGenerated alone would skip the inner view and leave the custom layer selecting
|
|
671
682
|
// from an object that does not exist.
|
|
683
|
+
// One pass: overlay already applied → Pass 1 sees extra BaseView columns and logs
|
|
684
|
+
// EntityField INSERTs; Pass 2 DROPs/creates only GeneratedViewName, never the overlay.
|
|
672
685
|
const generatesView = (entity.BaseViewGenerated || entity.HasLayeredBaseView) && !entity.VirtualEntity;
|
|
673
686
|
const tvfSQL = generatesView ? this.generateRecursiveFKTVFs(entity) : '';
|
|
674
687
|
const viewPieces = generatesView
|
|
@@ -719,14 +732,7 @@ export class SQLCodeGenBase {
|
|
|
719
732
|
* This avoids false timestamp updates and unnecessary I/O.
|
|
720
733
|
*/
|
|
721
734
|
writeFileIfChanged(filePath, newContent) {
|
|
722
|
-
|
|
723
|
-
const existing = fs.readFileSync(filePath, 'utf-8');
|
|
724
|
-
if (existing === newContent) {
|
|
725
|
-
return false;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
fs.writeFileSync(filePath, newContent);
|
|
729
|
-
return true;
|
|
735
|
+
return writeFileIfChangedShared(filePath, newContent);
|
|
730
736
|
}
|
|
731
737
|
/**
|
|
732
738
|
* Fetches the current base view definition from SQL Server and compares it against newly generated SQL.
|
|
@@ -1106,7 +1112,7 @@ export class SQLCodeGenBase {
|
|
|
1106
1112
|
if (options.writeFiles) {
|
|
1107
1113
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
|
|
1108
1114
|
this.writeFileIfChanged(filePath, s);
|
|
1109
|
-
this.logSQLForNewOrModifiedEntity(options.entity, s, `spCreate SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, baseViewChanged);
|
|
1115
|
+
this.logSQLForNewOrModifiedEntity(options.entity, s, `spCreate SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, this.forceLogForRoutine(options.entity, spName, baseViewChanged));
|
|
1110
1116
|
files.push(filePath);
|
|
1111
1117
|
}
|
|
1112
1118
|
sRet += s + '\n' + this._dbProvider.BatchSeparator + '\n';
|
|
@@ -1117,7 +1123,7 @@ export class SQLCodeGenBase {
|
|
|
1117
1123
|
if (options.writeFiles) {
|
|
1118
1124
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
|
|
1119
1125
|
this.writeFileIfChanged(filePath, s);
|
|
1120
|
-
this.logSQLForNewOrModifiedEntity(options.entity, s, `spCreate Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, baseViewChanged);
|
|
1126
|
+
this.logSQLForNewOrModifiedEntity(options.entity, s, `spCreate Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, this.forceLogForRoutine(options.entity, spName, baseViewChanged));
|
|
1121
1127
|
files.push(filePath);
|
|
1122
1128
|
}
|
|
1123
1129
|
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
@@ -1136,7 +1142,7 @@ export class SQLCodeGenBase {
|
|
|
1136
1142
|
if (options.writeFiles) {
|
|
1137
1143
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
|
|
1138
1144
|
this.writeFileIfChanged(filePath, s);
|
|
1139
|
-
this.logSQLForNewOrModifiedEntity(options.entity, s, `spUpdate SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, baseViewChanged);
|
|
1145
|
+
this.logSQLForNewOrModifiedEntity(options.entity, s, `spUpdate SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, this.forceLogForRoutine(options.entity, spName, baseViewChanged));
|
|
1140
1146
|
files.push(filePath);
|
|
1141
1147
|
}
|
|
1142
1148
|
sRet += s + '\n' + this._dbProvider.BatchSeparator + '\n';
|
|
@@ -1147,7 +1153,7 @@ export class SQLCodeGenBase {
|
|
|
1147
1153
|
if (options.writeFiles) {
|
|
1148
1154
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
|
|
1149
1155
|
this.writeFileIfChanged(filePath, s);
|
|
1150
|
-
this.logSQLForNewOrModifiedEntity(options.entity, s, `spUpdate Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, baseViewChanged);
|
|
1156
|
+
this.logSQLForNewOrModifiedEntity(options.entity, s, `spUpdate Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, this.forceLogForRoutine(options.entity, spName, baseViewChanged));
|
|
1151
1157
|
files.push(filePath);
|
|
1152
1158
|
}
|
|
1153
1159
|
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
@@ -1172,7 +1178,7 @@ export class SQLCodeGenBase {
|
|
|
1172
1178
|
if (options.writeFiles) {
|
|
1173
1179
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, false, true));
|
|
1174
1180
|
this.writeFileIfChanged(filePath, s);
|
|
1175
|
-
this.logSQLForNewOrModifiedEntity(options.entity, s, `spDelete SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, baseViewChanged);
|
|
1181
|
+
this.logSQLForNewOrModifiedEntity(options.entity, s, `spDelete SQL for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, this.forceLogForRoutine(options.entity, spName, baseViewChanged));
|
|
1176
1182
|
files.push(filePath);
|
|
1177
1183
|
}
|
|
1178
1184
|
sRet += s + '\n' + this._dbProvider.BatchSeparator + '\n';
|
|
@@ -1183,7 +1189,7 @@ export class SQLCodeGenBase {
|
|
|
1183
1189
|
if (options.writeFiles) {
|
|
1184
1190
|
const filePath = path.join(options.directory, this.SQLUtilityObject.getDBObjectFileName('sp', options.entity.SchemaName, spName, true, true));
|
|
1185
1191
|
this.writeFileIfChanged(filePath, s);
|
|
1186
|
-
this.logSQLForNewOrModifiedEntity(options.entity, s, `spDelete Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, baseViewChanged);
|
|
1192
|
+
this.logSQLForNewOrModifiedEntity(options.entity, s, `spDelete Permissions for ${options.entity.Name}`, options.enableSQLLoggingForNewOrModifiedEntities, this.forceLogForRoutine(options.entity, spName, baseViewChanged));
|
|
1187
1193
|
files.push(filePath);
|
|
1188
1194
|
}
|
|
1189
1195
|
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
@@ -1229,6 +1235,42 @@ export class SQLCodeGenBase {
|
|
|
1229
1235
|
getSPName(entity, type) {
|
|
1230
1236
|
return this._dbProvider.getCRUDRoutineName(entity, type);
|
|
1231
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Snapshot every CRUD routine currently in the database (one query) so
|
|
1240
|
+
* generate-and-log can force-emit CREATE PROC for objects that vanished
|
|
1241
|
+
* without the entity landing on newEntityList/modifiedEntityList.
|
|
1242
|
+
*/
|
|
1243
|
+
async loadExistingRoutines(pool, entities) {
|
|
1244
|
+
this.existingRoutines = new Set();
|
|
1245
|
+
const schemas = [...new Set(entities.map((e) => e.SchemaName).filter((s) => !!s))];
|
|
1246
|
+
const sql = this._dbProvider.getRoutineNamesBySchemaSQL(schemas);
|
|
1247
|
+
if (!sql || !sql.trim()) {
|
|
1248
|
+
return;
|
|
1249
|
+
}
|
|
1250
|
+
try {
|
|
1251
|
+
const result = await pool.query(sql);
|
|
1252
|
+
for (const row of result.recordset ?? []) {
|
|
1253
|
+
const schemaName = String(row.schema_name ?? '').toLowerCase();
|
|
1254
|
+
const routineName = String(row.routine_name ?? '').toLowerCase();
|
|
1255
|
+
if (schemaName && routineName) {
|
|
1256
|
+
this.existingRoutines.add(`${schemaName}.${routineName}`);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
catch (e) {
|
|
1261
|
+
logWarning(`Could not load existing CRUD routines (missing-proc self-heal disabled this run): ${e instanceof Error ? e.message : String(e)}`);
|
|
1262
|
+
this.existingRoutines = null;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
isRoutineMissing(schema, routineName) {
|
|
1266
|
+
if (!this.existingRoutines) {
|
|
1267
|
+
return false;
|
|
1268
|
+
}
|
|
1269
|
+
return !this.existingRoutines.has(`${schema}.${routineName}`.toLowerCase());
|
|
1270
|
+
}
|
|
1271
|
+
forceLogForRoutine(entity, routineName, baseViewChanged) {
|
|
1272
|
+
return baseViewChanged || this.isRoutineMissing(entity.SchemaName, routineName);
|
|
1273
|
+
}
|
|
1232
1274
|
getEntityPermissionFileNames(entity) {
|
|
1233
1275
|
const files = [];
|
|
1234
1276
|
// all entities have a base view - and we always generate permissions for the base view even if not generated base view
|
|
@@ -1556,17 +1598,22 @@ export class SQLCodeGenBase {
|
|
|
1556
1598
|
const classNameFirstChar = entity.BaseTableCodeName.charAt(0).toLowerCase();
|
|
1557
1599
|
let relatedFieldsString = await this.generateBaseViewRelatedFieldsString(pool, entity.Fields);
|
|
1558
1600
|
const relatedFieldsJoinString = this.generateBaseViewJoins(entity, entity.Fields);
|
|
1559
|
-
// Geo
|
|
1560
|
-
//
|
|
1601
|
+
// Geo WRITE source: native lat/lng aliases, or RecordGeoCode JOIN when the entity
|
|
1602
|
+
// has writable Geo* fields and no native coords. Display-only entities (Person/Org
|
|
1603
|
+
// virtual PrimaryAddress*) do not get a RecordGeoCode join on their own ID.
|
|
1561
1604
|
if (entity.SupportsGeoCoding && entity.Name.trim().toLowerCase() !== 'mj: record geo codes') {
|
|
1562
1605
|
const qi = this._dbProvider.Dialect.QuoteIdentifier.bind(this._dbProvider.Dialect);
|
|
1563
|
-
const geoFieldsSelect =
|
|
1606
|
+
const geoFieldsSelect = HasNativeLatLngFields(entity.Fields)
|
|
1564
1607
|
? this.generateNativeGeoFields(entity.Fields, classNameFirstChar, qi)
|
|
1565
|
-
: this.generateRecordGeoCodeFields(qi);
|
|
1608
|
+
: (ShouldJoinRecordGeoCodes(entity) ? this.generateRecordGeoCodeFields(qi) : '');
|
|
1566
1609
|
if (geoFieldsSelect) {
|
|
1567
1610
|
relatedFieldsString += (relatedFieldsString ? ',\n' : '') + geoFieldsSelect;
|
|
1568
1611
|
}
|
|
1569
1612
|
}
|
|
1613
|
+
const embeddedGeo = this.generateEmbeddedGeoSelect(entity, classNameFirstChar);
|
|
1614
|
+
if (embeddedGeo.select) {
|
|
1615
|
+
relatedFieldsString += (relatedFieldsString ? ',\n' : '') + embeddedGeo.select;
|
|
1616
|
+
}
|
|
1570
1617
|
// GRANTs target the PUBLIC view (BaseView), not the one this method generates. For a
|
|
1571
1618
|
// LAYERED entity those are different objects, and the outer one may not exist yet: the
|
|
1572
1619
|
// documented setup is "name the inner view -> run CodeGen -> then create BaseView", so the
|
|
@@ -1680,10 +1727,9 @@ export class SQLCodeGenBase {
|
|
|
1680
1727
|
sOutput += `${ef.AllowsNull ? 'LEFT OUTER' : 'INNER'} JOIN\n ${qs(ef.RelatedEntitySchemaName, relatedTable)} AS ${ef._RelatedEntityTableAlias}\n ON\n ${qi(classNameFirstChar)}.${qi(ef.Name)} = ${ef._RelatedEntityTableAlias}.${qi(ef.RelatedEntityFieldName)}`;
|
|
1681
1728
|
}
|
|
1682
1729
|
}
|
|
1683
|
-
// Geo
|
|
1684
|
-
//
|
|
1685
|
-
|
|
1686
|
-
if (entity.SupportsGeoCoding && !this.hasNativeGeoFields(entityFields) && entity.Name.trim().toLowerCase() !== 'mj: record geo codes') {
|
|
1730
|
+
// Geo WRITE source: RecordGeoCode JOIN only when there are writable Geo* fields
|
|
1731
|
+
// and no native lat/lng. Display-only entities skip this join.
|
|
1732
|
+
if (ShouldJoinRecordGeoCodes(entity)) {
|
|
1687
1733
|
const dialect = this._dbProvider.Dialect;
|
|
1688
1734
|
const qi = dialect.QuoteIdentifier.bind(dialect);
|
|
1689
1735
|
const qs = dialect.QuoteSchema.bind(dialect);
|
|
@@ -1707,6 +1753,7 @@ export class SQLCodeGenBase {
|
|
|
1707
1753
|
}
|
|
1708
1754
|
sOutput += `LEFT OUTER JOIN\n ${qs(mjCoreSchema, 'vwRecordGeoCodes')} AS __mj_rgc\n ON\n __mj_rgc.${qi('EntityID')} = '${entity.ID}'\n AND __mj_rgc.${qi('RecordID')} = ${recordIdExpr}\n AND __mj_rgc.${qi('LocationType')} = 'Primary'`;
|
|
1709
1755
|
}
|
|
1756
|
+
sOutput += this.generateEmbeddedGeoJoins(entity, classNameFirstChar);
|
|
1710
1757
|
return sOutput;
|
|
1711
1758
|
}
|
|
1712
1759
|
/**
|
|
@@ -1723,21 +1770,53 @@ export class SQLCodeGenBase {
|
|
|
1723
1770
|
* on any geo-eligible entity whose RecordGeoCode-based view ran once.
|
|
1724
1771
|
*/
|
|
1725
1772
|
hasNativeGeoFields(entityFields) {
|
|
1726
|
-
|
|
1727
|
-
const hasLng = entityFields.some(f => f.ExtendedType === 'GeoLongitude' && !f.IsVirtual);
|
|
1728
|
-
return hasLat && hasLng;
|
|
1773
|
+
return HasNativeLatLngFields(entityFields);
|
|
1729
1774
|
}
|
|
1730
1775
|
/**
|
|
1731
1776
|
* Generate SELECT aliases for native lat/lng fields → __mj_Latitude / __mj_Longitude.
|
|
1732
1777
|
* Virtual fields are excluded — see hasNativeGeoFields for the rationale.
|
|
1733
1778
|
*/
|
|
1734
1779
|
generateNativeGeoFields(entityFields, classNameFirstChar, qi) {
|
|
1735
|
-
const latField = entityFields
|
|
1736
|
-
const lngField = entityFields
|
|
1780
|
+
const latField = NativeLatitudeField(entityFields);
|
|
1781
|
+
const lngField = NativeLongitudeField(entityFields);
|
|
1737
1782
|
if (!latField || !lngField)
|
|
1738
1783
|
return '';
|
|
1739
1784
|
return ` ${qi(classNameFirstChar)}.${qi(latField.Name)} AS ${qi('__mj_Latitude')},\n ${qi(classNameFirstChar)}.${qi(lngField.Name)} AS ${qi('__mj_Longitude')}`;
|
|
1740
1785
|
}
|
|
1786
|
+
/**
|
|
1787
|
+
* Bubble lat/lng from EmbeddedRecord peers as `__mj_Latitude_{FK}`.
|
|
1788
|
+
* Geocode lives on the Address (or other source); the parent only displays it.
|
|
1789
|
+
*/
|
|
1790
|
+
generateEmbeddedGeoSelect(entity, classNameFirstChar) {
|
|
1791
|
+
const specs = ListEmbeddedGeoSpecs(entity.Fields);
|
|
1792
|
+
if (specs.length === 0)
|
|
1793
|
+
return { select: '', joins: '' };
|
|
1794
|
+
const md = new Metadata(); // global-provider-ok: CodeGen is CLI tool
|
|
1795
|
+
const qi = this._dbProvider.Dialect.QuoteIdentifier.bind(this._dbProvider.Dialect);
|
|
1796
|
+
const qs = this._dbProvider.Dialect.QuoteSchema.bind(this._dbProvider.Dialect);
|
|
1797
|
+
const selects = [];
|
|
1798
|
+
const joins = [];
|
|
1799
|
+
for (const spec of specs) {
|
|
1800
|
+
const related = md.Entities.find(e => e.ID.toLowerCase() === spec.relatedEntityID.toLowerCase());
|
|
1801
|
+
if (!related)
|
|
1802
|
+
continue;
|
|
1803
|
+
const latF = NativeLatitudeField(related.Fields) ?? related.Fields.find(f => f.Name === '__mj_Latitude' || f.Name === 'PrimaryAddressLatitude');
|
|
1804
|
+
const lngF = NativeLongitudeField(related.Fields) ?? related.Fields.find(f => f.Name === '__mj_Longitude' || f.Name === 'PrimaryAddressLongitude');
|
|
1805
|
+
if (!latF || !lngF)
|
|
1806
|
+
continue;
|
|
1807
|
+
const alias = `__mj_emb_${spec.foreignKeyField}`;
|
|
1808
|
+
const schema = spec.relatedSchemaName || related.SchemaName;
|
|
1809
|
+
const table = spec.relatedBaseTable || related.BaseTable;
|
|
1810
|
+
const joinKind = spec.allowsNull ? 'LEFT OUTER' : 'INNER';
|
|
1811
|
+
joins.push(`${joinKind} JOIN\n ${qs(schema, table)} AS ${alias}\n ON\n ${qi(classNameFirstChar)}.${qi(spec.foreignKeyField)} = ${alias}.${qi(related.FirstPrimaryKey.Name)}`); // first-pk-ok: FK target — a single FK column joins to the related entity's single key
|
|
1812
|
+
selects.push(` ${alias}.${qi(latF.Name)} AS ${qi(spec.lat)},\n ${alias}.${qi(lngF.Name)} AS ${qi(spec.lng)}`);
|
|
1813
|
+
}
|
|
1814
|
+
return { select: selects.join(',\n'), joins: joins.join('\n') };
|
|
1815
|
+
}
|
|
1816
|
+
generateEmbeddedGeoJoins(entity, classNameFirstChar) {
|
|
1817
|
+
const { joins } = this.generateEmbeddedGeoSelect(entity, classNameFirstChar);
|
|
1818
|
+
return joins ? ((entity.Fields.length ? '\n' : '') + joins) : '';
|
|
1819
|
+
}
|
|
1741
1820
|
/**
|
|
1742
1821
|
* Generate SELECT aliases from the RecordGeoCode LEFT JOIN → __mj_Latitude / __mj_Longitude.
|
|
1743
1822
|
*/
|