2022-10-26 23:52:01 +08:00
package folderimpl
import (
"context"
2022-11-03 21:21:41 +08:00
"strings"
"time"
2022-10-26 23:52:01 +08:00
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
2022-12-01 00:12:56 +08:00
"github.com/grafana/grafana/pkg/infra/slugify"
2023-01-18 20:52:41 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-10-26 23:52:01 +08:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/setting"
2022-11-03 21:21:41 +08:00
"github.com/grafana/grafana/pkg/util"
2022-10-26 23:52:01 +08:00
)
type sqlStore struct {
db db . DB
log log . Logger
cfg * setting . Cfg
2022-11-08 21:59:55 +08:00
fm featuremgmt . FeatureToggles
2022-10-26 23:52:01 +08:00
}
// sqlStore implements the store interface.
var _ store = ( * sqlStore ) ( nil )
2022-11-08 21:59:55 +08:00
func ProvideStore ( db db . DB , cfg * setting . Cfg , features featuremgmt . FeatureToggles ) * sqlStore {
2022-10-26 23:52:01 +08:00
return & sqlStore { db : db , log : log . New ( "folder-store" ) , cfg : cfg , fm : features }
}
2022-11-03 21:21:41 +08:00
func ( ss * sqlStore ) Create ( ctx context . Context , cmd folder . CreateFolderCommand ) ( * folder . Folder , error ) {
if cmd . UID == "" {
return nil , folder . ErrBadRequest . Errorf ( "missing UID" )
2022-10-29 02:07:25 +08:00
}
2022-11-03 21:21:41 +08:00
var foldr * folder . Folder
2022-11-10 17:41:03 +08:00
/ *
version := 1
2022-11-23 22:44:45 +08:00
updatedBy := cmd . SignedInUser . UserID
createdBy := cmd . SignedInUser . UserID
2022-11-10 17:41:03 +08:00
* /
2022-11-28 23:48:44 +08:00
var lastInsertedID int64
2022-10-29 02:07:25 +08:00
err := ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-11-28 23:48:44 +08:00
var sql string
var args [ ] interface { }
2022-11-03 21:21:41 +08:00
if cmd . ParentUID == "" {
2022-11-28 23:48:44 +08:00
sql = "INSERT INTO folder(org_id, uid, title, description, created, updated) VALUES(?, ?, ?, ?, ?, ?)"
args = [ ] interface { } { cmd . OrgID , cmd . UID , cmd . Title , cmd . Description , time . Now ( ) , time . Now ( ) }
2022-11-03 21:21:41 +08:00
} else {
if cmd . ParentUID != folder . GeneralFolderUID {
if _ , err := ss . Get ( ctx , folder . GetFolderQuery {
UID : & cmd . ParentUID ,
OrgID : cmd . OrgID ,
} ) ; err != nil {
2022-11-10 17:41:03 +08:00
return folder . ErrFolderNotFound . Errorf ( "parent folder does not exist" )
2022-11-03 21:21:41 +08:00
}
}
2022-11-28 23:48:44 +08:00
sql = "INSERT INTO folder(org_id, uid, parent_uid, title, description, created, updated) VALUES(?, ?, ?, ?, ?, ?, ?)"
args = [ ] interface { } { cmd . OrgID , cmd . UID , cmd . ParentUID , cmd . Title , cmd . Description , time . Now ( ) , time . Now ( ) }
2022-11-03 21:21:41 +08:00
}
2022-11-28 23:48:44 +08:00
var err error
lastInsertedID , err = sess . WithReturningID ( ss . db . GetDialect ( ) . DriverName ( ) , sql , args )
2022-11-03 21:21:41 +08:00
if err != nil {
2022-11-28 23:48:44 +08:00
return err
2022-11-03 21:21:41 +08:00
}
foldr , err = ss . Get ( ctx , folder . GetFolderQuery {
2022-11-28 23:48:44 +08:00
ID : & lastInsertedID ,
2022-11-03 21:21:41 +08:00
} )
2022-10-29 02:07:25 +08:00
if err != nil {
return err
}
return nil
} )
return foldr , err
2022-10-26 23:52:01 +08:00
}
2022-10-28 21:35:49 +08:00
func ( ss * sqlStore ) Delete ( ctx context . Context , uid string , orgID int64 ) error {
return ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-11-03 21:21:41 +08:00
_ , err := sess . Exec ( "DELETE FROM folder WHERE uid=? AND org_id=?" , uid , orgID )
if err != nil {
return folder . ErrDatabaseError . Errorf ( "failed to delete folder: %w" , err )
}
return nil
2022-10-28 21:35:49 +08:00
} )
2022-10-26 23:52:01 +08:00
}
2022-11-03 21:21:41 +08:00
func ( ss * sqlStore ) Update ( ctx context . Context , cmd folder . UpdateFolderCommand ) ( * folder . Folder , error ) {
2022-12-20 21:00:33 +08:00
updated := time . Now ( )
uid := cmd . UID
2022-11-03 21:21:41 +08:00
2022-12-20 21:00:33 +08:00
var foldr * folder . Folder
2023-04-04 02:24:36 +08:00
if cmd . NewDescription == nil && cmd . NewTitle == nil && cmd . NewUID == nil && cmd . NewParentUID == nil {
return nil , folder . ErrBadRequest . Errorf ( "nothing to update" )
}
2022-10-28 21:35:49 +08:00
err := ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-11-04 17:04:24 +08:00
sql := strings . Builder { }
sql . Write ( [ ] byte ( "UPDATE folder SET " ) )
columnsToUpdate := [ ] string { "updated = ?" }
2022-12-20 21:00:33 +08:00
args := [ ] interface { } { updated }
2022-11-03 21:21:41 +08:00
if cmd . NewDescription != nil {
2022-11-04 17:04:24 +08:00
columnsToUpdate = append ( columnsToUpdate , "description = ?" )
2022-12-20 21:00:33 +08:00
args = append ( args , * cmd . NewDescription )
2022-11-03 21:21:41 +08:00
}
if cmd . NewTitle != nil {
2022-11-04 17:04:24 +08:00
columnsToUpdate = append ( columnsToUpdate , "title = ?" )
2022-12-20 21:00:33 +08:00
args = append ( args , * cmd . NewTitle )
2022-11-03 21:21:41 +08:00
}
if cmd . NewUID != nil {
2022-11-04 17:04:24 +08:00
columnsToUpdate = append ( columnsToUpdate , "uid = ?" )
2022-12-20 21:00:33 +08:00
uid = * cmd . NewUID
args = append ( args , * cmd . NewUID )
}
if cmd . NewParentUID != nil {
2023-04-04 02:24:36 +08:00
if * cmd . NewParentUID == "" {
columnsToUpdate = append ( columnsToUpdate , "parent_uid = NULL" )
} else {
columnsToUpdate = append ( columnsToUpdate , "parent_uid = ?" )
args = append ( args , * cmd . NewParentUID )
}
2022-11-08 18:33:13 +08:00
}
2022-11-04 17:04:24 +08:00
if len ( columnsToUpdate ) == 0 {
return folder . ErrBadRequest . Errorf ( "no columns to update" )
}
sql . Write ( [ ] byte ( strings . Join ( columnsToUpdate , ", " ) ) )
sql . Write ( [ ] byte ( " WHERE uid = ? AND org_id = ?" ) )
2022-12-20 21:00:33 +08:00
args = append ( args , cmd . UID , cmd . OrgID )
2022-11-04 17:04:24 +08:00
args = append ( [ ] interface { } { sql . String ( ) } , args ... )
res , err := sess . Exec ( args ... )
2022-11-03 21:21:41 +08:00
if err != nil {
return folder . ErrDatabaseError . Errorf ( "failed to update folder: %w" , err )
}
affected , err := res . RowsAffected ( )
if err != nil {
return folder . ErrInternal . Errorf ( "failed to get affected row: %w" , err )
}
if affected == 0 {
return folder . ErrInternal . Errorf ( "no folders are updated" )
}
2022-12-20 21:00:33 +08:00
foldr , err = ss . Get ( ctx , folder . GetFolderQuery {
UID : & uid ,
OrgID : cmd . OrgID ,
} )
if err != nil {
return err
}
2022-11-03 21:21:41 +08:00
return nil
2022-10-28 21:35:49 +08:00
} )
2022-10-26 23:52:01 +08:00
2022-12-20 21:00:33 +08:00
return foldr , err
2022-10-26 23:52:01 +08:00
}
2022-11-03 21:21:41 +08:00
func ( ss * sqlStore ) Get ( ctx context . Context , q folder . GetFolderQuery ) ( * folder . Folder , error ) {
foldr := & folder . Folder { }
2022-10-28 21:35:49 +08:00
err := ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-11-03 21:21:41 +08:00
exists := false
var err error
switch {
2023-01-06 22:04:17 +08:00
case q . UID != nil :
exists , err = sess . SQL ( "SELECT * FROM folder WHERE uid = ? AND org_id = ?" , q . UID , q . OrgID ) . Get ( foldr )
2022-11-03 21:21:41 +08:00
case q . ID != nil :
exists , err = sess . SQL ( "SELECT * FROM folder WHERE id = ?" , q . ID ) . Get ( foldr )
case q . Title != nil :
exists , err = sess . SQL ( "SELECT * FROM folder WHERE title = ? AND org_id = ?" , q . Title , q . OrgID ) . Get ( foldr )
default :
return folder . ErrBadRequest . Errorf ( "one of ID, UID, or Title must be included in the command" )
}
2022-10-28 21:35:49 +08:00
if err != nil {
2022-11-03 21:21:41 +08:00
return folder . ErrDatabaseError . Errorf ( "failed to get folder: %w" , err )
2022-10-28 21:35:49 +08:00
}
if ! exists {
return folder . ErrFolderNotFound . Errorf ( "folder not found" )
}
return nil
} )
2023-01-25 16:14:32 +08:00
foldr . URL = dashboards . GetFolderURL ( foldr . UID , slugify . Slugify ( foldr . Title ) )
2022-10-28 21:35:49 +08:00
return foldr , err
2022-10-26 23:52:01 +08:00
}
2022-11-03 21:21:41 +08:00
func ( ss * sqlStore ) GetParents ( ctx context . Context , q folder . GetParentsQuery ) ( [ ] * folder . Folder , error ) {
2023-03-30 16:46:11 +08:00
if q . UID == "" {
return [ ] * folder . Folder { } , nil
}
2022-10-29 02:07:25 +08:00
var folders [ ] * folder . Folder
2022-11-03 21:21:41 +08:00
recQuery := `
WITH RECURSIVE RecQry AS (
SELECT * FROM folder WHERE uid = ? AND org_id = ?
UNION ALL SELECT f . * FROM folder f INNER JOIN RecQry r ON f . uid = r . parent_uid and f . org_id = r . org_id
2022-10-29 02:07:25 +08:00
)
2022-11-03 21:21:41 +08:00
SELECT * FROM RecQry ;
`
2022-10-29 02:07:25 +08:00
2023-03-20 16:27:08 +08:00
recursiveQueriesAreSupported , err := ss . db . RecursiveQueriesAreSupported ( )
if err != nil {
return nil , err
}
switch recursiveQueriesAreSupported {
case true :
if err := ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
err := sess . SQL ( recQuery , q . UID , q . OrgID ) . Find ( & folders )
if err != nil {
return folder . ErrDatabaseError . Errorf ( "failed to get folder parents: %w" , err )
2022-11-09 03:53:05 +08:00
}
2023-03-20 16:27:08 +08:00
return nil
} ) ; err != nil {
return nil , err
2022-11-09 03:53:05 +08:00
}
2023-03-20 16:27:08 +08:00
default :
ss . log . Debug ( "recursive CTE subquery is not supported; it fallbacks to the iterative implementation" )
return ss . getParentsMySQL ( ctx , q )
2022-11-09 03:53:05 +08:00
}
2022-11-25 02:28:53 +08:00
if len ( folders ) < 1 {
// the query is expected to return at least the same folder
// if it's empty it means that the folder does not exist
return nil , folder . ErrFolderNotFound
}
2022-11-09 03:53:05 +08:00
return util . Reverse ( folders [ 1 : ] ) , nil
2022-10-26 23:52:01 +08:00
}
2022-12-19 16:52:04 +08:00
func ( ss * sqlStore ) GetChildren ( ctx context . Context , q folder . GetChildrenQuery ) ( [ ] * folder . Folder , error ) {
2022-10-28 21:35:49 +08:00
var folders [ ] * folder . Folder
2022-10-29 02:07:25 +08:00
2022-10-28 21:35:49 +08:00
err := ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-11-03 21:21:41 +08:00
sql := strings . Builder { }
2022-12-19 16:52:04 +08:00
args := make ( [ ] interface { } , 0 , 2 )
if q . UID == "" {
2023-04-27 22:24:47 +08:00
sql . Write ( [ ] byte ( "SELECT * FROM folder WHERE parent_uid IS NULL AND org_id=? ORDER BY title ASC" ) )
2022-12-19 16:52:04 +08:00
args = append ( args , q . OrgID )
} else {
2023-04-27 22:24:47 +08:00
sql . Write ( [ ] byte ( "SELECT * FROM folder WHERE parent_uid=? AND org_id=? ORDER BY title ASC" ) )
2022-12-19 16:52:04 +08:00
args = append ( args , q . UID , q . OrgID )
}
2022-11-03 21:21:41 +08:00
if q . Limit != 0 {
2022-12-19 16:52:04 +08:00
var offset int64 = 0
if q . Page > 0 {
offset = q . Limit * ( q . Page - 1 )
2022-11-03 21:21:41 +08:00
}
sql . Write ( [ ] byte ( ss . db . GetDialect ( ) . LimitOffset ( q . Limit , offset ) ) )
}
2022-12-19 16:52:04 +08:00
err := sess . SQL ( sql . String ( ) , args ... ) . Find ( & folders )
2022-11-03 21:21:41 +08:00
if err != nil {
return folder . ErrDatabaseError . Errorf ( "failed to get folder children: %w" , err )
}
return nil
2022-10-28 21:35:49 +08:00
} )
return folders , err
2022-10-26 23:52:01 +08:00
}
2022-10-29 02:07:25 +08:00
2022-11-09 03:53:05 +08:00
func ( ss * sqlStore ) getParentsMySQL ( ctx context . Context , cmd folder . GetParentsQuery ) ( folders [ ] * folder . Folder , err error ) {
err = ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
uid := ""
ok , err := sess . SQL ( "SELECT parent_uid FROM folder WHERE org_id=? AND uid=?" , cmd . OrgID , cmd . UID ) . Get ( & uid )
if err != nil {
return err
}
if ! ok {
return folder . ErrFolderNotFound
}
for {
f := & folder . Folder { }
ok , err := sess . SQL ( "SELECT * FROM folder WHERE org_id=? AND uid=?" , cmd . OrgID , uid ) . Get ( f )
2022-10-29 02:07:25 +08:00
if err != nil {
2022-11-09 03:53:05 +08:00
return err
}
if ! ok {
break
}
folders = append ( folders , f )
uid = f . ParentUID
if len ( folders ) > folder . MaxNestedFolderDepth {
2023-04-20 22:47:51 +08:00
return folder . ErrMaximumDepthReached . Errorf ( "failed to get parent folders iteratively" )
2022-10-29 02:07:25 +08:00
}
}
return nil
} )
2022-11-28 23:48:44 +08:00
return util . Reverse ( folders ) , err
2022-10-29 02:07:25 +08:00
}
2022-12-08 21:49:17 +08:00
func ( ss * sqlStore ) GetHeight ( ctx context . Context , foldrUID string , orgID int64 , parentUID * string ) ( int , error ) {
height := - 1
queue := [ ] string { foldrUID }
2022-12-15 00:07:55 +08:00
for len ( queue ) > 0 && height <= folder . MaxNestedFolderDepth {
2022-12-08 21:49:17 +08:00
length := len ( queue )
height ++
for i := 0 ; i < length ; i ++ {
ele := queue [ 0 ]
queue = queue [ 1 : ]
if parentUID != nil && * parentUID == ele {
return 0 , folder . ErrCircularReference
}
2022-12-19 16:52:04 +08:00
folders , err := ss . GetChildren ( ctx , folder . GetChildrenQuery { UID : ele , OrgID : orgID } )
2022-12-08 21:49:17 +08:00
if err != nil {
return 0 , err
}
for _ , f := range folders {
queue = append ( queue , f . UID )
}
}
}
2022-12-15 00:07:55 +08:00
if height > folder . MaxNestedFolderDepth {
ss . log . Warn ( "folder height exceeds the maximum allowed depth, You might have a circular reference" , "uid" , foldrUID , "orgId" , orgID , "maxDepth" , folder . MaxNestedFolderDepth )
}
2022-12-08 21:49:17 +08:00
return height , nil
}