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