async def _execute_trans(self, sql: TCL) -> result.Result: try: if sql.is_start: if self._trans: raise Exception("Transactions cannot be nested.") self._trans = self._create_trans(sql.trans_args) await self._trans.begin(sql) return result.OK(0, 0, 0, 0, '', False) elif sql.is_commit: if self._trans: await self._trans.commit(sql) await self._trans.close() self._trans = None return result.OK(0, 0, 0, 0, '', False) elif sql.is_rollback: if self._trans: await self._trans.rollback(sql) await self._trans.close() self._trans = None return result.OK(0, 0, 0, 0, '', False) else: return result.Error(1000, "unknown what to do.") except Exception as e: await self._trans.close() self._trans = None return result.Error(1000, str(e))
async def rollback(self, sql: TCL) -> Optional[result.Result]: r = await self.a2pc_client.rollback(self.xid) if r.status == 0: self.status = TransStatus.END return result.OK(0, 0, 0, 0, "", False) else: return result.Error(r.status, r.msg)
async def begin(self, sql: TCL) -> Optional[result.Result]: self.status = TransStatus.BEGINNING r = await self.a2pc_client.begin() if r.status == 0: self.xid = r.xid else: return result.Error(r.status, r.msg) self.status = TransStatus.ACTIVE
async def query(self, sql) -> result.Result: try: await self._ensure_connected() await self._conn.query(sql) self._result = self._conn._result return self.read_result() except MySQLError as e: return result.Error(e.args[0], e.args[1])
async def batch(self, sql) -> Union[DictCursor, result.Error]: try: await self._ensure_connected() cur = await self._conn.cursor() await cur.execute(sql) self._result = cur._result return cur except MySQLError as e: return result.Error(10003, str(e))
async def _execute_ending(self, r: List[Union[result.Result, A2PCResponse]], node: List[DBTableStrategyBackend], sql: DMLW): lock = r[-1] r = r[:-1] ending_sql = "COMMIT" return_v = None if not isinstance(lock, A2PCResponse): ending_sql = "ROLLBACK" return_v = result.Error( 1000, "{} acquire lock fail: {}".format(str(sql.raw), str(lock))) elif lock.status != 0: logger.warning("{} acquire lock fail: {}".format( str(sql.raw), lock.msg)) ending_sql = "ROLLBACK" return_v = result.Error( 1000, "{} acquire lock fail: {}".format(str(sql.raw), lock.msg)) if any([isinstance(i, result.Error) for i in r]): errors = [i for i in r if isinstance(i, result.Error)] for i in errors: logger.warning("{} error with code[{}],msg: {}".format( str(sql.raw), i.error_code, i.message)) ending_sql = "ROLLBACK" return_v = errors[0] c = [] for i in node: c.append(self._execute_ending_sql(ending_sql, i)) # 如果在 rollback 的时候异常,抛错给 app,即使数据库没有 rollback, # 也会在超时机制下回滚。 backend 遇到异常会关闭 session 触发 mysql # 的超时 # 如果在 commit 的时候出现异常,返回错误给客户端,客户端可以重试, # 成功就走正常流程,或者选择回滚,回滚就走事务回滚流程 await asyncio.gather(*c) if ending_sql == "ROLLBACK": return return_v # type: ignore return result.OK(1, 0, 2, 0, "", False)
async def execute_dml(self, sql: DML) -> result.Result: if not sql.table: return await self.execute_other(sql) table = self.db.get_table(str(sql.table)) if not table.is_allow_write_sql(sql): return result.Error( 1034, "current zone dont allowed execute this sql{}".format( str(sql.raw))) if isinstance(sql, Select): return await self.execute_select(sql) elif isinstance(sql, Insert): return await self.execute_insert(sql) elif isinstance(sql, Update): return await self.execute_update(sql) elif isinstance(sql, Delete): return await self.execute_delete(sql) else: return await self.execute_other(sql)