Files
opsxdb/server/internal/db/search.go
T

132 lines
4.2 KiB
Go

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()
}