Skip to content

Commit 3f09c1c

Browse files
authored
Merge pull request #294 from Germiniku/database-datasource
Database datasource
2 parents 12c3c3b + 2946b9d commit 3f09c1c

File tree

29 files changed

+1302
-19
lines changed

29 files changed

+1302
-19
lines changed

query/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ require (
5858
github.com/klauspost/compress v1.16.7 // indirect
5959
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
6060
github.com/leodido/go-urn v1.2.4 // indirect
61+
github.com/lib/pq v1.10.9 // indirect
6162
github.com/mattn/go-colorable v0.1.13 // indirect
6263
github.com/mattn/go-isatty v0.0.19 // indirect
6364
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect

query/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
104104
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
105105
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
106106
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
107+
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
108+
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
107109
github.com/lithammer/shortuuid/v3 v3.0.5 h1:cUCI9JNIWsjVThijRm4K3jInhXZj8+xJxbUGNfm84ms=
108110
github.com/lithammer/shortuuid/v3 v3.0.5/go.mod h1:2QdoCtD4SBzugx2qs3gdR3LXY6McxZYCNEHwDmYvOAE=
109111
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package mysql
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"database/sql/driver"
7+
"fmt"
8+
"reflect"
9+
"sync"
10+
"time"
11+
12+
"github.com/DataObserve/datav/query/pkg/colorlog"
13+
"github.com/DataObserve/datav/query/pkg/models"
14+
"github.com/gin-gonic/gin"
15+
16+
_ "github.com/go-sql-driver/mysql"
17+
)
18+
19+
var datasourceName = "mysql"
20+
21+
type MysqlPlugin struct {
22+
}
23+
24+
var (
25+
conns = make(map[int64]*sql.DB)
26+
connsLock = &sync.Mutex{}
27+
)
28+
29+
func (*MysqlPlugin) Query(c *gin.Context, ds *models.Datasource) models.PluginResult {
30+
query := c.Query("query")
31+
conn, ok := conns[ds.Id]
32+
if !ok {
33+
conn, err := connectToMysql(ds.URL, ds.Data["database"], ds.Data["username"], ds.Data["password"])
34+
if err != nil {
35+
colorlog.RootLogger.Warn("connect to mysql error:", err, "ds_id", ds.Id, "url", ds.URL)
36+
return models.PluginResult{
37+
Status: models.PluginStatusError,
38+
Error: err.Error(),
39+
}
40+
}
41+
connsLock.Lock()
42+
conns[ds.Id] = conn
43+
connsLock.Unlock()
44+
}
45+
rows, err := conn.Query(query)
46+
if err != nil {
47+
colorlog.RootLogger.Info("Error query mysql :", "error", err, "ds_id", ds.Id, "query", query)
48+
return models.PluginResult{
49+
Status: models.PluginStatusError,
50+
Error: err.Error(),
51+
}
52+
}
53+
defer rows.Close()
54+
55+
columns, err := rows.Columns()
56+
if err != nil {
57+
colorlog.RootLogger.Info("Error get rows columns :", "error", err, "ds_id", ds.Id, "query", query)
58+
return models.PluginResult{
59+
Status: models.PluginStatusError,
60+
Error: err.Error(),
61+
}
62+
}
63+
columnTypes, err := rows.ColumnTypes()
64+
if err != nil {
65+
colorlog.RootLogger.Info("Error get rows column types :", "error", err, "ds_id", ds.Id, "query", query)
66+
return models.PluginResult{
67+
Status: models.PluginStatusError,
68+
Error: err.Error(),
69+
}
70+
}
71+
72+
types := make(map[string]string)
73+
data := make([][]interface{}, 0)
74+
for rows.Next() {
75+
v := make([]interface{}, len(columns))
76+
for i := 0; i < len(columns); i++ {
77+
t := columnTypes[i].ScanType()
78+
v[i] = reflect.New(t).Interface()
79+
80+
tp := t.String()
81+
82+
if tp == reflect.TypeOf(sql.NullTime{}).String() {
83+
types[columns[i]] = "time"
84+
}
85+
}
86+
87+
err = rows.Scan(v...)
88+
if err != nil {
89+
colorlog.RootLogger.Info("Error scan mysql :", "error", err, "ds_id", ds.Id)
90+
continue
91+
}
92+
93+
for i, v0 := range v {
94+
v1, ok := v0.(*sql.NullTime)
95+
if ok {
96+
v[i] = v1.Time.Unix()
97+
} else {
98+
v2, ok := v0.(driver.Valuer)
99+
if ok {
100+
v[i], _ = v2.Value()
101+
}
102+
}
103+
}
104+
data = append(data, v)
105+
}
106+
107+
return models.PluginResult{
108+
Status: models.PluginStatusSuccess,
109+
Error: "",
110+
Data: map[string]interface{}{
111+
"columns": columns,
112+
"data": data,
113+
"types": types,
114+
}}
115+
}
116+
117+
func (*MysqlPlugin) TestDatasource(c *gin.Context) models.PluginResult {
118+
return TestMysqlDatasource(c)
119+
}
120+
121+
func init() {
122+
// register datasource
123+
models.RegisterPlugin(datasourceName, &MysqlPlugin{})
124+
}
125+
126+
func TestMysqlDatasource(c *gin.Context) models.PluginResult {
127+
url := c.Query("url")
128+
database := c.Query("database")
129+
username := c.Query("username")
130+
password := c.Query("password")
131+
db, err := connectToMysql(url, database, username, password)
132+
if err != nil {
133+
return models.GenPluginResult(models.PluginStatusError, err.Error(), nil)
134+
}
135+
if err = db.PingContext(context.Background()); err != nil {
136+
return models.GenPluginResult(models.PluginStatusError, err.Error(), nil)
137+
}
138+
return models.GenPluginResult(models.PluginStatusSuccess, "", nil)
139+
}
140+
141+
func connectToMysql(url, database, username, password string) (*sql.DB, error) {
142+
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true", username, password, url, database)
143+
colorlog.RootLogger.Debug("connect to mysql dsn: ", dsn)
144+
145+
db, err := sql.Open("mysql", dsn)
146+
if err != nil {
147+
return nil, err
148+
}
149+
db.SetMaxIdleConns(5)
150+
db.SetMaxOpenConns(5)
151+
db.SetConnMaxLifetime(time.Duration(10) * time.Minute)
152+
db.SetConnMaxIdleTime(time.Duration(10) * time.Minute)
153+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
154+
defer cancel()
155+
if err = db.PingContext(ctx); err != nil {
156+
return nil, err
157+
}
158+
return db, nil
159+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
package builtin
22

3-
import _ "github.com/DataObserve/datav/query/internal/plugins/builtin/datav"
3+
import (
4+
_ "github.com/DataObserve/datav/query/internal/plugins/builtin/mysql"
5+
_ "github.com/DataObserve/datav/query/internal/plugins/builtin/postgresql"
6+
)
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package postgresql
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"database/sql/driver"
7+
"fmt"
8+
"reflect"
9+
"sync"
10+
"time"
11+
12+
"github.com/DataObserve/datav/query/pkg/colorlog"
13+
"github.com/DataObserve/datav/query/pkg/models"
14+
"github.com/gin-gonic/gin"
15+
_ "github.com/lib/pq"
16+
)
17+
18+
var datasourceName = "postgresql"
19+
20+
type PostgreSQLPlugin struct {
21+
}
22+
23+
var (
24+
conns = make(map[int64]*sql.DB)
25+
connsLock = &sync.Mutex{}
26+
)
27+
28+
func (*PostgreSQLPlugin) Query(c *gin.Context, ds *models.Datasource) models.PluginResult {
29+
query := c.Query("query")
30+
conn, ok := conns[ds.Id]
31+
if !ok {
32+
conn, err := connectToPostgreSQL(ds.URL, ds.Data["database"], ds.Data["username"], ds.Data["password"])
33+
if err != nil {
34+
colorlog.RootLogger.Warn("connect to postgresql error:", err, "ds_id", ds.Id, "url", ds.URL)
35+
return models.PluginResult{
36+
Status: models.PluginStatusError,
37+
Error: err.Error(),
38+
}
39+
}
40+
connsLock.Lock()
41+
conns[ds.Id] = conn
42+
connsLock.Unlock()
43+
}
44+
rows, err := conn.Query(query)
45+
if err != nil {
46+
colorlog.RootLogger.Info("Error query postgresql :", "error", err, "ds_id", ds.Id, "query", query)
47+
return models.PluginResult{
48+
Status: models.PluginStatusError,
49+
Error: err.Error(),
50+
}
51+
}
52+
defer rows.Close()
53+
54+
columns, err := rows.Columns()
55+
if err != nil {
56+
colorlog.RootLogger.Info("Error get rows columns :", "error", err, "ds_id", ds.Id, "query", query)
57+
return models.PluginResult{
58+
Status: models.PluginStatusError,
59+
Error: err.Error(),
60+
}
61+
}
62+
columnTypes, err := rows.ColumnTypes()
63+
if err != nil {
64+
colorlog.RootLogger.Info("Error get rows column types :", "error", err, "ds_id", ds.Id, "query", query)
65+
return models.PluginResult{
66+
Status: models.PluginStatusError,
67+
Error: err.Error(),
68+
}
69+
}
70+
71+
types := make(map[string]string)
72+
data := make([][]interface{}, 0)
73+
for rows.Next() {
74+
v := make([]interface{}, len(columns))
75+
for i := 0; i < len(columns); i++ {
76+
t := columnTypes[i].ScanType()
77+
v[i] = reflect.New(t).Interface()
78+
79+
tp := t.String()
80+
if tp == reflect.TypeOf(time.Time{}).String() {
81+
types[columns[i]] = "time"
82+
}
83+
}
84+
85+
err = rows.Scan(v...)
86+
if err != nil {
87+
colorlog.RootLogger.Info("Error scan postgresql :", "error", err, "ds_id", ds.Id)
88+
continue
89+
}
90+
91+
for i, v0 := range v {
92+
v1, ok := v0.(*time.Time)
93+
if ok {
94+
v[i] = v1.Unix()
95+
} else {
96+
v2, ok := v0.(driver.Valuer)
97+
if ok {
98+
v[i], _ = v2.Value()
99+
}
100+
}
101+
}
102+
data = append(data, v)
103+
}
104+
105+
return models.PluginResult{
106+
Status: models.PluginStatusSuccess,
107+
Error: "",
108+
Data: map[string]interface{}{
109+
"columns": columns,
110+
"data": data,
111+
"types": types,
112+
}}
113+
}
114+
115+
func (*PostgreSQLPlugin) TestDatasource(c *gin.Context) models.PluginResult {
116+
url := c.Query("url")
117+
database := c.Query("database")
118+
username := c.Query("username")
119+
password := c.Query("password")
120+
db, err := connectToPostgreSQL(url, database, username, password)
121+
if err != nil {
122+
return models.GenPluginResult(models.PluginStatusError, err.Error(), nil)
123+
}
124+
if err = db.PingContext(context.Background()); err != nil {
125+
return models.GenPluginResult(models.PluginStatusError, err.Error(), nil)
126+
}
127+
return models.GenPluginResult(models.PluginStatusSuccess, "", nil)
128+
}
129+
130+
func init() {
131+
// register datasource
132+
models.RegisterPlugin(datasourceName, &PostgreSQLPlugin{})
133+
}
134+
135+
func connectToPostgreSQL(url, database, username, password string) (*sql.DB, error) {
136+
dsn := fmt.Sprintf("postgres://%s:%s@%s/%s", username, password, url, database)
137+
colorlog.RootLogger.Debug("connect to postgresql dsn: ", dsn)
138+
139+
db, err := sql.Open("postgres", dsn)
140+
if err != nil {
141+
return nil, err
142+
}
143+
db.SetMaxIdleConns(5)
144+
db.SetMaxOpenConns(5)
145+
db.SetConnMaxLifetime(time.Duration(10) * time.Minute)
146+
db.SetConnMaxIdleTime(time.Duration(10) * time.Minute)
147+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
148+
defer cancel()
149+
if err = db.PingContext(ctx); err != nil {
150+
return nil, err
151+
}
152+
return db, nil
153+
}

