File Coverage

blib/lib/MongoDB/Role/_SessionSupport.pm
Criterion Covered Total %
statement 27 81 33.3
branch 0 36 0.0
condition 0 12 0.0
subroutine 9 15 60.0
pod n/a
total 36 144 25.0


line stmt bran cond sub pod time code
1             # Copyright 2018 - present MongoDB, Inc.
2             #
3             # Licensed under the Apache License, Version 2.0 (the "License");
4             # you may not use this file except in compliance with the License.
5             # You may obtain a copy of the License at
6             #
7             # http://www.apache.org/licenses/LICENSE-2.0
8             #
9             # Unless required by applicable law or agreed to in writing, software
10             # distributed under the License is distributed on an "AS IS" BASIS,
11             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12             # See the License for the specific language governing permissions and
13             # limitations under the License.
14              
15 60     60   75132 use strict;
  60         165  
  60         2008  
16 60     60   350 use warnings;
  60         151  
  60         2145  
17             package MongoDB::Role::_SessionSupport;
18              
19             # MongoDB role to add support for sessions on Ops
20              
21 60     60   336 use version;
  60         135  
  60         373  
22             our $VERSION = 'v2.2.1';
23              
24 60     60   4929 use Moo::Role;
  60         139  
  60         409  
25 60     60   21949 use MongoDB::_Types -types, 'to_IxHash';
  60         200  
  60         501  
26 60     60   329905 use MongoDB::_Constants;
  60         171  
  60         7695  
27 60     60   438 use Safe::Isa;
  60         149  
  60         8225  
28 60     60   432 use boolean;
  60         168  
  60         588  
29 60     60   4250 use namespace::clean;
  60         150  
  60         459  
30              
31             requires qw/ session retryable_write /;
32              
33             sub _apply_session_and_cluster_time {
34 0     0     my ( $self, $link, $query_ref ) = @_;
35              
36             # Assume that no session means no session support. Also means no way of
37             # getting clusterTime.
38 0 0         return unless defined $self->session;
39              
40             # Also assumption that the session was created from the current client -
41             # not an issue in implicit sessions, but explicit sessions may have been
42             # created by another client in the same scope. This should have been
43             # checked further up the call chain
44              
45 0           $$query_ref = to_IxHash( $$query_ref );
46 0           ($$query_ref)->Push( 'lsid' => $self->session->session_id );
47              
48 0 0 0       if ( $self->retryable_write || ! $self->session->_in_transaction_state( TXN_NONE ) ) {
49 0           ($$query_ref)->Push( 'txnNumber' => $self->session->_server_session->transaction_id );
50             }
51              
52 0 0         if ( ! $self->session->_in_transaction_state( TXN_NONE ) ) {
53 0           ($$query_ref)->Push( 'autocommit' => false );
54             }
55              
56 0 0         if ( $self->session->_in_transaction_state( TXN_STARTING ) ) {
    0          
57 0           ($$query_ref)->Push( 'startTransaction' => true );
58             # delete first to not merge options
59 0           ($$query_ref)->Delete( 'readConcern' );
60 0           ($$query_ref)->Push( @{ $self->session->_get_transaction_read_concern->as_args( $self->session ) } );
  0            
61             } elsif ( ! $self->session->_in_transaction_state( TXN_NONE ) ) {
62             # read concern only valid outside a transaction or when starting
63 0           ($$query_ref)->Delete( 'readConcern' );
64             }
65              
66             # write concern not allowed in transactions except when ending. We can
67             # safely delete it here as you can only pass writeConcern through by
68             # arguments to client of collection.
69 0 0         if ( $self->session->_in_transaction_state( TXN_STARTING, TXN_IN_PROGRESS ) ) {
70 0           ($$query_ref)->Delete( 'writeConcern' );
71             }
72              
73 0 0 0       if ( $self->session->_in_transaction_state( TXN_ABORTED, TXN_COMMITTED )
74             && ! ($$query_ref)->EXISTS('writeConcern')
75             ) {
76 0           ($$query_ref)->Push( @{ $self->session->_get_transaction_write_concern->as_args() } );
  0            
77             }
78              
79             # MUST be the last thing to touch the transaction state before sending,
80             # so the various starting specific query modifications can be applied
81             # The spec states that this should happen after the command even on error,
82             # so happening before the command is sent is still valid
83 0 0         if ( $self->session->_in_transaction_state( TXN_STARTING ) ) {
84 0           $self->session->_set__transaction_state( TXN_IN_PROGRESS );
85             # Set the session address for operation pinning in transactions against mongos
86 0           $self->session->_set__address( $link->address );
87             }
88              
89 0           $self->session->_server_session->update_last_use;
90              
91 0           my $cluster_time = $self->session->get_latest_cluster_time;
92              
93 0 0 0       if ( defined $cluster_time && $link->supports_clusterTime ) {
94             # Gossip the clusterTime
95 0           ($$query_ref)->Push( '$clusterTime' => $cluster_time );
96             }
97              
98 0           return;
99             }
100              
101             # Somethign about this makes me wonder if clustertime is not going to be set
102             # when it should: not all requests have sessions, but any response has the
103             # chance to have a clustertime. If we do not have a client available to put the
104             # clustertime in, then depending on the response there may be no way to
105             # retreive the clustertime further down the call stack.
106             sub _update_session_and_cluster_time {
107 0     0     my ( $self, $response ) = @_;
108              
109             # No point continuing as theres nothing to do even if clusterTime is returned
110 0 0         return unless defined $self->session;
111              
112 0           my $cluster_time = $self->__extract_from( $response, '$clusterTime' );
113              
114 0 0         if ( defined $cluster_time ) {
115 0           $self->session->client->_update_cluster_time( $cluster_time );
116 0           $self->session->advance_cluster_time( $cluster_time );
117             }
118              
119 0           my $recovery_token = $self->__extract_from( $response, 'recoveryToken' );
120              
121 0 0         if ( defined $recovery_token ) {
122 0           $self->session->_set__recovery_token( $recovery_token );
123             }
124              
125 0           return;
126             }
127              
128             sub _update_session_pre_assert {
129 0     0     my ( $self, $response ) = @_;
130              
131 0 0         return unless defined $self->session;
132              
133 0           my $operation_time = $self->__extract_from( $response, 'operationTime' );
134 0 0         $self->session->advance_operation_time( $operation_time ) if defined $operation_time;
135              
136 0           return;
137             }
138              
139             # Certain errors have to happen as soon as possible, such as write concern
140             # errors in a retryable write. This has to be seperate to the other functions
141             # due to not all result objects having the base response inside, so cannot be
142             # used to parse operationTime or $clusterTime
143             sub _assert_session_errors {
144 0     0     my ( $self, $response ) = @_;
145              
146 0 0         if ( $self->retryable_write ) {
147 0           $response->assert_no_write_concern_error;
148             }
149              
150 0           return;
151             }
152              
153             sub _update_session_connection_error {
154 0     0     my ( $self, $err ) = @_;
155              
156 0 0         return unless defined $self->session;
157 0 0 0       $self->session->_server_session->mark_dirty
158             if $err->$_isa("MongoDB::ConnectionError") || $err->$_isa("MongoDB::NetworkTimeout");
159 0           return $self->session->_maybe_apply_error_labels_and_unpin( $err );
160             }
161              
162             sub __extract_from {
163 0     0     my ( $self, $response, $key ) = @_;
164              
165 0 0         if ( $response->$_isa( 'MongoDB::CommandResult' ) ) {
166 0           return $response->output->{ $key };
167             } else {
168 0           return $response->{ $key };
169             }
170             }
171              
172             1;