@@ -2043,14 +2043,19 @@ class Transaction(pg_api.Transaction):
20432043
20442044 mode = None
20452045 isolation = None
2046+ gid = None
20462047
2047- _e_factors = ('database' , 'isolation' , 'mode' )
2048+ _e_factors = ('database' , 'gid' , ' isolation' , 'mode' )
20482049
20492050 def _e_metas (self ):
20502051 yield (None , self .state )
20512052
2052- def __init__ (self , database , isolation = None , mode = None ):
2053+ def __init__ (self , database , isolation = None , mode = None , gid = None ):
20532054 self .database = database
2055+ self .gid = gid
2056+ if gid is not None :
2057+ # XXX: remove in 1.1
2058+ warnings .warn ("two phase interfaces will not exist in 1.1; do not use the 'gid' parameter" , DeprecationWarning , stacklevel = 3 )
20542059 self .isolation = isolation
20552060 self .mode = mode
20562061 self .state = 'initialized'
@@ -2081,10 +2086,26 @@ def __exit__(self, typ, value, tb):
20812086 # If an error occurs, clean up the transaction state
20822087 # and raise as needed.
20832088 except pg_exc .ActiveTransactionError as err :
2084- if not self .database .closed :
2089+ ##
2090+ # Failed COMMIT PREPARED <gid>?
2091+ # Likely cases:
2092+ # - User exited block without preparing the transaction.
2093+ ##
2094+ if not self .database .closed and self .gid is not None :
20852095 # adjust the state so rollback will do the right thing and abort.
20862096 self .state = 'open'
20872097 self .rollback ()
2098+ ##
2099+ # The other exception that *can* occur is
2100+ # UndefinedObjectError in which:
2101+ # - User issued C/RB P <gid> before exit, but not via xact methods.
2102+ # - User adjusted gid after prepare().
2103+ #
2104+ # But the occurrence of this exception means it's not in an active
2105+ # transaction, which means no cleanup other than raise is necessary.
2106+ err .details ['cause' ] = \
2107+ "The prepared transaction was not " \
2108+ "prepared prior to the block's exit."
20882109 raise
20892110 elif issubclass (typ , Exception ):
20902111 # There's an exception, so only rollback if the connection
@@ -2130,7 +2151,7 @@ def start(self):
21302151 )
21312152 else :
21322153 self .type = 'savepoint'
2133- if (self .isolation , self .mode ) != (None ,None ):
2154+ if (self .gid , self . isolation , self .mode ) != (None , None ,None ):
21342155 em = element .ClientError ((
21352156 (b'S' , 'ERROR' ),
21362157 (b'C' , '--OPE' ),
@@ -2143,15 +2164,60 @@ def start(self):
21432164 self .state = 'open'
21442165 begin = start
21452166
2167+ @staticmethod
2168+ def _prepare_string (id ):
2169+ "2pc prepared transaction 'gid'"
2170+ return "PREPARE TRANSACTION '" + id .replace ("'" , "''" ) + "';"
2171+
21462172 @staticmethod
21472173 def _release_string (id ):
21482174 'release "";'
21492175 return 'RELEASE "xact(' + id .replace ('"' , '""' ) + ')";'
21502176
2177+ def prepare (self ):
2178+ if self .state == 'prepared' :
2179+ return
2180+ if self .state != 'open' :
2181+ em = element .ClientError ((
2182+ (b'S' , 'ERROR' ),
2183+ (b'C' , '--OPE' ),
2184+ (b'M' , "transaction state must be 'open' in order to prepare" ),
2185+ ))
2186+ self .database .typio .raise_client_error (em , creator = self )
2187+ if self .type != 'block' :
2188+ em = element .ClientError ((
2189+ (b'S' , 'ERROR' ),
2190+ (b'C' , '--OPE' ),
2191+ (b'M' , "improper transaction type to prepare" ),
2192+ ))
2193+ self .database .typio .raise_client_error (em , creator = self )
2194+ q = self ._prepare_string (self .gid )
2195+ self .database .execute (q )
2196+ self .state = 'prepared'
2197+
2198+ def recover (self ):
2199+ if self .state != 'initialized' :
2200+ em = element .ClientError ((
2201+ (b'S' , 'ERROR' ),
2202+ (b'C' , '--OPE' ),
2203+ (b'M' , "improper state for prepared transaction recovery" ),
2204+ ))
2205+ self .database .typio .raise_client_error (em , creator = self )
2206+ if self .database .sys .xact_is_prepared (self .gid ):
2207+ self .state = 'prepared'
2208+ self .type = 'block'
2209+ else :
2210+ em = element .ClientError ((
2211+ (b'S' , 'ERROR' ),
2212+ (b'C' , '42704' ), # UndefinedObjectError
2213+ (b'M' , "prepared transaction does not exist" ),
2214+ ))
2215+ self .database .typio .raise_client_error (em , creator = self )
2216+
21512217 def commit (self ):
21522218 if self .state == 'committed' :
21532219 return
2154- if self .state != ' open' :
2220+ if self .state not in ( 'prepared' , ' open') :
21552221 em = element .ClientError ((
21562222 (b'S' , 'ERROR' ),
21572223 (b'C' , '--OPE' ),
@@ -2160,8 +2226,19 @@ def commit(self):
21602226 self .database .typio .raise_client_error (em , creator = self )
21612227
21622228 if self .type == 'block' :
2163- q = 'COMMIT'
2229+ if self .gid is not None :
2230+ # User better have prepared it.
2231+ q = "COMMIT PREPARED '" + self .gid .replace ("'" , "''" ) + "';"
2232+ else :
2233+ q = 'COMMIT'
21642234 else :
2235+ if self .gid is not None :
2236+ em = element .ClientError ((
2237+ (b'S' , 'ERROR' ),
2238+ (b'C' , '--OPE' ),
2239+ (b'M' , "savepoint configured with global identifier" ),
2240+ ))
2241+ self .database .typio .raise_client_error (em , creator = self )
21652242 q = self ._release_string (hex (id (self )))
21662243 self .database .execute (q )
21672244 self .state = 'committed'
@@ -2182,7 +2259,10 @@ def rollback(self):
21822259 self .database .typio .raise_client_error (em , creator = self )
21832260
21842261 if self .type == 'block' :
2185- q = 'ABORT;'
2262+ if self .state == 'prepared' :
2263+ q = "ROLLBACK PREPARED '" + self .gid .replace ("'" , "''" ) + "'"
2264+ else :
2265+ q = 'ABORT;'
21862266 elif self .type == 'savepoint' :
21872267 q = self ._rollback_to_string (hex (id (self )))
21882268 else :
@@ -2264,8 +2344,8 @@ def do(self, language : str, source : str,
22642344 sql = "DO " + qlit (source ) + " LANGUAGE " + qid (language ) + ";"
22652345 self .execute (sql )
22662346
2267- def xact (self , isolation = None , mode = None ):
2268- x = Transaction (self , isolation = isolation , mode = mode )
2347+ def xact (self , gid = None , isolation = None , mode = None ):
2348+ x = Transaction (self , gid = gid , isolation = isolation , mode = mode )
22692349 return x
22702350
22712351 def prepare (self ,
0 commit comments