query/internal/plugins/external/plugins.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ package external
22

33
/* Auto generated by datav plugins building tools */
44

5-
import _ "github.com/DataObserve/datav/query/internal/plugins/external/clickhouse"
5+
import (
6+
_ "github.com/DataObserve/datav/query/internal/plugins/external/clickhouse"
7+
)

query/internal/proxy/datasource.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,13 @@ func ProxyDatasource(c *gin.Context) {
4949
queryPlugin := models.GetPlugin(ds.Type)
5050
if queryPlugin != nil {
5151
result := queryPlugin.Query(c, ds)
52-
c.JSON(http.StatusOK, result)
53-
return
52+
if result.Status == models.PluginStatusSuccess {
53+
c.JSON(http.StatusOK, result)
54+
return
55+
} else {
56+
c.JSON(http.StatusInternalServerError, common.RespError(result.Error))
57+
return
58+
}
5459
}
5560

5661
targetURL := c.Param("path")
@@ -108,5 +113,5 @@ func TestDatasource(c *gin.Context) {
108113
return
109114
}
110115

111-
c.JSON(http.StatusOK, models.GenPluginResult(models.PluginStatusSuccess, "query plugin not exist", nil))
116+
c.JSON(http.StatusOK, models.GenPluginResult(models.PluginStatusError, "query plugin not exist", nil))
112117
}

ui/src/pages/new/Datasource.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ const initDatasource: Datasource = {
2828
name: '',
2929
url: null,
3030
type: DatasourceTypePrometheus,
31-
teamId: globalTeamId
31+
teamId: globalTeamId,
32+
data: {}
3233
}
3334

3435
const NewDatasourcePage = () => {

0 commit comments

Comments
 (0)