You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

postgresql.go 8.9 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package postgresql
  2. import (
  3. "context"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "time"
  8. "github.com/ccfos/nightingale/v6/datasource"
  9. "github.com/ccfos/nightingale/v6/pkg/macros"
  10. "github.com/ccfos/nightingale/v6/dskit/postgres"
  11. "github.com/ccfos/nightingale/v6/dskit/sqlbase"
  12. "github.com/ccfos/nightingale/v6/dskit/types"
  13. "github.com/ccfos/nightingale/v6/models"
  14. "github.com/mitchellh/mapstructure"
  15. "github.com/toolkits/pkg/logger"
  16. )
  17. const (
  18. PostgreSQLType = "pgsql"
  19. )
  20. var (
  21. regx = "(?i)from\\s+([a-zA-Z0-9_]+)\\.([a-zA-Z0-9_]+)\\.([a-zA-Z0-9_]+)"
  22. )
  23. func init() {
  24. datasource.RegisterDatasource(PostgreSQLType, new(PostgreSQL))
  25. }
  26. type PostgreSQL struct {
  27. Shards []*postgres.PostgreSQL `json:"pgsql.shards" mapstructure:"pgsql.shards"`
  28. }
  29. type QueryParam struct {
  30. Ref string `json:"ref" mapstructure:"ref"`
  31. Database string `json:"database" mapstructure:"database"`
  32. Table string `json:"table" mapstructure:"table"`
  33. SQL string `json:"sql" mapstructure:"sql"`
  34. Keys datasource.Keys `json:"keys" mapstructure:"keys"`
  35. From int64 `json:"from" mapstructure:"from"`
  36. To int64 `json:"to" mapstructure:"to"`
  37. }
  38. func (p *PostgreSQL) InitClient() error {
  39. if len(p.Shards) == 0 {
  40. return fmt.Errorf("not found postgresql addr, please check datasource config")
  41. }
  42. for _, shard := range p.Shards {
  43. if db, err := shard.NewConn(context.TODO(), "postgres"); err != nil {
  44. defer sqlbase.CloseDB(db)
  45. return err
  46. }
  47. }
  48. return nil
  49. }
  50. func (p *PostgreSQL) Init(settings map[string]interface{}) (datasource.Datasource, error) {
  51. newest := new(PostgreSQL)
  52. err := mapstructure.Decode(settings, newest)
  53. return newest, err
  54. }
  55. func (p *PostgreSQL) Validate(ctx context.Context) error {
  56. if len(p.Shards) == 0 || len(strings.TrimSpace(p.Shards[0].Addr)) == 0 {
  57. return fmt.Errorf("postgresql addr is invalid, please check datasource setting")
  58. }
  59. if len(strings.TrimSpace(p.Shards[0].User)) == 0 {
  60. return fmt.Errorf("postgresql user is invalid, please check datasource setting")
  61. }
  62. return nil
  63. }
  64. // Equal compares whether two objects are the same, used for caching
  65. func (p *PostgreSQL) Equal(d datasource.Datasource) bool {
  66. newest, ok := d.(*PostgreSQL)
  67. if !ok {
  68. logger.Errorf("unexpected plugin type, expected is postgresql")
  69. return false
  70. }
  71. if len(p.Shards) == 0 || len(newest.Shards) == 0 {
  72. return false
  73. }
  74. oldShard := p.Shards[0]
  75. newShard := newest.Shards[0]
  76. if oldShard.Addr != newShard.Addr {
  77. return false
  78. }
  79. if oldShard.User != newShard.User {
  80. return false
  81. }
  82. if oldShard.Password != newShard.Password {
  83. return false
  84. }
  85. if oldShard.MaxQueryRows != newShard.MaxQueryRows {
  86. return false
  87. }
  88. if oldShard.Timeout != newShard.Timeout {
  89. return false
  90. }
  91. if oldShard.MaxIdleConns != newShard.MaxIdleConns {
  92. return false
  93. }
  94. if oldShard.MaxOpenConns != newShard.MaxOpenConns {
  95. return false
  96. }
  97. if oldShard.ConnMaxLifetime != newShard.ConnMaxLifetime {
  98. return false
  99. }
  100. return true
  101. }
  102. func (p *PostgreSQL) ShowDatabases(ctx context.Context) ([]string, error) {
  103. return p.Shards[0].ShowDatabases(ctx, "")
  104. }
  105. func (p *PostgreSQL) ShowTables(ctx context.Context, database string) ([]string, error) {
  106. p.Shards[0].DB = database
  107. rets, err := p.Shards[0].ShowTables(ctx, "")
  108. if err != nil {
  109. return nil, err
  110. }
  111. tables := make([]string, 0, len(rets))
  112. for scheme, tabs := range rets {
  113. for _, tab := range tabs {
  114. tables = append(tables, scheme+"."+tab)
  115. }
  116. }
  117. return tables, nil
  118. }
  119. func (p *PostgreSQL) MakeLogQuery(ctx context.Context, query interface{}, eventTags []string, start, end int64) (interface{}, error) {
  120. return nil, nil
  121. }
  122. func (p *PostgreSQL) MakeTSQuery(ctx context.Context, query interface{}, eventTags []string, start, end int64) (interface{}, error) {
  123. return nil, nil
  124. }
  125. func (p *PostgreSQL) QueryMapData(ctx context.Context, query interface{}) ([]map[string]string, error) {
  126. return nil, nil
  127. }
  128. func (p *PostgreSQL) QueryData(ctx context.Context, query interface{}) ([]models.DataResp, error) {
  129. postgresqlQueryParam := new(QueryParam)
  130. if err := mapstructure.Decode(query, postgresqlQueryParam); err != nil {
  131. return nil, err
  132. }
  133. if strings.Contains(postgresqlQueryParam.SQL, "$__") {
  134. var err error
  135. postgresqlQueryParam.SQL, err = macros.Macro(postgresqlQueryParam.SQL, postgresqlQueryParam.From, postgresqlQueryParam.To)
  136. if err != nil {
  137. return nil, err
  138. }
  139. }
  140. if postgresqlQueryParam.Database != "" {
  141. p.Shards[0].DB = postgresqlQueryParam.Database
  142. } else {
  143. db, err := parseDBName(postgresqlQueryParam.SQL)
  144. if err != nil {
  145. return nil, err
  146. }
  147. p.Shards[0].DB = db
  148. }
  149. timeout := p.Shards[0].Timeout
  150. if timeout == 0 {
  151. timeout = 60
  152. }
  153. timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
  154. defer cancel()
  155. items, err := p.Shards[0].QueryTimeseries(timeoutCtx, &sqlbase.QueryParam{
  156. Sql: postgresqlQueryParam.SQL,
  157. Keys: types.Keys{
  158. ValueKey: postgresqlQueryParam.Keys.ValueKey,
  159. LabelKey: postgresqlQueryParam.Keys.LabelKey,
  160. TimeKey: postgresqlQueryParam.Keys.TimeKey,
  161. },
  162. })
  163. if err != nil {
  164. logger.Warningf("query:%+v get data err:%v", postgresqlQueryParam, err)
  165. return []models.DataResp{}, err
  166. }
  167. data := make([]models.DataResp, 0)
  168. for i := range items {
  169. data = append(data, models.DataResp{
  170. Ref: postgresqlQueryParam.Ref,
  171. Metric: items[i].Metric,
  172. Values: items[i].Values,
  173. })
  174. }
  175. // parse resp to time series data
  176. logger.Infof("req:%+v keys:%+v \n data:%v", postgresqlQueryParam, postgresqlQueryParam.Keys, data)
  177. return data, nil
  178. }
  179. func (p *PostgreSQL) QueryLog(ctx context.Context, query interface{}) ([]interface{}, int64, error) {
  180. postgresqlQueryParam := new(QueryParam)
  181. if err := mapstructure.Decode(query, postgresqlQueryParam); err != nil {
  182. return nil, 0, err
  183. }
  184. if postgresqlQueryParam.Database != "" {
  185. p.Shards[0].DB = postgresqlQueryParam.Database
  186. } else {
  187. db, err := parseDBName(postgresqlQueryParam.SQL)
  188. if err != nil {
  189. return nil, 0, err
  190. }
  191. p.Shards[0].DB = db
  192. }
  193. if strings.Contains(postgresqlQueryParam.SQL, "$__") {
  194. var err error
  195. postgresqlQueryParam.SQL, err = macros.Macro(postgresqlQueryParam.SQL, postgresqlQueryParam.From, postgresqlQueryParam.To)
  196. if err != nil {
  197. return nil, 0, err
  198. }
  199. }
  200. timeout := p.Shards[0].Timeout
  201. if timeout == 0 {
  202. timeout = 60
  203. }
  204. timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
  205. defer cancel()
  206. items, err := p.Shards[0].Query(timeoutCtx, &sqlbase.QueryParam{
  207. Sql: postgresqlQueryParam.SQL,
  208. })
  209. if err != nil {
  210. logger.Warningf("query:%+v get data err:%v", postgresqlQueryParam, err)
  211. return []interface{}{}, 0, err
  212. }
  213. logs := make([]interface{}, 0)
  214. for i := range items {
  215. logs = append(logs, items[i])
  216. }
  217. return logs, 0, nil
  218. }
  219. func (p *PostgreSQL) DescribeTable(ctx context.Context, query interface{}) ([]*types.ColumnProperty, error) {
  220. postgresqlQueryParam := new(QueryParam)
  221. if err := mapstructure.Decode(query, postgresqlQueryParam); err != nil {
  222. return nil, err
  223. }
  224. p.Shards[0].DB = postgresqlQueryParam.Database
  225. pairs := strings.Split(postgresqlQueryParam.Table, ".") // format: scheme.table_name
  226. scheme := ""
  227. table := postgresqlQueryParam.Table
  228. if len(pairs) == 2 {
  229. scheme = pairs[0]
  230. table = pairs[1]
  231. }
  232. return p.Shards[0].DescTable(ctx, scheme, table)
  233. }
  234. func parseDBName(sql string) (db string, err error) {
  235. re := regexp.MustCompile(regx)
  236. matches := re.FindStringSubmatch(sql)
  237. if len(matches) != 4 {
  238. return "", fmt.Errorf("no valid table name in format database.schema.table found")
  239. }
  240. return matches[1], nil
  241. }
  242. func extractColumns(sql string) ([]string, error) {
  243. // 将 SQL 转换为小写以简化匹配
  244. sql = strings.ToLower(sql)
  245. // 匹配 SELECT 和 FROM 之间的内容
  246. re := regexp.MustCompile(`select\s+(.*?)\s+from`)
  247. matches := re.FindStringSubmatch(sql)
  248. if len(matches) < 2 {
  249. return nil, fmt.Errorf("no columns found or invalid SQL syntax")
  250. }
  251. // 提取列部分
  252. columnsString := matches[1]
  253. // 分割列
  254. columns := splitColumns(columnsString)
  255. // 清理每个列名
  256. for i, col := range columns {
  257. columns[i] = strings.TrimSpace(col)
  258. }
  259. return columns, nil
  260. }
  261. func splitColumns(columnsString string) []string {
  262. var columns []string
  263. var currentColumn strings.Builder
  264. parenthesesCount := 0
  265. inQuotes := false
  266. for _, char := range columnsString {
  267. switch char {
  268. case '(':
  269. parenthesesCount++
  270. currentColumn.WriteRune(char)
  271. case ')':
  272. parenthesesCount--
  273. currentColumn.WriteRune(char)
  274. case '\'', '"':
  275. inQuotes = !inQuotes
  276. currentColumn.WriteRune(char)
  277. case ',':
  278. if parenthesesCount == 0 && !inQuotes {
  279. columns = append(columns, currentColumn.String())
  280. currentColumn.Reset()
  281. } else {
  282. currentColumn.WriteRune(char)
  283. }
  284. default:
  285. currentColumn.WriteRune(char)
  286. }
  287. }
  288. if currentColumn.Len() > 0 {
  289. columns = append(columns, currentColumn.String())
  290. }
  291. return columns
  292. }