mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-12 01:24:12 +08:00
- SQL、CSV、JSON 与 XLSX 改为资源受限的流式解析,并支持压缩、编码和方言预检 - 增加遇错停止/继续策略、持久任务、错误行导出、源文件身份与安全取消 - 为数据库驱动补齐上下文事务和写入结果未知语义,避免失败重放与脏会话复用
104 lines
2.8 KiB
Go
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
|
|
}
|