@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 +223 -19
- package/dist/SQLServerDataProvider.d.ts +93 -114
- package/dist/SQLServerDataProvider.d.ts.map +1 -1
- package/dist/SQLServerDataProvider.js +413 -444
- package/dist/SQLServerDataProvider.js.map +1 -1
- package/dist/SqlLogger.d.ts +61 -0
- package/dist/SqlLogger.d.ts.map +1 -0
- package/dist/SqlLogger.js +282 -0
- package/dist/SqlLogger.js.map +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +37 -0
- package/dist/types.js.map +1 -0
- package/package.json +10 -9
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
|
|
467
|
+
## SQL Logging
|
|
462
468
|
|
|
463
|
-
The SQL Server Data Provider includes
|
|
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
|
-
- **
|
|
473
|
-
- **
|
|
474
|
-
- **
|
|
475
|
-
- **
|
|
476
|
-
- **
|
|
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
|
-
###
|
|
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('./
|
|
492
|
+
const logger = await dataProvider.createSqlLogger('./logs/sql/operations.sql', {
|
|
488
493
|
formatAsMigration: false,
|
|
489
|
-
|
|
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
|
-
|
|
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
|
-
|
|
582
|
+
### Session Management Methods
|
|
516
583
|
|
|
517
|
-
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
/**************************************************************************/
|