mirror of
https://github.com/tnb-labs/panel.git
synced 2026-10-13 16:58:05 +08:00
上一轮打通 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>
86 lines
2.0 KiB
Go
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()
|
|
}
|