2023-06-24 20:39:55 +08:00
// SiYuan - Refactor your thinking
2022-05-26 15:18:53 +08:00
// Copyright (c) 2020-present, b3log.org
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package treenode
import (
2024-06-20 22:53:27 +08:00
"bytes"
"database/sql"
"errors"
2022-05-26 15:18:53 +08:00
"os"
2024-06-20 22:53:27 +08:00
"runtime"
"runtime/debug"
2024-06-21 23:07:26 +08:00
"strings"
2022-05-26 15:18:53 +08:00
"sync"
2026-03-09 11:41:04 +08:00
"sync/atomic"
2022-05-26 15:18:53 +08:00
"time"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
"github.com/88250/lute/parse"
2022-07-17 12:22:32 +08:00
"github.com/siyuan-note/logging"
2022-05-26 15:18:53 +08:00
"github.com/siyuan-note/siyuan/kernel/util"
)
type BlockTree struct {
ID string // 块 ID
RootID string // 根 ID
ParentID string // 父 ID
BoxID string // 笔记本 ID
2023-01-08 22:47:43 +08:00
Path string // 文档数据路径
HPath string // 文档可读路径
Updated string // 更新时间
2023-01-25 20:46:17 +08:00
Type string // 类型
2023-01-08 22:47:43 +08:00
}
2024-06-20 22:53:27 +08:00
var (
db * sql . DB
)
2026-03-09 11:41:04 +08:00
var (
initDatabaseLock = sync . Mutex { }
isInitializingDatabase = atomic . Bool { }
)
2024-06-20 22:53:27 +08:00
func initDatabase ( forceRebuild bool ) ( err error ) {
2026-03-09 11:41:04 +08:00
initDatabaseLock . Lock ( )
defer initDatabaseLock . Unlock ( )
isInitializingDatabase . Store ( true )
defer isInitializingDatabase . Store ( false )
2024-06-20 22:53:27 +08:00
initDBConnection ( )
if ! forceRebuild {
if ! gulu . File . IsExist ( util . BlockTreeDBPath ) {
forceRebuild = true
}
}
if ! forceRebuild {
return
}
closeDatabase ( )
2026-03-09 11:25:05 +08:00
util . RemoveDatabaseFile ( util . BlockTreeDBPath )
2024-06-20 22:53:27 +08:00
initDBConnection ( )
initDBTables ( )
logging . LogInfof ( "reinitialized database [%s]" , util . BlockTreeDBPath )
return
}
func initDBTables ( ) {
_ , err := db . Exec ( "DROP TABLE IF EXISTS blocktrees" )
2024-09-04 04:40:50 +03:00
if err != nil {
2026-01-17 21:50:31 +08:00
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "drop table [blocks] failed: %s" , err )
2024-06-20 22:53:27 +08:00
}
_ , err = db . Exec ( "CREATE TABLE blocktrees (id, root_id, parent_id, box_id, path, hpath, updated, type)" )
2024-09-04 04:40:50 +03:00
if err != nil {
2026-01-17 21:50:31 +08:00
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "create table [blocktrees] failed: %s" , err )
2024-06-20 22:53:27 +08:00
}
2024-07-14 22:25:13 +08:00
_ , err = db . Exec ( "CREATE INDEX idx_blocktrees_id ON blocktrees(id)" )
2024-09-04 04:40:50 +03:00
if err != nil {
2026-01-17 21:50:31 +08:00
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "create index [idx_blocktrees_id] failed: %s" , err )
2024-07-14 22:25:13 +08:00
}
2026-02-07 13:02:37 +08:00
_ , err = db . Exec ( "CREATE INDEX idx_blocktrees_root_id ON blocktrees(root_id)" )
if err != nil {
2026-02-07 13:02:53 +08:00
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "create index [idx_blocktrees_root_id] failed: %s" , err )
2026-02-07 13:02:37 +08:00
}
2024-06-20 22:53:27 +08:00
}
func initDBConnection ( ) {
if nil != db {
closeDatabase ( )
}
2024-12-27 19:50:08 +08:00
util . LogDatabaseSize ( util . BlockTreeDBPath )
2024-06-20 22:53:27 +08:00
dsn := util . BlockTreeDBPath + "?_journal_mode=WAL" +
"&_synchronous=OFF" +
"&_mmap_size=2684354560" +
"&_secure_delete=OFF" +
"&_cache_size=-20480" +
"&_page_size=32768" +
"&_busy_timeout=7000" +
"&_ignore_check_constraints=ON" +
"&_temp_store=MEMORY" +
"&_case_sensitive_like=OFF"
var err error
db , err = sql . Open ( "sqlite3_extended" , dsn )
2024-09-04 04:40:50 +03:00
if err != nil {
2026-01-17 21:50:31 +08:00
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "create database failed: %s" , err )
2024-06-20 22:53:27 +08:00
}
db . SetMaxIdleConns ( 7 )
db . SetMaxOpenConns ( 7 )
db . SetConnMaxLifetime ( 365 * 24 * time . Hour )
}
func CloseDatabase ( ) {
closeDatabase ( )
}
func closeDatabase ( ) {
if nil == db {
return
}
2024-09-04 04:40:50 +03:00
if err := db . Close ( ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "close database failed: %s" , err )
}
debug . FreeOSMemory ( )
2026-02-11 23:44:12 +08:00
db = nil
runtime . GC ( )
2024-06-20 22:53:27 +08:00
return
}
2023-11-03 11:15:33 +08:00
func GetBlockTreesByType ( typ string ) ( ret [ ] * BlockTree ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE type = ?"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , typ )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2023-11-03 11:15:33 +08:00
}
2024-06-20 22:53:27 +08:00
ret = append ( ret , & block )
}
2023-11-03 11:15:33 +08:00
return
}
2023-01-26 17:12:38 +08:00
func GetBlockTreeByPath ( path string ) ( ret * BlockTree ) {
2024-06-20 22:53:27 +08:00
ret = & BlockTree { }
sqlStmt := "SELECT * FROM blocktrees WHERE path = ?"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt , path ) . Scan ( & ret . ID , & ret . RootID , & ret . ParentID , & ret . BoxID , & ret . Path , & ret . HPath , & ret . Updated , & ret . Type )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
ret = nil
if errors . Is ( err , sql . ErrNoRows ) {
return
2022-11-04 21:37:10 +08:00
}
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
2023-01-26 17:12:38 +08:00
return
2022-11-04 21:37:10 +08:00
}
2022-08-31 01:39:04 +08:00
func CountTrees ( ) ( ret int ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE type = 'd'"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt ) . Scan ( & ret )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
if errors . Is ( err , sql . ErrNoRows ) {
return 0
2023-01-26 17:12:38 +08:00
}
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
}
2022-08-31 01:39:04 +08:00
return
}
2022-08-31 10:50:17 +08:00
func CountBlocks ( ) ( ret int ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT COUNT(*) FROM blocktrees"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt ) . Scan ( & ret )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
if errors . Is ( err , sql . ErrNoRows ) {
return 0
}
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
}
2023-01-26 17:12:38 +08:00
return
2022-11-17 09:38:20 +08:00
}
2023-01-26 17:12:38 +08:00
func GetBlockTreeRootByPath ( boxID , path string ) ( ret * BlockTree ) {
2024-06-20 22:53:27 +08:00
ret = & BlockTree { }
2024-06-22 00:30:01 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND path = ? AND type = 'd'"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt , boxID , path ) . Scan ( & ret . ID , & ret . RootID , & ret . ParentID , & ret . BoxID , & ret . Path , & ret . HPath , & ret . Updated , & ret . Type )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
ret = nil
if errors . Is ( err , sql . ErrNoRows ) {
return
2022-05-26 15:18:53 +08:00
}
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
2023-01-26 17:12:38 +08:00
return
2022-05-26 15:18:53 +08:00
}
2023-01-26 17:12:38 +08:00
func GetBlockTreeRootByHPath ( boxID , hPath string ) ( ret * BlockTree ) {
2024-06-20 22:53:27 +08:00
ret = & BlockTree { }
2023-08-16 11:34:37 +08:00
hPath = gulu . Str . RemoveInvisible ( hPath )
2024-06-22 00:26:57 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ? AND type = 'd'"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt , boxID , hPath ) . Scan ( & ret . ID , & ret . RootID , & ret . ParentID , & ret . BoxID , & ret . Path , & ret . HPath , & ret . Updated , & ret . Type )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
ret = nil
if errors . Is ( err , sql . ErrNoRows ) {
return
2022-05-26 15:18:53 +08:00
}
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
2023-01-26 17:12:38 +08:00
return
2022-05-26 15:18:53 +08:00
}
2023-11-15 09:08:41 +08:00
func GetBlockTreeRootsByHPath ( boxID , hPath string ) ( ret [ ] * BlockTree ) {
hPath = gulu . Str . RemoveInvisible ( hPath )
2024-06-22 00:26:57 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ? AND type = 'd'"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , boxID , hPath )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2023-11-15 09:08:41 +08:00
}
2024-06-20 22:53:27 +08:00
ret = append ( ret , & block )
}
2023-11-15 09:08:41 +08:00
return
}
2024-09-23 12:07:50 +08:00
func GetBlockTreeByHPathPreferredParentID ( boxID , hPath , preferredParentID string ) ( ret * BlockTree ) {
2023-08-16 11:34:37 +08:00
hPath = gulu . Str . RemoveInvisible ( hPath )
2023-05-01 12:35:17 +08:00
var roots [ ] * BlockTree
2024-09-23 12:07:50 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ? AND hpath = ? AND parent_id = ? LIMIT 1"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , boxID , hPath , preferredParentID )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2023-05-01 12:35:17 +08:00
}
2024-06-20 22:53:27 +08:00
if "" == preferredParentID {
ret = & block
return
}
roots = append ( roots , & block )
}
2023-05-01 12:35:17 +08:00
if 1 > len ( roots ) {
return
}
for _ , root := range roots {
if root . ID == preferredParentID {
ret = root
return
}
}
ret = roots [ 0 ]
return
}
2024-04-21 18:18:23 +08:00
func ExistBlockTree ( id string ) bool {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE id = ?"
var count int
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt , id ) . Scan ( & count )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
if errors . Is ( err , sql . ErrNoRows ) {
return false
}
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
2024-04-21 18:18:23 +08:00
return false
}
2024-06-20 22:53:27 +08:00
return 0 < count
2024-04-21 18:18:23 +08:00
}
2024-06-23 21:37:28 +08:00
func ExistBlockTrees ( ids [ ] string ) ( ret map [ string ] bool ) {
ret = map [ string ] bool { }
2024-07-02 11:07:30 +08:00
if 1 > len ( ids ) {
return
}
2024-06-23 21:37:28 +08:00
for _ , id := range ids {
ret [ id ] = false
}
sqlStmt := "SELECT id FROM blocktrees WHERE id IN ('" + strings . Join ( ids , "','" ) + "')"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-23 21:37:28 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var id string
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & id ) ; err != nil {
2024-06-23 21:37:28 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
}
ret [ id ] = true
}
return
}
2024-06-21 23:07:26 +08:00
func GetBlockTrees ( ids [ ] string ) ( ret map [ string ] * BlockTree ) {
ret = map [ string ] * BlockTree { }
2024-07-02 11:07:30 +08:00
if 1 > len ( ids ) {
return
}
2024-11-07 17:23:42 +08:00
stmtBuf := bytes . Buffer { }
stmtBuf . WriteString ( "SELECT * FROM blocktrees WHERE id IN (" )
for i := range ids {
stmtBuf . WriteString ( "?" )
if i == len ( ids ) - 1 {
stmtBuf . WriteString ( ")" )
} else {
stmtBuf . WriteString ( "," )
}
}
var args [ ] any
for _ , id := range ids {
args = append ( args , id )
}
stmt := stmtBuf . String ( )
2026-03-09 11:41:04 +08:00
rows , err := query ( stmt , args ... )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-11-07 17:23:42 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , stmt , err )
2024-06-21 23:07:26 +08:00
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-21 23:07:26 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
}
ret [ block . ID ] = & block
}
return
}
2023-01-26 17:12:38 +08:00
func GetBlockTree ( id string ) ( ret * BlockTree ) {
2022-05-26 15:18:53 +08:00
if "" == id {
2023-01-26 17:12:38 +08:00
return
2022-05-26 15:18:53 +08:00
}
2024-06-20 22:53:27 +08:00
ret = & BlockTree { }
sqlStmt := "SELECT * FROM blocktrees WHERE id = ?"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt , id ) . Scan ( & ret . ID , & ret . RootID , & ret . ParentID , & ret . BoxID , & ret . Path , & ret . HPath , & ret . Updated , & ret . Type )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
ret = nil
if errors . Is ( err , sql . ErrNoRows ) {
return
}
2026-03-04 11:16:53 +08:00
logging . LogErrorf ( "sql query [%s] failed: %v\n\t%s" , sqlStmt , err , logging . ShortStack ( ) )
2023-01-26 17:12:38 +08:00
return
}
return
2022-05-26 15:18:53 +08:00
}
func SetBlockTreePath ( tree * parse . Tree ) {
2023-02-02 14:44:19 +08:00
RemoveBlockTreesByRootID ( tree . ID )
IndexBlockTree ( tree )
2022-05-26 15:18:53 +08:00
}
func RemoveBlockTreesByRootID ( rootID string ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "DELETE FROM blocktrees WHERE root_id = ?"
2026-03-09 11:41:04 +08:00
_ , err := exec ( sqlStmt , rootID )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql exec [%s] failed: %s" , sqlStmt , err )
return
2022-05-26 15:18:53 +08:00
}
}
2025-11-24 10:30:06 +08:00
func CountBlockTreesByPathPrefix ( pathPrefix string ) ( ret int ) {
sqlStmt := "SELECT COUNT(*) FROM blocktrees WHERE path LIKE ?"
2026-03-09 11:41:04 +08:00
err := queryRow ( sqlStmt , pathPrefix + "%" ) . Scan ( & ret )
2025-11-24 10:30:06 +08:00
if err != nil {
if errors . Is ( err , sql . ErrNoRows ) {
return 0
}
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
}
return
}
2023-04-12 19:41:19 +08:00
func GetBlockTreesByPathPrefix ( pathPrefix string ) ( ret [ ] * BlockTree ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE path LIKE ?"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , pathPrefix + "%" )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2023-04-12 19:41:19 +08:00
}
2024-06-20 22:53:27 +08:00
ret = append ( ret , & block )
}
2023-04-12 19:41:19 +08:00
return
}
2023-10-08 16:35:06 +08:00
func GetBlockTreesByRootID ( rootID string ) ( ret [ ] * BlockTree ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE root_id = ?"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , rootID )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2023-10-08 16:35:06 +08:00
}
2024-06-20 22:53:27 +08:00
ret = append ( ret , & block )
}
2023-10-08 16:35:06 +08:00
return
}
2022-05-26 15:18:53 +08:00
func RemoveBlockTreesByPathPrefix ( pathPrefix string ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "DELETE FROM blocktrees WHERE path LIKE ?"
2026-03-09 11:41:04 +08:00
_ , err := exec ( sqlStmt , pathPrefix + "%" )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql exec [%s] failed: %s" , sqlStmt , err )
return
2022-05-26 15:18:53 +08:00
}
}
2023-05-09 10:19:13 +08:00
func GetBlockTreesByBoxID ( boxID string ) ( ret [ ] * BlockTree ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT * FROM blocktrees WHERE box_id = ?"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , boxID )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var block BlockTree
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & block . ID , & block . RootID , & block . ParentID , & block . BoxID , & block . Path , & block . HPath , & block . Updated , & block . Type ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2023-05-09 10:19:13 +08:00
}
2024-06-20 22:53:27 +08:00
ret = append ( ret , & block )
}
2023-05-09 10:19:13 +08:00
return
}
2022-12-10 22:50:02 +08:00
func RemoveBlockTreesByBoxID ( boxID string ) ( ids [ ] string ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "SELECT id FROM blocktrees WHERE box_id = ?"
2026-03-09 11:41:04 +08:00
rows , err := query ( sqlStmt , boxID )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql query [%s] failed: %s" , sqlStmt , err )
return
}
defer rows . Close ( )
for rows . Next ( ) {
var id string
2024-09-04 04:40:50 +03:00
if err = rows . Scan ( & id ) ; err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "query scan field failed: %s" , err )
return
2022-05-26 15:18:53 +08:00
}
2024-06-20 22:53:27 +08:00
ids = append ( ids , id )
}
2023-01-26 17:12:38 +08:00
2024-06-20 22:53:27 +08:00
sqlStmt = "DELETE FROM blocktrees WHERE box_id = ?"
2026-03-09 11:41:04 +08:00
_ , err = exec ( sqlStmt , boxID )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql exec [%s] failed: %s" , sqlStmt , err )
return
2022-05-26 15:18:53 +08:00
}
2022-12-10 22:50:02 +08:00
return
2022-05-26 15:18:53 +08:00
}
2025-12-21 15:42:02 +08:00
func RemoveBlockTreesByIDs ( ids [ ] string ) {
if 1 > len ( ids ) {
return
}
sqlStmt := "DELETE FROM blocktrees WHERE id IN ('" + strings . Join ( ids , "','" ) + "')"
2026-03-09 11:41:04 +08:00
_ , err := exec ( sqlStmt )
2025-12-21 15:42:02 +08:00
if err != nil {
logging . LogErrorf ( "sql exec [%s] failed: %s" , sqlStmt , err )
return
}
}
2022-05-26 15:18:53 +08:00
func RemoveBlockTree ( id string ) {
2024-06-20 22:53:27 +08:00
sqlStmt := "DELETE FROM blocktrees WHERE id = ?"
2026-03-09 11:41:04 +08:00
_ , err := exec ( sqlStmt , id )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "sql exec [%s] failed: %s" , sqlStmt , err )
2023-01-26 17:12:38 +08:00
return
}
2022-05-26 15:18:53 +08:00
}
2024-06-20 22:53:27 +08:00
var indexBlockTreeLock = sync . Mutex { }
2023-01-25 20:46:17 +08:00
func IndexBlockTree ( tree * parse . Tree ) {
2023-02-02 22:35:01 +08:00
var changedNodes [ ] * ast . Node
2022-05-26 15:18:53 +08:00
ast . Walk ( tree . Root , func ( n * ast . Node , entering bool ) ast . WalkStatus {
2024-06-20 22:53:27 +08:00
if ! entering || ! n . IsBlock ( ) || "" == n . ID {
2022-05-26 15:18:53 +08:00
return ast . WalkContinue
}
2023-01-26 17:12:38 +08:00
2024-06-20 22:53:27 +08:00
changedNodes = append ( changedNodes , n )
return ast . WalkContinue
} )
2025-12-21 15:42:02 +08:00
if 1 > len ( changedNodes ) {
return
}
2024-06-20 22:53:27 +08:00
indexBlockTreeLock . Lock ( )
defer indexBlockTreeLock . Unlock ( )
tx , err := db . Begin ( )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "begin transaction failed: %s" , err )
return
}
2025-12-21 15:42:02 +08:00
execInsertBlocktrees ( tx , tree , changedNodes )
2026-03-09 10:47:04 +08:00
if err = tx . Commit ( ) ; err != nil {
logging . LogErrorf ( "commit transaction failed: %s" , err )
}
2024-06-20 22:53:27 +08:00
}
func UpsertBlockTree ( tree * parse . Tree ) {
oldBts := map [ string ] * BlockTree { }
bts := GetBlockTreesByRootID ( tree . ID )
for _ , bt := range bts {
oldBts [ bt . ID ] = bt
}
2023-02-02 22:35:01 +08:00
2024-06-20 22:53:27 +08:00
var changedNodes [ ] * ast . Node
ast . Walk ( tree . Root , func ( n * ast . Node , entering bool ) ast . WalkStatus {
if ! entering || ! n . IsBlock ( ) || "" == n . ID {
return ast . WalkContinue
}
2023-02-02 22:35:01 +08:00
2024-06-20 22:53:27 +08:00
if oldBt , found := oldBts [ n . ID ] ; found {
if oldBt . Updated != n . IALAttr ( "updated" ) || oldBt . Type != TypeAbbr ( n . Type . String ( ) ) || oldBt . Path != tree . Path || oldBt . BoxID != tree . Box || oldBt . HPath != tree . HPath {
2023-02-02 22:35:01 +08:00
children := ChildBlockNodes ( n ) // 需要考虑子块,因为一些操作(比如移动块)后需要同时更新子块
changedNodes = append ( changedNodes , children ... )
2023-01-31 22:20:33 +08:00
}
} else {
2023-02-02 22:35:01 +08:00
children := ChildBlockNodes ( n )
changedNodes = append ( changedNodes , children ... )
2023-01-31 22:20:33 +08:00
}
2022-05-26 15:18:53 +08:00
return ast . WalkContinue
} )
2023-02-02 22:35:01 +08:00
2025-12-21 15:42:02 +08:00
if 1 > len ( changedNodes ) {
return
}
2024-06-20 22:53:27 +08:00
ids := bytes . Buffer { }
for i , n := range changedNodes {
ids . WriteString ( "'" )
ids . WriteString ( n . ID )
ids . WriteString ( "'" )
if i < len ( changedNodes ) - 1 {
ids . WriteString ( "," )
}
2023-02-02 22:35:01 +08:00
}
2024-06-20 22:53:27 +08:00
indexBlockTreeLock . Lock ( )
defer indexBlockTreeLock . Unlock ( )
2022-05-26 15:18:53 +08:00
2024-06-20 22:53:27 +08:00
tx , err := db . Begin ( )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "begin transaction failed: %s" , err )
2022-09-29 21:52:01 +08:00
return
2022-05-26 15:18:53 +08:00
}
2024-06-20 22:53:27 +08:00
sqlStmt := "DELETE FROM blocktrees WHERE id IN (" + ids . String ( ) + ")"
_ , err = tx . Exec ( sqlStmt )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
tx . Rollback ( )
logging . LogErrorf ( "sql exec [%s] failed: %s" , sqlStmt , err )
2022-05-26 15:18:53 +08:00
return
}
2025-12-21 15:42:02 +08:00
execInsertBlocktrees ( tx , tree , changedNodes )
2026-03-09 10:47:04 +08:00
if err = tx . Commit ( ) ; err != nil {
logging . LogErrorf ( "commit transaction failed: %s" , err )
}
2025-12-21 15:42:02 +08:00
}
func execInsertBlocktrees ( tx * sql . Tx , tree * parse . Tree , changedNodes [ ] * ast . Node ) {
sqlStmt := "INSERT INTO blocktrees (id, root_id, parent_id, box_id, path, hpath, updated, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
stmt , err := tx . Prepare ( sqlStmt )
if err != nil {
tx . Rollback ( )
2026-03-09 11:25:05 +08:00
logging . LogErrorf ( "exec database stmt [%s] failed: %s\n %s" , sqlStmt , err , logging . ShortStack ( ) )
if strings . Contains ( err . Error ( ) , "database disk image is malformed" ) {
closeDatabase ( )
util . RemoveDatabaseFile ( util . BlockTreeDBPath )
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "database disk image [%s] is malformed, please restart SiYuan kernel to rebuild it\n\t%s" , util . BlockTreeDBPath , err )
}
2025-12-21 15:42:02 +08:00
return
}
defer stmt . Close ( )
2024-06-20 22:53:27 +08:00
for _ , n := range changedNodes {
var parentID string
if nil != n . Parent {
parentID = n . Parent . ID
2023-01-26 17:12:38 +08:00
}
2024-09-04 04:40:50 +03:00
if _ , err = tx . Exec ( sqlStmt , n . ID , tree . ID , parentID , tree . Box , tree . Path , tree . HPath , n . IALAttr ( "updated" ) , TypeAbbr ( n . Type . String ( ) ) ) ; err != nil {
2024-06-20 22:53:27 +08:00
tx . Rollback ( )
2026-03-09 11:25:05 +08:00
logging . LogErrorf ( "exec database stmt [%s] failed: %s\n %s" , stmt , err , logging . ShortStack ( ) )
if strings . Contains ( err . Error ( ) , "database disk image is malformed" ) {
closeDatabase ( )
util . RemoveDatabaseFile ( util . BlockTreeDBPath )
logging . LogFatalf ( logging . ExitCodeUnavailableDatabase , "database disk image [%s] is malformed, please restart SiYuan kernel to rebuild it\n\t%s" , util . BlockTreeDBPath , err )
}
2023-06-18 22:23:21 +08:00
return
}
2022-05-26 15:18:53 +08:00
}
}
2024-06-20 22:53:27 +08:00
func InitBlockTree ( force bool ) {
err := initDatabase ( force )
2024-09-04 04:40:50 +03:00
if err != nil {
2024-06-20 22:53:27 +08:00
logging . LogErrorf ( "init database failed: %s" , err )
2026-01-17 21:50:31 +08:00
os . Exit ( logging . ExitCodeUnavailableDatabase )
2023-07-18 01:08:18 +08:00
return
}
2024-06-20 22:53:27 +08:00
return
2023-01-26 17:12:38 +08:00
}
2022-05-26 15:18:53 +08:00
2023-01-26 17:12:38 +08:00
func CeilTreeCount ( count int ) int {
if 100 > count {
return 100
2022-05-26 15:18:53 +08:00
}
2023-01-26 17:12:38 +08:00
for i := 1 ; i < 40 ; i ++ {
if count < i * 500 {
return i * 500
}
2022-05-26 15:18:53 +08:00
}
2023-01-26 17:12:38 +08:00
return 500 * 40 + 1
}
2023-01-16 13:50:02 +08:00
2023-01-26 17:12:38 +08:00
func CeilBlockCount ( count int ) int {
if 5000 > count {
return 5000
2022-05-26 15:18:53 +08:00
}
2022-11-22 21:11:44 +08:00
2023-01-26 17:12:38 +08:00
for i := 1 ; i < 100 ; i ++ {
if count < i * 10000 {
return i * 10000
}
}
return 10000 * 100 + 1
}
2026-03-09 11:41:04 +08:00
func queryRow ( query string , args ... interface { } ) * sql . Row {
query = strings . TrimSpace ( query )
if "" == query {
logging . LogErrorf ( "statement is empty" )
return nil
}
if isInitializingDatabase . Load ( ) {
logging . LogWarnf ( "database is initializing, ignoring query [%s]" , query )
return nil
}
if nil == db {
return nil
}
return db . QueryRow ( query , args ... )
}
func query ( query string , args ... interface { } ) ( * sql . Rows , error ) {
query = strings . TrimSpace ( query )
if "" == query {
return nil , errors . New ( "statement is empty" )
}
if isInitializingDatabase . Load ( ) {
logging . LogWarnf ( "database is initializing, ignoring query [%s]" , query )
return nil , errors . New ( "database is initializing" )
}
if nil == db {
return nil , errors . New ( "database is nil" )
}
return db . Query ( query , args ... )
}
func exec ( stmt string , args ... interface { } ) ( sql . Result , error ) {
stmt = strings . TrimSpace ( stmt )
if "" == stmt {
return nil , errors . New ( "statement is empty" )
}
if isInitializingDatabase . Load ( ) {
logging . LogWarnf ( "database is initializing, ignoring exec [%s]" , stmt )
return nil , errors . New ( "database is initializing" )
}
if nil == db {
return nil , errors . New ( "database is nil" )
}
return db . Exec ( stmt , args ... )
}