@@ -10,10 +10,11 @@ import { PubSubListener } from '../client/pub-sub';
10
10
import { ErrorReply } from '../errors' ;
11
11
import { RedisTcpSocketOptions } from '../client/socket' ;
12
12
import { ClientSideCacheConfig , PooledClientSideCacheProvider } from '../client/cache' ;
13
- import { BasicCommandParser } from '../client/parser' ;
13
+ import { BasicCommandParser , CommandParser } from '../client/parser' ;
14
14
import { ASKING_CMD } from '../commands/ASKING' ;
15
15
import SingleEntryCache from '../single-entry-cache'
16
- import { POLICIES , PolicyResolver , StaticPolicyResolver } from './request-response-policies' ;
16
+ import { POLICIES , PolicyResolver , REQUEST_POLICIES_WITH_DEFAULTS , RESPONSE_POLICIES_WITH_DEFAULTS , StaticPolicyResolver } from './request-response-policies' ;
17
+ import { aggregateLogicalAnd , aggregateLogicalOr , aggregateMax , aggregateMerge , aggregateMin , aggregateSum } from './request-response-policies/generic-aggregators' ;
17
18
interface ClusterCommander <
18
19
M extends RedisModules ,
19
20
F extends RedisFunctions ,
@@ -190,10 +191,9 @@ export default class RedisCluster<
190
191
command . parseCommand ( parser , ...args ) ;
191
192
192
193
return this . _self . _execute (
193
- parser . firstKey ,
194
+ parser ,
194
195
command . IS_READ_ONLY ,
195
196
this . _commandOptions ,
196
- parser . commandName ! ,
197
197
( client , opts ) => client . _executeCommand ( command , parser , opts , transformReply )
198
198
) ;
199
199
} ;
@@ -207,10 +207,9 @@ export default class RedisCluster<
207
207
command . parseCommand ( parser , ...args ) ;
208
208
209
209
return this . _self . _execute (
210
- parser . firstKey ,
210
+ parser ,
211
211
command . IS_READ_ONLY ,
212
212
this . _self . _commandOptions ,
213
- parser . commandName ! ,
214
213
( client , opts ) => client . _executeCommand ( command , parser , opts , transformReply )
215
214
) ;
216
215
} ;
@@ -226,10 +225,9 @@ export default class RedisCluster<
226
225
fn . parseCommand ( parser , ...args ) ;
227
226
228
227
return this . _self . _execute (
229
- parser . firstKey ,
228
+ parser ,
230
229
fn . IS_READ_ONLY ,
231
230
this . _self . _commandOptions ,
232
- parser . commandName ! ,
233
231
( client , opts ) => client . _executeCommand ( fn , parser , opts , transformReply )
234
232
) ;
235
233
} ;
@@ -245,10 +243,9 @@ export default class RedisCluster<
245
243
script . parseCommand ( parser , ...args ) ;
246
244
247
245
return this . _self . _execute (
248
- parser . firstKey ,
246
+ parser ,
249
247
script . IS_READ_ONLY ,
250
248
this . _commandOptions ,
251
- parser . commandName ! ,
252
249
( client , opts ) => client . _executeScript ( script , parser , opts , transformReply )
253
250
) ;
254
251
} ;
@@ -459,64 +456,157 @@ export default class RedisCluster<
459
456
}
460
457
461
458
async _execute < T > (
462
- firstKey : RedisArgument | undefined ,
459
+ parser : CommandParser ,
463
460
isReadonly : boolean | undefined ,
464
461
options : ClusterCommandOptions | undefined ,
465
- commandName : string ,
466
462
fn : ( client : RedisClientType < M , F , S , RESP , TYPE_MAPPING > , opts ?: ClusterCommandOptions ) => Promise < T >
467
463
) : Promise < T > {
468
464
469
465
const maxCommandRedirections = this . _options . maxCommandRedirections ?? 16 ;
470
- const policyResult = this . _policyResolver . resolvePolicy ( commandName )
471
466
472
- if ( policyResult . ok ) {
473
- //TODO
474
- } else {
475
- //TODO
467
+ const policyResult = this . _policyResolver . resolvePolicy ( parser . commandIdentifier ) ;
468
+
469
+ if ( ! policyResult . ok ) {
470
+ throw new Error ( `Policy resolution error for ${ parser . commandIdentifier } : ${ policyResult . error } ` ) ;
476
471
}
477
-
478
- let client = await this . _slots . getClient ( firstKey , isReadonly ) ;
479
- let i = 0 ;
480
472
481
- let myFn = fn ;
473
+ const requestPolicy = policyResult . value . request
474
+ const responsePolicy = policyResult . value . response
475
+
476
+ let clients : Array < RedisClientType < M , F , S , RESP , TYPE_MAPPING > > ;
477
+ // https://redis.io/docs/latest/develop/reference/command-tips
478
+ switch ( requestPolicy ) {
479
+
480
+ case REQUEST_POLICIES_WITH_DEFAULTS . ALL_NODES :
481
+ clients = this . _slots . getAllClients ( )
482
+ break ;
483
+
484
+ case REQUEST_POLICIES_WITH_DEFAULTS . ALL_SHARDS :
485
+ clients = this . _slots . getAllMasterClients ( )
486
+ break ;
487
+
488
+ case REQUEST_POLICIES_WITH_DEFAULTS . MULTI_SHARD :
489
+ clients = await Promise . all (
490
+ parser . keys . map ( ( key ) => this . _slots . getClient ( key , isReadonly ) )
491
+ ) ;
492
+ break ;
493
+
494
+ case REQUEST_POLICIES_WITH_DEFAULTS . SPECIAL :
495
+ throw new Error ( `Special request policy not implemented for ${ parser . commandIdentifier } ` ) ;
496
+
497
+ case REQUEST_POLICIES_WITH_DEFAULTS . DEFAULT_KEYLESS :
498
+ //TODO handle undefined case?
499
+ clients = [ this . _slots . getRandomNode ( ) . client ! ]
500
+ break ;
501
+
502
+ case REQUEST_POLICIES_WITH_DEFAULTS . DEFAULT_KEYED :
503
+ clients = [ await this . _slots . getClient ( parser . firstKey , isReadonly ) ]
504
+ break ;
505
+
506
+ default :
507
+ throw new Error ( `Unknown request policy ${ requestPolicy } ` ) ;
482
508
483
- while ( true ) {
484
- try {
485
- return await myFn ( client , options ) ;
486
- } catch ( err ) {
487
- myFn = fn ;
509
+ }
488
510
489
- // TODO: error class
490
- if ( ++ i > maxCommandRedirections || ! ( err instanceof Error ) ) {
491
- throw err ;
492
- }
511
+ const responsePromises = clients . map ( async client => {
493
512
494
- if ( err . message . startsWith ( 'ASK' ) ) {
495
- const address = err . message . substring ( err . message . lastIndexOf ( ' ' ) + 1 ) ;
496
- let redirectTo = await this . _slots . getMasterByAddress ( address ) ;
497
- if ( ! redirectTo ) {
498
- await this . _slots . rediscover ( client ) ;
499
- redirectTo = await this . _slots . getMasterByAddress ( address ) ;
513
+ let i = 0 ;
514
+
515
+ let myFn = fn ;
516
+
517
+ while ( true ) {
518
+ try {
519
+ return await myFn ( client , options ) ;
520
+ } catch ( err ) {
521
+ myFn = fn ;
522
+
523
+ // TODO: error class
524
+ if ( ++ i > maxCommandRedirections || ! ( err instanceof Error ) ) {
525
+ throw err ;
500
526
}
501
527
502
- if ( ! redirectTo ) {
503
- throw new Error ( `Cannot find node ${ address } ` ) ;
528
+ if ( err . message . startsWith ( 'ASK' ) ) {
529
+ const address = err . message . substring ( err . message . lastIndexOf ( ' ' ) + 1 ) ;
530
+ let redirectTo = await this . _slots . getMasterByAddress ( address ) ;
531
+ if ( ! redirectTo ) {
532
+ await this . _slots . rediscover ( client ) ;
533
+ redirectTo = await this . _slots . getMasterByAddress ( address ) ;
534
+ }
535
+
536
+ if ( ! redirectTo ) {
537
+ throw new Error ( `Cannot find node ${ address } ` ) ;
538
+ }
539
+
540
+ client = redirectTo ;
541
+ myFn = this . _handleAsk ( fn ) ;
542
+ continue ;
543
+ }
544
+
545
+ if ( err . message . startsWith ( 'MOVED' ) ) {
546
+ await this . _slots . rediscover ( client ) ;
547
+ client = await this . _slots . getClient ( parser . firstKey , isReadonly ) ;
548
+ continue ;
504
549
}
505
550
506
- client = redirectTo ;
507
- myFn = this . _handleAsk ( fn ) ;
508
- continue ;
509
- }
510
-
511
- if ( err . message . startsWith ( 'MOVED' ) ) {
512
- await this . _slots . rediscover ( client ) ;
513
- client = await this . _slots . getClient ( firstKey , isReadonly ) ;
514
- continue ;
515
- }
551
+ throw err ;
552
+ }
553
+ }
516
554
517
- throw err ;
518
- }
555
+ } )
556
+
557
+ switch ( responsePolicy ) {
558
+ case RESPONSE_POLICIES_WITH_DEFAULTS . ONE_SUCCEEDED : {
559
+ return Promise . any ( responsePromises ) ;
560
+ }
561
+
562
+ case RESPONSE_POLICIES_WITH_DEFAULTS . ALL_SUCCEEDED : {
563
+ const responses = await Promise . all ( responsePromises ) ;
564
+ return responses [ 0 ]
565
+ }
566
+
567
+ case RESPONSE_POLICIES_WITH_DEFAULTS . AGG_LOGICAL_AND : {
568
+ const responses = await Promise . all ( responsePromises )
569
+ return aggregateLogicalAnd ( responses ) ;
570
+ }
571
+
572
+ case RESPONSE_POLICIES_WITH_DEFAULTS . AGG_LOGICAL_OR : {
573
+ const responses = await Promise . all ( responsePromises )
574
+ return aggregateLogicalOr ( responses ) ;
575
+ }
576
+
577
+ case RESPONSE_POLICIES_WITH_DEFAULTS . AGG_MIN : {
578
+ const responses = await Promise . all ( responsePromises ) ;
579
+ return aggregateMin ( responses ) ;
580
+ }
581
+
582
+ case RESPONSE_POLICIES_WITH_DEFAULTS . AGG_MAX : {
583
+ const responses = await Promise . all ( responsePromises ) ;
584
+ return aggregateMax ( responses ) ;
585
+ }
586
+
587
+ case RESPONSE_POLICIES_WITH_DEFAULTS . AGG_SUM : {
588
+ const responses = await Promise . all ( responsePromises ) ;
589
+ return aggregateSum ( responses ) ;
590
+ }
591
+
592
+ case RESPONSE_POLICIES_WITH_DEFAULTS . SPECIAL : {
593
+ throw new Error ( `Special response policy not implemented for ${ parser . commandIdentifier } ` ) ;
594
+ }
595
+
596
+ case RESPONSE_POLICIES_WITH_DEFAULTS . DEFAULT_KEYLESS : {
597
+ const responses = await Promise . all ( responsePromises ) ;
598
+ return aggregateMerge ( responses ) ;
599
+ }
600
+
601
+ case RESPONSE_POLICIES_WITH_DEFAULTS . DEFAULT_KEYED : {
602
+ const responses = await Promise . all ( responsePromises ) ;
603
+ return responses as T ;
604
+ }
605
+
606
+ default :
607
+ throw new Error ( `Unknown response policy ${ responsePolicy } ` ) ;
519
608
}
609
+
520
610
}
521
611
522
612
async sendCommand < T = ReplyUnion > (
@@ -526,11 +616,15 @@ export default class RedisCluster<
526
616
options ?: ClusterCommandOptions ,
527
617
// defaultPolicies?: CommandPolicies
528
618
) : Promise < T > {
619
+
620
+ const parser = new BasicCommandParser ( ) ;
621
+ firstKey && parser . push ( firstKey )
622
+ args . forEach ( arg => parser . push ( arg ) ) ;
623
+
529
624
return this . _self . _execute (
530
- firstKey ,
625
+ parser ,
531
626
isReadonly ,
532
627
options ,
533
- args [ 0 ] instanceof Buffer ? args [ 0 ] . toString ( ) : args [ 0 ] ,
534
628
( client , opts ) => client . sendCommand ( args , opts )
535
629
) ;
536
630
}
0 commit comments