Local queries (#641)
* Read local queries from pgweb home directory * Refactor local query functionality * Allow picking local query in the query tab * WIP * Disable local query dropdown during execution * Only allow local queries running in a single session mode * Add middleware to enforce local query endpoint availability * Fix query check * Add query store tests * Make query store errors portable * Skip building specific tests on windows
This commit is contained in:
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/sosedoff/pgweb/pkg/command"
|
||||
"github.com/sosedoff/pgweb/pkg/connection"
|
||||
"github.com/sosedoff/pgweb/pkg/metrics"
|
||||
"github.com/sosedoff/pgweb/pkg/queries"
|
||||
"github.com/sosedoff/pgweb/pkg/shared"
|
||||
"github.com/sosedoff/pgweb/static"
|
||||
)
|
||||
@@ -27,6 +28,9 @@ var (
|
||||
|
||||
// DbSessions represents the mapping for client connections
|
||||
DbSessions *SessionManager
|
||||
|
||||
// QueryStore reads the SQL queries stores in the home directory
|
||||
QueryStore *queries.Store
|
||||
)
|
||||
|
||||
// DB returns a database connection from the client context
|
||||
@@ -555,6 +559,7 @@ func GetInfo(c *gin.Context) {
|
||||
"features": gin.H{
|
||||
"session_lock": command.Opts.LockSession,
|
||||
"query_timeout": command.Opts.QueryTimeout,
|
||||
"local_queries": QueryStore != nil,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -606,3 +611,78 @@ func GetFunction(c *gin.Context) {
|
||||
res, err := DB(c).Function(c.Param("id"))
|
||||
serveResult(c, res, err)
|
||||
}
|
||||
|
||||
func GetLocalQueries(c *gin.Context) {
|
||||
connCtx, err := DB(c).GetConnContext()
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
storeQueries, err := QueryStore.ReadAll()
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
queries := []localQuery{}
|
||||
for _, q := range storeQueries {
|
||||
if !q.IsPermitted(connCtx.Host, connCtx.User, connCtx.Database, connCtx.Mode) {
|
||||
continue
|
||||
}
|
||||
|
||||
queries = append(queries, localQuery{
|
||||
ID: q.ID,
|
||||
Title: q.Meta.Title,
|
||||
Description: q.Meta.Description,
|
||||
Query: cleanQuery(q.Data),
|
||||
})
|
||||
}
|
||||
|
||||
successResponse(c, queries)
|
||||
}
|
||||
|
||||
func RunLocalQuery(c *gin.Context) {
|
||||
query, err := QueryStore.Read(c.Param("id"))
|
||||
if err != nil {
|
||||
if err == queries.ErrQueryFileNotExist {
|
||||
query = nil
|
||||
} else {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if query == nil {
|
||||
errorResponse(c, 404, "query not found")
|
||||
return
|
||||
}
|
||||
|
||||
connCtx, err := DB(c).GetConnContext()
|
||||
if err != nil {
|
||||
badRequest(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !query.IsPermitted(connCtx.Host, connCtx.User, connCtx.Database, connCtx.Mode) {
|
||||
errorResponse(c, 404, "query not found")
|
||||
return
|
||||
}
|
||||
|
||||
if c.Request.Method == http.MethodGet {
|
||||
successResponse(c, localQuery{
|
||||
ID: query.ID,
|
||||
Title: query.Meta.Title,
|
||||
Description: query.Meta.Description,
|
||||
Query: query.Data,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
statement := cleanQuery(query.Data)
|
||||
if statement == "" {
|
||||
badRequest(c, errQueryRequired)
|
||||
return
|
||||
}
|
||||
|
||||
HandleQuery(statement, c)
|
||||
}
|
||||
|
||||
@@ -56,3 +56,14 @@ func corsMiddleware() gin.HandlerFunc {
|
||||
c.Header("Access-Control-Allow-Origin", command.Opts.CorsOrigin)
|
||||
}
|
||||
}
|
||||
|
||||
func requireLocalQueries() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if QueryStore == nil {
|
||||
badRequest(c, "local queries are disabled")
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,9 @@ func SetupRoutes(router *gin.Engine) {
|
||||
api.GET("/history", GetHistory)
|
||||
api.GET("/bookmarks", GetBookmarks)
|
||||
api.GET("/export", DataExport)
|
||||
api.GET("/local_queries", requireLocalQueries(), GetLocalQueries)
|
||||
api.GET("/local_queries/:id", requireLocalQueries(), RunLocalQuery)
|
||||
api.POST("/local_queries/:id", requireLocalQueries(), RunLocalQuery)
|
||||
}
|
||||
|
||||
func SetupMetrics(engine *gin.Engine) {
|
||||
|
||||
8
pkg/api/types.go
Normal file
8
pkg/api/types.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package api
|
||||
|
||||
type localQuery struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
Reference in New Issue
Block a user