@memberjunction/sqlserver-dataprovider 3.4.0 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +573 -758
- package/dist/NodeFileSystemProvider.d.ts +16 -0
- package/dist/NodeFileSystemProvider.d.ts.map +1 -0
- package/dist/NodeFileSystemProvider.js +35 -0
- package/dist/NodeFileSystemProvider.js.map +1 -0
- package/dist/SQLServerDataProvider.d.ts +23 -4
- package/dist/SQLServerDataProvider.d.ts.map +1 -1
- package/dist/SQLServerDataProvider.js +189 -177
- package/dist/SQLServerDataProvider.js.map +1 -1
- package/dist/SQLServerTransactionGroup.js +16 -43
- package/dist/SQLServerTransactionGroup.js.map +1 -1
- package/dist/SqlLogger.js +11 -43
- package/dist/SqlLogger.js.map +1 -1
- package/dist/UserCache.d.ts +1 -1
- package/dist/UserCache.d.ts.map +1 -1
- package/dist/UserCache.js +9 -38
- package/dist/UserCache.js.map +1 -1
- package/dist/__tests__/setup.d.ts +5 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +17 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -17
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -17
- package/dist/index.js.map +1 -1
- package/dist/queryParameterProcessor.js +4 -32
- package/dist/queryParameterProcessor.js.map +1 -1
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -6
- package/dist/types.js.map +1 -1
- package/package.json +23 -22
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* @fileoverview SQL Server Data Provider for MemberJunction
|
|
4
3
|
*
|
|
@@ -11,49 +10,25 @@
|
|
|
11
10
|
* @version 2.0
|
|
12
11
|
* @since 1.0
|
|
13
12
|
*/
|
|
14
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
-
if (k2 === undefined) k2 = k;
|
|
16
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
-
}
|
|
20
|
-
Object.defineProperty(o, k2, desc);
|
|
21
|
-
}) : (function(o, m, k, k2) {
|
|
22
|
-
if (k2 === undefined) k2 = k;
|
|
23
|
-
o[k2] = m[k];
|
|
24
|
-
}));
|
|
25
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
-
}) : function(o, v) {
|
|
28
|
-
o["default"] = v;
|
|
29
|
-
});
|
|
30
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.SQLServerDataProvider = void 0;
|
|
39
13
|
/**************************************************************************************************************
|
|
40
14
|
* The SQLServerDataProvider provides a data provider for the entities framework that uses SQL Server directly
|
|
41
15
|
* In practice - this FILE will NOT exist in the entities library, we need to move to its own separate project
|
|
42
16
|
* so it is only included by the consumer of the entities library if they want to use it.
|
|
43
17
|
**************************************************************************************************************/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
18
|
+
import { ApplicationInfo, EntityFieldTSType, ProviderType, UserInfo, AuditLogTypeInfo, AuthorizationInfo, TransactionItem, EntityPermissionType, EntitySaveOptions, LogError, StripStopWords, LogStatus, CompositeKey, EntityDeleteOptions, BaseEntityResult, Metadata, DatabaseProviderBase, QueryCache, InMemoryLocalStorageProvider, } from '@memberjunction/core';
|
|
19
|
+
import { QueryParameterProcessor } from './queryParameterProcessor.js';
|
|
20
|
+
import { NodeFileSystemProvider } from './NodeFileSystemProvider.js';
|
|
21
|
+
import { ViewInfo, } from '@memberjunction/core-entities';
|
|
22
|
+
import { AIEngine } from '@memberjunction/aiengine';
|
|
23
|
+
import { QueueManager } from '@memberjunction/queue';
|
|
24
|
+
import sql from 'mssql';
|
|
25
|
+
import { BehaviorSubject, Subject, concatMap, from, tap, catchError, of } from 'rxjs';
|
|
26
|
+
import { SQLServerTransactionGroup } from './SQLServerTransactionGroup.js';
|
|
27
|
+
import { SqlLoggingSessionImpl } from './SqlLogger.js';
|
|
28
|
+
import { EntityActionEngineServer } from '@memberjunction/actions';
|
|
29
|
+
import { EncryptionEngine } from '@memberjunction/encryption';
|
|
30
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
31
|
+
import { MJGlobal, SQLExpressionValidator } from '@memberjunction/global';
|
|
57
32
|
/**
|
|
58
33
|
* Core SQL execution function - handles the actual database query execution
|
|
59
34
|
* This is outside the class to allow both static and instance methods to use it
|
|
@@ -155,24 +130,27 @@ async function executeSQLCore(query, parameters, context, options) {
|
|
|
155
130
|
* await provider.Config();
|
|
156
131
|
* ```
|
|
157
132
|
*/
|
|
158
|
-
class SQLServerDataProvider extends
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
133
|
+
export class SQLServerDataProvider extends DatabaseProviderBase {
|
|
134
|
+
constructor() {
|
|
135
|
+
super(...arguments);
|
|
136
|
+
this._transactionDepth = 0;
|
|
137
|
+
this._savepointCounter = 0;
|
|
138
|
+
this._savepointStack = [];
|
|
139
|
+
// Query cache instance
|
|
140
|
+
this.queryCache = new QueryCache();
|
|
141
|
+
this._bAllowRefresh = true;
|
|
142
|
+
this._needsDatetimeOffsetAdjustment = false;
|
|
143
|
+
this._datetimeOffsetTestComplete = false;
|
|
144
|
+
// Instance SQL execution queue for serializing transaction queries
|
|
145
|
+
// Non-transactional queries bypass this queue for maximum parallelism
|
|
146
|
+
this._sqlQueue$ = new Subject();
|
|
147
|
+
// Transaction state management
|
|
148
|
+
this._transactionState$ = new BehaviorSubject(false);
|
|
149
|
+
this._deferredTasks = [];
|
|
150
|
+
}
|
|
151
|
+
static { this._sqlLoggingSessionsKey = 'MJ_SQLServerDataProvider_SqlLoggingSessions'; }
|
|
174
152
|
get _sqlLoggingSessions() {
|
|
175
|
-
const g =
|
|
153
|
+
const g = MJGlobal.Instance.GetGlobalObjectStore();
|
|
176
154
|
if (g) {
|
|
177
155
|
if (!g[SQLServerDataProvider._sqlLoggingSessionsKey]) {
|
|
178
156
|
g[SQLServerDataProvider._sqlLoggingSessionsKey] = new Map();
|
|
@@ -183,14 +161,6 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
183
161
|
throw new Error('No global object store available for SQL logging session');
|
|
184
162
|
}
|
|
185
163
|
}
|
|
186
|
-
// Instance SQL execution queue for serializing transaction queries
|
|
187
|
-
// Non-transactional queries bypass this queue for maximum parallelism
|
|
188
|
-
_sqlQueue$ = new rxjs_1.Subject();
|
|
189
|
-
// Subscription for the queue processor
|
|
190
|
-
_queueSubscription;
|
|
191
|
-
// Transaction state management
|
|
192
|
-
_transactionState$ = new rxjs_1.BehaviorSubject(false);
|
|
193
|
-
_deferredTasks = [];
|
|
194
164
|
/**
|
|
195
165
|
* Observable that emits the current transaction state (true when active, false when not)
|
|
196
166
|
* External code can subscribe to this to know when transactions start and end
|
|
@@ -258,7 +228,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
258
228
|
return super.Config(configData, providerToUse); // now parent class can do it's config
|
|
259
229
|
}
|
|
260
230
|
catch (e) {
|
|
261
|
-
|
|
231
|
+
LogError(e);
|
|
262
232
|
throw e;
|
|
263
233
|
}
|
|
264
234
|
}
|
|
@@ -270,13 +240,13 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
270
240
|
// Each instance gets its own queue processor, but only do this ONCE if we get this method called more than once we don't need to reinit
|
|
271
241
|
// the sub, taht would cause duplicate rprocessing.
|
|
272
242
|
if (!this._queueSubscription) {
|
|
273
|
-
this._queueSubscription = this._sqlQueue$.pipe(
|
|
243
|
+
this._queueSubscription = this._sqlQueue$.pipe(concatMap(item => from(executeSQLCore(item.query, item.parameters, item.context, item.options)).pipe(
|
|
274
244
|
// Handle success
|
|
275
|
-
|
|
245
|
+
tap(result => item.resolve(result)),
|
|
276
246
|
// Handle errors
|
|
277
|
-
|
|
247
|
+
catchError(error => {
|
|
278
248
|
item.reject(error);
|
|
279
|
-
return
|
|
249
|
+
return of(null); // Continue processing queue even on errors
|
|
280
250
|
})))).subscribe();
|
|
281
251
|
}
|
|
282
252
|
}
|
|
@@ -348,9 +318,9 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
348
318
|
* ```
|
|
349
319
|
*/
|
|
350
320
|
async CreateSqlLogger(filePath, options) {
|
|
351
|
-
const sessionId = (
|
|
321
|
+
const sessionId = uuidv4();
|
|
352
322
|
const mjCoreSchema = this.ConfigData.MJCoreSchemaName;
|
|
353
|
-
const session = new
|
|
323
|
+
const session = new SqlLoggingSessionImpl(sessionId, filePath, {
|
|
354
324
|
defaultSchemaName: mjCoreSchema,
|
|
355
325
|
...options // if defaultSchemaName is not provided, it will use the MJCoreSchemaName, otherwise
|
|
356
326
|
// the caller's defaultSchemaName will be used
|
|
@@ -504,7 +474,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
504
474
|
*/
|
|
505
475
|
static async LogSQLStatement(query, parameters, description, isMutation = false, simpleSQLFallback, contextUser) {
|
|
506
476
|
// Get the current provider instance
|
|
507
|
-
const provider =
|
|
477
|
+
const provider = Metadata.Provider;
|
|
508
478
|
if (provider && provider._sqlLoggingSessions.size > 0) {
|
|
509
479
|
await provider._logSqlStatement(query, parameters, description, false, isMutation, simpleSQLFallback, contextUser);
|
|
510
480
|
}
|
|
@@ -668,7 +638,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
668
638
|
};
|
|
669
639
|
}
|
|
670
640
|
catch (e) {
|
|
671
|
-
|
|
641
|
+
LogError(e);
|
|
672
642
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
673
643
|
return {
|
|
674
644
|
Success: false,
|
|
@@ -721,7 +691,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
721
691
|
let finalSQL = query.SQL;
|
|
722
692
|
let appliedParameters = {};
|
|
723
693
|
if (query.UsesTemplate) {
|
|
724
|
-
const processingResult =
|
|
694
|
+
const processingResult = QueryParameterProcessor.processQueryTemplate(query, parameters);
|
|
725
695
|
if (!processingResult.success) {
|
|
726
696
|
throw new Error(processingResult.error);
|
|
727
697
|
}
|
|
@@ -730,7 +700,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
730
700
|
}
|
|
731
701
|
else if (parameters && Object.keys(parameters).length > 0) {
|
|
732
702
|
// Warn if parameters were provided but query doesn't use templates
|
|
733
|
-
|
|
703
|
+
LogStatus('Warning: Parameters provided but query does not use templates. Parameters will be ignored.');
|
|
734
704
|
}
|
|
735
705
|
return { finalSQL, appliedParameters };
|
|
736
706
|
}
|
|
@@ -746,7 +716,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
746
716
|
if (!cachedEntry) {
|
|
747
717
|
return null;
|
|
748
718
|
}
|
|
749
|
-
|
|
719
|
+
LogStatus(`Cache hit for query ${query.Name} (${query.ID})`);
|
|
750
720
|
// Apply pagination to cached results
|
|
751
721
|
const { paginatedResult, totalRowCount } = this.applyQueryPagination(cachedEntry.results, params);
|
|
752
722
|
const remainingTTL = (cachedEntry.timestamp + (cachedEntry.ttlMinutes * 60 * 1000)) - Date.now();
|
|
@@ -829,7 +799,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
829
799
|
}
|
|
830
800
|
// Cache the full result set (before pagination)
|
|
831
801
|
this.queryCache.set(query.ID, parameters, results, cacheConfig);
|
|
832
|
-
|
|
802
|
+
LogStatus(`Cached results for query ${query.Name} (${query.ID})`);
|
|
833
803
|
}
|
|
834
804
|
/**
|
|
835
805
|
* Internal implementation of batch query execution.
|
|
@@ -951,7 +921,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
951
921
|
};
|
|
952
922
|
}
|
|
953
923
|
catch (e) {
|
|
954
|
-
|
|
924
|
+
LogError(e);
|
|
955
925
|
return {
|
|
956
926
|
success: false,
|
|
957
927
|
results: [],
|
|
@@ -1092,7 +1062,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1092
1062
|
else
|
|
1093
1063
|
stack.push(variable); // add to the stack for circular reference detection
|
|
1094
1064
|
// variable values is the view ID of the view that we want to get its WHERE CLAUSE, so we need to get the view entity
|
|
1095
|
-
const innerViewEntity = await
|
|
1065
|
+
const innerViewEntity = await ViewInfo.GetViewEntity(variableValue, user);
|
|
1096
1066
|
if (innerViewEntity) {
|
|
1097
1067
|
// we have the inner view, so now call this function recursively to get the where clause for the inner view
|
|
1098
1068
|
const innerWhere = await this.RenderViewWhereClause(innerViewEntity, user, stack);
|
|
@@ -1115,7 +1085,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1115
1085
|
return sWhere;
|
|
1116
1086
|
}
|
|
1117
1087
|
catch (e) {
|
|
1118
|
-
|
|
1088
|
+
LogError(e);
|
|
1119
1089
|
throw e;
|
|
1120
1090
|
}
|
|
1121
1091
|
}
|
|
@@ -1126,7 +1096,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1126
1096
|
// This is the internal implementation - pre/post processing is handled by ProviderBase.RunView()
|
|
1127
1097
|
// Log aggregate input for debugging
|
|
1128
1098
|
if (params?.Aggregates?.length) {
|
|
1129
|
-
|
|
1099
|
+
LogStatus(`[SQLServerDataProvider] InternalRunView received aggregates: entityName=${params.EntityName}, viewID=${params.ViewID}, viewName=${params.ViewName}, aggregateCount=${params.Aggregates.length}, aggregates=${JSON.stringify(params.Aggregates.map(a => ({ expression: a.expression, alias: a.alias })))}`);
|
|
1130
1100
|
}
|
|
1131
1101
|
const startTime = new Date();
|
|
1132
1102
|
try {
|
|
@@ -1138,9 +1108,9 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1138
1108
|
if (params.ViewEntity)
|
|
1139
1109
|
viewEntity = params.ViewEntity;
|
|
1140
1110
|
else if (params.ViewID && params.ViewID.length > 0)
|
|
1141
|
-
viewEntity = await
|
|
1111
|
+
viewEntity = await ViewInfo.GetViewEntity(params.ViewID, contextUser);
|
|
1142
1112
|
else if (params.ViewName && params.ViewName.length > 0)
|
|
1143
|
-
viewEntity = await
|
|
1113
|
+
viewEntity = await ViewInfo.GetViewEntityByName(params.ViewName, contextUser);
|
|
1144
1114
|
if (!viewEntity) {
|
|
1145
1115
|
// if we don't have viewEntity, that means it is a dynamic view, so we need EntityName at a minimum
|
|
1146
1116
|
if (!params.EntityName || params.EntityName.length === 0)
|
|
@@ -1244,9 +1214,9 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1244
1214
|
}
|
|
1245
1215
|
}
|
|
1246
1216
|
// NEXT, apply Row Level Security (RLS)
|
|
1247
|
-
if (!entityInfo.UserExemptFromRowLevelSecurity(user,
|
|
1217
|
+
if (!entityInfo.UserExemptFromRowLevelSecurity(user, EntityPermissionType.Read)) {
|
|
1248
1218
|
// user is NOT exempt from RLS, so we need to apply it
|
|
1249
|
-
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user,
|
|
1219
|
+
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
|
|
1250
1220
|
if (rlsWhereClause && rlsWhereClause.length > 0) {
|
|
1251
1221
|
if (bHasWhere) {
|
|
1252
1222
|
whereSQL += ` AND (${rlsWhereClause})`;
|
|
@@ -1427,7 +1397,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1427
1397
|
}
|
|
1428
1398
|
catch (e) {
|
|
1429
1399
|
const exceptionStopTime = new Date();
|
|
1430
|
-
|
|
1400
|
+
LogError(e);
|
|
1431
1401
|
return {
|
|
1432
1402
|
RowCount: 0,
|
|
1433
1403
|
TotalRowCount: 0,
|
|
@@ -1566,7 +1536,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1566
1536
|
};
|
|
1567
1537
|
}
|
|
1568
1538
|
catch (e) {
|
|
1569
|
-
|
|
1539
|
+
LogError(e);
|
|
1570
1540
|
return {
|
|
1571
1541
|
success: false,
|
|
1572
1542
|
results: [],
|
|
@@ -1649,8 +1619,8 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1649
1619
|
}
|
|
1650
1620
|
}
|
|
1651
1621
|
// Row Level Security
|
|
1652
|
-
if (!entityInfo.UserExemptFromRowLevelSecurity(user,
|
|
1653
|
-
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user,
|
|
1622
|
+
if (!entityInfo.UserExemptFromRowLevelSecurity(user, EntityPermissionType.Read)) {
|
|
1623
|
+
const rlsWhereClause = entityInfo.GetUserRowLevelSecurityWhereClause(user, EntityPermissionType.Read, '');
|
|
1654
1624
|
if (rlsWhereClause && rlsWhereClause.length > 0) {
|
|
1655
1625
|
if (bHasWhere) {
|
|
1656
1626
|
whereSQL += ` AND (${rlsWhereClause})`;
|
|
@@ -1745,7 +1715,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1745
1715
|
return results.map(r => r.RecordID);
|
|
1746
1716
|
}
|
|
1747
1717
|
catch (e) {
|
|
1748
|
-
|
|
1718
|
+
LogError(e);
|
|
1749
1719
|
return [];
|
|
1750
1720
|
}
|
|
1751
1721
|
}
|
|
@@ -1782,7 +1752,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1782
1752
|
return results;
|
|
1783
1753
|
}
|
|
1784
1754
|
catch (e) {
|
|
1785
|
-
|
|
1755
|
+
LogError(e);
|
|
1786
1756
|
return [];
|
|
1787
1757
|
}
|
|
1788
1758
|
}
|
|
@@ -1829,7 +1799,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1829
1799
|
// Validate: if impliedDeletes < 0, there are unexplained rows on the server
|
|
1830
1800
|
// This could happen with direct SQL inserts that bypassed MJ's tracking
|
|
1831
1801
|
if (impliedDeletes < 0) {
|
|
1832
|
-
|
|
1802
|
+
LogStatus(`Differential validation failed for ${entityInfo.Name}: impliedDeletes=${impliedDeletes} (negative). ` +
|
|
1833
1803
|
`clientRowCount=${clientRowCount}, newInserts=${newInserts}, serverRowCount=${serverRowCount}. ` +
|
|
1834
1804
|
`Falling back to full refresh.`);
|
|
1835
1805
|
return this.runFullQueryAndReturn(params, viewIndex, contextUser);
|
|
@@ -1837,7 +1807,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1837
1807
|
// Validate: if impliedDeletes > actualDeletes, there are "hidden" deletes
|
|
1838
1808
|
// not tracked in RecordChanges (e.g., direct SQL deletes)
|
|
1839
1809
|
if (impliedDeletes > actualDeletes) {
|
|
1840
|
-
|
|
1810
|
+
LogStatus(`Differential validation failed for ${entityInfo.Name}: hidden deletes detected. ` +
|
|
1841
1811
|
`impliedDeletes=${impliedDeletes}, actualDeletes=${actualDeletes}. ` +
|
|
1842
1812
|
`clientRowCount=${clientRowCount}, newInserts=${newInserts}, serverRowCount=${serverRowCount}. ` +
|
|
1843
1813
|
`Falling back to full refresh.`);
|
|
@@ -1860,7 +1830,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1860
1830
|
};
|
|
1861
1831
|
}
|
|
1862
1832
|
catch (e) {
|
|
1863
|
-
|
|
1833
|
+
LogError(e);
|
|
1864
1834
|
return {
|
|
1865
1835
|
viewIndex,
|
|
1866
1836
|
status: 'error',
|
|
@@ -1915,7 +1885,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
1915
1885
|
if (!aggregates || aggregates.length === 0) {
|
|
1916
1886
|
return { aggregateSQL: null, validationErrors: [] };
|
|
1917
1887
|
}
|
|
1918
|
-
const validator =
|
|
1888
|
+
const validator = SQLExpressionValidator.Instance;
|
|
1919
1889
|
const validationErrors = [];
|
|
1920
1890
|
const validExpressions = [];
|
|
1921
1891
|
const fieldNames = entityInfo.Fields.map(f => f.Name);
|
|
@@ -2059,7 +2029,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2059
2029
|
if (field)
|
|
2060
2030
|
fieldList.push(field);
|
|
2061
2031
|
else
|
|
2062
|
-
|
|
2032
|
+
LogError(`Field ${f} not found in entity ${entityInfo.Name}`);
|
|
2063
2033
|
});
|
|
2064
2034
|
}
|
|
2065
2035
|
else {
|
|
@@ -2075,7 +2045,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2075
2045
|
fieldList.push(c.EntityField);
|
|
2076
2046
|
}
|
|
2077
2047
|
else {
|
|
2078
|
-
|
|
2048
|
+
LogError(`View Field ${c.Name} doesn't match an Entity Field in entity ${entityInfo.Name}. This can happen if the view was saved with a field that no longer exists in the entity. It is best to update the view to remove this field.`);
|
|
2079
2049
|
}
|
|
2080
2050
|
}
|
|
2081
2051
|
});
|
|
@@ -2088,7 +2058,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2088
2058
|
}
|
|
2089
2059
|
}
|
|
2090
2060
|
catch (e) {
|
|
2091
|
-
|
|
2061
|
+
LogError(e);
|
|
2092
2062
|
}
|
|
2093
2063
|
finally {
|
|
2094
2064
|
return fieldList;
|
|
@@ -2139,7 +2109,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2139
2109
|
else {
|
|
2140
2110
|
// we have multiple words, so we need to convert the spaces to AND
|
|
2141
2111
|
// but first, let's strip the stopwords out of the string
|
|
2142
|
-
u =
|
|
2112
|
+
u = StripStopWords(userSearchString);
|
|
2143
2113
|
// next, include "AND" between all the words so that we have a full text search on all the words
|
|
2144
2114
|
u = u.replace(/ /g, ' AND ');
|
|
2145
2115
|
}
|
|
@@ -2200,7 +2170,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2200
2170
|
throw new Error(`Error saving audit log record`);
|
|
2201
2171
|
}
|
|
2202
2172
|
catch (err) {
|
|
2203
|
-
|
|
2173
|
+
LogError(err);
|
|
2204
2174
|
return null;
|
|
2205
2175
|
}
|
|
2206
2176
|
}
|
|
@@ -2224,7 +2194,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2224
2194
|
// START ---- IEntityDataProvider
|
|
2225
2195
|
/**************************************************************************/
|
|
2226
2196
|
get ProviderType() {
|
|
2227
|
-
return
|
|
2197
|
+
return ProviderType.Database;
|
|
2228
2198
|
}
|
|
2229
2199
|
async GetRecordFavoriteStatus(userId, entityName, CompositeKey, contextUser) {
|
|
2230
2200
|
const id = await this.GetRecordFavoriteID(userId, entityName, CompositeKey, contextUser);
|
|
@@ -2240,7 +2210,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2240
2210
|
return null;
|
|
2241
2211
|
}
|
|
2242
2212
|
catch (e) {
|
|
2243
|
-
|
|
2213
|
+
LogError(e);
|
|
2244
2214
|
throw e;
|
|
2245
2215
|
}
|
|
2246
2216
|
}
|
|
@@ -2273,7 +2243,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2273
2243
|
}
|
|
2274
2244
|
}
|
|
2275
2245
|
catch (e) {
|
|
2276
|
-
|
|
2246
|
+
LogError(e);
|
|
2277
2247
|
throw e;
|
|
2278
2248
|
}
|
|
2279
2249
|
}
|
|
@@ -2283,7 +2253,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2283
2253
|
return this.ExecuteSQL(sSQL, undefined, undefined, contextUser);
|
|
2284
2254
|
}
|
|
2285
2255
|
catch (e) {
|
|
2286
|
-
|
|
2256
|
+
LogError(e);
|
|
2287
2257
|
throw e;
|
|
2288
2258
|
}
|
|
2289
2259
|
}
|
|
@@ -2303,7 +2273,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2303
2273
|
// we do this in SQL by combining the pirmary key name and value for each row using the default separator defined by the CompositeKey class
|
|
2304
2274
|
// the output of this should be like the following 'Field1|Value1||Field2|Value2||Field3|Value3' where the || is the CompositeKey.DefaultFieldDelimiter and the | is the CompositeKey.DefaultValueDelimiter
|
|
2305
2275
|
const quotes = entity.FirstPrimaryKey.NeedsQuotes ? "'" : '';
|
|
2306
|
-
const primaryKeySelectString = `CONCAT(${entity.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${
|
|
2276
|
+
const primaryKeySelectString = `CONCAT(${entity.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${CompositeKey.DefaultFieldDelimiter}',`)})`;
|
|
2307
2277
|
// for this entity, check to see if it has any fields that are soft links, and for each of those, generate the SQL
|
|
2308
2278
|
entity.Fields.filter((f) => f.EntityIDFieldName && f.EntityIDFieldName.length > 0).forEach((f) => {
|
|
2309
2279
|
// each field in f must be processed
|
|
@@ -2332,7 +2302,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2332
2302
|
const entityInfo = this.Entities.find((e) => e.Name.trim().toLowerCase() === entityDependency.EntityName?.trim().toLowerCase());
|
|
2333
2303
|
const quotes = entityInfo.FirstPrimaryKey.NeedsQuotes ? "'" : '';
|
|
2334
2304
|
const relatedEntityInfo = this.Entities.find((e) => e.Name.trim().toLowerCase() === entityDependency.RelatedEntityName?.trim().toLowerCase());
|
|
2335
|
-
const primaryKeySelectString = `CONCAT(${entityInfo.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${
|
|
2305
|
+
const primaryKeySelectString = `CONCAT(${entityInfo.PrimaryKeys.map((pk) => `'${pk.Name}|', CAST(${pk.Name} AS NVARCHAR(MAX))`).join(`,'${CompositeKey.DefaultFieldDelimiter}',`)})`;
|
|
2336
2306
|
if (sSQL.length > 0)
|
|
2337
2307
|
sSQL += ' UNION ALL ';
|
|
2338
2308
|
sSQL += `SELECT
|
|
@@ -2382,13 +2352,13 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2382
2352
|
// entityInfo.PrimaryKeys.forEach((pk) => {
|
|
2383
2353
|
// pkeyValues.push({FieldName: pk.Name, Value: r[pk.Name]}) // add all of the primary keys, which often is as simple as just "ID", but this is generic way to do it
|
|
2384
2354
|
// })
|
|
2385
|
-
const compositeKey = new
|
|
2355
|
+
const compositeKey = new CompositeKey();
|
|
2386
2356
|
// the row r will have a PrimaryKeyValue field that is a string that is a concatenation of the primary key field names and values
|
|
2387
2357
|
// we need to parse that out so that we can then pass it to the CompositeKey object
|
|
2388
2358
|
const pkeys = {};
|
|
2389
|
-
const keyValues = r.PrimaryKeyValue.split(
|
|
2359
|
+
const keyValues = r.PrimaryKeyValue.split(CompositeKey.DefaultFieldDelimiter);
|
|
2390
2360
|
keyValues.forEach((kv) => {
|
|
2391
|
-
const parts = kv.split(
|
|
2361
|
+
const parts = kv.split(CompositeKey.DefaultValueDelimiter);
|
|
2392
2362
|
pkeys[parts[0]] = parts[1];
|
|
2393
2363
|
});
|
|
2394
2364
|
compositeKey.LoadFromEntityInfoAndRecord(entityInfo, pkeys);
|
|
@@ -2404,7 +2374,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2404
2374
|
}
|
|
2405
2375
|
catch (e) {
|
|
2406
2376
|
// log and throw
|
|
2407
|
-
|
|
2377
|
+
LogError(e);
|
|
2408
2378
|
throw e;
|
|
2409
2379
|
}
|
|
2410
2380
|
}
|
|
@@ -2536,7 +2506,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2536
2506
|
return result;
|
|
2537
2507
|
}
|
|
2538
2508
|
catch (e) {
|
|
2539
|
-
|
|
2509
|
+
LogError(e);
|
|
2540
2510
|
await this.RollbackTransaction();
|
|
2541
2511
|
// attempt to persist the status to the DB, although that might fail
|
|
2542
2512
|
await this.CompleteMergeLogging(mergeRecordLog, result, contextUser);
|
|
@@ -2568,7 +2538,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2568
2538
|
throw new Error(`Error saving record merge log`);
|
|
2569
2539
|
}
|
|
2570
2540
|
catch (e) {
|
|
2571
|
-
|
|
2541
|
+
LogError(e);
|
|
2572
2542
|
throw e;
|
|
2573
2543
|
}
|
|
2574
2544
|
}
|
|
@@ -2600,7 +2570,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2600
2570
|
}
|
|
2601
2571
|
catch (e) {
|
|
2602
2572
|
// do nothing here because we often will get here since some conditions lead to no DB updates possible...
|
|
2603
|
-
|
|
2573
|
+
LogError(e);
|
|
2604
2574
|
// don't bubble up the error here as we're sometimes already in an exception block in caller
|
|
2605
2575
|
}
|
|
2606
2576
|
}
|
|
@@ -2653,7 +2623,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2653
2623
|
let oldData = null;
|
|
2654
2624
|
// use SQL Server CONCAT function to combine all of the primary key values and then combine them together
|
|
2655
2625
|
// using the default field delimiter and default value delimiter as defined in the CompositeKey class
|
|
2656
|
-
const concatPKIDString = `CONCAT(${entity.EntityInfo.PrimaryKeys.map((pk) => `'${pk.CodeName}','${
|
|
2626
|
+
const concatPKIDString = `CONCAT(${entity.EntityInfo.PrimaryKeys.map((pk) => `'${pk.CodeName}','${CompositeKey.DefaultValueDelimiter}',${pk.Name}`).join(`,'${CompositeKey.DefaultFieldDelimiter}',`)})`;
|
|
2657
2627
|
if (!bNewRecord)
|
|
2658
2628
|
oldData = entity.GetAll(true); // get all the OLD values, only do for existing records, for new records, not relevant
|
|
2659
2629
|
const logRecordChangeSQL = this.GetLogRecordChangeSQL(entity.GetAll(false), oldData, entity.EntityInfo.Name, '@ID', entity.EntityInfo, bNewRecord ? 'Create' : 'Update', user, false);
|
|
@@ -2711,7 +2681,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2711
2681
|
* @internal
|
|
2712
2682
|
*/
|
|
2713
2683
|
GetEntityAIActions(entityInfo, before) {
|
|
2714
|
-
return
|
|
2684
|
+
return AIEngine.Instance.EntityAIActions.filter((a) => a.EntityID === entityInfo.ID && a.TriggerEvent.toLowerCase().trim() === (before ? 'before save' : 'after save'));
|
|
2715
2685
|
}
|
|
2716
2686
|
/**
|
|
2717
2687
|
* Handles entity actions (non-AI) for save, delete, or validate operations
|
|
@@ -2726,14 +2696,14 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2726
2696
|
async HandleEntityActions(entity, baseType, before, user) {
|
|
2727
2697
|
// use the EntityActionEngine for this
|
|
2728
2698
|
try {
|
|
2729
|
-
const engine =
|
|
2699
|
+
const engine = EntityActionEngineServer.Instance;
|
|
2730
2700
|
await engine.Config(false, user);
|
|
2731
2701
|
const newRecord = entity.IsSaved ? false : true;
|
|
2732
2702
|
const baseTypeType = baseType === 'save' ? (newRecord ? 'Create' : 'Update') : 'Delete';
|
|
2733
2703
|
const invocationType = baseType === 'validate' ? 'Validate' : before ? 'Before' + baseTypeType : 'After' + baseTypeType;
|
|
2734
2704
|
const invocationTypeEntity = engine.InvocationTypes.find((i) => i.Name === invocationType);
|
|
2735
2705
|
if (!invocationTypeEntity) {
|
|
2736
|
-
|
|
2706
|
+
LogError(`Invocation Type ${invocationType} not found in metadata`);
|
|
2737
2707
|
return [];
|
|
2738
2708
|
// throw new Error(`Invocation Type ${invocationType} not found in metadata`);
|
|
2739
2709
|
}
|
|
@@ -2751,7 +2721,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2751
2721
|
return results;
|
|
2752
2722
|
}
|
|
2753
2723
|
catch (e) {
|
|
2754
|
-
|
|
2724
|
+
LogError(e);
|
|
2755
2725
|
return [];
|
|
2756
2726
|
}
|
|
2757
2727
|
}
|
|
@@ -2770,10 +2740,10 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2770
2740
|
if (baseType === 'delete')
|
|
2771
2741
|
return;
|
|
2772
2742
|
// Make sure AI Metadata is loaded here...
|
|
2773
|
-
await
|
|
2743
|
+
await AIEngine.Instance.Config(false, user);
|
|
2774
2744
|
const actions = this.GetEntityAIActions(entity.EntityInfo, before); // get the actions we need to do for this entity
|
|
2775
2745
|
if (actions && actions.length > 0) {
|
|
2776
|
-
const ai =
|
|
2746
|
+
const ai = AIEngine.Instance;
|
|
2777
2747
|
for (let i = 0; i < actions.length; i++) {
|
|
2778
2748
|
const a = actions[i];
|
|
2779
2749
|
if ((a.TriggerEvent === 'before save' && before) || (a.TriggerEvent === 'after save' && !before)) {
|
|
@@ -2796,11 +2766,11 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2796
2766
|
}
|
|
2797
2767
|
else {
|
|
2798
2768
|
// No transaction active, add the task immediately
|
|
2799
|
-
|
|
2769
|
+
QueueManager.AddTask('Entity AI Action', p, null, user);
|
|
2800
2770
|
}
|
|
2801
2771
|
}
|
|
2802
2772
|
catch (e) {
|
|
2803
|
-
|
|
2773
|
+
LogError(e.message);
|
|
2804
2774
|
}
|
|
2805
2775
|
}
|
|
2806
2776
|
}
|
|
@@ -2808,16 +2778,16 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2808
2778
|
}
|
|
2809
2779
|
}
|
|
2810
2780
|
catch (e) {
|
|
2811
|
-
|
|
2781
|
+
LogError(e);
|
|
2812
2782
|
}
|
|
2813
2783
|
}
|
|
2814
2784
|
async Save(entity, user, options) {
|
|
2815
|
-
const entityResult = new
|
|
2785
|
+
const entityResult = new BaseEntityResult();
|
|
2816
2786
|
try {
|
|
2817
2787
|
entity.RegisterTransactionPreprocessing();
|
|
2818
2788
|
const bNewRecord = !entity.IsSaved;
|
|
2819
2789
|
if (!options)
|
|
2820
|
-
options = new
|
|
2790
|
+
options = new EntitySaveOptions();
|
|
2821
2791
|
const bReplay = !!options.ReplayOnly;
|
|
2822
2792
|
if (!bReplay && !bNewRecord && !entity.EntityInfo.AllowUpdateAPI) {
|
|
2823
2793
|
// existing record and not allowed to update
|
|
@@ -2885,7 +2855,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2885
2855
|
// we are part of a transaction group, so just add our query to the list
|
|
2886
2856
|
// and when the transaction is committed, we will send all the queries at once
|
|
2887
2857
|
this._bAllowRefresh = false; // stop refreshes of metadata while we're doing work
|
|
2888
|
-
entity.TransactionGroup.AddTransaction(new
|
|
2858
|
+
entity.TransactionGroup.AddTransaction(new TransactionItem(entity, entityResult.Type === 'create' ? 'Create' : 'Update', sSQL, null, {
|
|
2889
2859
|
dataSource: this._pool,
|
|
2890
2860
|
simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
|
|
2891
2861
|
entityName: entity.EntityInfo.Name
|
|
@@ -2927,10 +2897,12 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2927
2897
|
else {
|
|
2928
2898
|
try {
|
|
2929
2899
|
// Execute SQL with optional simple SQL fallback for loggers
|
|
2900
|
+
// IS-A: use entity's ProviderTransaction when available for shared transaction
|
|
2930
2901
|
const rawResult = await this.ExecuteSQL(sSQL, null, {
|
|
2931
2902
|
isMutation: true,
|
|
2932
2903
|
description: `Save ${entity.EntityInfo.Name}`,
|
|
2933
|
-
simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined
|
|
2904
|
+
simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
|
|
2905
|
+
connectionSource: entity.ProviderTransaction ?? undefined
|
|
2934
2906
|
}, user);
|
|
2935
2907
|
// Process rows with user context for decryption
|
|
2936
2908
|
result = await this.ProcessEntityRows(rawResult, entity.EntityInfo, user);
|
|
@@ -2971,7 +2943,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
2971
2943
|
this._bAllowRefresh = true; // allow refreshes again if we get a failure here
|
|
2972
2944
|
entityResult.EndedAt = new Date();
|
|
2973
2945
|
entityResult.Message = e.message;
|
|
2974
|
-
|
|
2946
|
+
LogError(e);
|
|
2975
2947
|
throw e; // rethrow the error
|
|
2976
2948
|
}
|
|
2977
2949
|
}
|
|
@@ -3028,7 +3000,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3028
3000
|
*/
|
|
3029
3001
|
async generateSPParams(entity, isUpdate, contextUser) {
|
|
3030
3002
|
// Generate a unique suffix for variable names to avoid collisions in batch scripts
|
|
3031
|
-
const uniqueSuffix = '_' + (
|
|
3003
|
+
const uniqueSuffix = '_' + uuidv4().substring(0, 8).replace(/-/g, '');
|
|
3032
3004
|
const declarations = [];
|
|
3033
3005
|
const setStatements = [];
|
|
3034
3006
|
const execParams = [];
|
|
@@ -3074,7 +3046,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3074
3046
|
if (f.Encrypt && f.EncryptionKeyID && value !== null && value !== undefined) {
|
|
3075
3047
|
// Lazy-load encryption engine only when needed
|
|
3076
3048
|
if (!encryptionEngine) {
|
|
3077
|
-
encryptionEngine =
|
|
3049
|
+
encryptionEngine = EncryptionEngine.Instance;
|
|
3078
3050
|
await encryptionEngine.Config(false, contextUser);
|
|
3079
3051
|
}
|
|
3080
3052
|
// Only encrypt if the value is not already encrypted
|
|
@@ -3148,7 +3120,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3148
3120
|
generateSetStatementValue(f, value) {
|
|
3149
3121
|
let val = value;
|
|
3150
3122
|
switch (f.TSType) {
|
|
3151
|
-
case
|
|
3123
|
+
case EntityFieldTSType.Boolean:
|
|
3152
3124
|
// check to see if the value is a string and if it is equal to true, if so, set the value to 1
|
|
3153
3125
|
if (typeof value === 'string' && value.trim().toLowerCase() === 'true')
|
|
3154
3126
|
val = 1;
|
|
@@ -3157,7 +3129,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3157
3129
|
else
|
|
3158
3130
|
val = value ? 1 : 0;
|
|
3159
3131
|
return val.toString();
|
|
3160
|
-
case
|
|
3132
|
+
case EntityFieldTSType.String:
|
|
3161
3133
|
// Handle string escaping for SET statements
|
|
3162
3134
|
if (typeof val === 'string') {
|
|
3163
3135
|
val = val.replace(/'/g, "''");
|
|
@@ -3169,7 +3141,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3169
3141
|
val = val.replace(/'/g, "''");
|
|
3170
3142
|
}
|
|
3171
3143
|
return `${f.UnicodePrefix}'${val}'`;
|
|
3172
|
-
case
|
|
3144
|
+
case EntityFieldTSType.Date:
|
|
3173
3145
|
if (val !== null && val !== undefined) {
|
|
3174
3146
|
if (typeof val === 'number') {
|
|
3175
3147
|
// we have a timestamp - milliseconds since Unix Epoch
|
|
@@ -3182,7 +3154,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3182
3154
|
val = val.toISOString(); // convert the date to ISO format for storage in the DB
|
|
3183
3155
|
}
|
|
3184
3156
|
return `'${val}'`;
|
|
3185
|
-
case
|
|
3157
|
+
case EntityFieldTSType.Number:
|
|
3186
3158
|
return val.toString();
|
|
3187
3159
|
default:
|
|
3188
3160
|
// For other types, convert to string and quote if needed
|
|
@@ -3200,7 +3172,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3200
3172
|
let quotes = '';
|
|
3201
3173
|
let val = value;
|
|
3202
3174
|
switch (f.TSType) {
|
|
3203
|
-
case
|
|
3175
|
+
case EntityFieldTSType.Boolean:
|
|
3204
3176
|
// check to see if the value is a string and if it is equal to true, if so, set the value to 1
|
|
3205
3177
|
if (typeof value === 'string' && value.trim().toLowerCase() === 'true')
|
|
3206
3178
|
val = 1;
|
|
@@ -3209,10 +3181,10 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3209
3181
|
else
|
|
3210
3182
|
val = value ? 1 : 0;
|
|
3211
3183
|
break;
|
|
3212
|
-
case
|
|
3184
|
+
case EntityFieldTSType.String:
|
|
3213
3185
|
quotes = "'";
|
|
3214
3186
|
break;
|
|
3215
|
-
case
|
|
3187
|
+
case EntityFieldTSType.Date:
|
|
3216
3188
|
quotes = "'";
|
|
3217
3189
|
if (val !== null && val !== undefined) {
|
|
3218
3190
|
if (typeof val === 'number') {
|
|
@@ -3369,6 +3341,10 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3369
3341
|
if (Array.isArray(obj)) {
|
|
3370
3342
|
return obj.map(item => this.escapeQuotesInProperties(item, quoteToEscape));
|
|
3371
3343
|
}
|
|
3344
|
+
// Handle Date objects - convert to ISO string before they lose their value
|
|
3345
|
+
if (obj instanceof Date) {
|
|
3346
|
+
return obj.toISOString();
|
|
3347
|
+
}
|
|
3372
3348
|
// Handle objects recursively
|
|
3373
3349
|
if (typeof obj === 'object') {
|
|
3374
3350
|
const sRet = {};
|
|
@@ -3441,14 +3417,14 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3441
3417
|
bDiff = false; // this branch of logic ensures that undefined and null are treated the same
|
|
3442
3418
|
else {
|
|
3443
3419
|
switch (f.TSType) {
|
|
3444
|
-
case
|
|
3420
|
+
case EntityFieldTSType.String:
|
|
3445
3421
|
bDiff = oldData[key] !== newData[key];
|
|
3446
3422
|
break;
|
|
3447
|
-
case
|
|
3423
|
+
case EntityFieldTSType.Date:
|
|
3448
3424
|
bDiff = new Date(oldData[key]).getTime() !== new Date(newData[key]).getTime();
|
|
3449
3425
|
break;
|
|
3450
|
-
case
|
|
3451
|
-
case
|
|
3426
|
+
case EntityFieldTSType.Number:
|
|
3427
|
+
case EntityFieldTSType.Boolean:
|
|
3452
3428
|
bDiff = oldData[key] !== newData[key];
|
|
3453
3429
|
break;
|
|
3454
3430
|
}
|
|
@@ -3502,7 +3478,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3502
3478
|
const ret = d[0];
|
|
3503
3479
|
// we need to post process the retrieval to see if we have any char or nchar fields and we need to remove their trailing spaces
|
|
3504
3480
|
for (const field of entity.EntityInfo.Fields) {
|
|
3505
|
-
if (field.TSType ===
|
|
3481
|
+
if (field.TSType === EntityFieldTSType.String &&
|
|
3506
3482
|
field.Type.toLowerCase().includes('char') &&
|
|
3507
3483
|
!field.Type.toLowerCase().includes('varchar')) {
|
|
3508
3484
|
// trim trailing spaces for char and nchar fields
|
|
@@ -3634,11 +3610,11 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3634
3610
|
return { fullSQL: sSQL, simpleSQL: sSimpleSQL };
|
|
3635
3611
|
}
|
|
3636
3612
|
async Delete(entity, options, user) {
|
|
3637
|
-
const result = new
|
|
3613
|
+
const result = new BaseEntityResult();
|
|
3638
3614
|
try {
|
|
3639
3615
|
entity.RegisterTransactionPreprocessing();
|
|
3640
3616
|
if (!options)
|
|
3641
|
-
options = new
|
|
3617
|
+
options = new EntityDeleteOptions();
|
|
3642
3618
|
const bReplay = options.ReplayOnly;
|
|
3643
3619
|
if (!entity.IsSaved && !bReplay)
|
|
3644
3620
|
// existing record and not allowed to update
|
|
@@ -3666,7 +3642,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3666
3642
|
entity.RaiseReadyForTransaction();
|
|
3667
3643
|
// we are part of a transaction group, so just add our query to the list
|
|
3668
3644
|
// and when the transaction is committed, we will send all the queries at once
|
|
3669
|
-
entity.TransactionGroup.AddTransaction(new
|
|
3645
|
+
entity.TransactionGroup.AddTransaction(new TransactionItem(entity, 'Delete', sSQL, null, {
|
|
3670
3646
|
dataSource: this._pool,
|
|
3671
3647
|
simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
|
|
3672
3648
|
entityName: entity.EntityInfo.Name
|
|
@@ -3707,10 +3683,12 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3707
3683
|
d = [entity.GetAll()]; // just return the entity as it was before the save as we are NOT saving anything as we are in replay mode
|
|
3708
3684
|
}
|
|
3709
3685
|
else {
|
|
3686
|
+
// IS-A: use entity's ProviderTransaction when available for shared transaction
|
|
3710
3687
|
d = await this.ExecuteSQL(sSQL, null, {
|
|
3711
3688
|
isMutation: true,
|
|
3712
3689
|
description: `Delete ${entity.EntityInfo.Name}`,
|
|
3713
|
-
simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined
|
|
3690
|
+
simpleSQLFallback: entity.EntityInfo.TrackRecordChanges ? sqlDetails.simpleSQL : undefined,
|
|
3691
|
+
connectionSource: entity.ProviderTransaction ?? undefined
|
|
3714
3692
|
}, user);
|
|
3715
3693
|
}
|
|
3716
3694
|
if (d && d.length > 0) {
|
|
@@ -3747,7 +3725,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3747
3725
|
}
|
|
3748
3726
|
}
|
|
3749
3727
|
catch (e) {
|
|
3750
|
-
|
|
3728
|
+
LogError(e);
|
|
3751
3729
|
result.Message = e.message;
|
|
3752
3730
|
result.Success = false;
|
|
3753
3731
|
result.EndedAt = new Date();
|
|
@@ -3964,7 +3942,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3964
3942
|
// the reason we continue below if we have NOT loaded Entities is that when the system first bootstraps, DATASET gets loaded
|
|
3965
3943
|
// FIRST before Entities are loaded to load the entity metadata so this would ALWAYS fail :)
|
|
3966
3944
|
// entity not found, return a failed result, shouldn't ever get here due to the foreign key constraint on the table
|
|
3967
|
-
|
|
3945
|
+
LogError(`Entity not found for dataset item ${item.Code} in dataset ${datasetName}`);
|
|
3968
3946
|
return null;
|
|
3969
3947
|
}
|
|
3970
3948
|
else {
|
|
@@ -3978,7 +3956,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
3978
3956
|
}
|
|
3979
3957
|
});
|
|
3980
3958
|
if (invalidColumns.length > 0) {
|
|
3981
|
-
|
|
3959
|
+
LogError(`Invalid columns specified for dataset item ${item.Code} in dataset ${datasetName}: ${invalidColumns.join(', ')}`);
|
|
3982
3960
|
return null;
|
|
3983
3961
|
}
|
|
3984
3962
|
}
|
|
@@ -4097,7 +4075,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4097
4075
|
const appEntities = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwApplicationEntities ORDER BY ApplicationName`, undefined, undefined, contextUser);
|
|
4098
4076
|
const ret = [];
|
|
4099
4077
|
for (let i = 0; i < apps.length; i++) {
|
|
4100
|
-
ret.push(new
|
|
4078
|
+
ret.push(new ApplicationInfo(this, {
|
|
4101
4079
|
...apps[i],
|
|
4102
4080
|
ApplicationEntities: appEntities.filter((ae) => ae.ApplicationName.trim().toLowerCase() === apps[i].Name.trim().toLowerCase()),
|
|
4103
4081
|
}));
|
|
@@ -4108,7 +4086,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4108
4086
|
const alts = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwAuditLogTypes`, null, undefined, contextUser);
|
|
4109
4087
|
const ret = [];
|
|
4110
4088
|
for (let i = 0; i < alts.length; i++) {
|
|
4111
|
-
const alt = new
|
|
4089
|
+
const alt = new AuditLogTypeInfo(alts[i]);
|
|
4112
4090
|
ret.push(alt);
|
|
4113
4091
|
}
|
|
4114
4092
|
return ret;
|
|
@@ -4118,7 +4096,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4118
4096
|
const userRoles = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwUserRoles ORDER BY UserID`, undefined, undefined, contextUser);
|
|
4119
4097
|
const ret = [];
|
|
4120
4098
|
for (let i = 0; i < users.length; i++) {
|
|
4121
|
-
ret.push(new
|
|
4099
|
+
ret.push(new UserInfo(this, {
|
|
4122
4100
|
...users[i],
|
|
4123
4101
|
UserRoles: userRoles.filter((ur) => ur.UserID === users[i].ID),
|
|
4124
4102
|
}));
|
|
@@ -4130,7 +4108,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4130
4108
|
const authRoles = await this.ExecuteSQL(`SELECT * FROM [${this.MJCoreSchemaName}].vwAuthorizationRoles ORDER BY AuthorizationName`, undefined, undefined, contextUser);
|
|
4131
4109
|
const ret = [];
|
|
4132
4110
|
for (let i = 0; i < auths.length; i++) {
|
|
4133
|
-
ret.push(new
|
|
4111
|
+
ret.push(new AuthorizationInfo(this, {
|
|
4134
4112
|
...auths[i],
|
|
4135
4113
|
AuthorizationRoles: authRoles.filter((ar) => ar.AuthorizationName.trim().toLowerCase() === auths[i].Name.trim().toLowerCase()),
|
|
4136
4114
|
}));
|
|
@@ -4161,7 +4139,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4161
4139
|
return rows;
|
|
4162
4140
|
}
|
|
4163
4141
|
// Find all datetime fields in the entity
|
|
4164
|
-
const datetimeFields = entityInfo.Fields.filter((field) => field.TSType ===
|
|
4142
|
+
const datetimeFields = entityInfo.Fields.filter((field) => field.TSType === EntityFieldTSType.Date);
|
|
4165
4143
|
// Find all encrypted fields in the entity
|
|
4166
4144
|
const encryptedFields = entityInfo.Fields.filter((field) => field.Encrypt && field.EncryptionKeyID);
|
|
4167
4145
|
// If there are no fields requiring processing, return the rows as-is
|
|
@@ -4173,7 +4151,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4173
4151
|
// Get encryption engine instance (lazy - only if we have encrypted fields)
|
|
4174
4152
|
let encryptionEngine = null;
|
|
4175
4153
|
if (encryptedFields.length > 0) {
|
|
4176
|
-
encryptionEngine =
|
|
4154
|
+
encryptionEngine = EncryptionEngine.Instance;
|
|
4177
4155
|
await encryptionEngine.Config(false, contextUser);
|
|
4178
4156
|
}
|
|
4179
4157
|
// Process each row - need to use Promise.all for async decryption
|
|
@@ -4262,7 +4240,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4262
4240
|
// Log error but don't fail the entire operation
|
|
4263
4241
|
// Return the encrypted value so the caller knows something is wrong
|
|
4264
4242
|
const message = decryptError instanceof Error ? decryptError.message : String(decryptError);
|
|
4265
|
-
|
|
4243
|
+
LogError(`Failed to decrypt field "${field.Name}" on entity "${entityInfo.Name}": ${message}. ` +
|
|
4266
4244
|
'The encrypted value will be returned unchanged.');
|
|
4267
4245
|
// Keep the encrypted value in the row - let the caller decide what to do
|
|
4268
4246
|
}
|
|
@@ -4299,7 +4277,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4299
4277
|
// This prevents EREQINPROG errors when multiple queries try to use the same transaction
|
|
4300
4278
|
return new Promise((resolve, reject) => {
|
|
4301
4279
|
this._sqlQueue$.next({
|
|
4302
|
-
id: (
|
|
4280
|
+
id: uuidv4(),
|
|
4303
4281
|
query,
|
|
4304
4282
|
parameters,
|
|
4305
4283
|
context,
|
|
@@ -4384,7 +4362,8 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4384
4362
|
async ExecuteSQL(query, parameters = null, options, contextUser) {
|
|
4385
4363
|
try {
|
|
4386
4364
|
// Use internal method with logging options
|
|
4387
|
-
|
|
4365
|
+
// Pass connectionSource if provided (used by IS-A chain orchestration for shared transactions)
|
|
4366
|
+
const result = await this._internalExecuteSQL(query, parameters, options?.connectionSource, {
|
|
4388
4367
|
description: options?.description,
|
|
4389
4368
|
ignoreLogging: options?.ignoreLogging,
|
|
4390
4369
|
isMutation: options?.isMutation,
|
|
@@ -4618,7 +4597,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4618
4597
|
}
|
|
4619
4598
|
}
|
|
4620
4599
|
catch (e) {
|
|
4621
|
-
|
|
4600
|
+
LogError(e);
|
|
4622
4601
|
throw e;
|
|
4623
4602
|
}
|
|
4624
4603
|
}
|
|
@@ -4703,6 +4682,35 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4703
4682
|
this._datetimeOffsetTestComplete = true;
|
|
4704
4683
|
}
|
|
4705
4684
|
}
|
|
4685
|
+
/**
|
|
4686
|
+
* Begin an independent transaction for IS-A chain orchestration.
|
|
4687
|
+
* Returns a new sql.Transaction object that is NOT linked to the provider's
|
|
4688
|
+
* internal transaction state (used by TransactionGroup). Each IS-A chain
|
|
4689
|
+
* gets its own transaction to avoid interference with other operations.
|
|
4690
|
+
*/
|
|
4691
|
+
async BeginISATransaction() {
|
|
4692
|
+
const transaction = new sql.Transaction(this._pool);
|
|
4693
|
+
await transaction.begin();
|
|
4694
|
+
return transaction;
|
|
4695
|
+
}
|
|
4696
|
+
/**
|
|
4697
|
+
* Commit an IS-A chain transaction.
|
|
4698
|
+
* @param txn The sql.Transaction object returned from BeginISATransaction()
|
|
4699
|
+
*/
|
|
4700
|
+
async CommitISATransaction(txn) {
|
|
4701
|
+
if (txn && txn instanceof sql.Transaction) {
|
|
4702
|
+
await txn.commit();
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
/**
|
|
4706
|
+
* Rollback an IS-A chain transaction.
|
|
4707
|
+
* @param txn The sql.Transaction object returned from BeginISATransaction()
|
|
4708
|
+
*/
|
|
4709
|
+
async RollbackISATransaction(txn) {
|
|
4710
|
+
if (txn && txn instanceof sql.Transaction) {
|
|
4711
|
+
await txn.rollback();
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4706
4714
|
async BeginTransaction() {
|
|
4707
4715
|
try {
|
|
4708
4716
|
this._transactionDepth++;
|
|
@@ -4726,7 +4734,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4726
4734
|
}
|
|
4727
4735
|
catch (e) {
|
|
4728
4736
|
this._transactionDepth--; // Restore depth on error
|
|
4729
|
-
|
|
4737
|
+
LogError(e);
|
|
4730
4738
|
throw e; // force caller to handle
|
|
4731
4739
|
}
|
|
4732
4740
|
}
|
|
@@ -4757,7 +4765,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4757
4765
|
}
|
|
4758
4766
|
}
|
|
4759
4767
|
catch (e) {
|
|
4760
|
-
|
|
4768
|
+
LogError(e);
|
|
4761
4769
|
throw e; // force caller to handle
|
|
4762
4770
|
}
|
|
4763
4771
|
}
|
|
@@ -4783,7 +4791,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4783
4791
|
const deferredCount = this._deferredTasks.length;
|
|
4784
4792
|
this._deferredTasks = [];
|
|
4785
4793
|
if (deferredCount > 0) {
|
|
4786
|
-
|
|
4794
|
+
LogStatus(`Cleared ${deferredCount} deferred tasks after transaction rollback`);
|
|
4787
4795
|
}
|
|
4788
4796
|
}
|
|
4789
4797
|
else {
|
|
@@ -4809,7 +4817,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4809
4817
|
this._savepointCounter = 0;
|
|
4810
4818
|
this._transactionState$.next(false);
|
|
4811
4819
|
}
|
|
4812
|
-
|
|
4820
|
+
LogError(e);
|
|
4813
4821
|
throw e; // force caller to handle
|
|
4814
4822
|
}
|
|
4815
4823
|
}
|
|
@@ -4821,7 +4829,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4821
4829
|
async RefreshIfNeeded() {
|
|
4822
4830
|
// Skip refresh if a transaction is active
|
|
4823
4831
|
if (this.isTransactionActive) {
|
|
4824
|
-
|
|
4832
|
+
LogStatus('Skipping metadata refresh - transaction is active');
|
|
4825
4833
|
return false;
|
|
4826
4834
|
}
|
|
4827
4835
|
// Call parent implementation if no transaction
|
|
@@ -4835,7 +4843,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4835
4843
|
async processDeferredTasks() {
|
|
4836
4844
|
if (this._deferredTasks.length === 0)
|
|
4837
4845
|
return;
|
|
4838
|
-
|
|
4846
|
+
LogStatus(`Processing ${this._deferredTasks.length} deferred tasks after transaction commit`);
|
|
4839
4847
|
// Copy and clear the deferred tasks array
|
|
4840
4848
|
const tasksToProcess = [...this._deferredTasks];
|
|
4841
4849
|
this._deferredTasks = [];
|
|
@@ -4844,25 +4852,30 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4844
4852
|
try {
|
|
4845
4853
|
if (task.type === 'Entity AI Action') {
|
|
4846
4854
|
// Process the AI action now that we're outside the transaction
|
|
4847
|
-
await
|
|
4855
|
+
await QueueManager.AddTask('Entity AI Action', task.data, task.options, task.user);
|
|
4848
4856
|
}
|
|
4849
4857
|
// Add other task types here as needed
|
|
4850
4858
|
}
|
|
4851
4859
|
catch (error) {
|
|
4852
|
-
|
|
4860
|
+
LogError(`Failed to process deferred ${task.type} task: ${error}`);
|
|
4853
4861
|
// Continue processing other tasks even if one fails
|
|
4854
4862
|
}
|
|
4855
4863
|
}
|
|
4856
|
-
|
|
4864
|
+
LogStatus(`Completed processing deferred tasks`);
|
|
4857
4865
|
}
|
|
4858
4866
|
get LocalStorageProvider() {
|
|
4859
4867
|
if (!this._localStorageProvider)
|
|
4860
|
-
this._localStorageProvider = new
|
|
4868
|
+
this._localStorageProvider = new InMemoryLocalStorageProvider();
|
|
4861
4869
|
return this._localStorageProvider;
|
|
4862
4870
|
}
|
|
4863
|
-
|
|
4871
|
+
get FileSystemProvider() {
|
|
4872
|
+
if (!this._fileSystemProvider)
|
|
4873
|
+
this._fileSystemProvider = new NodeFileSystemProvider();
|
|
4874
|
+
return this._fileSystemProvider;
|
|
4875
|
+
}
|
|
4876
|
+
async InternalGetEntityRecordNames(info, contextUser) {
|
|
4864
4877
|
const promises = info.map(async (item) => {
|
|
4865
|
-
const r = await this.
|
|
4878
|
+
const r = await this.InternalGetEntityRecordName(item.EntityName, item.CompositeKey, contextUser);
|
|
4866
4879
|
return {
|
|
4867
4880
|
EntityName: item.EntityName,
|
|
4868
4881
|
CompositeKey: item.CompositeKey,
|
|
@@ -4873,7 +4886,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4873
4886
|
});
|
|
4874
4887
|
return Promise.all(promises);
|
|
4875
4888
|
}
|
|
4876
|
-
async
|
|
4889
|
+
async InternalGetEntityRecordName(entityName, CompositeKey, contextUser) {
|
|
4877
4890
|
try {
|
|
4878
4891
|
const sql = this.GetEntityRecordNameSQL(entityName, CompositeKey);
|
|
4879
4892
|
if (sql) {
|
|
@@ -4883,13 +4896,13 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4883
4896
|
return data[0][fields[0]]; // return first field
|
|
4884
4897
|
}
|
|
4885
4898
|
else {
|
|
4886
|
-
|
|
4899
|
+
LogError(`Entity ${entityName} record ${CompositeKey.ToString()} not found, returning null`);
|
|
4887
4900
|
return null;
|
|
4888
4901
|
}
|
|
4889
4902
|
}
|
|
4890
4903
|
}
|
|
4891
4904
|
catch (e) {
|
|
4892
|
-
|
|
4905
|
+
LogError(e);
|
|
4893
4906
|
return null;
|
|
4894
4907
|
}
|
|
4895
4908
|
}
|
|
@@ -4900,7 +4913,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4900
4913
|
else {
|
|
4901
4914
|
const f = e.NameField;
|
|
4902
4915
|
if (!f) {
|
|
4903
|
-
|
|
4916
|
+
LogError(`Entity ${entityName} does not have an IsNameField or a field with the column name of Name, returning null, use recordId`);
|
|
4904
4917
|
return null;
|
|
4905
4918
|
}
|
|
4906
4919
|
else {
|
|
@@ -4919,7 +4932,7 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4919
4932
|
}
|
|
4920
4933
|
}
|
|
4921
4934
|
async CreateTransactionGroup() {
|
|
4922
|
-
return new
|
|
4935
|
+
return new SQLServerTransactionGroup();
|
|
4923
4936
|
}
|
|
4924
4937
|
/**************************************************************************/
|
|
4925
4938
|
// END ---- IMetadataProvider
|
|
@@ -4928,5 +4941,4 @@ class SQLServerDataProvider extends core_1.DatabaseProviderBase {
|
|
|
4928
4941
|
return this;
|
|
4929
4942
|
}
|
|
4930
4943
|
}
|
|
4931
|
-
exports.SQLServerDataProvider = SQLServerDataProvider;
|
|
4932
4944
|
//# sourceMappingURL=SQLServerDataProvider.js.map
|