132 lines
4.2 KiB
Go
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()
|
|
}
|