Files
panel/pkg/db/sqlite.go
T
耗子andClaude Opus 5 e0588a1468 refactor: 把 context 打通到数据库、对象存储与容器 API
上一轮打通 shell 路径时留下三处接口本身不带 ctx,导致底下的
context.Background() 无法消除。这三处 contextcheck 都不报(接口方法没有
ctx 形参,静态分析认为合理),属于 lint 发现不了的死角:

- pkg/db.Operator 的 17 个方法加 ctx,MySQL/Postgres/ClickHouse/SQLite/
  MongoDB/Elasticsearch 实现同步改。其中 Elasticsearch 的 resty 请求原先
  根本没挂 ctx,不只是传了 Background
- pkg/storage.Storage 的 6 个方法加 ctx,local/s3/sftp/webdav 四个实现与
  s3sdk 子包同步改;sftp 连接改用 net.Dialer.DialContext,连接阶段可取消
- internal/data/container*.go 的 moby client 调用,Container/Image/Network/
  Volume 四个 Repo 共 26 个方法加 ctx

非测试代码的 context.Background() 从 71 处降到 14 处,剩下的是应用根 ctx、
有意的独立超时,以及几个瞬时本机操作的叶子函数。双平台 lint 保持 0 issues。

新增 23 处 context.WithoutCancel,其中两处的理由值得记:

备份上传断开取消链,是因为 ClearStorageExpired 按 mtime 保留最新 N 份,
上传被取消留下的截断文件会被当成最新的有效备份,把真正完好的旧备份挤出
保留窗口——静默的数据丢失。而 Put 失败路径没有任何远端清理逻辑,本地侧
反而有(任务队列独享 TMPDIR + defer RemoveAll),两边不对称。

Postgres.UserDrop 是 REASSIGN OWNED → DROP OWNED → DROP USER 三步,中途
取消会留下丢了所有权却没被删掉的用户;MySQL 权限变更后的 FLUSH 被跳过则
新授权根本不生效。

ImagePull 经核实不需要断开:moby 的 LayerDownloadManager 把未完成的层写在
临时文件里并在取消时删除,层只有完整校验后才注册进 layer store,取消不会
留下悬空层。

顺带删掉 8 处 //nolint:contextcheck,它们当初就是因为 Operator 接口没有
ctx 才挂上的。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 01:17:02 +08:00

86 lines
2.0 KiB
Go

package db
import (
"context"
"database/sql"
"fmt"
_ "modernc.org/sqlite"
)
// SQLite 通过 database/sql 操作 SQLite 文件
type SQLite struct {
db *sql.DB
path string
}
// NewSQLite 打开 SQLite 数据库文件
func NewSQLite(ctx context.Context, path string) (*SQLite, error) {
db, err := sql.Open("sqlite", path)
if err != nil {
return nil, fmt.Errorf("open sqlite failed: %w", err)
}
if err = db.PingContext(ctx); err != nil {
return nil, fmt.Errorf("connect to sqlite failed: %w", err)
}
return &SQLite{db: db, path: path}, nil
}
func (r *SQLite) Close() {
_ = r.db.Close()
}
func (r *SQLite) Ping(ctx context.Context) error {
return r.db.PingContext(ctx)
}
// Tables 获取所有表
func (r *SQLite) Tables(ctx context.Context) ([]string, error) {
rows, err := r.db.QueryContext(ctx, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name")
if err != nil {
return nil, err
}
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
var tables []string
for rows.Next() {
var name string
if err = rows.Scan(&name); err != nil {
continue
}
tables = append(tables, name)
}
return tables, rows.Err()
}
// SQLiteColumn 表列信息
type SQLiteColumn struct {
CID int `json:"cid"`
Name string `json:"name"`
Type string `json:"type"`
NotNull bool `json:"not_null"`
Default string `json:"default"`
PrimaryKey bool `json:"primary_key"`
}
// TableInfo 获取表结构
func (r *SQLite) TableInfo(ctx context.Context, name string) ([]SQLiteColumn, error) {
rows, err := r.db.QueryContext(ctx, fmt.Sprintf("PRAGMA table_info('%s')", name))
if err != nil {
return nil, err
}
defer func(rows *sql.Rows) { _ = rows.Close() }(rows)
var columns []SQLiteColumn
for rows.Next() {
var col SQLiteColumn
var dflt sql.NullString
if err = rows.Scan(&col.CID, &col.Name, &col.Type, &col.NotNull, &dflt, &col.PrimaryKey); err != nil {
continue
}
col.Default = dflt.String
columns = append(columns, col)
}
return columns, rows.Err()
}