def rollback_to_savepoint(self, name: str) -> TransactionState: if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') for sp in reversed(self._savepoints.values()): if sp.name == name: self._current = sp return sp raise errors.TransactionError(f'there is no {name!r} savepoint')
def release_savepoint(self, name: str): if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') for sp in reversed(self._savepoints.values()): if sp.name == name: sp_id = sp.id break else: raise errors.TransactionError(f'there is no {name!r} savepoint') self._savepoints.pop(sp_id)
async def try_compile_rollback(self, dbver: int, eql: bytes): statements = edgeql.parse_block(eql.decode()) stmt = statements[0] unit = None if isinstance(stmt, qlast.RollbackTransaction): sql = b'ROLLBACK;' unit = dbstate.QueryUnit(dbver=dbver, status=b'ROLLBACK', sql=(sql, ), tx_rollback=True, cacheable=False) elif isinstance(stmt, qlast.RollbackToSavepoint): sql = f'ROLLBACK TO {pg_common.quote_ident(stmt.name)};'.encode() unit = dbstate.QueryUnit(dbver=dbver, status=b'ROLLBACK TO SAVEPOINT', sql=(sql, ), tx_savepoint_rollback=True, cacheable=False) if unit is not None: return unit, len(statements) - 1 raise errors.TransactionError( 'expected a ROLLBACK or ROLLBACK TO SAVEPOINT command' ) # pragma: no cover
def declare_savepoint(self, name: str): if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') sp_id = time.monotonic_ns() # Save the savepoint state so that we can rollback to it. self._stack.append( TransactionState(id=sp_id, name=name, schema=self.get_schema(), modaliases=self.get_modaliases(), config=self.get_session_config())) # The top of the stack is the "current" state. self._stack.append( TransactionState(id=sp_id, name=None, schema=self.get_schema(), modaliases=self.get_modaliases(), config=self.get_session_config())) copy = self.copy() self._constate._savepoints_log[sp_id] = copy return sp_id
def release_savepoint(self, name: str): if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') new_stack = [] released = False for st in reversed(self._stack): if not released and st.name == name: released = True continue else: new_stack.append(st) if not released: raise errors.TransactionError(f'there is no {name!r} savepoint') else: self._stack = new_stack[::-1]
def rollback_to_savepoint(self, name: str): if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') new_stack = self._stack.copy() while new_stack: top_new_state = new_stack[-1] if top_new_state.name == name: self._stack = new_stack # Add a nameless copy of the savepoint's state -- new # "working" state. self._stack.append(self._stack[-1]._replace(name=None)) return self._stack[-1] else: new_stack.pop() raise errors.TransactionError(f'there is no {name!r} savepoint')
def rollback_to_savepoint(self, name: str) -> TransactionState: if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') sp_ids_to_erase = [] for sp in reversed(self._savepoints.values()): if sp.name == name: self._current = sp break sp_ids_to_erase.append(sp.id) else: raise errors.TransactionError(f'there is no {name!r} savepoint') for sp_id in sp_ids_to_erase: self._savepoints.pop(sp_id) return sp
def commit_tx(self): if self._current_tx.is_implicit(): raise errors.TransactionError('cannot commit: not in transaction') latest_state = self._current_tx._stack[-1] self._init_current_tx(latest_state.schema, latest_state.modaliases, latest_state.config) return latest_state
def declare_savepoint(self, name: str): if self.is_implicit(): raise errors.TransactionError( 'savepoints can only be used in transaction blocks') sp_id = self._constate._new_txid() sp_state = self._current._replace(id=sp_id, name=name) self._savepoints[sp_id] = sp_state self._constate._savepoints_log[sp_id] = sp_state return sp_id
def commit_tx(self): if self._current_tx.is_implicit(): raise errors.TransactionError('cannot commit: not in transaction') latest_state = self._current_tx._current self._init_current_tx( user_schema=latest_state.user_schema, global_schema=latest_state.global_schema, modaliases=latest_state.modaliases, session_config=latest_state.session_config, database_config=latest_state.database_config, system_config=latest_state.system_config, cached_reflection=latest_state.cached_reflection, ) return latest_state
def start_tx(self): if self._current_tx.is_implicit(): self._current_tx.make_explicit() else: raise errors.TransactionError('already in transaction')
def static_interpret_backend_error(fields): err_details = get_error_details(fields) # handle some generic errors if possible err = get_generic_exception_from_err_details(err_details) if err is not None: return err if err_details.code == PGErrorCode.NotNullViolationError: if err_details.table_name or err_details.column_name: return SchemaRequired else: return errors.InternalServerError(err_details.message) elif err_details.code in constraint_errors: source = pointer = None for errtype, ere in constraint_res.items(): m = ere.match(err_details.message) if m: error_type = errtype break else: return errors.InternalServerError(err_details.message) if error_type == 'cardinality': return errors.CardinalityViolationError('cardinality violation', source=source, pointer=pointer) elif error_type == 'link_target': if err_details.detail_json: srcname = err_details.detail_json.get('source') ptrname = err_details.detail_json.get('pointer') target = err_details.detail_json.get('target') expected = err_details.detail_json.get('expected') if srcname and ptrname: srcname = sn.QualName.from_string(srcname) ptrname = sn.QualName.from_string(ptrname) lname = '{}.{}'.format(srcname, ptrname.name) else: lname = '' msg = (f'invalid target for link {lname!r}: {target!r} ' f'(expecting {expected!r})') else: msg = 'invalid target for link' return errors.UnknownLinkError(msg) elif error_type == 'link_target_del': return errors.ConstraintViolationError(err_details.message, details=err_details.detail) elif error_type == 'constraint': if err_details.constraint_name is None: return errors.InternalServerError(err_details.message) constraint_id, _, _ = err_details.constraint_name.rpartition(';') try: constraint_id = uuidgen.UUID(constraint_id) except ValueError: return errors.InternalServerError(err_details.message) return SchemaRequired elif error_type == 'newconstraint': # We can reconstruct what went wrong from the schema_name, # table_name, and column_name. But we don't expect # constraint_name to be present (because the constraint is # not yet present in the schema?). if (err_details.schema_name and err_details.table_name and err_details.column_name): return SchemaRequired else: return errors.InternalServerError(err_details.message) elif error_type == 'scalar': return SchemaRequired elif error_type == 'id': return errors.ConstraintViolationError( 'unique link constraint violation') elif err_details.code in SCHEMA_CODES: if err_details.code == PGErrorCode.InvalidDatetimeFormatError: hint = None if err_details.detail_json: hint = err_details.detail_json.get('hint') if err_details.message.startswith('missing required time zone'): return errors.InvalidValueError(err_details.message, hint=hint) elif err_details.message.startswith('unexpected time zone'): return errors.InvalidValueError(err_details.message, hint=hint) return SchemaRequired elif err_details.code == PGErrorCode.InvalidParameterValue: return errors.InvalidValueError( err_details.message, details=err_details.detail if err_details.detail else None) elif err_details.code == PGErrorCode.WrongObjectType: return errors.InvalidValueError( err_details.message, details=err_details.detail if err_details.detail else None) elif err_details.code == PGErrorCode.DivisionByZeroError: return errors.DivisionByZeroError(err_details.message) elif err_details.code == PGErrorCode.ReadOnlySQLTransactionError: return errors.TransactionError( 'cannot execute query in a read-only transaction') elif err_details.code == PGErrorCode.TransactionSerializationFailure: return errors.TransactionSerializationError(err_details.message) elif err_details.code == PGErrorCode.TransactionDeadlockDetected: return errors.TransactionDeadlockError(err_details.message) elif err_details.code == PGErrorCode.InvalidCatalogNameError: return errors.AuthenticationError(err_details.message) elif err_details.code == PGErrorCode.ObjectInUse: return errors.ExecutionError(err_details.message) return errors.InternalServerError(err_details.message)
async def commit_transaction(self): if not self.transactions: raise errors.TransactionError( 'there is no transaction in progress') transaction, _ = self.transactions.pop() await transaction.commit()
def make_explicit(self): if self._implicit: self._implicit = False else: raise errors.TransactionError('already in explicit transaction')
async def rollback_transaction(self): if not self.transactions: raise errors.TransactionError( 'there is no transaction in progress') transaction, self.schema = self.transactions.pop() await transaction.rollback()
def static_interpret_backend_error(fields): err_details = get_error_details(fields) # handle some generic errors if possible err = get_generic_exception_from_err_details(err_details) if err is not None: return err if err_details.code == pgerrors.ERROR_NOT_NULL_VIOLATION: if err_details.table_name or err_details.column_name: return SchemaRequired else: return errors.InternalServerError(err_details.message) elif err_details.code in constraint_errors: source = pointer = None for errtype, ere in constraint_res.items(): m = ere.match(err_details.message) if m: error_type = errtype break else: return errors.InternalServerError(err_details.message) if error_type == 'cardinality': return errors.CardinalityViolationError( 'cardinality violation', source=source, pointer=pointer) elif error_type == 'link_target': if err_details.detail_json: srcname = err_details.detail_json.get('source') ptrname = err_details.detail_json.get('pointer') target = err_details.detail_json.get('target') expected = err_details.detail_json.get('expected') if srcname and ptrname: srcname = sn.QualName.from_string(srcname) ptrname = sn.QualName.from_string(ptrname) lname = '{}.{}'.format(srcname, ptrname.name) else: lname = '' msg = ( f'invalid target for link {lname!r}: {target!r} ' f'(expecting {expected!r})' ) else: msg = 'invalid target for link' return errors.UnknownLinkError(msg) elif error_type == 'link_target_del': return errors.ConstraintViolationError( err_details.message, details=err_details.detail) elif error_type == 'constraint': if err_details.constraint_name is None: return errors.InternalServerError(err_details.message) constraint_id, _, _ = err_details.constraint_name.rpartition(';') try: constraint_id = uuidgen.UUID(constraint_id) except ValueError: return errors.InternalServerError(err_details.message) return SchemaRequired elif error_type == 'newconstraint': # We can reconstruct what went wrong from the schema_name, # table_name, and column_name. But we don't expect # constraint_name to be present (because the constraint is # not yet present in the schema?). if (err_details.schema_name and err_details.table_name and err_details.column_name): return SchemaRequired else: return errors.InternalServerError(err_details.message) elif error_type == 'scalar': return SchemaRequired elif error_type == 'id': return errors.ConstraintViolationError( 'unique link constraint violation') elif err_details.code in SCHEMA_CODES: if err_details.code == pgerrors.ERROR_INVALID_DATETIME_FORMAT: hint = None if err_details.detail_json: hint = err_details.detail_json.get('hint') if err_details.message.startswith('missing required time zone'): return errors.InvalidValueError(err_details.message, hint=hint) elif err_details.message.startswith('unexpected time zone'): return errors.InvalidValueError(err_details.message, hint=hint) return SchemaRequired elif err_details.code == pgerrors.ERROR_INVALID_PARAMETER_VALUE: return errors.InvalidValueError( err_details.message, details=err_details.detail if err_details.detail else None ) elif err_details.code == pgerrors.ERROR_WRONG_OBJECT_TYPE: if err_details.column_name: return SchemaRequired return errors.InvalidValueError( err_details.message, details=err_details.detail if err_details.detail else None ) elif err_details.code == pgerrors.ERROR_DIVISION_BY_ZERO: return errors.DivisionByZeroError(err_details.message) elif err_details.code == pgerrors.ERROR_INTERVAL_FIELD_OVERFLOW: return errors.NumericOutOfRangeError(err_details.message) elif err_details.code == pgerrors.ERROR_READ_ONLY_SQL_TRANSACTION: return errors.TransactionError( 'cannot execute query in a read-only transaction') elif err_details.code == pgerrors.ERROR_SERIALIZATION_FAILURE: return errors.TransactionSerializationError(err_details.message) elif err_details.code == pgerrors.ERROR_DEADLOCK_DETECTED: return errors.TransactionDeadlockError(err_details.message) elif err_details.code == pgerrors.ERROR_INVALID_CATALOG_NAME: return errors.UnknownDatabaseError(err_details.message) elif err_details.code == pgerrors.ERROR_OBJECT_IN_USE: return errors.ExecutionError(err_details.message) elif err_details.code == pgerrors.ERROR_DUPLICATE_DATABASE: return errors.DuplicateDatabaseDefinitionError(err_details.message) elif ( err_details.code == pgerrors.ERROR_CARDINALITY_VIOLATION and err_details.constraint_name == 'std::assert_single' ): return errors.CardinalityViolationError(err_details.message) return errors.InternalServerError(err_details.message)
def interpret_backend_error(schema, fields): # See https://www.postgresql.org/docs/current/protocol-error-fields.html # for the full list of PostgreSQL error message fields. message = fields.get('M') detail = fields.get('D') detail_json = None if detail and detail.startswith('{'): detail_json = json.loads(detail) detail = None if detail_json: errcode = detail_json.get('code') if errcode: try: errcls = type( errors.EdgeDBError).get_error_class_from_code(errcode) except LookupError: pass else: err = errcls(message) err.set_linecol(detail_json.get('line', -1), detail_json.get('column', -1)) return err try: code = PGError(fields['C']) except ValueError: return errors.InternalServerError(message) schema_name = fields.get('s') table_name = fields.get('t') column_name = fields.get('c') constraint_name = fields.get('n') if code == PGError.NotNullViolationError: source_name = pointer_name = None if schema_name and table_name: tabname = (schema_name, table_name) source = common.get_object_from_backend_name( schema, s_objtypes.ObjectType, tabname) source_name = source.get_displayname(schema) if column_name: pointer_name = column_name if pointer_name is not None: pname = f'{source_name}.{pointer_name}' return errors.MissingRequiredError( f'missing value for required property {pname}') else: return errors.InternalServerError(message) elif code in constraint_errors: source = pointer = None for errtype, ere in constraint_res.items(): m = ere.match(message) if m: error_type = errtype break else: return errors.InternalServerError(message) if error_type == 'cardinality': return errors.CardinalityViolationError('cardinality violation', source=source, pointer=pointer) elif error_type == 'link_target': if detail_json: srcname = detail_json.get('source') ptrname = detail_json.get('pointer') target = detail_json.get('target') expected = detail_json.get('expected') if srcname and ptrname: srcname = sn.Name(srcname) ptrname = sn.Name(ptrname) lname = '{}.{}'.format(srcname, ptrname.name) else: lname = '' msg = (f'invalid target for link {lname!r}: {target!r} ' f'(expecting {expected!r})') else: msg = 'invalid target for link' return errors.UnknownLinkError(msg) elif error_type == 'link_target_del': return errors.ConstraintViolationError(message, details=detail) elif error_type == 'constraint': if constraint_name is None: return errors.InternalServerError(message) constraint_id, _, _ = constraint_name.rpartition(';') try: constraint_id = uuid.UUID(constraint_id) except ValueError: return errors.InternalServerError(message) constraint = schema.get_by_id(constraint_id) return errors.ConstraintViolationError( constraint.format_error_message(schema)) elif error_type == 'id': return errors.ConstraintViolationError( 'unique link constraint violation') elif code == PGError.InvalidParameterValue: return errors.InvalidValueError(message, details=detail if detail else None) elif code == PGError.InvalidTextRepresentation: return errors.InvalidValueError(translate_pgtype(schema, message)) elif code == PGError.NumericValueOutOfRange: return errors.NumericOutOfRangeError(translate_pgtype(schema, message)) elif code == PGError.DivisionByZeroError: return errors.DivisionByZeroError(message) elif code == PGError.ReadOnlySQLTransactionError: return errors.TransactionError( 'cannot execute query in a read-only transaction') elif code in {PGError.InvalidDatetimeFormatError, PGError.DatetimeError}: return errors.InvalidValueError(translate_pgtype(schema, message)) elif code == PGError.TransactionSerializationFailure: return errors.TransactionSerializationError(message) elif code == PGError.TransactionDeadlockDetected: return errors.TransactionDeadlockError(message) return errors.InternalServerError(message)
def static_interpret_backend_error(fields): err_details = get_error_details(fields) # handle some generic errors if possible err = get_generic_exception_from_err_details(err_details) if err is not None: return err if err_details.code == PGErrorCode.NotNullViolationError: if err_details.schema_name and err_details.table_name: return SchemaRequired else: return errors.InternalServerError(err_details.message) elif err_details.code in constraint_errors: source = pointer = None for errtype, ere in constraint_res.items(): m = ere.match(err_details.message) if m: error_type = errtype break else: return errors.InternalServerError(err_details.message) if error_type == 'cardinality': return errors.CardinalityViolationError('cardinality violation', source=source, pointer=pointer) elif error_type == 'link_target': if err_details.detail_json: srcname = err_details.detail_json.get('source') ptrname = err_details.detail_json.get('pointer') target = err_details.detail_json.get('target') expected = err_details.detail_json.get('expected') if srcname and ptrname: srcname = sn.Name(srcname) ptrname = sn.Name(ptrname) lname = '{}.{}'.format(srcname, ptrname.name) else: lname = '' msg = (f'invalid target for link {lname!r}: {target!r} ' f'(expecting {expected!r})') else: msg = 'invalid target for link' return errors.UnknownLinkError(msg) elif error_type == 'link_target_del': return errors.ConstraintViolationError(err_details.message, details=err_details.detail) elif error_type == 'constraint': if err_details.constraint_name is None: return errors.InternalServerError(err_details.message) constraint_id, _, _ = err_details.constraint_name.rpartition(';') try: constraint_id = uuid.UUID(constraint_id) except ValueError: return errors.InternalServerError(err_details.message) return SchemaRequired elif error_type == 'id': return errors.ConstraintViolationError( 'unique link constraint violation') elif err_details.code in SCHEMA_CODES: return SchemaRequired elif err_details.code == PGErrorCode.InvalidParameterValue: return errors.InvalidValueError( err_details.message, details=err_details.detail if err_details.detail else None) elif err_details.code == PGErrorCode.DivisionByZeroError: return errors.DivisionByZeroError(err_details.message) elif err_details.code == PGErrorCode.ReadOnlySQLTransactionError: return errors.TransactionError( 'cannot execute query in a read-only transaction') elif err_details.code == PGErrorCode.TransactionSerializationFailure: return errors.TransactionSerializationError(err_details.message) elif err_details.code == PGErrorCode.TransactionDeadlockDetected: return errors.TransactionDeadlockError(err_details.message) return errors.InternalServerError(err_details.message)