package db import ( "context" "database/sql" "strings" ) // ObjectHit 对象搜索结果条目。 type ObjectHit struct { Schema string `json:"schema"` Name string `json:"name"` Type string `json:"type"` // table | view } // SearchObjects 按关键词模糊搜索表/视图,最多返回 50 条。redis 返回空。 func SearchObjects(ctx context.Context, c ConnReq) ([]ObjectHit, error) { ctx = reqCtx(ctx) if isRedis(c.Driver) { return []ObjectHit{}, nil } var out []ObjectHit err := withDB(c, func(db *sql.DB) error { var err error switch { case isMySQLFamily(c.Driver): out, err = searchObjectsMySQL(ctx, db, c.Database, c.Keyword) case isPostgresFamily(c.Driver): out, err = searchObjectsPostgres(ctx, db, c.Keyword) case isMSSQL(c.Driver): out, err = searchObjectsMSSQL(ctx, db, c.Keyword) case driverKey(c.Driver) == "oracle": out, err = searchObjectsOracle(ctx, db, c.Database, c.User, c.Keyword) case driverKey(c.Driver) == "sqlite": out, err = searchObjectsSQLite(ctx, db, c.Keyword) default: err = unsupportedDriverErr(c) } return err }) if err != nil { return nil, err } if out == nil { out = []ObjectHit{} } return out, nil } func likePattern(keyword string) string { return "%" + keyword + "%" } // hitType information_schema 的 table_type 含 VIEW 即为视图。 func hitType(tableType string) string { if strings.Contains(strings.ToUpper(tableType), "VIEW") { return "view" } return "table" } func searchObjectsMySQL(ctx context.Context, dbh *sql.DB, database, keyword string) ([]ObjectHit, error) { // database 为空时用当前库 DATABASE() q := `SELECT table_schema, table_name, table_type FROM information_schema.TABLES WHERE table_schema = DATABASE() AND table_name LIKE ? LIMIT 50` args := []interface{}{likePattern(keyword)} if strings.TrimSpace(database) != "" { q = `SELECT table_schema, table_name, table_type FROM information_schema.TABLES WHERE table_schema = ? AND table_name LIKE ? LIMIT 50` args = []interface{}{database, likePattern(keyword)} } return scanObjectHits(ctx, dbh, q, args, "mysql") } func searchObjectsPostgres(ctx context.Context, dbh *sql.DB, keyword string) ([]ObjectHit, error) { return scanObjectHits(ctx, dbh, `SELECT table_schema, table_name, table_type FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog','information_schema') AND table_name LIKE $1 LIMIT 50`, []interface{}{likePattern(keyword)}, "pg") } func searchObjectsMSSQL(ctx context.Context, dbh *sql.DB, keyword string) ([]ObjectHit, error) { // MSSQL 无 LIMIT,用 TOP 50 return scanObjectHits(ctx, dbh, `SELECT TOP 50 TABLE_SCHEMA, TABLE_NAME, TABLE_TYPE FROM information_schema.TABLES WHERE TABLE_NAME LIKE @p1`, []interface{}{likePattern(keyword)}, "mssql") } func searchObjectsOracle(ctx context.Context, dbh *sql.DB, database, user, keyword string) ([]ObjectHit, error) { owner := strings.ToUpper(strings.TrimSpace(database)) if owner == "" { owner = strings.ToUpper(strings.TrimSpace(user)) } return scanObjectHits(ctx, dbh, `SELECT owner, table_name, 'table' FROM all_tables WHERE owner = :1 AND table_name LIKE :2 UNION ALL SELECT owner, view_name, 'view' FROM all_views WHERE owner = :3 AND view_name LIKE :4 FETCH FIRST 50 ROWS ONLY`, []interface{}{owner, likePattern(keyword), owner, likePattern(keyword)}, "oracle") } func searchObjectsSQLite(ctx context.Context, dbh *sql.DB, keyword string) ([]ObjectHit, error) { return scanObjectHits(ctx, dbh, `SELECT '', name, type FROM sqlite_master WHERE name LIKE ? AND type IN ('table','view') LIMIT 50`, []interface{}{likePattern(keyword)}, "sqlite") } func scanObjectHits(ctx context.Context, dbh *sql.DB, q string, args []interface{}, dialect string) ([]ObjectHit, error) { rows, err := dbh.QueryContext(ctx, q, args...) if err != nil { return nil, err } defer rows.Close() out := []ObjectHit{} for rows.Next() { var schema, name, typ string if err := rows.Scan(&schema, &name, &typ); err != nil { return nil, err } t := "table" switch dialect { case "sqlite": t = typ // sqlite_master.type 直接是 table/view default: t = hitType(typ) } out = append(out, ObjectHit{Schema: schema, Name: name, Type: t}) } return out, rows.Err() }