2022-10-26 23:52:01 +08:00
package folderimpl
import (
"context"
2024-01-25 15:27:13 +08:00
"fmt"
2023-12-16 01:34:08 +08:00
"runtime"
2022-11-03 21:21:41 +08:00
"strings"
"time"
2022-10-26 23:52:01 +08:00
2023-05-10 21:20:16 +08:00
"github.com/grafana/dskit/concurrency"
2023-11-08 22:28:49 +08:00
2022-10-26 23:52:01 +08:00
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
2024-01-25 18:10:35 +08:00
"github.com/grafana/grafana/pkg/infra/metrics"
2024-01-10 23:48:28 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-10-26 23:52:01 +08:00
"github.com/grafana/grafana/pkg/services/folder"
2024-01-25 15:27:13 +08:00
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
2022-10-26 23:52:01 +08:00
"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
)
2024-01-25 15:27:13 +08:00
const DEFAULT_BATCH_SIZE = 999
2022-10-26 23:52:01 +08:00
type sqlStore struct {
db db . DB
log log . Logger
cfg * setting . Cfg
}
// sqlStore implements the store interface.
var _ store = ( * sqlStore ) ( nil )
2024-01-23 00:03:30 +08:00
func ProvideStore ( db db . DB , cfg * setting . Cfg ) * sqlStore {
return & sqlStore { db : db , log : log . New ( "folder-store" ) , cfg : cfg }
2022-10-26 23:52:01 +08:00
}
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
2023-08-30 23:46:47 +08:00
var args [ ] any
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(?, ?, ?, ?, ?, ?)"
2023-08-30 23:46:47 +08:00
args = [ ] any { 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(?, ?, ?, ?, ?, ?, ?)"
2023-08-30 23:46:47 +08:00
args = [ ] any { 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
}
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2022-11-03 21:21:41 +08:00
foldr , err = ss . Get ( ctx , folder . GetFolderQuery {
2023-11-15 23:30:00 +08:00
ID : & lastInsertedID , // nolint:staticcheck
2022-11-03 21:21:41 +08:00
} )
2022-10-29 02:07:25 +08:00
if err != nil {
return err
}
return nil
} )
2023-05-10 21:20:16 +08:00
return foldr . WithURL ( ) , 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
2023-09-12 19:28:33 +08:00
if cmd . NewDescription == nil && cmd . NewTitle == nil && cmd . NewParentUID == nil {
2023-04-04 02:24:36 +08:00
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 { }
2023-05-26 18:08:50 +08:00
sql . WriteString ( "UPDATE folder SET " )
2022-11-04 17:04:24 +08:00
columnsToUpdate := [ ] string { "updated = ?" }
2023-08-30 23:46:47 +08:00
args := [ ] any { 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
}
2022-12-20 21:00:33 +08:00
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" )
}
2023-05-26 18:08:50 +08:00
sql . WriteString ( strings . Join ( columnsToUpdate , ", " ) )
sql . WriteString ( " 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
2023-08-30 23:46:47 +08:00
args = append ( [ ] any { sql . String ( ) } , args ... )
2022-11-04 17:04:24 +08:00
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 {
2023-11-10 20:03:00 +08:00
return folder . ErrInternal . Errorf ( "no folders are updated: %w" , folder . ErrFolderNotFound )
2022-11-03 21:21:41 +08:00
}
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
2023-05-10 21:20:16 +08:00
return foldr . WithURL ( ) , 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 )
2023-11-15 23:30:00 +08:00
// nolint:staticcheck
2022-11-03 21:21:41 +08:00
case q . ID != nil :
2024-01-25 18:10:35 +08:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Folder ) . Inc ( )
2022-11-03 21:21:41 +08:00
exists , err = sess . SQL ( "SELECT * FROM folder WHERE id = ?" , q . ID ) . Get ( foldr )
case q . Title != nil :
2024-01-25 17:29:56 +08:00
s := strings . Builder { }
s . WriteString ( "SELECT * FROM folder WHERE title = ? AND org_id = ?" )
args := [ ] any { * q . Title , q . OrgID }
if q . ParentUID != nil {
s . WriteString ( " AND parent_uid = ?" )
args = append ( args , * q . ParentUID )
} else {
s . WriteString ( " AND parent_uid IS NULL" )
}
exists , err = sess . SQL ( s . String ( ) , args ... ) . Get ( foldr )
2022-11-03 21:21:41 +08:00
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 {
2024-01-10 23:48:28 +08:00
// embed dashboards.ErrFolderNotFound
return folder . ErrFolderNotFound . Errorf ( "%w" , dashboards . ErrFolderNotFound )
2022-10-28 21:35:49 +08:00
}
return nil
} )
2024-01-10 23:48:28 +08:00
2023-05-10 21:20:16 +08:00
return foldr . WithURL ( ) , 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-05-10 21:20:16 +08:00
2023-12-16 01:34:08 +08:00
if err := concurrency . ForEachJob ( ctx , len ( folders ) , runtime . NumCPU ( ) , func ( ctx context . Context , idx int ) error {
2023-05-10 21:20:16 +08:00
folders [ idx ] . WithURL ( )
return nil
} ) ; err != nil {
ss . log . Debug ( "failed to set URL to folders" , "err" , err )
}
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
2024-01-26 00:24:52 +08:00
return nil , folder . ErrFolderNotFound . Errorf ( "folder not found" )
2022-11-25 02:28:53 +08:00
}
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 { }
2023-08-30 23:46:47 +08:00
args := make ( [ ] any , 0 , 2 )
2022-12-19 16:52:04 +08:00
if q . UID == "" {
2023-12-16 01:34:08 +08:00
sql . WriteString ( "SELECT * FROM folder WHERE parent_uid IS NULL AND org_id=?" )
2022-12-19 16:52:04 +08:00
args = append ( args , q . OrgID )
} else {
2023-12-16 01:34:08 +08:00
sql . WriteString ( "SELECT * FROM folder WHERE parent_uid=? AND org_id=?" )
2022-12-19 16:52:04 +08:00
args = append ( args , q . UID , q . OrgID )
}
2022-11-03 21:21:41 +08:00
2023-12-16 01:34:08 +08:00
if q . FolderUIDs != nil {
sql . WriteString ( " AND uid IN (?" )
for range q . FolderUIDs [ 1 : ] {
sql . WriteString ( ", ?" )
}
sql . WriteString ( ")" )
for _ , uid := range q . FolderUIDs {
args = append ( args , uid )
}
}
sql . WriteString ( " ORDER BY title ASC" )
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
}
2023-05-26 18:08:50 +08:00
sql . WriteString ( ss . db . GetDialect ( ) . LimitOffset ( q . Limit , offset ) )
2022-11-03 21:21:41 +08:00
}
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 )
}
2023-05-10 21:20:16 +08:00
2023-12-16 01:34:08 +08:00
if err := concurrency . ForEachJob ( ctx , len ( folders ) , runtime . NumCPU ( ) , func ( ctx context . Context , idx int ) error {
2023-05-10 21:20:16 +08:00
folders [ idx ] . WithURL ( )
return nil
} ) ; err != nil {
ss . log . Debug ( "failed to set URL to folders" , "err" , err )
}
2022-11-03 21:21:41 +08:00
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
2024-01-18 22:12:49 +08:00
func ( ss * sqlStore ) getParentsMySQL ( ctx context . Context , q folder . GetParentsQuery ) ( folders [ ] * folder . Folder , err error ) {
2022-11-09 03:53:05 +08:00
err = ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
uid := ""
2024-01-18 22:12:49 +08:00
ok , err := sess . SQL ( "SELECT parent_uid FROM folder WHERE org_id=? AND uid=?" , q . OrgID , q . UID ) . Get ( & uid )
2022-11-09 03:53:05 +08:00
if err != nil {
return err
}
if ! ok {
2024-01-26 00:24:52 +08:00
return folder . ErrFolderNotFound . Errorf ( "folder not found" )
2022-11-09 03:53:05 +08:00
}
for {
f := & folder . Folder { }
2024-01-18 22:12:49 +08:00
ok , err := sess . SQL ( "SELECT * FROM folder WHERE org_id=? AND uid=?" , q . 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
}
2023-05-10 21:20:16 +08:00
folders = append ( folders , f . WithURL ( ) )
2022-11-09 03:53:05 +08:00
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
}
2023-11-08 22:28:49 +08:00
2024-01-25 15:27:13 +08:00
// GetFolders returns org folders by their UIDs.
// If UIDs is empty, it returns all folders in the org.
// If WithFullpath is true it computes also the full path of a folder.
// The full path is a string that contains the titles of all parent folders separated by a slash.
// For example, if the folder structure is:
//
// A
// └── B
// └── C
//
// The full path of C is "A/B/C".
// The full path of B is "A/B".
// The full path of A is "A".
// If a folder contains a slash in its title, it is escaped with a backslash.
// For example, if the folder structure is:
//
// A
// └── B/C
//
// The full path of C is "A/B\/C".
//
// If FullpathUIDs is true it computes a string that contains the UIDs of all parent folders separated by slash.
// For example, if the folder structure is:
//
// A (uid: "uid1")
// └── B (uid: "uid2")
// └── C (uid: "uid3")
//
// The full path UIDs of C is "uid1/uid2/uid3".
// The full path UIDs of B is "uid1/uid2".
// The full path UIDs of A is "uid1".
func ( ss * sqlStore ) GetFolders ( ctx context . Context , q getFoldersQuery ) ( [ ] * folder . Folder , error ) {
if q . BatchSize == 0 {
q . BatchSize = DEFAULT_BATCH_SIZE
2023-11-08 22:28:49 +08:00
}
2024-01-25 15:27:13 +08:00
2023-11-08 22:28:49 +08:00
var folders [ ] * folder . Folder
if err := ss . db . WithDbSession ( ctx , func ( sess * db . Session ) error {
2024-01-25 15:27:13 +08:00
return batch ( len ( q . UIDs ) , int ( q . BatchSize ) , func ( start , end int ) error {
partialFolders := make ( [ ] * folder . Folder , 0 , q . BatchSize )
partialUIDs := q . UIDs [ start : min ( end , len ( q . UIDs ) ) ]
s := strings . Builder { }
s . WriteString ( ` SELECT f0.id, f0.org_id, f0.uid, f0.parent_uid, f0.title, f0.description, f0.created, f0.updated ` )
// compute full path column if requested
if q . WithFullpath {
s . WriteString ( fmt . Sprintf ( ` , %s AS fullpath ` , getFullpathSQL ( ss . db . GetDialect ( ) ) ) )
}
// compute full path UIDs column if requested
if q . WithFullpathUIDs {
s . WriteString ( fmt . Sprintf ( ` , %s AS fullpath_uids ` , getFullapathUIDsSQL ( ss . db . GetDialect ( ) ) ) )
}
s . WriteString ( ` FROM folder f0 ` )
// join the same table multiple times to compute the full path of a folder
if q . WithFullpath || q . WithFullpathUIDs || len ( q . ancestorUIDs ) > 0 {
s . WriteString ( getFullpathJoinsSQL ( ) )
}
s . WriteString ( ` WHERE f0.org_id=? ` )
args := [ ] any { q . OrgID }
if len ( partialUIDs ) > 0 {
s . WriteString ( ` AND f0.uid IN (? ` + strings . Repeat ( ", ?" , len ( partialUIDs ) - 1 ) + ` ) ` )
for _ , uid := range partialUIDs {
args = append ( args , uid )
}
}
if len ( q . ancestorUIDs ) == 0 {
err := sess . SQL ( s . String ( ) , args ... ) . Find ( & partialFolders )
if err != nil {
return err
}
folders = append ( folders , partialFolders ... )
return nil
}
// filter out folders if they are not in the subtree of the given ancestor folders
if err := batch ( len ( q . ancestorUIDs ) , int ( q . BatchSize ) , func ( start2 , end2 int ) error {
s2 , args2 := getAncestorsSQL ( ss . db . GetDialect ( ) , q . ancestorUIDs , start2 , end2 , s . String ( ) , args )
err := sess . SQL ( s2 , args2 ... ) . Find ( & partialFolders )
if err != nil {
return err
}
folders = append ( folders , partialFolders ... )
return nil
} ) ; err != nil {
return err
}
return nil
} )
2023-11-08 22:28:49 +08:00
} ) ; err != nil {
return nil , err
}
// Add URLs
for i , f := range folders {
2024-01-25 15:27:13 +08:00
f . Fullpath = strings . TrimLeft ( f . Fullpath , "/" )
f . FullpathUIDs = strings . TrimLeft ( f . FullpathUIDs , "/" )
2023-11-08 22:28:49 +08:00
folders [ i ] = f . WithURL ( )
}
return folders , nil
}
2024-01-25 15:27:13 +08:00
func getFullpathSQL ( dialect migrator . Dialect ) string {
concatCols := make ( [ ] string , 0 , folder . MaxNestedFolderDepth )
concatCols = append ( concatCols , "COALESCE(REPLACE(f0.title, '/', '\\/'), '')" )
for i := 1 ; i <= folder . MaxNestedFolderDepth ; i ++ {
concatCols = append ( [ ] string { fmt . Sprintf ( "COALESCE(REPLACE(f%d.title, '/', '\\/'), '')" , i ) , "'/'" } , concatCols ... )
}
return dialect . Concat ( concatCols ... )
}
func getFullapathUIDsSQL ( dialect migrator . Dialect ) string {
concatCols := make ( [ ] string , 0 , folder . MaxNestedFolderDepth )
concatCols = append ( concatCols , "COALESCE(f0.uid, '')" )
for i := 1 ; i <= folder . MaxNestedFolderDepth ; i ++ {
concatCols = append ( [ ] string { fmt . Sprintf ( "COALESCE(f%d.uid, '')" , i ) , "'/'" } , concatCols ... )
}
return dialect . Concat ( concatCols ... )
}
// getFullpathJoinsSQL returns a SQL fragment that joins the same table multiple times to get the full path of a folder.
func getFullpathJoinsSQL ( ) string {
joins := make ( [ ] string , 0 , folder . MaxNestedFolderDepth )
for i := 1 ; i <= folder . MaxNestedFolderDepth ; i ++ {
joins = append ( joins , fmt . Sprintf ( ` LEFT JOIN folder f%d ON f%d.org_id = f%d.org_id AND f%d.uid = f%d.parent_uid ` , i , i , i - 1 , i , i - 1 ) )
}
return strings . Join ( joins , "\n" )
}
func getAncestorsSQL ( dialect migrator . Dialect , ancestorUIDs [ ] string , start int , end int , origSQL string , origArgs [ ] any ) ( string , [ ] any ) {
s2 := strings . Builder { }
s2 . WriteString ( origSQL )
args2 := make ( [ ] any , 0 , len ( ancestorUIDs ) * folder . MaxNestedFolderDepth )
args2 = append ( args2 , origArgs ... )
partialAncestorUIDs := ancestorUIDs [ start : min ( end , len ( ancestorUIDs ) ) ]
partialArgs := make ( [ ] any , 0 , len ( partialAncestorUIDs ) )
for _ , uid := range partialAncestorUIDs {
partialArgs = append ( partialArgs , uid )
}
s2 . WriteString ( ` AND ( f0.uid IN (? ` + strings . Repeat ( ", ?" , len ( partialAncestorUIDs ) - 1 ) + ` ) ` )
args2 = append ( args2 , partialArgs ... )
for i := 1 ; i <= folder . MaxNestedFolderDepth ; i ++ {
s2 . WriteString ( fmt . Sprintf ( ` OR f%d.uid IN (? ` + strings . Repeat ( ", ?" , len ( partialAncestorUIDs ) - 1 ) + ` ) ` , i ) )
args2 = append ( args2 , partialArgs ... )
}
s2 . WriteString ( ` ) ` )
return s2 . String ( ) , args2
}
func batch ( count , batchSize int , eachFn func ( start , end int ) error ) error {
if count == 0 {
if err := eachFn ( 0 , 0 ) ; err != nil {
return err
}
return nil
}
for i := 0 ; i < count ; {
end := i + batchSize
if end > count {
end = count
}
if err := eachFn ( i , end ) ; err != nil {
return err
}
i = end
}
return nil
}