150 lines
3.4 KiB
Go
150 lines
3.4 KiB
Go
package document_repo
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/XingfenD/yoresee_doc/internal/model"
|
|
"github.com/XingfenD/yoresee_doc/pkg/key"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type DocumentGetSubtreeOperation struct {
|
|
repo *DocumentRepository
|
|
rootParentID int64
|
|
knowledgeID *int64
|
|
depth *int
|
|
|
|
directoryOnly bool
|
|
|
|
tx *gorm.DB
|
|
}
|
|
|
|
func (r *DocumentRepository) GetSubtree(rootParentID int64) *DocumentGetSubtreeOperation {
|
|
return &DocumentGetSubtreeOperation{
|
|
repo: r,
|
|
rootParentID: rootParentID,
|
|
}
|
|
}
|
|
|
|
func (op *DocumentGetSubtreeOperation) WithTx(tx *gorm.DB) *DocumentGetSubtreeOperation {
|
|
op.tx = tx
|
|
return op
|
|
}
|
|
|
|
func (op *DocumentGetSubtreeOperation) WithKnowledgeID(knowledgeID *int64) *DocumentGetSubtreeOperation {
|
|
op.knowledgeID = knowledgeID
|
|
return op
|
|
}
|
|
|
|
func (op *DocumentGetSubtreeOperation) WithDepth(depth *int) *DocumentGetSubtreeOperation {
|
|
op.depth = depth
|
|
return op
|
|
}
|
|
|
|
func (op *DocumentGetSubtreeOperation) WithDirectoryOnly(with bool) *DocumentGetSubtreeOperation {
|
|
op.directoryOnly = with
|
|
return op
|
|
}
|
|
|
|
func (op *DocumentGetSubtreeOperation) Exec(ctx context.Context) ([]*model.Document, error) {
|
|
db := op.repo.db
|
|
if op.tx != nil {
|
|
db = op.tx
|
|
}
|
|
|
|
var documents []*model.Document
|
|
|
|
if op.depth != nil && *op.depth == 0 {
|
|
return documents, nil
|
|
}
|
|
|
|
type pathDepth struct {
|
|
Path string
|
|
Depth int
|
|
}
|
|
var root pathDepth
|
|
err := db.Model(&model.Document{}).
|
|
Select("path, depth").
|
|
Where("id = ?", op.rootParentID).
|
|
Take(&root).Error
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return documents, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// in-transaction query should bypass cache to avoid stale reads
|
|
if op.tx != nil || op.repo.redis == nil {
|
|
return op.queryWithRoot(db, root.Path, root.Depth)
|
|
}
|
|
|
|
version, err := op.repo.getSubtreeVersion(ctx, root.Path)
|
|
if err == nil {
|
|
cacheKey := key.KeyDocSubtree(root.Path, version, op.depth)
|
|
if cachedIDs, ok, err := getCachedSubtreeIDs(ctx, cacheKey); err == nil && ok {
|
|
return op.repo.fetchDocumentsByIDs(cachedIDs)
|
|
}
|
|
|
|
val, err, _ := subtreeCacheSF.Do(cacheKey, func() (interface{}, error) {
|
|
dbDocs, err := op.queryWithRoot(db, root.Path, root.Depth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ids := make([]int64, 0, len(dbDocs))
|
|
for _, doc := range dbDocs {
|
|
ids = append(ids, doc.ID)
|
|
}
|
|
setCachedSubtreeIDs(ctx, cacheKey, ids)
|
|
return dbDocs, nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if typed, ok := val.([]*model.Document); ok {
|
|
return typed, nil
|
|
}
|
|
}
|
|
|
|
return op.queryWithRoot(db, root.Path, root.Depth)
|
|
}
|
|
|
|
func (op *DocumentGetSubtreeOperation) queryWithRoot(db *gorm.DB, rootPath string, rootDepth int) ([]*model.Document, error) {
|
|
var documents []*model.Document
|
|
|
|
query := `
|
|
SELECT *
|
|
FROM document_metas
|
|
WHERE deleted_at IS NULL
|
|
AND id <> ?
|
|
AND path <@ ?
|
|
`
|
|
if op.directoryOnly {
|
|
query = `
|
|
SELECT id, external_id, title, parent_id, type
|
|
FROM document_metas
|
|
WHERE deleted_at IS NULL
|
|
AND id <> ?
|
|
AND path <@ ?
|
|
`
|
|
}
|
|
args := []interface{}{op.rootParentID, rootPath}
|
|
if op.knowledgeID != nil {
|
|
query += " AND knowledge_id = ?"
|
|
args = append(args, *op.knowledgeID)
|
|
}
|
|
if op.depth != nil {
|
|
maxDepth := rootDepth + *op.depth
|
|
query += " AND depth <= ?"
|
|
args = append(args, maxDepth)
|
|
}
|
|
query += " ORDER BY depth, created_at"
|
|
|
|
if err := db.Raw(query, args...).Find(&documents).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return documents, nil
|
|
}
|