@memberjunction/sqlserver-dataprovider 2.49.0 → 2.51.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 CHANGED
@@ -21,6 +21,8 @@ The `@memberjunction/sqlserver-dataprovider` package implements MemberJunction's
21
21
  - **Duplicate Detection**: Built-in support for duplicate record detection
22
22
  - **Audit Logging**: Comprehensive audit trail capabilities
23
23
  - **Row-Level Security**: Enforce data access controls at the database level
24
+ - **SQL Logging**: Real-time SQL statement capture for debugging and migration generation
25
+ - **Session Management**: Multiple concurrent SQL logging sessions with user filtering
24
26
 
25
27
  ## Installation
26
28
 
@@ -414,6 +416,10 @@ The main class that implements IEntityDataProvider, IMetadataProvider, IRunViewP
414
416
  - `RunReport(params: RunReportParams, contextUser?: UserInfo): Promise<RunReportResult>` - Execute a report
415
417
  - `RunQuery(params: RunQueryParams, contextUser?: UserInfo): Promise<RunQueryResult>` - Execute a query
416
418
  - `ExecuteSQL(sql: string, params?: any, maxRows?: number): Promise<any[]>` - Execute raw SQL
419
+ - `createSqlLogger(filePath: string, options?: SqlLoggingOptions): Promise<SqlLoggingSession>` - Create a new SQL logging session
420
+ - `getActiveSqlLoggingSessions(): SqlLoggingSession[]` - Get all active logging sessions
421
+ - `disposeAllSqlLoggingSessions(): Promise<void>` - Stop and clean up all logging sessions
422
+ - `isSqlLoggingEnabled(): boolean` - Check if SQL logging is available
417
423
 
418
424
  ### SQLServerProviderConfigData
419
425
 
@@ -458,24 +464,23 @@ Helper function to initialize and configure the SQL Server data provider.
458
464
  setupSQLServerClient(config: SQLServerProviderConfigData): Promise<SQLServerDataProvider>
459
465
  ```
460
466
 
461
- ## SQL Output Logging
467
+ ## SQL Logging
462
468
 
463
- The SQL Server Data Provider includes built-in SQL output logging capabilities that allow you to capture all SQL statements executed during operations. This is particularly useful for:
464
-
465
- - **Creating migration files** from application operations
466
- - **Debugging database operations** by reviewing exact SQL statements
467
- - **Performance analysis** by examining query patterns
468
- - **Compliance and auditing** requirements
469
+ The SQL Server Data Provider includes comprehensive SQL logging capabilities that allow you to capture SQL statements in real-time. This feature supports both programmatic access and runtime control through the MemberJunction UI.
469
470
 
470
471
  ### Key Features
471
472
 
472
- - **Session-based logging** with unique identifiers for each logging session
473
- - **Parallel execution support** - logging runs alongside SQL execution without impacting performance
474
- - **Multiple output formats** - standard SQL logs or migration-ready files with Flyway naming
475
- - **Disposable pattern** - automatic resource cleanup when logging session ends
476
- - **Parameter capture** - logs both SQL statements and their parameters
473
+ - **Real-time SQL capture** - Monitor SQL statements as they execute
474
+ - **Session-based logging** with unique identifiers and names
475
+ - **User filtering** - Capture SQL from specific users only
476
+ - **Multiple output formats** - Standard SQL logs or migration-ready files
477
+ - **Runtime control** - Start/stop sessions through GraphQL API and UI
478
+ - **Owner-level security** - Only users with Owner privileges can access SQL logging
479
+ - **Automatic cleanup** - Sessions auto-expire and clean up empty files
480
+ - **Concurrent sessions** - Support multiple active logging sessions
481
+ - **Parameter capture** - Logs both SQL statements and their parameters
477
482
 
478
- ### Basic Usage
483
+ ### Programmatic Usage
479
484
 
480
485
  ```typescript
481
486
  import { SQLServerDataProvider } from '@memberjunction/sqlserver-dataprovider';
@@ -484,9 +489,12 @@ const dataProvider = new SQLServerDataProvider(/* config */);
484
489
  await dataProvider.initialize();
485
490
 
486
491
  // Create a SQL logging session
