@@ -293,14 +293,16 @@ def __enter__(self):
293
293
def __exit__ (self , exc_type , exc_value , traceback ):
294
294
self .close ()
295
295
296
- def route (self , database ):
296
+ def route (self , database = None , bookmarks = None ):
297
297
""" Fetch a routing table from the server for the given
298
298
`database`. For Bolt 4.3 and above, this appends a ROUTE
299
299
message; for earlier versions, a procedure call is made via
300
300
the regular Cypher execution mechanism. In all cases, this is
301
301
sent to the network, and a response is fetched.
302
302
303
303
:param database: database for which to fetch a routing table
304
+ :param bookmarks: iterable of bookmark values after which this
305
+ transaction should begin
304
306
:return: dictionary of raw routing data
305
307
"""
306
308
@@ -480,12 +482,14 @@ def time_remaining():
480
482
raise ClientError ("Failed to obtain a connection from pool "
481
483
"within {!r}s" .format (timeout ))
482
484
483
- def acquire (self , access_mode = None , timeout = None , database = None ):
485
+ def acquire (self , access_mode = None , timeout = None , database = None ,
486
+ bookmarks = None ):
484
487
""" Acquire a connection to a server that can satisfy a set of parameters.
485
488
486
489
:param access_mode:
487
490
:param timeout:
488
491
:param database:
492
+ :param bookmarks:
489
493
"""
490
494
491
495
def release (self , * connections ):
@@ -587,7 +591,8 @@ def __init__(self, opener, pool_config, workspace_config, routing_context, addre
587
591
def __repr__ (self ):
588
592
return "<{} address={!r}>" .format (self .__class__ .__name__ , self .address )
589
593
590
- def acquire (self , access_mode = None , timeout = None , database = None ):
594
+ def acquire (self , access_mode = None , timeout = None , database = None ,
595
+ bookmarks = None ):
591
596
# The access_mode and database is not needed for a direct connection, its just there for consistency.
592
597
return self ._acquire (self .address , timeout )
593
598
@@ -673,11 +678,13 @@ def create_routing_table(self, database):
673
678
if database not in self .routing_tables :
674
679
self .routing_tables [database ] = RoutingTable (database = database , routers = self .get_default_database_initial_router_addresses ())
675
680
676
- def fetch_routing_info (self , address , database , timeout ):
681
+ def fetch_routing_info (self , address , database , bookmarks , timeout ):
677
682
""" Fetch raw routing info from a given router address.
678
683
679
684
:param address: router address
680
685
:param database: the database name to get routing table for
686
+ :param bookmarks: iterable of bookmark values after which the routing
687
+ info should be fetched
681
688
:param timeout: connection acquisition timeout in seconds
682
689
683
690
:return: list of routing records, or None if no connection
@@ -687,7 +694,9 @@ def fetch_routing_info(self, address, database, timeout):
687
694
"""
688
695
try :
689
696
with self ._acquire (address , timeout ) as cx :
690
- routing_table = cx .route (database or self .workspace_config .database )
697
+ routing_table = cx .route (
698
+ database or self .workspace_config .database , bookmarks
699
+ )
691
700
except BoltRoutingError as error :
692
701
# Connection was successful, but routing support is
693
702
# broken. This may indicate that the routing procedure
@@ -709,21 +718,23 @@ def fetch_routing_info(self, address, database, timeout):
709
718
self .deactivate (address )
710
719
return routing_table
711
720
712
- def fetch_routing_table (self , * , address , timeout , database ):
721
+ def fetch_routing_table (self , * , address , timeout , database , bookmarks ):
713
722
""" Fetch a routing table from a given router address.
714
723
715
724
:param address: router address
716
725
:param timeout: seconds
717
726
:param database: the database name
718
727
:type: str
728
+ :param bookmarks: bookmarks used when fetching routing table
719
729
720
730
:return: a new RoutingTable instance or None if the given router is
721
731
currently unable to provide routing information
722
732
723
733
:raise neo4j.exceptions.ServiceUnavailable: if no writers are available
724
734
:raise neo4j._exceptions.BoltProtocolError: if the routing information received is unusable
725
735
"""
726
- new_routing_info = self .fetch_routing_info (address , database , timeout )
736
+ new_routing_info = self .fetch_routing_info (address , database , bookmarks ,
737
+ timeout )
727
738
if new_routing_info is None :
728
739
return None
729
740
elif not new_routing_info :
@@ -752,26 +763,31 @@ def fetch_routing_table(self, *, address, timeout, database):
752
763
# At least one of each is fine, so return this table
753
764
return new_routing_table
754
765
755
- def update_routing_table_from (self , * routers , database = None ):
766
+ def update_routing_table_from (self , * routers , database = None ,
767
+ bookmarks = None ):
756
768
""" Try to update routing tables with the given routers.
757
769
758
770
:return: True if the routing table is successfully updated,
759
771
otherwise False
760
772
"""
761
773
log .debug ("Attempting to update routing table from {}" .format (", " .join (map (repr , routers ))))
762
774
for router in routers :
763
- new_routing_table = self .fetch_routing_table (address = router , timeout = self .pool_config .connection_timeout , database = database )
775
+ new_routing_table = self .fetch_routing_table (
776
+ address = router , timeout = self .pool_config .connection_timeout ,
777
+ database = database , bookmarks = bookmarks
778
+ )
764
779
if new_routing_table is not None :
765
780
self .routing_tables [database ].update (new_routing_table )
766
781
log .debug ("[#0000] C: <UPDATE ROUTING TABLE> address={!r} ({!r})" .format (router , self .routing_tables [database ]))
767
782
return True
768
783
return False
769
784
770
- def update_routing_table (self , * , database ):
785
+ def update_routing_table (self , * , database , bookmarks ):
771
786
""" Update the routing table from the first router able to provide
772
787
valid routing information.
773
788
774
789
:param database: The database name
790
+ :param bookmarks: bookmarks used when fetching routing table
775
791
776
792
:raise neo4j.exceptions.ServiceUnavailable:
777
793
"""
@@ -782,15 +798,22 @@ def update_routing_table(self, *, database):
782
798
if self .routing_tables [database ].missing_fresh_writer ():
783
799
# TODO: Test this state
784
800
has_tried_initial_routers = True
785
- if self .update_routing_table_from (self .first_initial_routing_address , database = database ):
801
+ if self .update_routing_table_from (
802
+ self .first_initial_routing_address , database = database ,
803
+ bookmarks = bookmarks
804
+ ):
786
805
# Why is only the first initial routing address used?
787
806
return
788
-
789
- if self . update_routing_table_from ( * existing_routers , database = database ):
807
+ if self . update_routing_table_from ( * existing_routers , database = database ,
808
+ bookmarks = bookmarks ):
790
809
return
791
810
792
- if not has_tried_initial_routers and self .first_initial_routing_address not in existing_routers :
793
- if self .update_routing_table_from (self .first_initial_routing_address , database = database ):
811
+ if (not has_tried_initial_routers
812
+ and self .first_initial_routing_address not in existing_routers ):
813
+ if self .update_routing_table_from (
814
+ self .first_initial_routing_address , database = database ,
815
+ bookmarks = bookmarks
816
+ ):
794
817
# Why is only the first initial routing address used?
795
818
return
796
819
@@ -804,7 +827,8 @@ def update_connection_pool(self, *, database):
804
827
if address not in servers :
805
828
super (Neo4jPool , self ).deactivate (address )
806
829
807
- def ensure_routing_table_is_fresh (self , * , access_mode , database ):
830
+ def ensure_routing_table_is_fresh (self , * , access_mode , database ,
831
+ bookmarks ):
808
832
""" Update the routing table if stale.
809
833
810
834
This method performs two freshness checks, before and after acquiring
@@ -823,7 +847,7 @@ def ensure_routing_table_is_fresh(self, *, access_mode, database):
823
847
return False
824
848
with self .refresh_lock :
825
849
826
- self .update_routing_table (database = database )
850
+ self .update_routing_table (database = database , bookmarks = bookmarks )
827
851
self .update_connection_pool (database = database )
828
852
829
853
for database in list (self .routing_tables .keys ()):
@@ -835,12 +859,14 @@ def ensure_routing_table_is_fresh(self, *, access_mode, database):
835
859
836
860
return True
837
861
838
- def _select_address (self , * , access_mode , database ):
862
+ def _select_address (self , * , access_mode , database , bookmarks ):
839
863
from neo4j .api import READ_ACCESS
840
864
""" Selects the address with the fewest in-use connections.
841
865
"""
842
866
self .create_routing_table (database )
843
- self .ensure_routing_table_is_fresh (access_mode = access_mode , database = database )
867
+ self .ensure_routing_table_is_fresh (
868
+ access_mode = access_mode , database = database , bookmarks = bookmarks
869
+ )
844
870
log .debug ("[#0000] C: <ROUTING TABLE ENSURE FRESH> %r" , self .routing_tables )
845
871
if access_mode == READ_ACCESS :
846
872
addresses = self .routing_tables [database ].readers
@@ -856,7 +882,8 @@ def _select_address(self, *, access_mode, database):
856
882
raise WriteServiceUnavailable ("No write service currently available" )
857
883
return choice (addresses_by_usage [min (addresses_by_usage )])
858
884
859
- def acquire (self , access_mode = None , timeout = None , database = None ):
885
+ def acquire (self , access_mode = None , timeout = None , database = None ,
886
+ bookmarks = None ):
860
887
if access_mode not in (WRITE_ACCESS , READ_ACCESS ):
861
888
raise ClientError ("Non valid 'access_mode'; {}" .format (access_mode ))
862
889
if not timeout :
@@ -867,7 +894,10 @@ def acquire(self, access_mode=None, timeout=None, database=None):
867
894
while True :
868
895
try :
869
896
# Get an address for a connection that have the fewest in-use connections.
870
- address = self ._select_address (access_mode = access_mode , database = database )
897
+ address = self ._select_address (
898
+ access_mode = access_mode , database = database ,
899
+ bookmarks = bookmarks
900
+ )
871
901
log .debug ("[#0000] C: <ACQUIRE ADDRESS> database=%r address=%r" , database , address )
872
902
except (ReadServiceUnavailable , WriteServiceUnavailable ) as err :
873
903
raise SessionExpired ("Failed to obtain connection towards '%s' server." % access_mode ) from err
0 commit comments