@memberjunction/codegen-lib 1.0.6 → 1.0.7
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/advanced_generation.d.ts +3 -0
- package/dist/advanced_generation.d.ts.map +1 -1
- package/dist/advanced_generation.js +39 -26
- package/dist/advanced_generation.js.map +1 -1
- package/dist/angular_client_codegen.d.ts +45 -2
- package/dist/angular_client_codegen.d.ts.map +1 -1
- package/dist/angular_client_codegen.js +423 -399
- package/dist/angular_client_codegen.js.map +1 -1
- package/dist/createNewUser.d.ts +6 -1
- package/dist/createNewUser.d.ts.map +1 -1
- package/dist/createNewUser.js +68 -53
- package/dist/createNewUser.js.map +1 -1
- package/dist/dbSchema.d.ts +10 -3
- package/dist/dbSchema.d.ts.map +1 -1
- package/dist/dbSchema.js +144 -130
- package/dist/dbSchema.js.map +1 -1
- package/dist/entity_subclasses_codegen.d.ts +11 -6
- package/dist/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/entity_subclasses_codegen.js +144 -131
- package/dist/entity_subclasses_codegen.js.map +1 -1
- package/dist/graphql_server_codegen.d.ts +28 -5
- package/dist/graphql_server_codegen.d.ts.map +1 -1
- package/dist/graphql_server_codegen.js +552 -531
- package/dist/graphql_server_codegen.js.map +1 -1
- package/dist/logging.d.ts +20 -0
- package/dist/logging.d.ts.map +1 -1
- package/dist/logging.js +61 -26
- package/dist/logging.js.map +1 -1
- package/dist/manageMetadata.d.ts +128 -7
- package/dist/manageMetadata.d.ts.map +1 -1
- package/dist/manageMetadata.js +992 -898
- package/dist/manageMetadata.js.map +1 -1
- package/dist/runCodeGen.d.ts +16 -0
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +185 -140
- package/dist/runCodeGen.js.map +1 -1
- package/dist/runCommand.d.ts +7 -2
- package/dist/runCommand.d.ts.map +1 -1
- package/dist/runCommand.js +104 -90
- package/dist/runCommand.js.map +1 -1
- package/dist/sql.d.ts +21 -16
- package/dist/sql.d.ts.map +1 -1
- package/dist/sql.js +98 -87
- package/dist/sql.js.map +1 -1
- package/dist/sql_codegen.d.ts +56 -13
- package/dist/sql_codegen.d.ts.map +1 -1
- package/dist/sql_codegen.js +836 -795
- package/dist/sql_codegen.js.map +1 -1
- package/dist/util.js +0 -2
- package/dist/util.js.map +1 -1
- package/package.json +32 -32
- package/readme.md +2 -2
- package/dist/graphql_client_codegen.d.ts +0 -4
- package/dist/graphql_client_codegen.js +0 -161
- package/dist/graphql_client_codegen.js.map +0 -1
- package/dist/react_client_codegen.d.ts +0 -4
- package/dist/react_client_codegen.js +0 -147
- package/dist/react_client_codegen.js.map +0 -1
package/dist/sql_codegen.js
CHANGED
|
@@ -15,6 +15,12 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (
|
|
|
15
15
|
}) : function(o, v) {
|
|
16
16
|
o["default"] = v;
|
|
17
17
|
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
18
24
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
25
|
if (mod && mod.__esModule) return mod;
|
|
20
26
|
var result = {};
|
|
@@ -26,7 +32,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
32
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
33
|
};
|
|
28
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.
|
|
35
|
+
exports.SQLCodeGenBase = exports.SPType = void 0;
|
|
30
36
|
const core_1 = require("@memberjunction/core");
|
|
31
37
|
const logging_1 = require("./logging");
|
|
32
38
|
const fs = __importStar(require("fs"));
|
|
@@ -36,625 +42,696 @@ const config_1 = require("./config");
|
|
|
36
42
|
const manageMetadata_1 = require("./manageMetadata");
|
|
37
43
|
const sqlserver_dataprovider_1 = require("@memberjunction/sqlserver-dataprovider");
|
|
38
44
|
const util_1 = require("./util");
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
const global_1 = require("@memberjunction/global");
|
|
46
|
+
exports.SPType = {
|
|
47
|
+
Create: 'Create',
|
|
48
|
+
Update: 'Update',
|
|
49
|
+
Delete: 'Delete',
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* 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
|
|
53
|
+
* 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
|
|
54
|
+
* of that abstract base class and other databases will be sub-classes of the abstract base class as well.
|
|
55
|
+
*/
|
|
56
|
+
let SQLCodeGenBase = class SQLCodeGenBase {
|
|
57
|
+
constructor() {
|
|
58
|
+
this._sqlUtilityObject = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(sql_1.SQLUtilityBase);
|
|
59
|
+
}
|
|
60
|
+
get SQLUtilityObject() {
|
|
61
|
+
return this._sqlUtilityObject;
|
|
62
|
+
}
|
|
63
|
+
async manageSQLScriptsAndExecution(ds, entities, directory) {
|
|
64
|
+
try {
|
|
65
|
+
// STEP 1 - execute any custom SQL scripts for object creation that need to happen first - for example, if
|
|
66
|
+
// we have custom base views, need to have them defined before we do
|
|
67
|
+
// the rest as the generated stuff might use custom base views in compiled
|
|
68
|
+
// objects like spCreate for a given entity might reference the vw for that entity
|
|
69
|
+
const startTime = new Date();
|
|
70
|
+
if (!await this.runCustomSQLScripts(ds, 'before-sql'))
|
|
71
|
+
return false;
|
|
72
|
+
(0, logging_1.logStatus)(` Time to run custom SQL scripts: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
|
|
73
|
+
// ALWAYS use the first filter where we only include entities that have IncludeInAPI = 1
|
|
74
|
+
const baselineEntities = entities.filter(e => e.IncludeInAPI);
|
|
75
|
+
const includedEntities = baselineEntities.filter(e => config_1.configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) === undefined); //only include entities that are NOT in the excludeSchemas list
|
|
76
|
+
const excludedEntities = baselineEntities.filter(e => config_1.configInfo.excludeSchemas.find(s => s.toLowerCase() === e.SchemaName.toLowerCase()) !== undefined); //only include entities that ARE in the excludeSchemas list in this array
|
|
77
|
+
// STEP 2(a) - generate all the SQL files and execute them
|
|
78
|
+
const step2StartTime = new Date();
|
|
79
|
+
if (!await this.generateAndExecuteEntitySQLToSeparateFiles(ds, includedEntities, directory, false)) {
|
|
80
|
+
(0, logging_1.logError)('Error generating and executing all entities SQL to separate files');
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
// STEP 2(b) - for the excludedEntities, while we don't want to generate SQL, we do want to generate the permissions files for them
|
|
84
|
+
if (!await this.generateAndExecuteEntitySQLToSeparateFiles(ds, excludedEntities, directory, true)) {
|
|
85
|
+
(0, logging_1.logError)('Error generating and executing permissions SQL for excluded entities to separate files');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
(0, logging_1.logStatus)(` Time to Generate/Execute Entity SQL: ${(new Date().getTime() - step2StartTime.getTime()) / 1000} seconds`);
|
|
89
|
+
// now that we've generated the SQL, let's create a combined file in each schema sub-directory for convenience for a DBA
|
|
90
|
+
this.createCombinedEntitySQLFiles(directory, baselineEntities);
|
|
91
|
+
const manageMD = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(manageMetadata_1.ManageMetadataBase);
|
|
92
|
+
// 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
|
|
93
|
+
if (!await manageMD.manageEntityFields(ds, config_1.configInfo.excludeSchemas)) {
|
|
94
|
+
(0, logging_1.logError)('Error managing entity fields');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
// 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
|
|
98
|
+
// STEP 4- Apply permissions, executing all .permissions files
|
|
99
|
+
const step4StartTime = new Date();
|
|
100
|
+
if (!await this.applyPermissions(ds, directory, baselineEntities)) {
|
|
101
|
+
(0, logging_1.logError)('Error applying permissions');
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
(0, logging_1.logStatus)(` Time to Apply Permissions: ${(new Date().getTime() - step4StartTime.getTime()) / 1000} seconds`);
|
|
105
|
+
// STEP 5 - execute any custom SQL scripts that should run afterwards
|
|
106
|
+
const step5StartTime = new Date();
|
|
107
|
+
if (!await this.runCustomSQLScripts(ds, 'after-sql'))
|
|
108
|
+
return false;
|
|
109
|
+
(0, logging_1.logStatus)(` Time to run custom SQL scripts: ${(new Date().getTime() - step5StartTime.getTime()) / 1000} seconds`);
|
|
110
|
+
(0, logging_1.logStatus)(' Total time to run generate and execute SQL scripts: ' + ((new Date().getTime() - startTime.getTime()) / 1000) + ' seconds');
|
|
111
|
+
// now - we need to tell our metadata object to refresh itself
|
|
112
|
+
const md = new core_1.Metadata();
|
|
113
|
+
await md.Refresh();
|
|
114
|
+
return true;
|
|
60
115
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
(0, logging_1.logError)('Error generating and executing permissions SQL for excluded entities to separate files');
|
|
116
|
+
catch (err) {
|
|
117
|
+
(0, logging_1.logError)(err);
|
|
64
118
|
return false;
|
|
65
119
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
(
|
|
72
|
-
|
|
120
|
+
}
|
|
121
|
+
async runCustomSQLScripts(ds, when) {
|
|
122
|
+
try {
|
|
123
|
+
const scripts = (0, config_1.customSqlScripts)(when);
|
|
124
|
+
let bSuccess = true;
|
|
125
|
+
if (scripts) {
|
|
126
|
+
for (let i = 0; i < scripts.length; ++i) {
|
|
127
|
+
const s = scripts[i];
|
|
128
|
+
if (!await this.SQLUtilityObject.executeSQLFile(ds, s.scriptFile, true)) {
|
|
129
|
+
(0, logging_1.logError)(`Error executing custom '${when}' SQL script ${s.scriptFile}`);
|
|
130
|
+
bSuccess = false; // keep going if we have more scripts, but make sure we return false
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return bSuccess;
|
|
73
135
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const step4StartTime = new Date();
|
|
77
|
-
if (!await applyPermissions(ds, directory, baselineEntities)) {
|
|
78
|
-
(0, logging_1.logError)('Error applying permissions');
|
|
136
|
+
catch (e) {
|
|
137
|
+
(0, logging_1.logError)(e);
|
|
79
138
|
return false;
|
|
80
139
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
(0, logging_1.logError)(`Error executing
|
|
108
|
-
bSuccess = false; // keep going
|
|
140
|
+
}
|
|
141
|
+
async applyPermissions(ds, directory, entities, batchSize = 5) {
|
|
142
|
+
try {
|
|
143
|
+
let bSuccess = true;
|
|
144
|
+
for (let i = 0; i < entities.length; i += batchSize) {
|
|
145
|
+
const batch = entities.slice(i, i + batchSize);
|
|
146
|
+
const promises = batch.map(async (e) => {
|
|
147
|
+
// generate the file names for the entity
|
|
148
|
+
const files = this.getEntityPermissionFileNames(e);
|
|
149
|
+
let innerSuccess = true;
|
|
150
|
+
for (const f of files) {
|
|
151
|
+
const fullPath = path_1.default.join(directory, f);
|
|
152
|
+
if (fs.existsSync(fullPath)) {
|
|
153
|
+
if (!await this.SQLUtilityObject.executeSQLFile(ds, fullPath, true))
|
|
154
|
+
innerSuccess = false; // we keep going, just note that something failed
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// we don't have the file, so we can't execute it, but we should log it as an error
|
|
158
|
+
// and then keep going
|
|
159
|
+
(0, logging_1.logError)(`Permissions file ${fullPath} does not exist for entity ${e.Name}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return innerSuccess;
|
|
163
|
+
});
|
|
164
|
+
const results = await Promise.all(promises);
|
|
165
|
+
if (results.includes(false)) {
|
|
166
|
+
(0, logging_1.logError)(`Error executing one or more permissions files in batch starting from index ${i}`);
|
|
167
|
+
bSuccess = false; // keep going, but will return false at the end
|
|
109
168
|
}
|
|
110
169
|
}
|
|
170
|
+
return bSuccess;
|
|
111
171
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
else {
|
|
136
|
-
// we don't have the file, so we can't execute it, but we should log it as an error
|
|
137
|
-
// and then keep going
|
|
138
|
-
(0, logging_1.logError)(`Permissions file ${fullPath} does not exist for entity ${e.Name}`);
|
|
172
|
+
catch (err) {
|
|
173
|
+
(0, logging_1.logError)(err);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* 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.
|
|
179
|
+
* @param ds The DataSource object to use to execute the SQL
|
|
180
|
+
* @param entities The array of EntityInfo objects to generate SQL for
|
|
181
|
+
* @param directory The directory to save the generated SQL files to
|
|
182
|
+
* @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.
|
|
183
|
+
*/
|
|
184
|
+
async generateAndExecuteEntitySQLToSeparateFiles(ds, entities, directory, onlyPermissions, writeFiles = true, batchSize = 5) {
|
|
185
|
+
try {
|
|
186
|
+
let bFail = false;
|
|
187
|
+
const totalEntities = entities.length;
|
|
188
|
+
for (let i = 0; i < totalEntities; i += batchSize) {
|
|
189
|
+
const batch = entities.slice(i, i + batchSize);
|
|
190
|
+
const promises = batch.map(async (e) => {
|
|
191
|
+
const pkeyField = e.Fields.find(f => f.IsPrimaryKey);
|
|
192
|
+
if (!pkeyField) {
|
|
193
|
+
(0, logging_1.logError)(`SKIPPING ENTITY: Entity ${e.Name}, because it does not have a primary key field defined. A table must have a primary key defined to quality to be a MemberJunction entity`);
|
|
194
|
+
return false;
|
|
139
195
|
}
|
|
196
|
+
return this.generateAndExecuteSingleEntitySQLToSeparateFiles(ds, e, directory, onlyPermissions, writeFiles);
|
|
197
|
+
});
|
|
198
|
+
const results = await Promise.all(promises);
|
|
199
|
+
if (results.includes(false)) {
|
|
200
|
+
bFail = true; // keep going, but will return false at the end
|
|
140
201
|
}
|
|
141
|
-
return innerSuccess;
|
|
142
|
-
});
|
|
143
|
-
const results = await Promise.all(promises);
|
|
144
|
-
if (results.includes(false)) {
|
|
145
|
-
(0, logging_1.logError)(`Error executing one or more permissions files in batch starting from index ${i}`);
|
|
146
|
-
bSuccess = false; // keep going, but will return false at the end
|
|
147
202
|
}
|
|
203
|
+
return !bFail;
|
|
148
204
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const promises = batch.map(async (e) => {
|
|
164
|
-
const pkeyField = e.Fields.find(f => f.IsPrimaryKey);
|
|
165
|
-
if (!pkeyField) {
|
|
166
|
-
(0, logging_1.logError)(`SKIPPING ENTITY: Entity ${e.Name}, because it does not have a primary key field defined. A table must have a primary key defined to quality to be a MemberJunction entity`);
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
return generateAndExecuteSingleEntitySQLToSeparateFiles(ds, e, directory, onlyPermissions);
|
|
170
|
-
});
|
|
171
|
-
const results = await Promise.all(promises);
|
|
172
|
-
if (results.includes(false)) {
|
|
173
|
-
bFail = true; // keep going, but will return false at the end
|
|
205
|
+
catch (err) {
|
|
206
|
+
(0, logging_1.logError)(err);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async createCombinedEntitySQLFiles(directory, entities) {
|
|
211
|
+
// first, get a disinct list of schemanames from the entities
|
|
212
|
+
const schemaNames = entities.map(e => e.SchemaName).filter((value, index, self) => self.indexOf(value) === index);
|
|
213
|
+
for (const s of schemaNames) {
|
|
214
|
+
// generate the all-entities.sql file and all-entities.permissions.sql file in each schema folder
|
|
215
|
+
const fullPath = path_1.default.join(directory, s);
|
|
216
|
+
if (fs.statSync(fullPath).isDirectory()) {
|
|
217
|
+
(0, util_1.combineFiles)(fullPath, '_all_entities.sql', '*.generated.sql', true);
|
|
218
|
+
(0, util_1.combineFiles)(fullPath, '_all_entities.permissions.sql', '*.permissions.generated.sql', true);
|
|
174
219
|
}
|
|
175
220
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
exports.generateAndExecuteSingleEntitySQLToSeparateFiles = generateAndExecuteSingleEntitySQLToSeparateFiles;
|
|
208
|
-
async function generateSingleEntitySQLToSeparateFiles(ds, entity, directory, onlyPermissions) {
|
|
209
|
-
try {
|
|
210
|
-
// create the directory if it doesn't exist
|
|
211
|
-
if (!fs.existsSync(directory))
|
|
212
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
213
|
-
// now do the same thing for the /schema directory within the provided directory
|
|
214
|
-
const schemaDirectory = path_1.default.join(directory, entity.SchemaName);
|
|
215
|
-
if (!fs.existsSync(schemaDirectory))
|
|
216
|
-
fs.mkdirSync(schemaDirectory, { recursive: true }); // create the directory if it doesn't exist
|
|
217
|
-
let sRet = '';
|
|
218
|
-
// BASE VIEW
|
|
219
|
-
if (!onlyPermissions && entity.BaseViewGenerated && !entity.VirtualEntity) {
|
|
220
|
-
// generate the base view
|
|
221
|
-
const s = generateSingleEntitySQLFileHeader(entity, entity.BaseView) + await generateBaseView(ds, entity);
|
|
222
|
-
const filePath = path_1.default.join(directory, (0, sql_1.getDBObjectFileName)('view', entity.SchemaName, entity.BaseView, false, true));
|
|
223
|
-
fs.writeFileSync(filePath, s);
|
|
224
|
-
sRet += s + '\nGO\n';
|
|
225
|
-
}
|
|
226
|
-
// always generate permissions for the base view
|
|
227
|
-
const s = generateSingleEntitySQLFileHeader(entity, 'Permissions for ' + entity.BaseView) + generateViewPermissions(entity);
|
|
228
|
-
fs.writeFileSync(path_1.default.join(directory, (0, sql_1.getDBObjectFileName)('view', entity.SchemaName, entity.BaseView, true, true)), s);
|
|
229
|
-
// now, append the permissions to the return string IF we did NOT generate the base view - because if we generated the base view, that
|
|
230
|
-
// 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)
|
|
231
|
-
if (!entity.BaseViewGenerated)
|
|
232
|
-
sRet += s + '\nGO\n';
|
|
233
|
-
// CREATE SP
|
|
234
|
-
if (entity.AllowCreateAPI && !entity.VirtualEntity) {
|
|
235
|
-
const spName = getSPName(entity, exports.SPType.Create);
|
|
236
|
-
if (!onlyPermissions && entity.spCreateGenerated) {
|
|
237
|
-
// generate the create SP
|
|
238
|
-
const s = generateSingleEntitySQLFileHeader(entity, spName) + generateSPCreate(entity);
|
|
239
|
-
fs.writeFileSync(path_1.default.join(directory, (0, sql_1.getDBObjectFileName)('sp', entity.SchemaName, spName, false, true)), s);
|
|
221
|
+
}
|
|
222
|
+
async generateAndExecuteSingleEntitySQLToSeparateFiles(ds, entity, directory, onlyPermissions, writeFiles = true) {
|
|
223
|
+
try {
|
|
224
|
+
const { sql, permissionsSQL } = await this.generateSingleEntitySQLToSeparateFiles(ds, entity, directory, onlyPermissions, writeFiles); // this creates the files and returns a single string with all the SQL we can then execute
|
|
225
|
+
return await this.SQLUtilityObject.executeSQLScript(ds, sql, true) &&
|
|
226
|
+
await this.SQLUtilityObject.executeSQLScript(ds, permissionsSQL, true);
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
(0, logging_1.logError)(err);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async generateSingleEntitySQLToSeparateFiles(ds, entity, directory, onlyPermissions, writeFiles = true) {
|
|
234
|
+
try {
|
|
235
|
+
// create the directory if it doesn't exist
|
|
236
|
+
if (writeFiles && !fs.existsSync(directory))
|
|
237
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
238
|
+
// now do the same thing for the /schema directory within the provided directory
|
|
239
|
+
const schemaDirectory = path_1.default.join(directory, entity.SchemaName);
|
|
240
|
+
if (writeFiles && !fs.existsSync(schemaDirectory))
|
|
241
|
+
fs.mkdirSync(schemaDirectory, { recursive: true }); // create the directory if it doesn't exist
|
|
242
|
+
let sRet = '';
|
|
243
|
+
let permissionsSQL = '';
|
|
244
|
+
// BASE VIEW
|
|
245
|
+
if (!onlyPermissions && entity.BaseViewGenerated && !entity.VirtualEntity) {
|
|
246
|
+
// generate the base view
|
|
247
|
+
const s = this.generateSingleEntitySQLFileHeader(entity, entity.BaseView) + await this.generateBaseView(ds, entity);
|
|
248
|
+
const filePath = path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('view', entity.SchemaName, entity.BaseView, false, true));
|
|
249
|
+
if (writeFiles)
|
|
250
|
+
fs.writeFileSync(filePath, s);
|
|
240
251
|
sRet += s + '\nGO\n';
|
|
241
252
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
// always generate permissions for the base view
|
|
254
|
+
const s = this.generateSingleEntitySQLFileHeader(entity, 'Permissions for ' + entity.BaseView) + this.generateViewPermissions(entity);
|
|
255
|
+
if (s.length > 0)
|
|
256
|
+
permissionsSQL += s + '\nGO\n';
|
|
257
|
+
if (writeFiles)
|
|
258
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('view', entity.SchemaName, entity.BaseView, true, true)), s);
|
|
259
|
+
// now, append the permissions to the return string IF we did NOT generate the base view - because if we generated the base view, that
|
|
245
260
|
// 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.
|
|
261
|
+
if (!entity.BaseViewGenerated)
|
|
247
262
|
sRet += s + '\nGO\n';
|
|
263
|
+
// CREATE SP
|
|
264
|
+
if (entity.AllowCreateAPI && !entity.VirtualEntity) {
|
|
265
|
+
const spName = this.getSPName(entity, exports.SPType.Create);
|
|
266
|
+
if (!onlyPermissions && entity.spCreateGenerated) {
|
|
267
|
+
// generate the create SP
|
|
268
|
+
const s = this.generateSingleEntitySQLFileHeader(entity, spName) + this.generateSPCreate(entity);
|
|
269
|
+
if (writeFiles)
|
|
270
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, spName, false, true)), s);
|
|
271
|
+
sRet += s + '\nGO\n';
|
|
272
|
+
}
|
|
273
|
+
const s = this.generateSPPermissions(entity, spName, exports.SPType.Create) + '\n\n';
|
|
274
|
+
if (s.length > 0)
|
|
275
|
+
permissionsSQL += s + '\nGO\n';
|
|
276
|
+
if (writeFiles)
|
|
277
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, spName, true, true)), s);
|
|
278
|
+
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
279
|
+
// 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)
|
|
280
|
+
if (!entity.spCreateGenerated)
|
|
281
|
+
sRet += s + '\nGO\n';
|
|
282
|
+
}
|
|
283
|
+
// UPDATE SP
|
|
284
|
+
if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
|
|
285
|
+
const spName = this.getSPName(entity, exports.SPType.Update);
|
|
286
|
+
if (!onlyPermissions && entity.spUpdateGenerated) {
|
|
287
|
+
// generate the update SP
|
|
288
|
+
const s = this.generateSingleEntitySQLFileHeader(entity, spName) + this.generateSPUpdate(entity);
|
|
289
|
+
if (writeFiles)
|
|
290
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, spName, false, true)), s);
|
|
291
|
+
sRet += s + '\nGO\n';
|
|
292
|
+
}
|
|
293
|
+
const s = this.generateSPPermissions(entity, spName, exports.SPType.Update) + '\n\n';
|
|
294
|
+
if (s.length > 0)
|
|
295
|
+
permissionsSQL += s + '\nGO\n';
|
|
296
|
+
if (writeFiles)
|
|
297
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, spName, true, true)), s);
|
|
298
|
+
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
299
|
+
// 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)
|
|
300
|
+
if (!entity.spUpdateGenerated)
|
|
301
|
+
sRet += s + '\nGO\n';
|
|
302
|
+
}
|
|
303
|
+
// DELETE SP
|
|
304
|
+
if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
|
|
305
|
+
const spName = this.getSPName(entity, exports.SPType.Delete);
|
|
306
|
+
if (!onlyPermissions && entity.spDeleteGenerated) {
|
|
307
|
+
// generate the delete SP
|
|
308
|
+
const s = this.generateSingleEntitySQLFileHeader(entity, spName) + this.generateSPDelete(entity);
|
|
309
|
+
if (writeFiles)
|
|
310
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, spName, false, true)), s);
|
|
311
|
+
sRet += s + '\nGO\n';
|
|
312
|
+
}
|
|
313
|
+
const s = this.generateSPPermissions(entity, spName, exports.SPType.Delete) + '\n\n';
|
|
314
|
+
if (s.length > 0)
|
|
315
|
+
permissionsSQL += s + '\nGO\n';
|
|
316
|
+
if (writeFiles)
|
|
317
|
+
fs.writeFileSync(path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, spName, true, true)), s);
|
|
318
|
+
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
319
|
+
// 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)
|
|
320
|
+
if (!entity.spDeleteGenerated)
|
|
321
|
+
sRet += s + '\nGO\n';
|
|
322
|
+
}
|
|
323
|
+
// check to see if the entity supports full text search or not
|
|
324
|
+
if (entity.FullTextSearchEnabled) {
|
|
325
|
+
// always generate the code so we can get the function name from the below function call
|
|
326
|
+
const ft = await this.generateEntityFullTextSearchSQL(ds, entity);
|
|
327
|
+
if (!onlyPermissions) {
|
|
328
|
+
// only write the actual sql out if we're not only generating permissions
|
|
329
|
+
const filePath = path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', entity.SchemaName, entity.BaseTable, false, true));
|
|
330
|
+
if (writeFiles)
|
|
331
|
+
fs.writeFileSync(filePath, ft.sql);
|
|
332
|
+
sRet += ft.sql + '\nGO\n';
|
|
333
|
+
}
|
|
334
|
+
const sP = this.generateFullTextSearchFunctionPermissions(entity, ft.functionName) + '\n\n';
|
|
335
|
+
if (sP.length > 0)
|
|
336
|
+
permissionsSQL += sP + '\nGO\n';
|
|
337
|
+
const filePath = path_1.default.join(directory, this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', entity.SchemaName, entity.BaseTable, true, true));
|
|
338
|
+
if (writeFiles)
|
|
339
|
+
fs.writeFileSync(filePath, sP);
|
|
340
|
+
// now, append the permissions to the return string IF we did NOT generate the function - because if we generated the function, that
|
|
341
|
+
// 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)
|
|
342
|
+
if (!entity.FullTextSearchFunctionGenerated)
|
|
343
|
+
sRet += sP + '\nGO\n';
|
|
344
|
+
}
|
|
345
|
+
return { sql: sRet, permissionsSQL: permissionsSQL };
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
(0, logging_1.logError)(err);
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
getSPName(entity, type) {
|
|
353
|
+
switch (type) {
|
|
354
|
+
case exports.SPType.Create:
|
|
355
|
+
return entity.spCreate && entity.spCreate.length > 0 ? entity.spCreate : 'spCreate' + entity.ClassName;
|
|
356
|
+
case exports.SPType.Update:
|
|
357
|
+
return entity.spUpdate && entity.spUpdate.length > 0 ? entity.spUpdate : 'spUpdate' + entity.ClassName;
|
|
358
|
+
case exports.SPType.Delete:
|
|
359
|
+
return entity.spDelete && entity.spDelete.length > 0 ? entity.spDelete : 'spDelete' + entity.ClassName;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
getEntityPermissionFileNames(entity) {
|
|
363
|
+
const files = [];
|
|
364
|
+
// all entities have a base view - and we always generate permissions for the base view even if not generated base view
|
|
365
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('view', entity.SchemaName, entity.BaseView, true, true));
|
|
366
|
+
// only add the SP files if the entity is not a virtual entity
|
|
367
|
+
if (!entity.VirtualEntity) {
|
|
368
|
+
// 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
|
|
369
|
+
if (entity.AllowCreateAPI)
|
|
370
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, exports.SPType.Create), true, true));
|
|
371
|
+
if (entity.AllowUpdateAPI)
|
|
372
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, exports.SPType.Update), true, true));
|
|
373
|
+
if (entity.AllowDeleteAPI)
|
|
374
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('sp', entity.SchemaName, this.getSPName(entity, exports.SPType.Delete), true, true));
|
|
375
|
+
}
|
|
376
|
+
if (entity.FullTextSearchEnabled)
|
|
377
|
+
files.push(this.SQLUtilityObject.getDBObjectFileName('full_text_search_function', entity.SchemaName, entity.BaseTable, true, true));
|
|
378
|
+
return files;
|
|
379
|
+
}
|
|
380
|
+
async generateEntitySQL(ds, entity) {
|
|
381
|
+
let sOutput = '';
|
|
382
|
+
if (entity.BaseViewGenerated && !entity.VirtualEntity)
|
|
383
|
+
// generated the base view (will include permissions)
|
|
384
|
+
sOutput += await this.generateBaseView(ds, entity) + '\n\n';
|
|
385
|
+
else
|
|
386
|
+
// still generate the permissions for the view even if a custom view
|
|
387
|
+
sOutput += this.generateViewPermissions(entity) + '\n\n';
|
|
388
|
+
if (entity.AllowCreateAPI && !entity.VirtualEntity) {
|
|
389
|
+
if (entity.spCreateGenerated)
|
|
390
|
+
// generated SP, will include permissions
|
|
391
|
+
sOutput += this.generateSPCreate(entity) + '\n\n';
|
|
392
|
+
else
|
|
393
|
+
// custom SP, still generate the permissions
|
|
394
|
+
sOutput += this.generateSPPermissions(entity, entity.spCreate, exports.SPType.Create) + '\n\n';
|
|
248
395
|
}
|
|
249
|
-
// UPDATE SP
|
|
250
396
|
if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
const s = generateSPPermissions(entity, spName, exports.SPType.Update) + '\n\n';
|
|
259
|
-
fs.writeFileSync(path_1.default.join(directory, (0, sql_1.getDBObjectFileName)('sp', entity.SchemaName, spName, true, true)), s);
|
|
260
|
-
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
261
|
-
// 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)
|
|
262
|
-
if (!entity.spUpdateGenerated)
|
|
263
|
-
sRet += s + '\nGO\n';
|
|
397
|
+
if (entity.spUpdateGenerated)
|
|
398
|
+
// generated SP, will include permissions
|
|
399
|
+
sOutput += this.generateSPUpdate(entity) + '\n\n';
|
|
400
|
+
else
|
|
401
|
+
// custom SP, still generate the permissions
|
|
402
|
+
sOutput += this.generateSPPermissions(entity, entity.spUpdate, exports.SPType.Update) + '\n\n';
|
|
264
403
|
}
|
|
265
|
-
// DELETE SP
|
|
266
404
|
if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
const s = generateSPPermissions(entity, spName, exports.SPType.Delete) + '\n\n';
|
|
275
|
-
fs.writeFileSync(path_1.default.join(directory, (0, sql_1.getDBObjectFileName)('sp', entity.SchemaName, spName, true, true)), s);
|
|
276
|
-
// now, append the permissions to the return string IF we did NOT generate the proc - because if we generated the proc, that
|
|
277
|
-
// 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)
|
|
278
|
-
if (!entity.spDeleteGenerated)
|
|
279
|
-
sRet += s + '\nGO\n';
|
|
405
|
+
if (entity.spDeleteGenerated)
|
|
406
|
+
// generated SP, will include permissions
|
|
407
|
+
sOutput += this.generateSPDelete(entity) + '\n\n';
|
|
408
|
+
else
|
|
409
|
+
// custom SP, still generate the permissions
|
|
410
|
+
sOutput += this.generateSPPermissions(entity, entity.spDelete, exports.SPType.Delete) + '\n\n';
|
|
280
411
|
}
|
|
281
412
|
// check to see if the entity supports full text search or not
|
|
282
413
|
if (entity.FullTextSearchEnabled) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
414
|
+
sOutput += await this.generateEntityFullTextSearchSQL(ds, entity) + '\n\n';
|
|
415
|
+
}
|
|
416
|
+
return sOutput;
|
|
417
|
+
}
|
|
418
|
+
async generateEntityFullTextSearchSQL(ds, entity) {
|
|
419
|
+
let sql = '';
|
|
420
|
+
const catalogName = entity.FullTextCatalog && entity.FullTextCatalog.length > 0 ? entity.FullTextCatalog : config_1.dbDatabase + '_FullTextCatalog';
|
|
421
|
+
if (entity.FullTextCatalogGenerated) {
|
|
422
|
+
// this situation means we have a generated catalog and the user has provided a name specific to THIS entity
|
|
423
|
+
sql += ` -- CREATE THE FULL TEXT CATALOG FOR THE ENTITY, IF NOT ALREADY CREATED
|
|
424
|
+
IF NOT EXISTS (
|
|
425
|
+
SELECT *
|
|
426
|
+
FROM sys.fulltext_catalogs
|
|
427
|
+
WHERE name = '${catalogName}'
|
|
428
|
+
)
|
|
429
|
+
CREATE FULLTEXT CATALOG ${catalogName};
|
|
430
|
+
GO
|
|
431
|
+
`;
|
|
432
|
+
}
|
|
433
|
+
if (entity.FullTextIndexGenerated) {
|
|
434
|
+
const fullTextFields = entity.Fields.filter(f => f.FullTextSearchEnabled).map(f => `${f.Name} LANGUAGE 'English'`).join(', ');
|
|
435
|
+
if (fullTextFields.length === 0)
|
|
436
|
+
throw new Error(`FullTextIndexGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
|
|
437
|
+
// drop and recreate the full text index
|
|
438
|
+
const entity_pk_name = await this.getEntityPrimaryKeyIndexName(ds, entity);
|
|
439
|
+
sql += ` -- DROP AND RECREATE THE FULL TEXT INDEX
|
|
440
|
+
IF EXISTS (
|
|
441
|
+
SELECT *
|
|
442
|
+
FROM sys.fulltext_indexes
|
|
443
|
+
WHERE object_id = OBJECT_ID('${entity.SchemaName}.${entity.BaseTable}')
|
|
444
|
+
)
|
|
445
|
+
BEGIN
|
|
446
|
+
DROP FULLTEXT INDEX ON [${entity.SchemaName}].[${entity.BaseTable}];
|
|
447
|
+
END
|
|
448
|
+
GO
|
|
449
|
+
|
|
450
|
+
IF NOT EXISTS (
|
|
451
|
+
SELECT *
|
|
452
|
+
FROM sys.fulltext_indexes
|
|
453
|
+
WHERE object_id = OBJECT_ID('${entity.SchemaName}.${entity.BaseTable}')
|
|
454
|
+
)
|
|
455
|
+
BEGIN
|
|
456
|
+
CREATE FULLTEXT INDEX ON [${entity.SchemaName}].[${entity.BaseTable}]
|
|
457
|
+
(
|
|
458
|
+
${fullTextFields}
|
|
459
|
+
)
|
|
460
|
+
KEY INDEX ${entity_pk_name}
|
|
461
|
+
ON ${catalogName};
|
|
462
|
+
END
|
|
463
|
+
GO
|
|
464
|
+
`;
|
|
465
|
+
}
|
|
466
|
+
const functionName = entity.FullTextSearchFunction && entity.FullTextSearchFunction.length > 0 ? entity.FullTextSearchFunction : `fnSearch${entity.CodeName}`;
|
|
467
|
+
if (entity.FullTextSearchFunctionGenerated) {
|
|
468
|
+
const fullTextFieldsSimple = entity.Fields.filter(f => f.FullTextSearchEnabled).map(f => '[' + f.Name + ']').join(', ');
|
|
469
|
+
if (fullTextFieldsSimple.length === 0)
|
|
470
|
+
throw new Error(`FullTextSearchFunctionGenerated is true for entity ${entity.Name}, but no fields are marked as FullTextSearchEnabled`);
|
|
471
|
+
if (!entity.FullTextSearchFunction || entity.FullTextSearchFunction.length === 0) {
|
|
472
|
+
// update this in the DB
|
|
473
|
+
const md = new core_1.Metadata();
|
|
474
|
+
const u = sqlserver_dataprovider_1.UserCache.Instance.Users[0];
|
|
475
|
+
if (!u)
|
|
476
|
+
throw new Error('Could not find the first user in the cache, cant generate the full text search function without a user');
|
|
477
|
+
const e = await md.GetEntityObject('Entities', u);
|
|
478
|
+
await e.Load(entity.ID);
|
|
479
|
+
e.FullTextSearchFunction = functionName;
|
|
480
|
+
if (!await e.Save())
|
|
481
|
+
throw new Error(`Could not update the FullTextSearchFunction for entity ${entity.Name}`);
|
|
290
482
|
}
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
}
|
|
332
|
-
if (entity.FullTextSearchEnabled)
|
|
333
|
-
files.push((0, sql_1.getDBObjectFileName)('full_text_search_function', entity.SchemaName, entity.BaseTable, true, true));
|
|
334
|
-
return files;
|
|
335
|
-
}
|
|
336
|
-
exports.getEntityPermissionFileNames = getEntityPermissionFileNames;
|
|
337
|
-
async function generateEntitySQL(ds, entity) {
|
|
338
|
-
let sOutput = '';
|
|
339
|
-
if (entity.BaseViewGenerated && !entity.VirtualEntity)
|
|
340
|
-
// generated the base view (will include permissions)
|
|
341
|
-
sOutput += await generateBaseView(ds, entity) + '\n\n';
|
|
342
|
-
else
|
|
343
|
-
// still generate the permissions for the view even if a custom view
|
|
344
|
-
sOutput += generateViewPermissions(entity) + '\n\n';
|
|
345
|
-
if (entity.AllowCreateAPI && !entity.VirtualEntity) {
|
|
346
|
-
if (entity.spCreateGenerated)
|
|
347
|
-
// generated SP, will include permissions
|
|
348
|
-
sOutput += generateSPCreate(entity) + '\n\n';
|
|
349
|
-
else
|
|
350
|
-
// custom SP, still generate the permissions
|
|
351
|
-
sOutput += generateSPPermissions(entity, entity.spCreate, exports.SPType.Create) + '\n\n';
|
|
352
|
-
}
|
|
353
|
-
if (entity.AllowUpdateAPI && !entity.VirtualEntity) {
|
|
354
|
-
if (entity.spUpdateGenerated)
|
|
355
|
-
// generated SP, will include permissions
|
|
356
|
-
sOutput += generateSPUpdate(entity) + '\n\n';
|
|
357
|
-
else
|
|
358
|
-
// custom SP, still generate the permissions
|
|
359
|
-
sOutput += generateSPPermissions(entity, entity.spUpdate, exports.SPType.Update) + '\n\n';
|
|
360
|
-
}
|
|
361
|
-
if (entity.AllowDeleteAPI && !entity.VirtualEntity) {
|
|
362
|
-
if (entity.spDeleteGenerated)
|
|
363
|
-
// generated SP, will include permissions
|
|
364
|
-
sOutput += generateSPDelete(entity) + '\n\n';
|
|
483
|
+
const pkeyList = entity.PrimaryKeys.map(pk => '[' + pk.Name + ']').join(', ');
|
|
484
|
+
// drop and recreate the full text search function
|
|
485
|
+
sql += ` -- DROP AND RECREATE THE FULL TEXT SEARCH FUNCTION
|
|
486
|
+
-- Create an inline table-valued function to perform full-text search
|
|
487
|
+
-- Drop the function if it already exists
|
|
488
|
+
IF OBJECT_ID('${entity.SchemaName}.${functionName}', 'IF') IS NOT NULL
|
|
489
|
+
DROP FUNCTION ${entity.SchemaName}.${functionName};
|
|
490
|
+
GO
|
|
491
|
+
CREATE FUNCTION ${entity.SchemaName}.${functionName} (@searchTerm NVARCHAR(255))
|
|
492
|
+
RETURNS TABLE
|
|
493
|
+
AS
|
|
494
|
+
RETURN (
|
|
495
|
+
SELECT ${pkeyList}
|
|
496
|
+
FROM [${entity.SchemaName}].[${entity.BaseTable}]
|
|
497
|
+
WHERE CONTAINS((${fullTextFieldsSimple}), @searchTerm)
|
|
498
|
+
)
|
|
499
|
+
GO
|
|
500
|
+
`;
|
|
501
|
+
sql += this.generateFullTextSearchFunctionPermissions(entity, functionName) + '\n\nGO\n';
|
|
502
|
+
}
|
|
503
|
+
return { sql, functionName };
|
|
504
|
+
}
|
|
505
|
+
async getEntityPrimaryKeyIndexName(ds, entity) {
|
|
506
|
+
const sSQL = ` SELECT
|
|
507
|
+
i.name AS IndexName
|
|
508
|
+
FROM
|
|
509
|
+
sys.indexes i
|
|
510
|
+
INNER JOIN
|
|
511
|
+
sys.objects o ON i.object_id = o.object_id
|
|
512
|
+
INNER JOIN
|
|
513
|
+
sys.key_constraints kc ON i.object_id = kc.parent_object_id AND
|
|
514
|
+
i.index_id = kc.unique_index_id
|
|
515
|
+
WHERE
|
|
516
|
+
o.name = '${entity.BaseTable}' AND
|
|
517
|
+
o.schema_id = SCHEMA_ID('${entity.SchemaName}') AND
|
|
518
|
+
kc.type = 'PK';
|
|
519
|
+
`;
|
|
520
|
+
const result = await ds.query(sSQL);
|
|
521
|
+
if (result && result.length > 0)
|
|
522
|
+
return result[0].IndexName;
|
|
365
523
|
else
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
// this situation means we have a generated catalog and the user has provided a name specific to THIS entity
|
|
381
|
-
sql += ` -- CREATE THE FULL TEXT CATALOG FOR THE ENTITY, IF NOT ALREADY CREATED
|
|
382
|
-
IF NOT EXISTS (
|
|
383
|
-
SELECT *
|
|
384
|
-
FROM sys.fulltext_catalogs
|
|
385
|
-
WHERE name = '${catalogName}'
|
|
386
|
-
)
|
|
387
|
-
CREATE FULLTEXT CATALOG ${catalogName};
|
|
388
|
-
GO
|
|
524
|
+
throw new Error(`Could not find primary key index for entity ${entity.Name}`);
|
|
525
|
+
}
|
|
526
|
+
generateAllEntitiesSQLFileHeader() {
|
|
527
|
+
return `-----------------------------------------------------------------
|
|
528
|
+
-- SQL Code Generation for Entities
|
|
529
|
+
-- Generated: ${new Date().toLocaleString()}
|
|
530
|
+
--
|
|
531
|
+
-- This file contains the SQL code for the entities in the database
|
|
532
|
+
-- that are included in the API and have generated SQL elements like views and
|
|
533
|
+
-- stored procedures.
|
|
534
|
+
--
|
|
535
|
+
-- It is generated by the MemberJunction CodeGen tool.
|
|
536
|
+
-- It is not intended to be edited by hand.
|
|
537
|
+
-----------------------------------------------------------------
|
|
389
538
|
`;
|
|
390
539
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
WHERE object_id = OBJECT_ID('${entity.SchemaName}.${entity.BaseTable}')
|
|
402
|
-
)
|
|
403
|
-
BEGIN
|
|
404
|
-
DROP FULLTEXT INDEX ON [${entity.SchemaName}].[${entity.BaseTable}];
|
|
405
|
-
END
|
|
406
|
-
GO
|
|
407
|
-
|
|
408
|
-
IF NOT EXISTS (
|
|
409
|
-
SELECT *
|
|
410
|
-
FROM sys.fulltext_indexes
|
|
411
|
-
WHERE object_id = OBJECT_ID('${entity.SchemaName}.${entity.BaseTable}')
|
|
412
|
-
)
|
|
413
|
-
BEGIN
|
|
414
|
-
CREATE FULLTEXT INDEX ON [${entity.SchemaName}].[${entity.BaseTable}]
|
|
415
|
-
(
|
|
416
|
-
${fullTextFields}
|
|
417
|
-
)
|
|
418
|
-
KEY INDEX ${entity_pk_name}
|
|
419
|
-
ON ${catalogName};
|
|
420
|
-
END
|
|
421
|
-
GO
|
|
540
|
+
generateSingleEntitySQLFileHeader(entity, itemName) {
|
|
541
|
+
return `-----------------------------------------------------------------
|
|
542
|
+
-- SQL Code Generation
|
|
543
|
+
-- Entity: ${entity.Name}
|
|
544
|
+
-- Item: ${itemName}
|
|
545
|
+
-- Generated: ${new Date().toLocaleString()}
|
|
546
|
+
--
|
|
547
|
+
-- This was generated by the MemberJunction CodeGen tool.
|
|
548
|
+
-- This file should NOT be edited by hand.
|
|
549
|
+
-----------------------------------------------------------------
|
|
422
550
|
`;
|
|
423
551
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
DROP FUNCTION ${entity.SchemaName}.${functionName};
|
|
448
|
-
GO
|
|
449
|
-
CREATE FUNCTION ${entity.SchemaName}.${functionName} (@searchTerm NVARCHAR(255))
|
|
450
|
-
RETURNS TABLE
|
|
451
|
-
AS
|
|
452
|
-
RETURN (
|
|
453
|
-
SELECT ${pkeyList}
|
|
454
|
-
FROM [${entity.SchemaName}].[${entity.BaseTable}]
|
|
455
|
-
WHERE CONTAINS((${fullTextFieldsSimple}), @searchTerm)
|
|
456
|
-
)
|
|
457
|
-
GO
|
|
458
|
-
`;
|
|
459
|
-
sql += generateFullTextSearchFunctionPermissions(entity, functionName) + '\n\nGO\n';
|
|
460
|
-
}
|
|
461
|
-
return { sql, functionName };
|
|
462
|
-
}
|
|
463
|
-
async function getEntityPrimaryKeyIndexName(ds, entity) {
|
|
464
|
-
const sSQL = ` SELECT
|
|
465
|
-
i.name AS IndexName
|
|
466
|
-
FROM
|
|
467
|
-
sys.indexes i
|
|
468
|
-
INNER JOIN
|
|
469
|
-
sys.objects o ON i.object_id = o.object_id
|
|
470
|
-
INNER JOIN
|
|
471
|
-
sys.key_constraints kc ON i.object_id = kc.parent_object_id AND
|
|
472
|
-
i.index_id = kc.unique_index_id
|
|
473
|
-
WHERE
|
|
474
|
-
o.name = '${entity.BaseTable}' AND
|
|
475
|
-
o.schema_id = SCHEMA_ID('${entity.SchemaName}') AND
|
|
476
|
-
kc.type = 'PK';
|
|
552
|
+
async generateBaseView(ds, entity) {
|
|
553
|
+
const viewName = entity.BaseView ? entity.BaseView : `vw${entity.CodeName}`;
|
|
554
|
+
const baseTableFirstChar = entity.BaseTable.charAt(0).toLowerCase();
|
|
555
|
+
const relatedFieldsString = await this.generateBaseViewRelatedFieldsString(ds, entity.Fields);
|
|
556
|
+
const relatedFieldsJoinString = this.generateBaseViewJoins(entity.Fields);
|
|
557
|
+
const permissions = this.generateViewPermissions(entity);
|
|
558
|
+
return `
|
|
559
|
+
------------------------------------------------------------
|
|
560
|
+
----- BASE VIEW FOR ENTITY: ${entity.Name}
|
|
561
|
+
----- SCHEMA: ${entity.SchemaName}
|
|
562
|
+
----- BASE TABLE: ${entity.BaseTable}
|
|
563
|
+
----- PRIMARY KEY: ${entity.PrimaryKey.Name}
|
|
564
|
+
------------------------------------------------------------
|
|
565
|
+
DROP VIEW IF EXISTS [${entity.SchemaName}].[${viewName}]
|
|
566
|
+
GO
|
|
567
|
+
|
|
568
|
+
CREATE VIEW [${entity.SchemaName}].[${viewName}]
|
|
569
|
+
AS
|
|
570
|
+
SELECT
|
|
571
|
+
${baseTableFirstChar}.*${relatedFieldsString.length > 0 ? ',' : ''}${relatedFieldsString}
|
|
572
|
+
FROM
|
|
573
|
+
[${entity.SchemaName}].[${entity.BaseTable}] AS ${baseTableFirstChar}${relatedFieldsJoinString ? '\n' + relatedFieldsJoinString : ''}
|
|
574
|
+
GO${permissions}
|
|
477
575
|
`;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
[${entity.SchemaName}].[${entity.BaseTable}] AS ${baseTableFirstChar}${relatedFieldsJoinString ? '\n' + relatedFieldsJoinString : ''}
|
|
534
|
-
GO${permissions}
|
|
535
|
-
`;
|
|
536
|
-
}
|
|
537
|
-
function generateViewPermissions(entity) {
|
|
538
|
-
let sOutput = '';
|
|
539
|
-
for (let i = 0; i < entity.Permissions.length; i++) {
|
|
540
|
-
const ep = entity.Permissions[i];
|
|
541
|
-
sOutput += (sOutput == '' ? `GRANT SELECT ON [${entity.SchemaName}].[${entity.BaseView}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
|
|
542
|
-
}
|
|
543
|
-
return (sOutput == '' ? '' : '\n') + sOutput;
|
|
544
|
-
}
|
|
545
|
-
function generateBaseViewJoins(entityFields) {
|
|
546
|
-
let sOutput = '';
|
|
547
|
-
for (let i = 0; i < entityFields.length; i++) {
|
|
548
|
-
const ef = entityFields[i];
|
|
549
|
-
if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView && ef._RelatedEntityTableAlias) {
|
|
550
|
-
sOutput += sOutput == '' ? '' : '\n';
|
|
551
|
-
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}]`;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
return sOutput;
|
|
555
|
-
}
|
|
556
|
-
async function generateBaseViewRelatedFieldsString(ds, entityFields) {
|
|
557
|
-
let sOutput = '';
|
|
558
|
-
let fieldCount = 0;
|
|
559
|
-
for (let i = 0; i < entityFields.length; i++) {
|
|
560
|
-
const ef = entityFields[i];
|
|
561
|
-
if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView) {
|
|
562
|
-
const { nameField, nameFieldIsVirtual } = getIsNameFieldForSingleEntity(ef.RelatedEntity);
|
|
563
|
-
if (nameField !== '') {
|
|
564
|
-
// only add to the output, if we found a name field for the related entity.
|
|
565
|
-
ef._RelatedEntityTableAlias = ef.RelatedEntityClassName + '_' + ef.Name;
|
|
566
|
-
ef._RelatedEntityNameFieldIsVirtual = nameFieldIsVirtual;
|
|
567
|
-
// This next section generates a field name for the new virtual field and makes sure it doesn't collide with a field in the base table
|
|
568
|
-
const candidateName = stripID(ef.Name);
|
|
569
|
-
// check to make sure candidateName is not already a field name in the base table (other than a virtual field of course, as that is what we're creating)
|
|
570
|
-
// because if it is, we need to change it to something else
|
|
571
|
-
const bFound = entityFields.find(f => f.IsVirtual === false && f.Name.trim().toLowerCase() === candidateName.trim().toLowerCase()) !== undefined;
|
|
572
|
-
if (bFound)
|
|
573
|
-
ef._RelatedEntityNameFieldMap = candidateName + '_Virtual';
|
|
574
|
-
else
|
|
575
|
-
ef._RelatedEntityNameFieldMap = candidateName;
|
|
576
|
-
// now we have a safe field name alias for the new virtual field in the _RelatedEntityNameFieldMap property, so use it...
|
|
577
|
-
sOutput += `${fieldCount == 0 ? '' : ','}\n ${ef._RelatedEntityTableAlias}.[${nameField}] AS [${ef._RelatedEntityNameFieldMap}]`;
|
|
578
|
-
// check to see if the database already knows about the RelatedEntityNameFieldMap or not
|
|
579
|
-
if (ef.RelatedEntityNameFieldMap === null ||
|
|
580
|
-
ef.RelatedEntityNameFieldMap === undefined ||
|
|
581
|
-
ef.RelatedEntityNameFieldMap.trim().length === 0) {
|
|
582
|
-
// the database doesn't yet know about this RelatedEntityNameFieldMap, so we need to update it
|
|
583
|
-
// first update the actul field in the metadata object so it can be used from this point forward
|
|
584
|
-
// and it also reflects what the DB will hold
|
|
585
|
-
ef.RelatedEntityNameFieldMap = ef._RelatedEntityNameFieldMap;
|
|
586
|
-
// then update the database itself
|
|
587
|
-
await (0, manageMetadata_1.updateEntityFieldRelatedEntityNameFieldMap)(ds, ef.ID, ef.RelatedEntityNameFieldMap);
|
|
576
|
+
}
|
|
577
|
+
generateViewPermissions(entity) {
|
|
578
|
+
let sOutput = '';
|
|
579
|
+
for (let i = 0; i < entity.Permissions.length; i++) {
|
|
580
|
+
const ep = entity.Permissions[i];
|
|
581
|
+
sOutput += (sOutput == '' ? `GRANT SELECT ON [${entity.SchemaName}].[${entity.BaseView}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
|
|
582
|
+
}
|
|
583
|
+
return (sOutput == '' ? '' : '\n') + sOutput;
|
|
584
|
+
}
|
|
585
|
+
generateBaseViewJoins(entityFields) {
|
|
586
|
+
let sOutput = '';
|
|
587
|
+
for (let i = 0; i < entityFields.length; i++) {
|
|
588
|
+
const ef = entityFields[i];
|
|
589
|
+
if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView && ef._RelatedEntityTableAlias) {
|
|
590
|
+
sOutput += sOutput == '' ? '' : '\n';
|
|
591
|
+
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}]`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return sOutput;
|
|
595
|
+
}
|
|
596
|
+
async generateBaseViewRelatedFieldsString(ds, entityFields) {
|
|
597
|
+
let sOutput = '';
|
|
598
|
+
let fieldCount = 0;
|
|
599
|
+
const manageMD = global_1.MJGlobal.Instance.ClassFactory.CreateInstance(manageMetadata_1.ManageMetadataBase);
|
|
600
|
+
for (let i = 0; i < entityFields.length; i++) {
|
|
601
|
+
const ef = entityFields[i];
|
|
602
|
+
if (ef.RelatedEntityID && ef.IncludeRelatedEntityNameFieldInBaseView) {
|
|
603
|
+
const { nameField, nameFieldIsVirtual } = this.getIsNameFieldForSingleEntity(ef.RelatedEntity);
|
|
604
|
+
if (nameField !== '') {
|
|
605
|
+
// only add to the output, if we found a name field for the related entity.
|
|
606
|
+
ef._RelatedEntityTableAlias = ef.RelatedEntityClassName + '_' + ef.Name;
|
|
607
|
+
ef._RelatedEntityNameFieldIsVirtual = nameFieldIsVirtual;
|
|
608
|
+
// This next section generates a field name for the new virtual field and makes sure it doesn't collide with a field in the base table
|
|
609
|
+
const candidateName = this.stripID(ef.Name);
|
|
610
|
+
// check to make sure candidateName is not already a field name in the base table (other than a virtual field of course, as that is what we're creating)
|
|
611
|
+
// because if it is, we need to change it to something else
|
|
612
|
+
const bFound = entityFields.find(f => f.IsVirtual === false && f.Name.trim().toLowerCase() === candidateName.trim().toLowerCase()) !== undefined;
|
|
613
|
+
if (bFound)
|
|
614
|
+
ef._RelatedEntityNameFieldMap = candidateName + '_Virtual';
|
|
615
|
+
else
|
|
616
|
+
ef._RelatedEntityNameFieldMap = candidateName;
|
|
617
|
+
// now we have a safe field name alias for the new virtual field in the _RelatedEntityNameFieldMap property, so use it...
|
|
618
|
+
sOutput += `${fieldCount == 0 ? '' : ','}\n ${ef._RelatedEntityTableAlias}.[${nameField}] AS [${ef._RelatedEntityNameFieldMap}]`;
|
|
619
|
+
// check to see if the database already knows about the RelatedEntityNameFieldMap or not
|
|
620
|
+
if (ef.RelatedEntityNameFieldMap === null ||
|
|
621
|
+
ef.RelatedEntityNameFieldMap === undefined ||
|
|
622
|
+
ef.RelatedEntityNameFieldMap.trim().length === 0) {
|
|
623
|
+
// the database doesn't yet know about this RelatedEntityNameFieldMap, so we need to update it
|
|
624
|
+
// first update the actul field in the metadata object so it can be used from this point forward
|
|
625
|
+
// and it also reflects what the DB will hold
|
|
626
|
+
ef.RelatedEntityNameFieldMap = ef._RelatedEntityNameFieldMap;
|
|
627
|
+
// then update the database itself
|
|
628
|
+
await manageMD.updateEntityFieldRelatedEntityNameFieldMap(ds, ef.ID, ef.RelatedEntityNameFieldMap);
|
|
629
|
+
}
|
|
630
|
+
fieldCount++;
|
|
588
631
|
}
|
|
589
|
-
fieldCount++;
|
|
590
632
|
}
|
|
591
633
|
}
|
|
634
|
+
return sOutput;
|
|
635
|
+
}
|
|
636
|
+
getIsNameFieldForSingleEntity(entityName) {
|
|
637
|
+
const md = new core_1.Metadata(); // use the full metadata entity list, not the filtered version that we receive
|
|
638
|
+
const e = md.Entities.find(e => e.Name === entityName);
|
|
639
|
+
if (e) {
|
|
640
|
+
const ef = e.NameField;
|
|
641
|
+
if (e.NameField)
|
|
642
|
+
return { nameField: ef.Name, nameFieldIsVirtual: ef.IsVirtual };
|
|
643
|
+
}
|
|
644
|
+
else
|
|
645
|
+
(0, logging_1.logStatus)(`ERROR: Could not find entity with name ${entityName}`);
|
|
646
|
+
return { nameField: '', nameFieldIsVirtual: false };
|
|
592
647
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
|
|
648
|
+
stripID(name) {
|
|
649
|
+
if (name.endsWith('ID'))
|
|
650
|
+
return name.substring(0, name.length - 2);
|
|
651
|
+
else
|
|
652
|
+
return name;
|
|
653
|
+
}
|
|
654
|
+
generateSPPermissions(entity, spName, type) {
|
|
655
|
+
let sOutput = '';
|
|
656
|
+
for (let i = 0; i < entity.Permissions.length; i++) {
|
|
657
|
+
const ep = entity.Permissions[i];
|
|
658
|
+
if ((type == exports.SPType.Create && ep.CanCreate) ||
|
|
659
|
+
(type == exports.SPType.Update && ep.CanUpdate) ||
|
|
660
|
+
(type == exports.SPType.Delete && ep.CanDelete))
|
|
661
|
+
sOutput += (sOutput == '' ? `GRANT EXECUTE ON [${entity.SchemaName}].[${spName}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
|
|
662
|
+
}
|
|
663
|
+
return (sOutput == '' ? '' : '\n') + sOutput;
|
|
664
|
+
}
|
|
665
|
+
generateFullTextSearchFunctionPermissions(entity, functionName) {
|
|
666
|
+
let sOutput = '';
|
|
667
|
+
for (let i = 0; i < entity.Permissions.length; i++) {
|
|
668
|
+
const ep = entity.Permissions[i];
|
|
669
|
+
if (ep.CanRead)
|
|
670
|
+
sOutput += (sOutput == '' ? `GRANT SELECT ON [${entity.SchemaName}].[${functionName}] TO ` : ', ') + `[${ep.RoleSQLName}]`;
|
|
671
|
+
}
|
|
672
|
+
return (sOutput == '' ? '' : '\n') + sOutput;
|
|
673
|
+
}
|
|
674
|
+
generateSPCreate(entity) {
|
|
675
|
+
const spName = entity.spCreate ? entity.spCreate : `spCreate${entity.ClassName}`;
|
|
676
|
+
const autoGeneratedPrimaryKey = entity.PrimaryKey.AutoIncrement || entity.PrimaryKey.Type.toLowerCase().trim() === 'uniqueidentifier';
|
|
677
|
+
const efString = this.createEntityFieldsParamString(entity.Fields, !autoGeneratedPrimaryKey);
|
|
678
|
+
const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Create);
|
|
679
|
+
let primaryKeyInsertValue = '';
|
|
680
|
+
let selectInsertedRecord = '';
|
|
681
|
+
let additionalFieldList = '';
|
|
682
|
+
let additionalValueList = '';
|
|
683
|
+
if (entity.PrimaryKey.AutoIncrement) {
|
|
684
|
+
selectInsertedRecord = `SELECT * FROM [${entity.SchemaName}].[${entity.BaseView}] WHERE [${entity.PrimaryKey.Name}] = SCOPE_IDENTITY()`;
|
|
685
|
+
}
|
|
686
|
+
else if (entity.PrimaryKey.Type.toLowerCase().trim() === 'uniqueidentifier') {
|
|
687
|
+
primaryKeyInsertValue = `DECLARE @newId UNIQUEIDENTIFIER = NEWID();\n SET @${entity.PrimaryKey.Name} = @newId;\n`;
|
|
688
|
+
additionalFieldList = ',\n [' + entity.PrimaryKey.CodeName + ']';
|
|
689
|
+
additionalValueList = ',\n @newId';
|
|
690
|
+
selectInsertedRecord = `SELECT * FROM [${entity.SchemaName}].[${entity.BaseView}] WHERE [${entity.PrimaryKey.Name}] = @newId`;
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
selectInsertedRecord = `SELECT * FROM [${entity.SchemaName}].[${entity.BaseView}] WHERE `;
|
|
694
|
+
let isFirst = true;
|
|
695
|
+
for (let k of entity.PrimaryKeys) {
|
|
696
|
+
if (!isFirst)
|
|
697
|
+
selectInsertedRecord += ' AND ';
|
|
698
|
+
selectInsertedRecord += `[${k.CodeName}] = @${k.CodeName}`;
|
|
699
|
+
isFirst = false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return `
|
|
703
|
+
------------------------------------------------------------
|
|
704
|
+
----- CREATE PROCEDURE FOR ${entity.BaseTable}
|
|
705
|
+
------------------------------------------------------------
|
|
706
|
+
DROP PROCEDURE IF EXISTS [${entity.SchemaName}].[${spName}]
|
|
707
|
+
GO
|
|
708
|
+
|
|
709
|
+
CREATE PROCEDURE [${entity.SchemaName}].[${spName}]
|
|
710
|
+
${efString}
|
|
711
|
+
AS
|
|
712
|
+
BEGIN
|
|
713
|
+
SET NOCOUNT ON;
|
|
714
|
+
${primaryKeyInsertValue}
|
|
715
|
+
INSERT INTO
|
|
716
|
+
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
717
|
+
(
|
|
718
|
+
${this.createEntityFieldsInsertString(entity, entity.Fields, '')}${additionalFieldList}
|
|
719
|
+
)
|
|
720
|
+
VALUES
|
|
721
|
+
(
|
|
722
|
+
${this.createEntityFieldsInsertString(entity, entity.Fields, '@')}${additionalValueList}
|
|
723
|
+
)
|
|
724
|
+
-- return the new record from the base view, which might have some calculated fields
|
|
725
|
+
${selectInsertedRecord}
|
|
726
|
+
END
|
|
727
|
+
GO${permissions}
|
|
728
|
+
`;
|
|
729
|
+
}
|
|
730
|
+
generateSPUpdate(entity) {
|
|
731
|
+
const spName = entity.spUpdate ? entity.spUpdate : `spUpdate${entity.ClassName}`;
|
|
732
|
+
const efParamString = this.createEntityFieldsParamString(entity.Fields, true);
|
|
733
|
+
const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Update);
|
|
734
|
+
let selectInsertedRecord = `SELECT * FROM [${entity.SchemaName}].[${entity.BaseView}] WHERE `;
|
|
658
735
|
let isFirst = true;
|
|
659
736
|
for (let k of entity.PrimaryKeys) {
|
|
660
737
|
if (!isFirst)
|
|
@@ -662,226 +739,190 @@ function generateSPCreate(entity) {
|
|
|
662
739
|
selectInsertedRecord += `[${k.CodeName}] = @${k.CodeName}`;
|
|
663
740
|
isFirst = false;
|
|
664
741
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
return `
|
|
707
|
-
------------------------------------------------------------
|
|
708
|
-
----- UPDATE PROCEDURE FOR ${entity.BaseTable}
|
|
709
|
-
------------------------------------------------------------
|
|
710
|
-
DROP PROCEDURE IF EXISTS [${entity.SchemaName}].[${spName}]
|
|
711
|
-
GO
|
|
712
|
-
|
|
713
|
-
CREATE PROCEDURE [${entity.SchemaName}].[${spName}]
|
|
714
|
-
${efParamString}
|
|
715
|
-
AS
|
|
716
|
-
BEGIN
|
|
717
|
-
SET NOCOUNT ON;
|
|
718
|
-
UPDATE
|
|
719
|
-
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
720
|
-
SET
|
|
721
|
-
${createEntityFieldsUpdateString(entity.Fields)}
|
|
722
|
-
WHERE
|
|
723
|
-
[${entity.PrimaryKey.Name}] = @${entity.PrimaryKey.Name}
|
|
724
|
-
|
|
725
|
-
-- return the updated record so the caller can see the updated values and any calculated fields
|
|
726
|
-
${selectInsertedRecord}
|
|
727
|
-
END
|
|
728
|
-
GO${permissions}
|
|
729
|
-
`;
|
|
730
|
-
}
|
|
731
|
-
function createEntityFieldsParamString(entityFields, isUpdate) {
|
|
732
|
-
let sOutput = '', isFirst = true;
|
|
733
|
-
for (let i = 0; i < entityFields.length; ++i) {
|
|
734
|
-
const ef = entityFields[i];
|
|
735
|
-
const autoGeneratedPrimaryKey = ef.AutoIncrement || ef.Type.toLowerCase().trim() === 'uniqueidentifier';
|
|
736
|
-
if ((ef.AllowUpdateAPI || (ef.IsPrimaryKey && isUpdate)) &&
|
|
737
|
-
!ef.IsVirtual &&
|
|
738
|
-
((!ef.IsPrimaryKey || !autoGeneratedPrimaryKey) || isUpdate) &&
|
|
739
|
-
ef.Name.toLowerCase().trim() !== 'updatedat' &&
|
|
740
|
-
ef.Name.toLowerCase().trim() !== 'createdat' &&
|
|
741
|
-
ef.Type.toLowerCase().trim() !== 'uniqueidentifier') {
|
|
742
|
-
if (!isFirst)
|
|
743
|
-
sOutput += ',\n ';
|
|
744
|
-
else
|
|
745
|
-
isFirst = false;
|
|
746
|
-
sOutput += `@${ef.CodeName} ${ef.SQLFullType}`;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
return sOutput;
|
|
750
|
-
}
|
|
751
|
-
function createEntityFieldsInsertString(entity, entityFields, prefix) {
|
|
752
|
-
const autoGeneratedPrimaryKey = entity.PrimaryKey.AutoIncrement || entity.PrimaryKey.Type.toLowerCase().trim() === 'uniqueidentifier';
|
|
753
|
-
let sOutput = '', isFirst = true;
|
|
754
|
-
for (let i = 0; i < entityFields.length; ++i) {
|
|
755
|
-
const ef = entityFields[i];
|
|
756
|
-
if ((!ef.IsPrimaryKey || !autoGeneratedPrimaryKey) &&
|
|
757
|
-
ef.IsVirtual === false &&
|
|
758
|
-
ef.AllowUpdateAPI &&
|
|
759
|
-
ef.AutoIncrement === false &&
|
|
760
|
-
ef.Type.trim().toLowerCase() !== 'uniqueidentifier') {
|
|
761
|
-
if (!isFirst)
|
|
762
|
-
sOutput += ',\n ';
|
|
763
|
-
else
|
|
764
|
-
isFirst = false;
|
|
765
|
-
if (prefix !== '' && (ef.Name.toLowerCase().trim() === 'updatedat' ||
|
|
766
|
-
ef.Name.toLowerCase().trim() === 'createdat'))
|
|
767
|
-
sOutput += `GETDATE()`;
|
|
768
|
-
else {
|
|
769
|
-
let sVal = prefix + (prefix !== '' ? ef.CodeName : ef.Name); // if we have a prefix, then we need to use the CodeName, otherwise we use the actual field name
|
|
770
|
-
if (!prefix || prefix.length === 0)
|
|
771
|
-
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
|
|
772
|
-
sOutput += sVal;
|
|
742
|
+
return `
|
|
743
|
+
------------------------------------------------------------
|
|
744
|
+
----- UPDATE PROCEDURE FOR ${entity.BaseTable}
|
|
745
|
+
------------------------------------------------------------
|
|
746
|
+
DROP PROCEDURE IF EXISTS [${entity.SchemaName}].[${spName}]
|
|
747
|
+
GO
|
|
748
|
+
|
|
749
|
+
CREATE PROCEDURE [${entity.SchemaName}].[${spName}]
|
|
750
|
+
${efParamString}
|
|
751
|
+
AS
|
|
752
|
+
BEGIN
|
|
753
|
+
SET NOCOUNT ON;
|
|
754
|
+
UPDATE
|
|
755
|
+
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
756
|
+
SET
|
|
757
|
+
${this.createEntityFieldsUpdateString(entity.Fields)}
|
|
758
|
+
WHERE
|
|
759
|
+
[${entity.PrimaryKey.Name}] = @${entity.PrimaryKey.Name}
|
|
760
|
+
|
|
761
|
+
-- return the updated record so the caller can see the updated values and any calculated fields
|
|
762
|
+
${selectInsertedRecord}
|
|
763
|
+
END
|
|
764
|
+
GO${permissions}
|
|
765
|
+
`;
|
|
766
|
+
}
|
|
767
|
+
createEntityFieldsParamString(entityFields, isUpdate) {
|
|
768
|
+
let sOutput = '', isFirst = true;
|
|
769
|
+
for (let i = 0; i < entityFields.length; ++i) {
|
|
770
|
+
const ef = entityFields[i];
|
|
771
|
+
const autoGeneratedPrimaryKey = ef.AutoIncrement || ef.Type.toLowerCase().trim() === 'uniqueidentifier';
|
|
772
|
+
if ((ef.AllowUpdateAPI || (ef.IsPrimaryKey && isUpdate)) &&
|
|
773
|
+
!ef.IsVirtual &&
|
|
774
|
+
((!ef.IsPrimaryKey || !autoGeneratedPrimaryKey) || isUpdate) &&
|
|
775
|
+
ef.Name.toLowerCase().trim() !== 'updatedat' &&
|
|
776
|
+
ef.Name.toLowerCase().trim() !== 'createdat' &&
|
|
777
|
+
ef.Type.toLowerCase().trim() !== 'uniqueidentifier') {
|
|
778
|
+
if (!isFirst)
|
|
779
|
+
sOutput += ',\n ';
|
|
780
|
+
else
|
|
781
|
+
isFirst = false;
|
|
782
|
+
sOutput += `@${ef.CodeName} ${ef.SQLFullType}`;
|
|
773
783
|
}
|
|
774
784
|
}
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
785
|
+
return sOutput;
|
|
786
|
+
}
|
|
787
|
+
createEntityFieldsInsertString(entity, entityFields, prefix) {
|
|
788
|
+
const autoGeneratedPrimaryKey = entity.PrimaryKey.AutoIncrement || entity.PrimaryKey.Type.toLowerCase().trim() === 'uniqueidentifier';
|
|
789
|
+
let sOutput = '', isFirst = true;
|
|
790
|
+
for (let i = 0; i < entityFields.length; ++i) {
|
|
791
|
+
const ef = entityFields[i];
|
|
792
|
+
if ((!ef.IsPrimaryKey || !autoGeneratedPrimaryKey) &&
|
|
793
|
+
ef.IsVirtual === false &&
|
|
794
|
+
ef.AllowUpdateAPI &&
|
|
795
|
+
ef.AutoIncrement === false &&
|
|
796
|
+
ef.Type.trim().toLowerCase() !== 'uniqueidentifier') {
|
|
797
|
+
if (!isFirst)
|
|
798
|
+
sOutput += ',\n ';
|
|
799
|
+
else
|
|
800
|
+
isFirst = false;
|
|
801
|
+
if (prefix !== '' && (ef.Name.toLowerCase().trim() === 'updatedat' ||
|
|
802
|
+
ef.Name.toLowerCase().trim() === 'createdat'))
|
|
803
|
+
sOutput += `GETDATE()`;
|
|
804
|
+
else {
|
|
805
|
+
let sVal = prefix + (prefix !== '' ? ef.CodeName : ef.Name); // if we have a prefix, then we need to use the CodeName, otherwise we use the actual field name
|
|
806
|
+
if (!prefix || prefix.length === 0)
|
|
807
|
+
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
|
|
808
|
+
sOutput += sVal;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
794
811
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
812
|
+
return sOutput;
|
|
813
|
+
}
|
|
814
|
+
createEntityFieldsUpdateString(entityFields) {
|
|
815
|
+
let sOutput = '', isFirst = true;
|
|
816
|
+
for (let i = 0; i < entityFields.length; ++i) {
|
|
817
|
+
const ef = entityFields[i];
|
|
818
|
+
if (!ef.IsPrimaryKey &&
|
|
819
|
+
ef.IsVirtual === false &&
|
|
820
|
+
ef.AllowUpdateAPI &&
|
|
821
|
+
ef.AutoIncrement === false &&
|
|
822
|
+
ef.Name.toLowerCase().trim() !== 'createdat' &&
|
|
823
|
+
ef.Name.toLowerCase().trim() !== 'updatedat' &&
|
|
824
|
+
ef.Type.toLowerCase().trim() !== 'uniqueidentifier') {
|
|
825
|
+
if (!isFirst)
|
|
826
|
+
sOutput += ',\n ';
|
|
827
|
+
else
|
|
828
|
+
isFirst = false;
|
|
829
|
+
sOutput += `[${ef.Name}] = @${ef.CodeName}`; // always put field names in brackets for field names that have spaces or use reserved words. Also, we use CodeName for the param name, which is the field name unless it has spaces
|
|
830
|
+
}
|
|
831
|
+
else if (ef.Name.trim().toLowerCase() === 'updatedat') {
|
|
832
|
+
if (!isFirst)
|
|
833
|
+
sOutput += ',\n ';
|
|
834
|
+
else
|
|
835
|
+
isFirst = false;
|
|
836
|
+
sOutput += `[${ef.Name}] = GETDATE()`;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return sOutput;
|
|
840
|
+
}
|
|
841
|
+
generateSPDelete(entity) {
|
|
842
|
+
const spName = entity.spDelete ? entity.spDelete : `spDelete${entity.ClassName}`;
|
|
843
|
+
const sCascadeDeletes = this.generateCascadeDeletes(entity);
|
|
844
|
+
const permissions = this.generateSPPermissions(entity, spName, exports.SPType.Delete);
|
|
845
|
+
let sVariables = '';
|
|
846
|
+
let sSelect = '';
|
|
847
|
+
for (let k of entity.PrimaryKeys) {
|
|
848
|
+
if (sVariables !== '')
|
|
849
|
+
sVariables += ', ';
|
|
850
|
+
sVariables += `@${k.CodeName} ${k.SQLFullType}`;
|
|
851
|
+
if (sSelect !== '')
|
|
852
|
+
sSelect += ', ';
|
|
853
|
+
sSelect += `@${k.CodeName} AS [${k.CodeName}]`;
|
|
854
|
+
}
|
|
855
|
+
return `
|
|
856
|
+
------------------------------------------------------------
|
|
857
|
+
----- DELETE PROCEDURE FOR ${entity.BaseTable}
|
|
858
|
+
------------------------------------------------------------
|
|
859
|
+
DROP PROCEDURE IF EXISTS [${entity.SchemaName}].[${spName}]
|
|
860
|
+
GO
|
|
861
|
+
|
|
862
|
+
CREATE PROCEDURE [${entity.SchemaName}].[${spName}]
|
|
863
|
+
${sVariables}
|
|
864
|
+
AS
|
|
865
|
+
BEGIN
|
|
866
|
+
SET NOCOUNT ON;${sCascadeDeletes}
|
|
867
|
+
|
|
868
|
+
DELETE FROM
|
|
869
|
+
[${entity.SchemaName}].[${entity.BaseTable}]
|
|
870
|
+
WHERE
|
|
871
|
+
[${entity.PrimaryKey.Name}] = @${entity.PrimaryKey.CodeName}
|
|
872
|
+
|
|
873
|
+
SELECT ${sSelect} -- Return the primary key to indicate we successfully deleted the record
|
|
874
|
+
END
|
|
875
|
+
GO${permissions}
|
|
876
|
+
`;
|
|
877
|
+
}
|
|
878
|
+
generateCascadeDeletes(entity) {
|
|
879
|
+
let sOutput = '';
|
|
880
|
+
if (entity.CascadeDeletes) {
|
|
881
|
+
const md = new core_1.Metadata();
|
|
882
|
+
// we need to find all of the fields in other entities that are foreign keys to this entity
|
|
883
|
+
// and generate DELETE statements for those tables
|
|
884
|
+
for (let i = 0; i < md.Entities.length; ++i) {
|
|
885
|
+
const e = md.Entities[i];
|
|
886
|
+
for (let j = 0; j < e.Fields.length; ++j) {
|
|
887
|
+
const ef = e.Fields[j];
|
|
888
|
+
if (ef.RelatedEntityID === entity.ID && ef.IsVirtual === false) {
|
|
889
|
+
let sql = '';
|
|
890
|
+
if (ef.AllowsNull === false) {
|
|
891
|
+
// we have a non-virtual field that is a foreign key to this entity
|
|
892
|
+
// and only those that are non-null. If they allow null we want to UPDATE those rows to be null
|
|
893
|
+
// so we need to generate a DELETE statement for that table
|
|
894
|
+
sql = `
|
|
895
|
+
-- Cascade delete from ${e.BaseTable}
|
|
896
|
+
DELETE FROM
|
|
897
|
+
[${e.SchemaName}].[${e.BaseTable}]
|
|
898
|
+
WHERE
|
|
863
899
|
[${ef.CodeName}] = @${entity.PrimaryKey.CodeName}`;
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
-- Cascade update on ${e.BaseTable} - set FK to null before deleting rows in ${entity.BaseTable}
|
|
871
|
-
UPDATE
|
|
872
|
-
[${e.SchemaName}].[${e.BaseTable}]
|
|
873
|
-
SET
|
|
874
|
-
[${ef.CodeName}] = NULL
|
|
875
|
-
WHERE
|
|
900
|
+
}
|
|
901
|
+
else {
|
|
902
|
+
// we have a non-virtual field that is a foreign key to this entity
|
|
903
|
+
// and this field ALLOWS nulls, which means we don't delete the rows, we just update them to be null
|
|
904
|
+
// so they don't have an orphaned foreign key
|
|
905
|
+
sql = `
|
|
906
|
+
-- Cascade update on ${e.BaseTable} - set FK to null before deleting rows in ${entity.BaseTable}
|
|
907
|
+
UPDATE
|
|
908
|
+
[${e.SchemaName}].[${e.BaseTable}]
|
|
909
|
+
SET
|
|
910
|
+
[${ef.CodeName}] = NULL
|
|
911
|
+
WHERE
|
|
876
912
|
[${ef.CodeName}] = @${entity.PrimaryKey.CodeName}`;
|
|
913
|
+
}
|
|
914
|
+
if (sOutput !== '')
|
|
915
|
+
sOutput += '\n ';
|
|
916
|
+
sOutput += sql;
|
|
877
917
|
}
|
|
878
|
-
if (sOutput !== '')
|
|
879
|
-
sOutput += '\n ';
|
|
880
|
-
sOutput += sql;
|
|
881
918
|
}
|
|
882
919
|
}
|
|
883
920
|
}
|
|
921
|
+
return sOutput === '' ? '' : `${sOutput}\n `;
|
|
884
922
|
}
|
|
885
|
-
|
|
886
|
-
|
|
923
|
+
};
|
|
924
|
+
exports.SQLCodeGenBase = SQLCodeGenBase;
|
|
925
|
+
exports.SQLCodeGenBase = SQLCodeGenBase = __decorate([
|
|
926
|
+
(0, global_1.RegisterClass)(SQLCodeGenBase)
|
|
927
|
+
], SQLCodeGenBase);
|
|
887
928
|
//# sourceMappingURL=sql_codegen.js.map
|