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:
Dan Sosedoff
2023-02-02 16:13:14 -06:00
committed by GitHub
parent 1c3ab1fd1c
commit 41bf189e6b
23 changed files with 884 additions and 12 deletions

View File

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

View File

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

View File

@@ -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
View 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"`
}