@@ -146,8 +146,9 @@ public final class ValkeyClusterClient: Sendable {
146146 @inlinable
147147 public func execute< Command: ValkeyCommand > ( _ command: Command ) async throws -> Command . Response {
148148 let hashSlot = try self . hashSlot ( for: command. keysAffected)
149+ let nodeSelection = getNodeSelection ( readOnly: command. isReadOnly)
149150 var clientSelector : ( ) async throws -> ValkeyNodeClient = {
150- try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] )
151+ try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] , nodeSelection : nodeSelection )
151152 }
152153
153154 var asking = false
@@ -252,9 +253,11 @@ public final class ValkeyClusterClient: Sendable {
252253 _ commands: [ any ValkeyCommand ]
253254 ) async -> [ Result < RESPToken , any Error > ] {
254255 guard commands. count > 0 else { return [ ] }
256+ let readOnlyCommand = commands. reduce ( true ) { $0 && $1. isReadOnly }
257+ let nodeSelection = getNodeSelection ( readOnly: readOnlyCommand)
255258 // get a list of nodes and the commands that should be run on them
256259 do {
257- let nodes = try await self . splitCommandsAcrossNodes ( commands: commands)
260+ let nodes = try await self . splitCommandsAcrossNodes ( commands: commands, nodeSelection : nodeSelection )
258261 // if this list has one element, then just run the pipeline on that single node
259262 if nodes. count == 1 {
260263 do {
@@ -340,9 +343,10 @@ public final class ValkeyClusterClient: Sendable {
340343 _ commands: Commands
341344 ) async throws -> [ Result < RESPToken , Error > ] where Commands. Element == any ValkeyCommand {
342345 let hashSlot = try self . hashSlot ( for: commands. flatMap { $0. keysAffected } )
343-
346+ let readOnlyCommand = commands. reduce ( true ) { $0 && $1. isReadOnly }
347+ let nodeSelection = getNodeSelection ( readOnly: readOnlyCommand)
344348 var clientSelector : ( ) async throws -> ValkeyNodeClient = {
345- try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] )
349+ try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] , nodeSelection : nodeSelection )
346350 }
347351
348352 var asking = false
@@ -458,20 +462,32 @@ public final class ValkeyClusterClient: Sendable {
458462 ///
459463 /// - Parameters:
460464 /// - keys: Keys affected by operation. This is used to choose the cluster node
465+ /// - readOnly: Is this connection only going to be used with readonly commands
461466 /// - isolation: Actor isolation
462467 /// - operation: Closure handling Valkey connection
463468 /// - Returns: Value returned by closure
464469 @inlinable
465470 public func withConnection< Value> (
466471 forKeys keys: some Collection < ValkeyKey > ,
472+ readOnly: Bool = false ,
467473 isolation: isolated ( any Actor ) ? = #isolation,
468474 operation: ( ValkeyConnection ) async throws -> sending Value
469475 ) async throws -> Value {
470476 let hashSlots = keys. compactMap { HashSlot ( key: $0) }
471- let node = try await self . nodeClient ( for: hashSlots)
477+ let nodeSelection = getNodeSelection ( readOnly: readOnly)
478+ let node = try await self . nodeClient ( for: hashSlots, nodeSelection: nodeSelection)
472479 return try await node. withConnection ( isolation: isolation, operation: operation)
473480 }
474481
482+ @inlinable
483+ /* private */ func getNodeSelection( readOnly: Bool ) -> ValkeyClusterNodeSelection {
484+ if readOnly {
485+ self . clientConfiguration. readOnlyCommandNodeSelection. clusterNodeSelection
486+ } else {
487+ . primary
488+ }
489+ }
490+
475491 /// Starts running the cluster client.
476492 ///
477493 /// This method initiates:
@@ -557,7 +573,10 @@ public final class ValkeyClusterClient: Sendable {
557573 /// These array of indices are then used to create collections of commands to
558574 /// run on each node
559575 @usableFromInline
560- func splitCommandsAcrossNodes( commands: [ any ValkeyCommand ] ) async throws -> [ ValkeyServerAddress : NodeAndCommands ] . Values {
576+ func splitCommandsAcrossNodes(
577+ commands: [ any ValkeyCommand ] ,
578+ nodeSelection: ValkeyClusterNodeSelection
579+ ) async throws -> [ ValkeyServerAddress : NodeAndCommands ] . Values {
561580 var nodeMap : [ ValkeyServerAddress : NodeAndCommands ] = [ : ]
562581 var index = commands. startIndex
563582 var prevAddress : ValkeyServerAddress ? = nil
@@ -570,7 +589,7 @@ public final class ValkeyClusterClient: Sendable {
570589 // Get hash slot for key and add all the commands you have iterated through so far to the
571590 // node associated with that key and break out of loop
572591 let hashSlot = try self . hashSlot ( for: keysAffected)
573- let node = try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] )
592+ let node = try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] , nodeSelection : nodeSelection )
574593 let address = node. serverAddress
575594 let nodeAndCommands = NodeAndCommands ( node: node, commandIndices: . init( commands. startIndex..< index) )
576595 nodeMap [ address] = nodeAndCommands
@@ -586,7 +605,7 @@ public final class ValkeyClusterClient: Sendable {
586605 if keysAffected. count > 0 {
587606 // If command affects a key get hash slot for key and add command to the node associated with that key
588607 let hashSlot = try self . hashSlot ( for: keysAffected)
589- let node = try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] )
608+ let node = try await self . nodeClient ( for: hashSlot. map { [ $0] } ?? [ ] , nodeSelection : nodeSelection )
590609 prevAddress = node. serverAddress
591610 nodeMap [ prevAddress, default: . init( node: node, commandIndices: [ ] ) ] . commandIndices. append ( index)
592611 } else {
@@ -597,7 +616,7 @@ public final class ValkeyClusterClient: Sendable {
597616 }
598617 } else {
599618 // if none of the commands affect any keys then choose a random node
600- let node = try await self . nodeClient ( for: [ ] )
619+ let node = try await self . nodeClient ( for: [ ] , nodeSelection : nodeSelection )
601620 let address = node. serverAddress
602621 let nodeAndCommands = NodeAndCommands ( node: node, commandIndices: . init( commands. startIndex..< index) )
603622 nodeMap [ address] = nodeAndCommands
@@ -854,14 +873,17 @@ public final class ValkeyClusterClient: Sendable {
854873 /// - `ValkeyClusterError.clusterIsUnavailable` if no healthy nodes are available
855874 /// - `ValkeyClusterError.clusterIsMissingSlotAssignment` if the slot assignment cannot be determined
856875 @inlinable
857- package func nodeClient( for slots: some ( Collection < HashSlot > & Sendable ) ) async throws -> ValkeyNodeClient {
876+ package func nodeClient(
877+ for slots: some ( Collection < HashSlot > & Sendable ) ,
878+ nodeSelection: ValkeyClusterNodeSelection
879+ ) async throws -> ValkeyNodeClient {
858880 var retries = 0
859881 while retries < 3 {
860882 defer { retries += 1 }
861883
862884 do {
863885 return try self . stateLock. withLock { state -> ValkeyNodeClient in
864- try state. poolFastPath ( for: slots)
886+ try state. poolFastPath ( for: slots, nodeSelection : nodeSelection )
865887 }
866888 } catch let error as ValkeyClusterError where error == . clusterIsUnavailable {
867889 let waiterID = self . nextRequestID ( )
0 commit comments