Files
MyGoNavi/internal/db/write_outcome.go
Syngnat 390a249105 feat(data-import): 重构大文件导入与安全任务运行时
- SQL、CSV、JSON 与 XLSX 改为资源受限的流式解析,并支持压缩、编码和方言预检

- 增加遇错停止/继续策略、持久任务、错误行导出、源文件身份与安全取消

- 为数据库驱动补齐上下文事务和写入结果未知语义,避免失败重放与脏会话复用
2026-08-08 20:51:23 +08:00

104 lines
2.8 KiB
Go

package db
import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"fmt"
"io"
"net"
)
// WriteOutcomeUnknownError marks a write whose server-side outcome cannot be
// determined from the response. Callers must not retry or continue past it as
// though the row were known to have been rejected.
type WriteOutcomeUnknownError struct {
cause error
}
func (err *WriteOutcomeUnknownError) Error() string {
if err == nil || err.cause == nil {
return "write outcome is unknown"
}
return err.cause.Error()
}
func (err *WriteOutcomeUnknownError) Unwrap() error {
if err == nil {
return nil
}
return err.cause
}
// MarkWriteOutcomeUnknown preserves the original error while attaching the
// no-retry contract used by import and synchronization callers.
func MarkWriteOutcomeUnknown(err error) error {
if err == nil || IsWriteOutcomeUnknown(err) {
return err
}
return &WriteOutcomeUnknownError{cause: err}
}
func IsWriteOutcomeUnknown(err error) bool {
var unknown *WriteOutcomeUnknownError
return errors.As(err, &unknown)
}
// IsAmbiguousWriteResponse reports transport and cancellation failures that
// can occur after an autocommit statement was dispatched but before its server
// response was observed. A database semantic error is deliberately not
// classified as ambiguous because it proves the statement was rejected.
func IsAmbiguousWriteResponse(err error) bool {
if err == nil {
return false
}
if errors.Is(err, context.Canceled) ||
errors.Is(err, context.DeadlineExceeded) ||
errors.Is(err, driver.ErrBadConn) ||
errors.Is(err, io.EOF) ||
errors.Is(err, io.ErrClosedPipe) ||
errors.Is(err, io.ErrUnexpectedEOF) {
return true
}
var networkErr net.Error
return errors.As(err, &networkErr)
}
func markWriteOutcomeUnknownIfAmbiguous(ctx context.Context, err error) error {
if err == nil || IsWriteOutcomeUnknown(err) {
return err
}
if IsAmbiguousWriteResponse(err) || (ctx != nil && ctx.Err() != nil) {
return MarkWriteOutcomeUnknown(err)
}
return err
}
// rollbackUnfinishedWriteTransaction keeps a failed rollback from being
// flattened into the preceding statement error. A rollback failure means the
// transaction's final server-side state cannot safely be inferred.
func rollbackUnfinishedWriteTransaction(tx *sql.Tx, committed bool, resultErr *error) {
if tx == nil || committed {
return
}
rollbackErr := tx.Rollback()
if rollbackErr == nil || errors.Is(rollbackErr, sql.ErrTxDone) {
return
}
rollbackErr = fmt.Errorf("事务回滚失败:%w", rollbackErr)
if resultErr != nil && *resultErr != nil {
rollbackErr = errors.Join(*resultErr, rollbackErr)
}
if resultErr != nil {
*resultErr = MarkWriteOutcomeUnknown(rollbackErr)
}
}
func commitWriteTransaction(tx *sql.Tx) error {
if err := tx.Commit(); err != nil {
return MarkWriteOutcomeUnknown(fmt.Errorf("事务提交失败:%w", err))
}
return nil
}