487
- const logger = await dataProvider.createSqlLogger('./output/operations.sql', {
492
+ const logger = await dataProvider.createSqlLogger('./logs/sql/operations.sql', {
488
493
  formatAsMigration: false,
489
- description: 'User registration operations'
494
+ sessionName: 'User registration operations',
495
+ filterByUserId: 'user@example.com', // Only log SQL from this user
496
+ prettyPrint: true,
497
+ statementTypes: 'both' // Log both queries and mutations
490
498
  });
491
499
 
492
500
  // Perform your database operations - they will be automatically logged
@@ -495,26 +503,99 @@ await dataProvider.ExecuteSQL('INSERT INTO Users (Name, Email) VALUES (@name, @e
495
503
  email: 'john@example.com'
496
504
  });
497
505
 
506
+ // Check session status
507
+ console.log(`Session ${logger.id} has captured ${logger.statementCount} statements`);
508
+
498
509
  // Clean up the logging session
499
510
  await logger.dispose();
500
511
  ```
501
512
 
513
+ ### Runtime Control via GraphQL
514
+
515
+ ```typescript
516
+ // Start a new logging session
517
+ const mutation = `
518
+ mutation {
519
+ startSqlLogging(input: {
520
+ fileName: "debug-session.sql"
521
+ filterToCurrentUser: true
522
+ options: {
523
+ sessionName: "Debug Session"
524
+ prettyPrint: true
525
+ statementTypes: "both"
526
+ formatAsMigration: false
527
+ }
528
+ }) {
529
+ id
530
+ filePath
531
+ sessionName
532
+ }
533
+ }
534
+ `;
535
+
536
+ // List active sessions
537
+ const query = `
538
+ query {
539
+ activeSqlLoggingSessions {
540
+ id
541
+ sessionName
542
+ startTime
543
+ statementCount
544
+ filterByUserId
545
+ }
546
+ }
547
+ `;
548
+
549
+ // Stop a session
550
+ const stopMutation = `
551
+ mutation {
552
+ stopSqlLogging(sessionId: "session-id-here")
553
+ }
554
+ `;
555
+ ```
556
+
557
+ ### UI Integration
558
+
559
+ SQL logging can be controlled through the MemberJunction Explorer UI:
560
+
561
+ 1. **Settings Panel**: Navigate to Settings > SQL Logging (Owner access required)
562
+ 2. **Session Management**: Start/stop sessions with custom options
563
+ 3. **Real-time Monitoring**: View active sessions and statement counts
564
+ 4. **User Filtering**: Option to capture only your SQL statements
565
+ 5. **Log Viewing**: Preview log file contents (implementation dependent)
566
+
502
567
  ### Migration-Ready Format
503
568
 
504
569
  ```typescript
505
570
  // Create logger with migration formatting
506
571
  const migrationLogger = await dataProvider.createSqlLogger('./migrations/V20241215120000__User_Operations.sql', {
507
572
  formatAsMigration: true,
508
- description: 'User management operations for deployment'
573
+ sessionName: 'User management operations for deployment',
574
+ batchSeparator: 'GO',
575
+ logRecordChangeMetadata: true
509
576
  });
510
577
 
511
578
  // Your operations are logged in Flyway-compatible format
512
579
  // with proper headers and schema placeholders
513
580
  ```
514
581
 
515
- For comprehensive examples and advanced usage patterns, see [EXAMPLE_SQL_LOGGING.md](./EXAMPLE_SQL_LOGGING.md).
582
+ ### Session Management Methods
516
583
 
517
- > **Note**: This SQL output logging is different from the query execution logging available through subclassing the SQLServerDataProvider. SQL output logging captures statements for external use (like creating migrations), while execution logging is for debugging and monitoring the provider itself.
584
+ ```typescript
585
+ // Get all active sessions
586
+ const activeSessions = dataProvider.getActiveSqlLoggingSessions();
587
+ console.log(`${activeSessions.length} sessions currently active`);
588
+
589
+ // Dispose all sessions
590
+ await dataProvider.disposeAllSqlLoggingSessions();
591
+
592
+ // Check if logging is enabled
593
+ if (dataProvider.isSqlLoggingEnabled()) {
594
+ console.log('SQL logging is available');
595
+ }
596
+ ```
597
+
598
+ > **Security Note**: SQL logging requires Owner-level privileges in the MemberJunction system. Only users with `Type = 'Owner'` can create, manage, or access SQL logging sessions.
518
599
 
519
600
  ## Troubleshooting
520
601
 
@@ -559,4 +640,127 @@ export MJ_LOG_LEVEL=debug
559
640
 
560
641
  ## License
561
642
 
562
- ISC
643
+ ISC
644
+
645
+ ## SQL Server Connection Pooling and Best Practices
646
+
647
+ ### Overview
648
+
649
+ The MemberJunction SQL Server Data Provider is designed to support high-performance parallel database operations through proper connection pool management. The underlying `mssql` driver (node-mssql) is expressly designed to handle many concurrent database calls efficiently.
650
+
651
+ ### How MemberJunction Handles Parallelism
652
+
653
+ 1. **Single Shared Connection Pool**: MemberJunction creates one connection pool at server startup and reuses it throughout the application lifecycle. This pool is passed to the SQLServerDataProvider and used for all database operations.
654
+
655
+ 2. **Request-Per-Query Pattern**: Each database operation creates a new `sql.Request` object from the shared pool, allowing multiple queries to execute in parallel without blocking each other.
656
+
657
+ 3. **Configurable Pool Size**: The connection pool can be configured via `mj.config.cjs` to support your specific concurrency needs:
658
+
659
+ ```javascript
660
+ // In your mj.config.cjs at the root level
661
+ module.exports = {
662
+ databaseSettings: {
663
+ connectionPool: {
664
+ max: 50, // Maximum connections (default: 50)
665
+ min: 5, // Minimum connections (default: 5)
666
+ idleTimeoutMillis: 30000, // Idle timeout (default: 30s)
667
+ acquireTimeoutMillis: 30000 // Acquire timeout (default: 30s)
668
+ }
669
+ }
670
+ };
671
+ ```
672
+
673
+ ### Best Practices Implementation
674
+
675
+ MemberJunction follows SQL Server connection best practices:
676
+
677
+ #### ✅ What We Do Right
678
+
679
+ 1. **Create Pool Once**: The pool is created during server initialization and never recreated
680
+ 2. **Never Close Pool in Handlers**: The pool remains open for the server's lifetime
681
+ 3. **Fresh Request Per Query**: Each query gets its own `sql.Request` object
682
+ 4. **Proper Error Handling**: Connection failures are caught and logged appropriately
683
+ 5. **Read-Only Pool Option**: Separate pool for read operations if configured
684
+
685
+ #### ❌ Anti-Patterns We Avoid
686
+
687
+ 1. We don't create new connections for each request
688
+ 2. We don't open/close the pool repeatedly
689
+ 3. We don't share Request objects between queries
690
+ 4. We don't use hardcoded pool limits
691
+
692
+ ### Recommended Pool Settings
693
+
694
+ Based on your environment and load:
695
+
696
+ #### Development Environment
697
+ ```javascript
698
+ connectionPool: {
699
+ max: 10,
700
+ min: 2,
701
+ idleTimeoutMillis: 60000,
702
+ acquireTimeoutMillis: 15000
703
+ }
704
+ ```
705
+
706
+ #### Production - Standard Load
707
+ ```javascript
708
+ connectionPool: {
709
+ max: 50, // 2-4× CPU cores of your API server
710
+ min: 5,
711
+ idleTimeoutMillis: 30000,
712
+ acquireTimeoutMillis: 30000
713
+ }
714
+ ```
715
+
716
+ #### Production - High Load
717
+ ```javascript
718
+ connectionPool: {
719
+ max: 100, // Monitor SQL Server wait types to tune
720
+ min: 10,
721
+ idleTimeoutMillis: 30000,
722
+ acquireTimeoutMillis: 30000
723
+ }
724
+ ```
725
+
726
+ ### Performance Considerations
727
+
728
+ 1. **Pool Size**: The practical concurrency limit equals your pool size. With default settings (max: 50), you can have up to 50 concurrent SQL operations.
729
+
730
+ 2. **Connection Reuse**: The mssql driver efficiently reuses connections from the pool, minimizing connection overhead.
731
+
732
+ 3. **Queue Management**: When all connections are busy, additional requests queue in FIFO order until a connection becomes available.
733
+
734
+ 4. **Monitoring**: Watch for these SQL Server wait types to identify if pool size is too large:
735
+ - `RESOURCE_SEMAPHORE`: Memory pressure
736
+ - `THREADPOOL`: Worker thread exhaustion
737
+
738
+ ### Troubleshooting Connection Pool Issues
739
+
740
+ If you experience "connection pool exhausted" errors:
741
+
742
+ 1. **Increase Pool Size**: Adjust `max` in your configuration
743
+ 2. **Check for Leaks**: Ensure all queries complete properly
744
+ 3. **Monitor Long Queries**: Identify and optimize slow queries that hold connections
745
+ 4. **Review Concurrent Load**: Ensure pool size matches your peak concurrency needs
746
+
747
+ ### Technical Implementation Details
748
+
749
+ The connection pool is created in `/packages/MJServer/src/index.ts`:
750
+
751
+ ```typescript
752
+ const pool = new sql.ConnectionPool(createMSSQLConfig());
753
+ await pool.connect();
754
+ ```
755
+
756
+ And used in SQLServerDataProvider for each query:
757
+
758
+ ```typescript
759
+ const request = new sql.Request(this._pool);
760
+ const result = await request.query(sql);
761
+ ```
762
+
763
+ #### **Important**
764
+ If you are using `SQLServerDataProvider` outside of the context of MJServer/MJAPI it is your responsibility to create connection pool in alignment with whatever practices make sense for your project and pass that along to the SQLServerDataProvider configuration process.
765
+
766
+ This pattern ensures maximum parallelism while maintaining connection efficiency, allowing MemberJunction applications to scale to handle hundreds of concurrent database operations without blocking the Node.js event loop.
@@ -15,89 +15,13 @@
15
15
  * In practice - this FILE will NOT exist in the entities library, we need to move to its own separate project
16
16
  * so it is only included by the consumer of the entities library if they want to use it.
17
17
  **************************************************************************************************************/
18
- import { BaseEntity, IEntityDataProvider, IMetadataProvider, IRunViewProvider, ProviderConfigDataBase, RunViewResult, EntityInfo, EntityFieldInfo, ApplicationInfo, RunViewParams, ProviderBase, ProviderType, UserInfo, RoleInfo, RecordChange, UserRoleInfo, ILocalStorageProvider, RowLevelSecurityFilterInfo, AuditLogTypeInfo, AuthorizationInfo, TransactionGroupBase, EntitySaveOptions, RunReportParams, DatasetItemFilterType, DatasetResultType, DatasetStatusResultType, EntityRecordNameInput, EntityRecordNameResult, IRunReportProvider, RunReportResult, RecordDependency, RecordMergeRequest, RecordMergeResult, EntityDependency, IRunQueryProvider, RunQueryResult, PotentialDuplicateRequest, PotentialDuplicateResponse, CompositeKey, EntityDeleteOptions, DatasetItemResultType } from '@memberjunction/core';
18
+ import { BaseEntity, IEntityDataProvider, IMetadataProvider, IRunViewProvider, RunViewResult, EntityInfo, EntityFieldInfo, ApplicationInfo, RunViewParams, ProviderBase, ProviderType, UserInfo, RecordChange, ILocalStorageProvider, AuditLogTypeInfo, AuthorizationInfo, TransactionGroupBase, EntitySaveOptions, RunReportParams, DatasetItemFilterType, DatasetResultType, DatasetStatusResultType, EntityRecordNameInput, EntityRecordNameResult, IRunReportProvider, RunReportResult, RecordDependency, RecordMergeRequest, RecordMergeResult, EntityDependency, IRunQueryProvider, RunQueryResult, PotentialDuplicateRequest, PotentialDuplicateResponse, CompositeKey, EntityDeleteOptions, DatasetItemResultType } from '@memberjunction/core';
19
19
  import { AuditLogEntity, EntityAIActionEntity, RecordMergeLogEntity, UserViewEntityExtended } from '@memberjunction/core-entities';
20
20
  import * as sql from 'mssql';
21
+ import { Observable } from 'rxjs';
22
+ import { ExecuteSQLOptions, ExecuteSQLBatchOptions, SQLServerProviderConfigData, SqlLoggingOptions, SqlLoggingSession } from './types.js';
21
23
  import { RunQueryParams } from '@memberjunction/core/dist/generic/runQuery';
22
24
  import { ActionResult } from '@memberjunction/actions-base';
23
- /**
24
- * Configuration options for SQL execution with logging support
25
- */
26
- export interface ExecuteSQLOptions {
27
- /** Optional description for this SQL operation */
28
- description?: string;
29
- /** If true, this statement will not be logged to any logging session */
30
- ignoreLogging?: boolean;
31
- /** Whether this is a data mutation operation (INSERT/UPDATE/DELETE) */
32
- isMutation?: boolean;
33
- /** Simple SQL fallback for loggers with logRecordChangeMetadata=false (only for Save/Delete operations) */
34
- simpleSQLFallback?: string;
35
- }
36
- /**
37
- * Configuration options for batch SQL execution
38
- */
39
- export interface ExecuteSQLBatchOptions {
40
- /** Optional description for this batch operation */
41
- description?: string;
42
- /** If true, this batch will not be logged to any logging session */
43
- ignoreLogging?: boolean;
44
- /** Whether this batch contains data mutation operations */
45
- isMutation?: boolean;
46
- }
47
- /**
48
- * Configuration data specific to SQL Server provider
49
- */
50
- export declare class SQLServerProviderConfigData extends ProviderConfigDataBase {
51
- /**
52
- * Gets the SQL Server data source configuration
53
- */
54
- get DataSource(): any;
55
- /**
56
- * Gets the current user's email address
57
- */
58
- get CurrentUserEmail(): string;
59
- /**
60
- * Gets the interval in seconds for checking metadata refresh
61
- */
62
- get CheckRefreshIntervalSeconds(): number;
63
- constructor(dataSource: any, currentUserEmail: string, MJCoreSchemaName?: string, checkRefreshIntervalSeconds?: number, includeSchemas?: string[], excludeSchemas?: string[]);
64
- }
65
- /**
66
- * Configuration options for SQL logging sessions
67
- */
68
- export interface SqlLoggingOptions {
69
- /** Whether to format output as a migration file with schema placeholders */
70
- formatAsMigration?: boolean;
71
- /** Optional description to include as a comment at the start of the log */
72
- description?: string;
73
- /** Which types of statements to log: 'queries' (all), 'mutations' (only data changes), 'both' (default) */
74
- statementTypes?: 'queries' | 'mutations' | 'both';
75
- /** Optional batch separator to emit after each statement (e.g., "GO" for SQL Server) */
76
- batchSeparator?: string;
77
- /** Whether to pretty print SQL statements with proper formatting */
78
- prettyPrint?: boolean;
79
- /** Whether to log record change metadata wrapper SQL (default: false). When false, only core spCreate/spUpdate/spDelete calls are logged */
80
- logRecordChangeMetadata?: boolean;
81
- /** Whether to retain log files that contain no SQL statements (default: false). When false, empty log files are automatically deleted on dispose */
82
- retainEmptyLogFiles?: boolean;
83
- }
84
- /**
85
- * Interface for SQL logging session with disposable pattern
86
- */
87
- export interface SqlLoggingSession {
88
- /** Unique session ID */
89
- readonly id: string;
90
- /** File path where SQL is being logged */
91
- readonly filePath: string;
92
- /** Session start time */
93
- readonly startTime: Date;
94
- /** Number of statements logged so far */
95
- readonly statementCount: number;
96
- /** Configuration options for this session */
97
- readonly options: SqlLoggingOptions;
98
- /** Dispose method to stop logging and clean up resources */
99
- dispose(): Promise<void>;
100
- }
101
25
  /**
102
26
  * SQL Server implementation of the MemberJunction data provider interfaces.
103
27
  *
@@ -112,7 +36,7 @@ export interface SqlLoggingSession {
112
36
  *
113
37
  * @example
114
38
  * ```typescript
115
- * const config = new SQLServerProviderConfigData(dataSource, userEmail);
39
+ * const config = new SQLServerProviderConfigData(dataSource);
116
40
  * const provider = new SQLServerDataProvider(config);
117
41
  * await provider.Config();
118
42
  * ```
@@ -121,14 +45,27 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
121
45
  private _pool;
122
46
  private _poolConfig;
123
47
  private _transaction;
124
- private _transactionRequest;
125
- private _currentUserEmail;
126
48
  private _localStorageProvider;
127
49
  private _bAllowRefresh;
128
50
  private _recordDupeDetector;
129
51
  private _needsDatetimeOffsetAdjustment;
130
52
  private _datetimeOffsetTestComplete;
131
53
  private _sqlLoggingSessions;
54
+ private _transactionState$;
55
+ private _deferredTasks;
56
+ /**
57
+ * Observable that emits the current transaction state (true when active, false when not)
58
+ * External code can subscribe to this to know when transactions start and end
59
+ * @example
60
+ * provider.transactionState$.subscribe(isActive => {
61
+ * console.log('Transaction active:', isActive);
62
+ * });
63
+ */
64
+ get transactionState$(): Observable<boolean>;
65
+ /**
66
+ * Gets whether a transaction is currently active
67
+ */
68
+ get isTransactionActive(): boolean;
132
69
  /**
133
70
  * Gets the current configuration data for this provider instance
134
71
  */
@@ -173,7 +110,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
173
110
  * @example
174
111
  * ```typescript
175
112
  * // Basic usage
176
- * const session = await provider.createSqlLogger('./logs/metadata-sync.sql');
113
+ * const session = await provider.CreateSqlLogger('./logs/metadata-sync.sql');
177
114
  * try {
178
115
  * // Perform operations that will be logged
179
116
  * await provider.ExecuteSQL('INSERT INTO ...');
@@ -182,20 +119,21 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
182
119
  * }
183
120
  *
184
121
  * // With migration formatting
185
- * const session = await provider.createSqlLogger('./migrations/changes.sql', {
122
+ * const session = await provider.CreateSqlLogger('./migrations/changes.sql', {
186
123
  * formatAsMigration: true,
187
124
  * description: 'MetadataSync push operation'
188
125
  * });
189
126
  * ```
190
127
  */
191
- createSqlLogger(filePath: string, options?: SqlLoggingOptions): Promise<SqlLoggingSession>;
128
+ CreateSqlLogger(filePath: string, options?: SqlLoggingOptions): Promise<SqlLoggingSession>;
129
+ GetCurrentUser(): Promise<UserInfo>;
192
130
  /**
193
131
  * Gets information about all active SQL logging sessions.
194
132
  * Useful for monitoring and debugging.
195
133
  *
196
134
  * @returns Array of session information objects
197
135
  */
198
- getActiveSqlLoggingSessions(): Array<{
136
+ GetActiveSqlLoggingSessions(): Array<{
199
137
  id: string;
200
138
  filePath: string;
201
139
  startTime: Date;
@@ -206,7 +144,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
206
144
  * Disposes all active SQL logging sessions.
207
145
  * Useful for cleanup on provider shutdown.
208
146
  */
209
- disposeAllSqlLoggingSessions(): Promise<void>;
147
+ DisposeAllSqlLoggingSessions(): Promise<void>;
210
148
  /**
211
149
  * Internal method to log SQL statement to all active logging sessions.
212
150
  * This is called automatically by ExecuteSQL methods.
@@ -228,7 +166,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
228
166
  * @param isMutation - Whether this is a data mutation operation
229
167
  * @param simpleSQLFallback - Optional simple SQL to use for loggers with logRecordChangeMetadata=false
230
168
  */
231
- static LogSQLStatement(query: string, parameters?: any, description?: string, isMutation?: boolean, simpleSQLFallback?: string): Promise<void>;
169
+ static LogSQLStatement(query: string, parameters?: any, description?: string, isMutation?: boolean, simpleSQLFallback?: string, contextUser?: UserInfo): Promise<void>;
232
170
  /**************************************************************************/
233
171
  /**************************************************************************/
234
172
  /**************************************************************************/
@@ -262,17 +200,17 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
262
200
  runID: string;
263
201
  }>;
264
202
  protected createViewUserSearchSQL(entityInfo: EntityInfo, userSearchString: string): string;
265
- createAuditLogRecord(user: UserInfo, authorizationName: string | null, auditLogTypeName: string, status: string, details: string | null, entityId: string, recordId: any | null, auditLogDescription: string | null): Promise<AuditLogEntity>;
203
+ CreateAuditLogRecord(user: UserInfo, authorizationName: string | null, auditLogTypeName: string, status: string, details: string | null, entityId: string, recordId: any | null, auditLogDescription: string | null): Promise<AuditLogEntity>;
266
204
  protected CheckUserReadPermissions(entityName: string, contextUser: UserInfo): void;
267
205
  /**************************************************************************/
268
206
  /**************************************************************************/
269
207
  /**************************************************************************/
270
208
  /**************************************************************************/
271
209
  get ProviderType(): ProviderType;
272
- GetRecordFavoriteStatus(userId: string, entityName: string, CompositeKey: CompositeKey): Promise<boolean>;
273
- GetRecordFavoriteID(userId: string, entityName: string, CompositeKey: CompositeKey): Promise<string | null>;
210
+ GetRecordFavoriteStatus(userId: string, entityName: string, CompositeKey: CompositeKey, contextUser?: UserInfo): Promise<boolean>;
211
+ GetRecordFavoriteID(userId: string, entityName: string, CompositeKey: CompositeKey, contextUser?: UserInfo): Promise<string | null>;
274
212
  SetRecordFavoriteStatus(userId: string, entityName: string, CompositeKey: CompositeKey, isFavorite: boolean, contextUser: UserInfo): Promise<void>;
275
- GetRecordChanges(entityName: string, compositeKey: CompositeKey): Promise<RecordChange[]>;
213
+ GetRecordChanges(entityName: string, compositeKey: CompositeKey, contextUser?: UserInfo): Promise<RecordChange[]>;
276
214
  /**
277
215
  * This function will generate SQL statements for all of the possible soft links that are not traditional foreign keys but exist in entities
278
216
  * where there is a column that has the EntityIDFieldName set to a column name (not null). We need to get a list of all such soft link fields across ALL entities
@@ -290,7 +228,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
290
228
  * @param entityName the name of the entity to check
291
229
  * @param KeyValuePairs the primary key(s) to check - only send multiple if you have an entity with a composite primary key
292
230
  */
293
- GetRecordDependencies(entityName: string, compositeKey: CompositeKey): Promise<RecordDependency[]>;
231
+ GetRecordDependencies(entityName: string, compositeKey: CompositeKey, contextUser?: UserInfo): Promise<RecordDependency[]>;
294
232
  protected GetRecordDependencyLinkSQL(dep: EntityDependency, entity: EntityInfo, relatedEntity: EntityInfo, CompositeKey: CompositeKey): string;
295
233
  GetRecordDuplicates(params: PotentialDuplicateRequest, contextUser?: UserInfo): Promise<PotentialDuplicateResponse>;
296
234
  MergeRecords(request: RecordMergeRequest, contextUser?: UserInfo): Promise<RecordMergeResult>;
@@ -393,7 +331,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
393
331
  /**************************************************************************/
394
332
  /**************************************************************************/
395
333
  /**************************************************************************/
396
- GetDatasetByName(datasetName: string, itemFilters?: DatasetItemFilterType[]): Promise<DatasetResultType>;
334
+ GetDatasetByName(datasetName: string, itemFilters?: DatasetItemFilterType[], contextUser?: UserInfo): Promise<DatasetResultType>;
397
335
  /**
398
336
  * Constructs the SQL query for a dataset item.
399
337
  * @param item - The dataset item metadata
@@ -402,7 +340,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
402
340
  * @returns The SQL query string, or null if columns are invalid
403
341
  */
404
342
  protected GetDatasetItemSQL(item: any, itemFilters: any, datasetName: string): string | null;
405
- protected GetDatasetItem(item: any, itemFilters: any, datasetName: any): Promise<DatasetItemResultType>;
343
+ protected GetDatasetItem(item: any, itemFilters: any, datasetName: any, contextUser: UserInfo): Promise<DatasetItemResultType>;
406
344
  /**
407
345
  * Gets column info for a dataset item, which might be * for all columns or if a Columns field was provided in the DatasetItem table,
408
346
  * attempts to use those columns assuming they are valid.
@@ -411,16 +349,11 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
411
349
  * @returns
412
350
  */
413
351
  protected GetColumnsForDatasetItem(item: any, datasetName: string): string;
414
- GetDatasetStatusByName(datasetName: string, itemFilters?: DatasetItemFilterType[]): Promise<DatasetStatusResultType>;
415
- protected GetApplicationMetadata(): Promise<ApplicationInfo[]>;
416
- protected GetAuditLogTypeMetadata(): Promise<AuditLogTypeInfo[]>;
417
- protected GetUserMetadata(): Promise<UserInfo[]>;
418
- protected GetAuthorizationMetadata(): Promise<AuthorizationInfo[]>;
419
- protected GetCurrentUser(): Promise<UserInfo>;
420
- protected GetCurrentUserMetadata(): Promise<UserInfo>;
421
- protected GetRoleMetadata(): Promise<RoleInfo[]>;
422
- protected GetUserRoleMetadata(): Promise<UserRoleInfo[]>;
423
- protected GetRowLevelSecurityFilterMetadata(): Promise<RowLevelSecurityFilterInfo[]>;
352
+ GetDatasetStatusByName(datasetName: string, itemFilters?: DatasetItemFilterType[], contextUser?: UserInfo): Promise<DatasetStatusResultType>;
353
+ protected GetApplicationMetadata(contextUser: UserInfo): Promise<ApplicationInfo[]>;
354
+ protected GetAuditLogTypeMetadata(contextUser: UserInfo): Promise<AuditLogTypeInfo[]>;
355
+ protected GetUserMetadata(contextUser: UserInfo): Promise<UserInfo[]>;
356
+ protected GetAuthorizationMetadata(contextUser: UserInfo): Promise<AuthorizationInfo[]>;
424
357
  /**
425
358
  * Processes entity rows returned from SQL Server to handle timezone conversions for datetime fields.
426
359
  * This method specifically handles the conversion of datetime2 fields (which SQL Server returns without timezone info)
@@ -431,6 +364,40 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
431
364
  * @returns The processed rows with corrected datetime values
432
365
  */
433
366
  ProcessEntityRows(rows: any[], entityInfo: EntityInfo): Promise<any[]>;
367
+ /**
368
+ * Static method for executing SQL with proper handling of connections and logging.
369
+ * This is the single point where all SQL execution happens in the entire class.
370
+ *
371
+ * @param query - SQL query to execute
372
+ * @param parameters - Query parameters
373
+ * @param context - Execution context containing pool, transaction, and logging functions
374
+ * @param options - Options for SQL execution
375
+ * @returns Promise<sql.IResult<any>> - Query result
376
+ * @private
377
+ */
378
+ private static _internalExecuteSQLStatic;
379
+ /**
380
+ * Internal centralized method for executing SQL queries with consistent transaction and connection handling.
381
+ * This method ensures proper request object creation and management to avoid concurrency issues,
382
+ * particularly when using transactions where multiple operations may execute in parallel.
383
+ *
384
+ * @private
385
+ * @param query - The SQL query to execute
386
+ * @param parameters - Optional parameters for the query (array for positional, object for named)
387
+ * @param connectionSource - Optional specific connection source (pool, transaction, or request)
388
+ * @param loggingOptions - Optional logging configuration
389
+ * @returns Promise<sql.IResult<any>> - The raw mssql result object
390
+ *
391
+ * @remarks
392
+ * - Always creates a new Request object for each query to avoid "EREQINPROG" errors
393
+ * - Handles both positional (?) and named (@param) parameter styles
394
+ * - Automatically uses active transaction if one exists, otherwise uses connection pool
395
+ * - Handles SQL logging in parallel with query execution
396
+ * - Provides automatic retry with pool connection if transaction fails
397
+ *
398
+ * @throws {Error} Rethrows any SQL execution errors after logging
399
+ */
400
+ private _internalExecuteSQL;
434
401
  /**
435
402
  * This method can be used to execute raw SQL statements outside of the MJ infrastructure.
436
403
  * *CAUTION* - use this method with great care.
@@ -438,7 +405,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
438
405
  * @param parameters
439
406
  * @returns
440
407
  */
441
- ExecuteSQL(query: string, parameters?: any, options?: ExecuteSQLOptions): Promise<any>;
408
+ ExecuteSQL(query: string, parameters?: any, options?: ExecuteSQLOptions, contextUser?: UserInfo): Promise<any>;
442
409
  /**
443
410
  * Static helper method for executing SQL queries on an external connection pool.
444
411
  * This method is designed to be used by generated code where a connection pool
@@ -450,7 +417,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
450
417
  * @param parameters - Optional parameters for the query
451
418
  * @returns Promise<any[]> - Array of results (empty array if no results)
452
419
  */
453
- static ExecuteSQLWithPool(pool: sql.ConnectionPool, query: string, parameters?: any): Promise<any[]>;
420
+ static ExecuteSQLWithPool(pool: sql.ConnectionPool, query: string, parameters?: any, contextUser?: UserInfo): Promise<any[]>;
454
421
  /**
455
422
  * Static method to execute a batch of SQL queries using a provided connection source.
456
423
  * This allows the batch logic to be reused from external contexts like TransactionGroup.
@@ -462,7 +429,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
462
429
  * @param parameters - Optional array of parameter arrays, one for each query
463
430
  * @returns Promise<any[][]> - Array of result arrays, one for each query
464
431
  */
465
- static ExecuteSQLBatchStatic(connectionSource: sql.ConnectionPool | sql.Transaction | sql.Request, queries: string[], parameters?: any[][]): Promise<any[][]>;
432
+ static ExecuteSQLBatchStatic(connectionSource: sql.ConnectionPool | sql.Transaction | sql.Request, queries: string[], parameters?: any[][], contextUser?: UserInfo): Promise<any[][]>;
466
433
  /**
467
434
  * Executes multiple SQL queries in a single batch for optimal performance.
468
435
  * All queries are combined into a single SQL statement and executed together.
@@ -474,7 +441,7 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
474
441
  * @param options - Optional execution options for logging and description
475
442
  * @returns Promise<any[][]> - Array of result arrays, one for each query
476
443
  */
477
- ExecuteSQLBatch(queries: string[], parameters?: any[][], options?: ExecuteSQLBatchOptions): Promise<any[][]>;
444
+ ExecuteSQLBatch(queries: string[], parameters?: any[][], options?: ExecuteSQLBatchOptions, contextUser?: UserInfo): Promise<any[][]>;
478
445
  /**
479
446
  * Determines whether the database driver requires adjustment for datetimeoffset fields.
480
447
  * This method performs an empirical test on first use to detect if the driver (e.g., mssql)
@@ -502,12 +469,24 @@ export declare class SQLServerDataProvider extends ProviderBase implements IEnti
502
469
  * This empirical test determines if we need to adjust for incorrect timezone handling.
503
470
  */
504
471
  private testDatetimeOffsetHandling;
505
- protected BeginTransaction(): Promise<void>;
506
- protected CommitTransaction(): Promise<void>;
507
- protected RollbackTransaction(): Promise<void>;
472
+ BeginTransaction(): Promise<void>;
473
+ CommitTransaction(): Promise<void>;
474
+ RollbackTransaction(): Promise<void>;
475
+ /**
476
+ * Override RefreshIfNeeded to skip refresh when a transaction is active
477
+ * This prevents conflicts between metadata refresh operations and active transactions
478
+ * @returns Promise<boolean> - true if refresh was performed, false if skipped or no refresh needed
479
+ */
480
+ RefreshIfNeeded(): Promise<boolean>;
481
+ /**
482
+ * Process any deferred tasks that were queued during a transaction
483
+ * This is called after a successful transaction commit
484
+ * @private
485
+ */
486
+ private processDeferredTasks;
508
487
  get LocalStorageProvider(): ILocalStorageProvider;
509
- GetEntityRecordNames(info: EntityRecordNameInput[]): Promise<EntityRecordNameResult[]>;
510
- GetEntityRecordName(entityName: string, CompositeKey: CompositeKey): Promise<string>;
488
+ GetEntityRecordNames(info: EntityRecordNameInput[], contextUser?: UserInfo): Promise<EntityRecordNameResult[]>;
489
+ GetEntityRecordName(entityName: string, CompositeKey: CompositeKey, contextUser?: UserInfo): Promise<string>;
511
490
  protected GetEntityRecordNameSQL(entityName: string, CompositeKey: CompositeKey): string;
512
491
  CreateTransactionGroup(): Promise<TransactionGroupBase>;
513
492
  /**************************************************************************/