database.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194
  1. // SiYuan - Build Your Eternal Digital Garden
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package sql
  17. import (
  18. "bytes"
  19. "database/sql"
  20. "errors"
  21. "os"
  22. "path/filepath"
  23. "regexp"
  24. "runtime"
  25. "strings"
  26. "time"
  27. "unicode/utf8"
  28. "github.com/88250/gulu"
  29. "github.com/88250/lute/ast"
  30. "github.com/88250/lute/html"
  31. "github.com/88250/lute/parse"
  32. "github.com/mattn/go-sqlite3"
  33. _ "github.com/mattn/go-sqlite3"
  34. "github.com/siyuan-note/eventbus"
  35. "github.com/siyuan-note/logging"
  36. "github.com/siyuan-note/siyuan/kernel/treenode"
  37. "github.com/siyuan-note/siyuan/kernel/util"
  38. )
  39. var (
  40. db *sql.DB
  41. historyDB *sql.DB
  42. )
  43. func init() {
  44. regex := func(re, s string) (bool, error) {
  45. re = strings.ReplaceAll(re, "\\\\", "\\")
  46. return regexp.MatchString(re, s)
  47. }
  48. sql.Register("sqlite3_extended", &sqlite3.SQLiteDriver{
  49. ConnectHook: func(conn *sqlite3.SQLiteConn) error {
  50. return conn.RegisterFunc("regexp", regex, true)
  51. },
  52. })
  53. }
  54. func InitDatabase(forceRebuild bool) (err error) {
  55. ClearBlockCache()
  56. DisableCache()
  57. defer EnableCache()
  58. util.IncBootProgress(2, "Initializing database...")
  59. if forceRebuild {
  60. ClearQueue()
  61. }
  62. initDBConnection()
  63. if !forceRebuild {
  64. // 检查数据库结构版本,如果版本不一致的话说明改过表结构,需要重建
  65. if util.DatabaseVer == getDatabaseVer() {
  66. return
  67. }
  68. logging.LogInfof("the database structure is changed, rebuilding database...")
  69. }
  70. // 不存在库或者版本不一致都会走到这里
  71. closeDatabase()
  72. if gulu.File.IsExist(util.DBPath) {
  73. if err = removeDatabaseFile(); nil != err {
  74. logging.LogErrorf("remove database file [%s] failed: %s", util.DBPath, err)
  75. util.PushClearProgress()
  76. err = nil
  77. }
  78. }
  79. if gulu.File.IsExist(util.BlockTreePath) {
  80. os.RemoveAll(util.BlockTreePath)
  81. }
  82. initDBConnection()
  83. initDBTables()
  84. logging.LogInfof("reinitialized database [%s]", util.DBPath)
  85. return
  86. }
  87. func initDBTables() {
  88. db.Exec("DROP TABLE stat")
  89. _, err := db.Exec("CREATE TABLE stat (key, value)")
  90. if nil != err {
  91. logging.LogFatalf("create table [stat] failed: %s", err)
  92. }
  93. setDatabaseVer()
  94. db.Exec("DROP TABLE blocks")
  95. _, err = db.Exec("CREATE TABLE blocks (id, parent_id, root_id, hash, box, path, hpath, name, alias, memo, tag, content, fcontent, markdown, length, type, subtype, ial, sort, created, updated)")
  96. if nil != err {
  97. logging.LogFatalf("create table [blocks] failed: %s", err)
  98. }
  99. db.Exec("DROP TABLE blocks_fts")
  100. _, err = db.Exec("CREATE VIRTUAL TABLE blocks_fts USING fts5(id UNINDEXED, parent_id UNINDEXED, root_id UNINDEXED, hash UNINDEXED, box UNINDEXED, path UNINDEXED, hpath, name, alias, memo, tag, content, fcontent, markdown UNINDEXED, length UNINDEXED, type UNINDEXED, subtype UNINDEXED, ial, sort UNINDEXED, created UNINDEXED, updated UNINDEXED, tokenize=\"siyuan\")")
  101. if nil != err {
  102. logging.LogFatalf("create table [blocks_fts] failed: %s", err)
  103. }
  104. db.Exec("DROP TABLE blocks_fts_case_insensitive")
  105. _, err = db.Exec("CREATE VIRTUAL TABLE blocks_fts_case_insensitive USING fts5(id UNINDEXED, parent_id UNINDEXED, root_id UNINDEXED, hash UNINDEXED, box UNINDEXED, path UNINDEXED, hpath, name, alias, memo, tag, content, fcontent, markdown UNINDEXED, length UNINDEXED, type UNINDEXED, subtype UNINDEXED, ial, sort UNINDEXED, created UNINDEXED, updated UNINDEXED, tokenize=\"siyuan case_insensitive\")")
  106. if nil != err {
  107. logging.LogFatalf("create table [blocks_fts_case_insensitive] failed: %s", err)
  108. }
  109. db.Exec("DROP TABLE spans")
  110. _, err = db.Exec("CREATE TABLE spans (id, block_id, root_id, box, path, content, markdown, type, ial)")
  111. if nil != err {
  112. logging.LogFatalf("create table [spans] failed: %s", err)
  113. }
  114. db.Exec("DROP TABLE assets")
  115. _, err = db.Exec("CREATE TABLE assets (id, block_id, root_id, box, docpath, path, name, title, hash)")
  116. if nil != err {
  117. logging.LogFatalf("create table [assets] failed: %s", err)
  118. }
  119. db.Exec("DROP TABLE attributes")
  120. _, err = db.Exec("CREATE TABLE attributes (id, name, value, type, block_id, root_id, box, path)")
  121. if nil != err {
  122. logging.LogFatalf("create table [attributes] failed: %s", err)
  123. }
  124. db.Exec("DROP TABLE refs")
  125. _, err = db.Exec("CREATE TABLE refs (id, def_block_id, def_block_parent_id, def_block_root_id, def_block_path, block_id, root_id, box, path, content, markdown, type)")
  126. if nil != err {
  127. logging.LogFatalf("create table [refs] failed: %s", err)
  128. }
  129. db.Exec("DROP TABLE file_annotation_refs")
  130. _, err = db.Exec("CREATE TABLE file_annotation_refs (id, file_path, annotation_id, block_id, root_id, box, path, content, type)")
  131. if nil != err {
  132. logging.LogFatalf("create table [refs] failed: %s", err)
  133. }
  134. }
  135. func InitHistoryDatabase(forceRebuild bool) {
  136. initHistoryDBConnection()
  137. if !forceRebuild && gulu.File.IsExist(util.HistoryDBPath) {
  138. return
  139. }
  140. historyDB.Close()
  141. if err := os.RemoveAll(util.HistoryDBPath); nil != err {
  142. logging.LogErrorf("remove history database file [%s] failed: %s", util.HistoryDBPath, err)
  143. return
  144. }
  145. initHistoryDBConnection()
  146. initHistoryDBTables()
  147. }
  148. func initHistoryDBConnection() {
  149. if nil != historyDB {
  150. historyDB.Close()
  151. }
  152. dsn := util.HistoryDBPath + "?_journal_mode=OFF" +
  153. "&_synchronous=OFF" +
  154. "&_mmap_size=2684354560" +
  155. "&_secure_delete=OFF" +
  156. "&_cache_size=-20480" +
  157. "&_page_size=32768" +
  158. "&_busy_timeout=7000" +
  159. "&_ignore_check_constraints=ON" +
  160. "&_temp_store=MEMORY" +
  161. "&_case_sensitive_like=OFF" +
  162. "&_locking_mode=EXCLUSIVE"
  163. var err error
  164. historyDB, err = sql.Open("sqlite3_extended", dsn)
  165. if nil != err {
  166. logging.LogFatalf("create database failed: %s", err)
  167. }
  168. historyDB.SetMaxIdleConns(1)
  169. historyDB.SetMaxOpenConns(1)
  170. historyDB.SetConnMaxLifetime(365 * 24 * time.Hour)
  171. }
  172. func initHistoryDBTables() {
  173. historyDB.Exec("DROP TABLE histories_fts_case_insensitive")
  174. _, err := historyDB.Exec("CREATE VIRTUAL TABLE histories_fts_case_insensitive USING fts5(type UNINDEXED, op UNINDEXED, title, content, path UNINDEXED, created UNINDEXED, tokenize=\"siyuan case_insensitive\")")
  175. if nil != err {
  176. logging.LogFatalf("create table [histories_fts_case_insensitive] failed: %s", err)
  177. }
  178. }
  179. func initDBConnection() {
  180. if nil != db {
  181. closeDatabase()
  182. }
  183. dsn := util.DBPath + "?_journal_mode=WAL" +
  184. "&_synchronous=OFF" +
  185. "&_mmap_size=2684354560" +
  186. "&_secure_delete=OFF" +
  187. "&_cache_size=-20480" +
  188. "&_page_size=32768" +
  189. "&_busy_timeout=7000" +
  190. "&_ignore_check_constraints=ON" +
  191. "&_temp_store=MEMORY" +
  192. "&_case_sensitive_like=OFF"
  193. var err error
  194. db, err = sql.Open("sqlite3_extended", dsn)
  195. if nil != err {
  196. logging.LogFatalf("create database failed: %s", err)
  197. }
  198. db.SetMaxIdleConns(20)
  199. db.SetMaxOpenConns(20)
  200. db.SetConnMaxLifetime(365 * 24 * time.Hour)
  201. }
  202. var caseSensitive bool
  203. func SetCaseSensitive(b bool) {
  204. caseSensitive = b
  205. if b {
  206. db.Exec("PRAGMA case_sensitive_like = ON;")
  207. } else {
  208. db.Exec("PRAGMA case_sensitive_like = OFF;")
  209. }
  210. }
  211. func refsFromTree(tree *parse.Tree) (refs []*Ref, fileAnnotationRefs []*FileAnnotationRef) {
  212. ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
  213. if entering {
  214. return ast.WalkContinue
  215. }
  216. if treenode.IsBlockRef(n) {
  217. ref := buildRef(tree, n)
  218. refs = append(refs, ref)
  219. } else if treenode.IsFileAnnotationRef(n) {
  220. pathID := n.TextMarkFileAnnotationRefID
  221. idx := strings.LastIndex(pathID, "/")
  222. if -1 == idx {
  223. return ast.WalkContinue
  224. }
  225. filePath := pathID[:idx]
  226. annotationID := pathID[idx+1:]
  227. anchor := n.TextMarkTextContent
  228. text := filePath
  229. if "" != anchor {
  230. text = anchor
  231. }
  232. parentBlock := treenode.ParentBlock(n)
  233. ref := &FileAnnotationRef{
  234. ID: ast.NewNodeID(),
  235. FilePath: filePath,
  236. AnnotationID: annotationID,
  237. BlockID: parentBlock.ID,
  238. RootID: tree.ID,
  239. Box: tree.Box,
  240. Path: tree.Path,
  241. Content: text,
  242. Type: treenode.TypeAbbr(n.Type.String()),
  243. }
  244. fileAnnotationRefs = append(fileAnnotationRefs, ref)
  245. } else if treenode.IsEmbedBlockRef(n) {
  246. ref := buildEmbedRef(tree, n)
  247. refs = append(refs, ref)
  248. }
  249. return ast.WalkContinue
  250. })
  251. return
  252. }
  253. func buildRef(tree *parse.Tree, refNode *ast.Node) *Ref {
  254. markdown := treenode.ExportNodeStdMd(refNode, luteEngine)
  255. defBlockID, text, _ := treenode.GetBlockRef(refNode)
  256. var defBlockParentID, defBlockRootID, defBlockPath string
  257. defBlock := treenode.GetBlockTree(defBlockID)
  258. if nil != defBlock {
  259. defBlockParentID = defBlock.ParentID
  260. defBlockRootID = defBlock.RootID
  261. defBlockPath = defBlock.Path
  262. }
  263. parentBlock := treenode.ParentBlock(refNode)
  264. return &Ref{
  265. ID: ast.NewNodeID(),
  266. DefBlockID: defBlockID,
  267. DefBlockParentID: defBlockParentID,
  268. DefBlockRootID: defBlockRootID,
  269. DefBlockPath: defBlockPath,
  270. BlockID: parentBlock.ID,
  271. RootID: tree.ID,
  272. Box: tree.Box,
  273. Path: tree.Path,
  274. Content: text,
  275. Markdown: markdown,
  276. Type: treenode.TypeAbbr(refNode.Type.String()),
  277. }
  278. }
  279. func buildEmbedRef(tree *parse.Tree, embedNode *ast.Node) *Ref {
  280. defBlockID := getEmbedRef(embedNode)
  281. var defBlockParentID, defBlockRootID, defBlockPath string
  282. defBlock := treenode.GetBlockTree(defBlockID)
  283. if nil != defBlock {
  284. defBlockParentID = defBlock.ParentID
  285. defBlockRootID = defBlock.RootID
  286. defBlockPath = defBlock.Path
  287. }
  288. return &Ref{
  289. ID: ast.NewNodeID(),
  290. DefBlockID: defBlockID,
  291. DefBlockParentID: defBlockParentID,
  292. DefBlockRootID: defBlockRootID,
  293. DefBlockPath: defBlockPath,
  294. BlockID: embedNode.ID,
  295. RootID: tree.ID,
  296. Box: tree.Box,
  297. Path: tree.Path,
  298. Content: "", // 通过嵌入块构建引用时定义块可能还没有入库,所以这里统一不填充内容
  299. Markdown: "",
  300. Type: treenode.TypeAbbr(embedNode.Type.String()),
  301. }
  302. }
  303. func getEmbedRef(embedNode *ast.Node) (queryBlockID string) {
  304. queryBlockID = treenode.GetEmbedBlockRef(embedNode)
  305. return
  306. }
  307. func fromTree(node *ast.Node, tree *parse.Tree) (blocks []*Block, spans []*Span, assets []*Asset, attributes []*Attribute) {
  308. rootID := tree.Root.ID
  309. boxID := tree.Box
  310. p := tree.Path
  311. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  312. if !entering {
  313. return ast.WalkContinue
  314. }
  315. // 构造行级元素
  316. spanBlocks, spanSpans, spanAssets, spanAttrs, walkStatus := buildSpanFromNode(n, tree, rootID, boxID, p)
  317. if 0 < len(spanBlocks) {
  318. blocks = append(blocks, spanBlocks...)
  319. }
  320. if 0 < len(spanSpans) {
  321. spans = append(spans, spanSpans...)
  322. }
  323. if 0 < len(spanAssets) {
  324. assets = append(assets, spanAssets...)
  325. }
  326. if 0 < len(spanAttrs) {
  327. attributes = append(attributes, spanAttrs...)
  328. }
  329. // 构造属性
  330. attrs := buildAttributeFromNode(n, rootID, boxID, p)
  331. if 0 < len(attrs) {
  332. attributes = append(attributes, attrs...)
  333. }
  334. if -1 != walkStatus {
  335. return walkStatus
  336. }
  337. // 构造块级元素
  338. if "" == n.ID || !n.IsBlock() {
  339. return ast.WalkContinue
  340. }
  341. b, attrs := buildBlockFromNode(n, tree)
  342. blocks = append(blocks, b)
  343. if 0 < len(attrs) {
  344. attributes = append(attributes, attrs...)
  345. }
  346. return ast.WalkContinue
  347. })
  348. return
  349. }
  350. func buildAttributeFromNode(n *ast.Node, rootID, boxID, p string) (attributes []*Attribute) {
  351. switch n.Type {
  352. case ast.NodeKramdownSpanIAL:
  353. parentBlock := treenode.ParentBlock(n)
  354. attrs := parse.IALValMap(n)
  355. for name, val := range attrs {
  356. if !isAttr(name) {
  357. continue
  358. }
  359. attr := &Attribute{
  360. ID: ast.NewNodeID(),
  361. Name: name,
  362. Value: val,
  363. Type: "s",
  364. BlockID: parentBlock.ID,
  365. RootID: rootID,
  366. Box: boxID,
  367. Path: p,
  368. }
  369. attributes = append(attributes, attr)
  370. }
  371. case ast.NodeKramdownBlockIAL:
  372. attrs := parse.IALValMap(n)
  373. for name, val := range attrs {
  374. if !isAttr(name) {
  375. continue
  376. }
  377. attr := &Attribute{
  378. ID: ast.NewNodeID(),
  379. Name: name,
  380. Value: val,
  381. Type: "b",
  382. BlockID: n.ID,
  383. RootID: rootID,
  384. Box: boxID,
  385. Path: p,
  386. }
  387. attributes = append(attributes, attr)
  388. }
  389. }
  390. return
  391. }
  392. func isAttr(name string) bool {
  393. return strings.HasPrefix(name, "custom-") || "name" == name || "alias" == name || "memo" == name || "bookmark" == name || "fold" == name || "heading-fold" == name || "style" == name
  394. }
  395. func buildSpanFromNode(n *ast.Node, tree *parse.Tree, rootID, boxID, p string) (blocks []*Block, spans []*Span, assets []*Asset, attributes []*Attribute, walkStatus ast.WalkStatus) {
  396. boxLocalPath := filepath.Join(util.DataDir, boxID)
  397. docDirLocalPath := filepath.Join(boxLocalPath, p)
  398. switch n.Type {
  399. case ast.NodeImage:
  400. text := n.Text()
  401. markdown := treenode.ExportNodeStdMd(n, luteEngine)
  402. parentBlock := treenode.ParentBlock(n)
  403. span := &Span{
  404. ID: ast.NewNodeID(),
  405. BlockID: parentBlock.ID,
  406. RootID: rootID,
  407. Box: boxID,
  408. Path: p,
  409. Content: text,
  410. Markdown: markdown,
  411. Type: treenode.TypeAbbr(n.Type.String()),
  412. IAL: treenode.IALStr(n),
  413. }
  414. spans = append(spans, span)
  415. walkStatus = ast.WalkSkipChildren
  416. destNode := n.ChildByType(ast.NodeLinkDest)
  417. if nil == destNode {
  418. return
  419. }
  420. // assetsLinkDestsInTree
  421. if !util.IsAssetLinkDest(destNode.Tokens) {
  422. return
  423. }
  424. dest := gulu.Str.FromBytes(destNode.Tokens)
  425. var title string
  426. if titleNode := n.ChildByType(ast.NodeLinkTitle); nil != titleNode {
  427. title = gulu.Str.FromBytes(titleNode.Tokens)
  428. }
  429. var hash string
  430. var hashErr error
  431. if lp := assetLocalPath(dest, boxLocalPath, docDirLocalPath); "" != lp {
  432. if !gulu.File.IsDir(lp) {
  433. hash, hashErr = util.GetEtag(lp)
  434. if nil != hashErr {
  435. logging.LogErrorf("calc asset [%s] hash failed: %s", lp, hashErr)
  436. }
  437. }
  438. }
  439. name, _ := util.LastID(dest)
  440. asset := &Asset{
  441. ID: ast.NewNodeID(),
  442. BlockID: parentBlock.ID,
  443. RootID: rootID,
  444. Box: boxID,
  445. DocPath: p,
  446. Path: dest,
  447. Name: name,
  448. Title: title,
  449. Hash: hash,
  450. }
  451. assets = append(assets, asset)
  452. return
  453. case ast.NodeTextMark:
  454. typ := treenode.TypeAbbr(n.Type.String()) + " " + n.TextMarkType
  455. text := n.Content()
  456. markdown := treenode.ExportNodeStdMd(n, luteEngine)
  457. parentBlock := treenode.ParentBlock(n)
  458. span := &Span{
  459. ID: ast.NewNodeID(),
  460. BlockID: parentBlock.ID,
  461. RootID: rootID,
  462. Box: boxID,
  463. Path: p,
  464. Content: text,
  465. Markdown: markdown,
  466. Type: typ,
  467. IAL: treenode.IALStr(n),
  468. }
  469. spans = append(spans, span)
  470. if n.IsTextMarkType("a") {
  471. dest := n.TextMarkAHref
  472. if util.IsAssetLinkDest([]byte(dest)) {
  473. var title string
  474. if titleNode := n.ChildByType(ast.NodeLinkTitle); nil != titleNode {
  475. title = gulu.Str.FromBytes(titleNode.Tokens)
  476. }
  477. var hash string
  478. var hashErr error
  479. if lp := assetLocalPath(dest, boxLocalPath, docDirLocalPath); "" != lp {
  480. if !gulu.File.IsDir(lp) {
  481. hash, hashErr = util.GetEtag(lp)
  482. if nil != hashErr {
  483. logging.LogErrorf("calc asset [%s] hash failed: %s", lp, hashErr)
  484. }
  485. }
  486. }
  487. name, _ := util.LastID(dest)
  488. asset := &Asset{
  489. ID: ast.NewNodeID(),
  490. BlockID: parentBlock.ID,
  491. RootID: rootID,
  492. Box: boxID,
  493. DocPath: p,
  494. Path: dest,
  495. Name: name,
  496. Title: title,
  497. Hash: hash,
  498. }
  499. assets = append(assets, asset)
  500. }
  501. }
  502. walkStatus = ast.WalkSkipChildren
  503. return
  504. case ast.NodeDocument:
  505. if asset := docTitleImgAsset(n); nil != asset {
  506. assets = append(assets, asset)
  507. }
  508. if tags := docTagSpans(n); 0 < len(tags) {
  509. spans = append(spans, tags...)
  510. }
  511. case ast.NodeInlineHTML, ast.NodeHTMLBlock, ast.NodeIFrame, ast.NodeWidget, ast.NodeAudio, ast.NodeVideo:
  512. nodes, err := html.ParseFragment(bytes.NewReader(n.Tokens), &html.Node{Type: html.ElementNode})
  513. if nil != err {
  514. logging.LogErrorf("parse HTML failed: %s", err)
  515. walkStatus = ast.WalkContinue
  516. return
  517. }
  518. if 1 > len(nodes) &&
  519. ast.NodeHTMLBlock != n.Type { // HTML 块若内容为空时无法在数据库中查询到 https://github.com/siyuan-note/siyuan/issues/4691
  520. walkStatus = ast.WalkContinue
  521. return
  522. }
  523. if ast.NodeHTMLBlock == n.Type || ast.NodeIFrame == n.Type || ast.NodeWidget == n.Type || ast.NodeAudio == n.Type || ast.NodeVideo == n.Type {
  524. b, attrs := buildBlockFromNode(n, tree)
  525. blocks = append(blocks, b)
  526. attributes = append(attributes, attrs...)
  527. }
  528. if 1 > len(nodes) {
  529. walkStatus = ast.WalkContinue
  530. return
  531. }
  532. var src []byte
  533. for _, attr := range nodes[0].Attr {
  534. if "src" == attr.Key || "data-assets" == attr.Key || "custom-data-assets" == attr.Key {
  535. src = gulu.Str.ToBytes(attr.Val)
  536. break
  537. }
  538. }
  539. if 1 > len(src) {
  540. walkStatus = ast.WalkContinue
  541. return
  542. }
  543. if !util.IsAssetLinkDest(src) {
  544. walkStatus = ast.WalkContinue
  545. return
  546. }
  547. dest := string(src)
  548. var hash string
  549. var hashErr error
  550. if lp := assetLocalPath(dest, boxLocalPath, docDirLocalPath); "" != lp {
  551. hash, hashErr = util.GetEtag(lp)
  552. if nil != hashErr {
  553. logging.LogErrorf("calc asset [%s] hash failed: %s", lp, hashErr)
  554. }
  555. }
  556. parentBlock := treenode.ParentBlock(n)
  557. if ast.NodeInlineHTML != n.Type {
  558. parentBlock = n
  559. }
  560. name, _ := util.LastID(dest)
  561. asset := &Asset{
  562. ID: ast.NewNodeID(),
  563. BlockID: parentBlock.ID,
  564. RootID: rootID,
  565. Box: boxID,
  566. DocPath: p,
  567. Path: dest,
  568. Name: name,
  569. Title: "",
  570. Hash: hash,
  571. }
  572. assets = append(assets, asset)
  573. walkStatus = ast.WalkSkipChildren
  574. return
  575. }
  576. walkStatus = -1
  577. return
  578. }
  579. func BuildBlockFromNode(n *ast.Node, tree *parse.Tree) (block *Block) {
  580. block, _ = buildBlockFromNode(n, tree)
  581. return
  582. }
  583. func buildBlockFromNode(n *ast.Node, tree *parse.Tree) (block *Block, attributes []*Attribute) {
  584. boxID := tree.Box
  585. p := tree.Path
  586. rootID := tree.Root.ID
  587. name := html.UnescapeString(n.IALAttr("name"))
  588. alias := html.UnescapeString(n.IALAttr("alias"))
  589. memo := html.UnescapeString(n.IALAttr("memo"))
  590. tag := tagFromNode(n)
  591. var content, fcontent, markdown, parentID string
  592. ialContent := treenode.IALStr(n)
  593. hash := treenode.NodeHash(n, tree, luteEngine)
  594. var length int
  595. if ast.NodeDocument == n.Type {
  596. content = n.IALAttr("title")
  597. fcontent = content
  598. length = utf8.RuneCountInString(fcontent)
  599. } else if n.IsContainerBlock() {
  600. markdown, content = treenode.NodeStaticMdContent(n, luteEngine)
  601. fc := treenode.FirstLeafBlock(n)
  602. fcontent = treenode.NodeStaticContent(fc, nil)
  603. parentID = n.Parent.ID
  604. // 将标题块作为父节点
  605. if h := heading(n); nil != h {
  606. parentID = h.ID
  607. }
  608. length = utf8.RuneCountInString(fcontent)
  609. } else {
  610. markdown, content = treenode.NodeStaticMdContent(n, luteEngine)
  611. parentID = n.Parent.ID
  612. // 将标题块作为父节点
  613. if h := heading(n); nil != h {
  614. parentID = h.ID
  615. }
  616. length = utf8.RuneCountInString(content)
  617. }
  618. block = &Block{
  619. ID: n.ID,
  620. ParentID: parentID,
  621. RootID: rootID,
  622. Hash: hash,
  623. Box: boxID,
  624. Path: p,
  625. HPath: tree.HPath,
  626. Name: name,
  627. Alias: alias,
  628. Memo: memo,
  629. Tag: tag,
  630. Content: content,
  631. FContent: fcontent,
  632. Markdown: markdown,
  633. Length: length,
  634. Type: treenode.TypeAbbr(n.Type.String()),
  635. SubType: treenode.SubTypeAbbr(n),
  636. IAL: ialContent,
  637. Sort: nSort(n),
  638. Created: util.TimeFromID(n.ID),
  639. Updated: n.IALAttr("updated"),
  640. }
  641. attrs := parse.IAL2Map(n.KramdownIAL)
  642. for attrName, attrVal := range attrs {
  643. if !isAttr(attrName) {
  644. continue
  645. }
  646. attr := &Attribute{
  647. ID: ast.NewNodeID(),
  648. Name: attrName,
  649. Value: attrVal,
  650. Type: "b",
  651. BlockID: n.ID,
  652. RootID: rootID,
  653. Box: boxID,
  654. Path: p,
  655. }
  656. attributes = append(attributes, attr)
  657. }
  658. return
  659. }
  660. func tagFromNode(node *ast.Node) (ret string) {
  661. tagBuilder := bytes.Buffer{}
  662. if ast.NodeDocument == node.Type {
  663. tagIAL := html.UnescapeString(node.IALAttr("tags"))
  664. tags := strings.Split(tagIAL, ",")
  665. for _, t := range tags {
  666. t = strings.TrimSpace(t)
  667. if "" == t {
  668. continue
  669. }
  670. tagBuilder.WriteString("#")
  671. tagBuilder.WriteString(t)
  672. tagBuilder.WriteString("# ")
  673. }
  674. return strings.TrimSpace(tagBuilder.String())
  675. }
  676. ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
  677. if !entering {
  678. return ast.WalkContinue
  679. }
  680. if n.IsTextMarkType("tag") {
  681. tagBuilder.WriteString("#")
  682. tagBuilder.WriteString(n.Text())
  683. tagBuilder.WriteString("# ")
  684. }
  685. return ast.WalkContinue
  686. })
  687. return strings.TrimSpace(tagBuilder.String())
  688. }
  689. func heading(node *ast.Node) *ast.Node {
  690. currentLevel := 16
  691. if ast.NodeHeading == node.Type {
  692. currentLevel = node.HeadingLevel
  693. } else if ast.NodeSuperBlock == node.Type {
  694. superBlockHeading := treenode.SuperBlockHeading(node)
  695. if nil != superBlockHeading {
  696. node = superBlockHeading
  697. currentLevel = node.HeadingLevel
  698. }
  699. }
  700. for prev := node.Previous; nil != prev; prev = prev.Previous {
  701. if ast.NodeHeading == prev.Type {
  702. if prev.HeadingLevel < currentLevel {
  703. return prev
  704. }
  705. }
  706. }
  707. return nil
  708. }
  709. func DeleteBlockByIDs(tx *sql.Tx, ids []string) (err error) {
  710. return deleteBlocksByIDs(tx, ids)
  711. }
  712. func deleteByBoxTx(tx *sql.Tx, box string) (err error) {
  713. if err = deleteBlocksByBoxTx(tx, box); nil != err {
  714. return
  715. }
  716. if err = deleteSpansByBoxTx(tx, box); nil != err {
  717. return
  718. }
  719. if err = deleteAssetsByBoxTx(tx, box); nil != err {
  720. return
  721. }
  722. if err = deleteAttributesByBoxTx(tx, box); nil != err {
  723. return
  724. }
  725. if err = deleteBlockRefsByBoxTx(tx, box); nil != err {
  726. return
  727. }
  728. if err = deleteFileAnnotationRefsByBoxTx(tx, box); nil != err {
  729. return
  730. }
  731. return
  732. }
  733. func deleteBlocksByIDs(tx *sql.Tx, ids []string) (err error) {
  734. in := bytes.Buffer{}
  735. in.Grow(4096)
  736. in.WriteString("(")
  737. for i, id := range ids {
  738. in.WriteString("'")
  739. in.WriteString(id)
  740. in.WriteString("'")
  741. if i < len(ids)-1 {
  742. in.WriteString(",")
  743. }
  744. removeBlockCache(id)
  745. }
  746. in.WriteString(")")
  747. stmt := "DELETE FROM blocks WHERE id IN " + in.String()
  748. if err = execStmtTx(tx, stmt); nil != err {
  749. return
  750. }
  751. stmt = "DELETE FROM blocks_fts WHERE id IN " + in.String()
  752. if err = execStmtTx(tx, stmt); nil != err {
  753. return
  754. }
  755. stmt = "DELETE FROM blocks_fts_case_insensitive WHERE id IN " + in.String()
  756. if err = execStmtTx(tx, stmt); nil != err {
  757. return
  758. }
  759. return
  760. }
  761. func deleteBlocksByBoxTx(tx *sql.Tx, box string) (err error) {
  762. stmt := "DELETE FROM blocks WHERE box = ?"
  763. if err = execStmtTx(tx, stmt, box); nil != err {
  764. return
  765. }
  766. stmt = "DELETE FROM blocks_fts WHERE box = ?"
  767. if err = execStmtTx(tx, stmt, box); nil != err {
  768. return
  769. }
  770. stmt = "DELETE FROM blocks_fts_case_insensitive WHERE box = ?"
  771. if err = execStmtTx(tx, stmt, box); nil != err {
  772. return
  773. }
  774. ClearBlockCache()
  775. return
  776. }
  777. func deleteSpansByPathTx(tx *sql.Tx, box, path string) (err error) {
  778. stmt := "DELETE FROM spans WHERE box = ? AND path = ?"
  779. err = execStmtTx(tx, stmt, box, path)
  780. return
  781. }
  782. func deleteSpansByRootID(tx *sql.Tx, rootID string) (err error) {
  783. stmt := "DELETE FROM spans WHERE root_id =?"
  784. err = execStmtTx(tx, stmt, rootID)
  785. return
  786. }
  787. func deleteSpansByBoxTx(tx *sql.Tx, box string) (err error) {
  788. stmt := "DELETE FROM spans WHERE box = ?"
  789. err = execStmtTx(tx, stmt, box)
  790. return
  791. }
  792. func deleteAssetsByPathTx(tx *sql.Tx, box, path string) (err error) {
  793. stmt := "DELETE FROM assets WHERE box = ? AND docpath = ?"
  794. err = execStmtTx(tx, stmt, box, path)
  795. return
  796. }
  797. func deleteAttributeByBlockID(tx *sql.Tx, blockID string) (err error) {
  798. stmt := "DELETE FROM attributes WHERE block_id = ?"
  799. err = execStmtTx(tx, stmt, blockID)
  800. return
  801. }
  802. func deleteAttributesByPathTx(tx *sql.Tx, box, path string) (err error) {
  803. stmt := "DELETE FROM attributes WHERE box = ? AND path = ?"
  804. err = execStmtTx(tx, stmt, box, path)
  805. return
  806. }
  807. func deleteAssetsByBoxTx(tx *sql.Tx, box string) (err error) {
  808. stmt := "DELETE FROM assets WHERE box = ?"
  809. err = execStmtTx(tx, stmt, box)
  810. return
  811. }
  812. func deleteAttributesByBoxTx(tx *sql.Tx, box string) (err error) {
  813. stmt := "DELETE FROM attributes WHERE box = ?"
  814. err = execStmtTx(tx, stmt, box)
  815. return
  816. }
  817. func deleteRefsByPath(tx *sql.Tx, box, path string) (err error) {
  818. stmt := "DELETE FROM refs WHERE box = ? AND path = ?"
  819. err = execStmtTx(tx, stmt, box, path)
  820. return
  821. }
  822. func deleteRefsByPathTx(tx *sql.Tx, box, path string) (err error) {
  823. stmt := "DELETE FROM refs WHERE box = ? AND path = ?"
  824. err = execStmtTx(tx, stmt, box, path)
  825. return
  826. }
  827. func deleteRefsByBoxTx(tx *sql.Tx, box string) (err error) {
  828. if err = deleteFileAnnotationRefsByBoxTx(tx, box); nil != err {
  829. return
  830. }
  831. return deleteBlockRefsByBoxTx(tx, box)
  832. }
  833. func deleteBlockRefsByBoxTx(tx *sql.Tx, box string) (err error) {
  834. stmt := "DELETE FROM refs WHERE box = ?"
  835. err = execStmtTx(tx, stmt, box)
  836. return
  837. }
  838. func deleteFileAnnotationRefsByPath(tx *sql.Tx, box, path string) (err error) {
  839. stmt := "DELETE FROM file_annotation_refs WHERE box = ? AND path = ?"
  840. err = execStmtTx(tx, stmt, box, path)
  841. return
  842. }
  843. func deleteFileAnnotationRefsByPathTx(tx *sql.Tx, box, path string) (err error) {
  844. stmt := "DELETE FROM file_annotation_refs WHERE box = ? AND path = ?"
  845. err = execStmtTx(tx, stmt, box, path)
  846. return
  847. }
  848. func deleteFileAnnotationRefsByBoxTx(tx *sql.Tx, box string) (err error) {
  849. stmt := "DELETE FROM file_annotation_refs WHERE box = ?"
  850. err = execStmtTx(tx, stmt, box)
  851. return
  852. }
  853. func deleteByRootID(tx *sql.Tx, rootID string, context map[string]interface{}) (err error) {
  854. stmt := "DELETE FROM blocks WHERE root_id = ?"
  855. if err = execStmtTx(tx, stmt, rootID); nil != err {
  856. return
  857. }
  858. stmt = "DELETE FROM spans WHERE root_id = ?"
  859. if err = execStmtTx(tx, stmt, rootID); nil != err {
  860. return
  861. }
  862. stmt = "DELETE FROM assets WHERE root_id = ?"
  863. if err = execStmtTx(tx, stmt, rootID); nil != err {
  864. return
  865. }
  866. stmt = "DELETE FROM refs WHERE root_id = ?"
  867. if err = execStmtTx(tx, stmt, rootID); nil != err {
  868. return
  869. }
  870. stmt = "DELETE FROM file_annotation_refs WHERE root_id = ?"
  871. if err = execStmtTx(tx, stmt, rootID); nil != err {
  872. return
  873. }
  874. ClearBlockCache()
  875. eventbus.Publish(eventbus.EvtSQLDeleteBlocks, context, rootID)
  876. return
  877. }
  878. func batchDeleteByPathPrefix(tx *sql.Tx, boxID, pathPrefix string) (err error) {
  879. stmt := "DELETE FROM blocks WHERE box = ? AND path LIKE ?"
  880. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  881. return
  882. }
  883. stmt = "DELETE FROM blocks_fts WHERE box = ? AND path LIKE ?"
  884. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  885. return
  886. }
  887. stmt = "DELETE FROM blocks_fts_case_insensitive WHERE box = ? AND path LIKE ?"
  888. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  889. return
  890. }
  891. stmt = "DELETE FROM spans WHERE box = ? AND path LIKE ?"
  892. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  893. return
  894. }
  895. stmt = "DELETE FROM assets WHERE box = ? AND docpath LIKE ?"
  896. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  897. return
  898. }
  899. stmt = "DELETE FROM refs WHERE box = ? AND path LIKE ?"
  900. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  901. return
  902. }
  903. stmt = "DELETE FROM file_annotation_refs WHERE box = ? AND path LIKE ?"
  904. if err = execStmtTx(tx, stmt, boxID, pathPrefix+"%"); nil != err {
  905. return
  906. }
  907. ClearBlockCache()
  908. return
  909. }
  910. func batchUpdateHPath(tx *sql.Tx, boxID, rootID, oldHPath, newHPath string) (err error) {
  911. stmt := "UPDATE blocks SET hpath = ? WHERE box = ? AND root_id = ? AND hpath = ?"
  912. if err = execStmtTx(tx, stmt, newHPath, boxID, rootID, oldHPath); nil != err {
  913. return
  914. }
  915. stmt = "UPDATE blocks_fts SET hpath = ? WHERE box = ? AND root_id = ? AND hpath = ?"
  916. if err = execStmtTx(tx, stmt, newHPath, boxID, rootID, oldHPath); nil != err {
  917. return
  918. }
  919. stmt = "UPDATE blocks_fts_case_insensitive SET hpath = ? WHERE box = ? AND root_id = ? AND hpath = ?"
  920. if err = execStmtTx(tx, stmt, newHPath, boxID, rootID, oldHPath); nil != err {
  921. return
  922. }
  923. ClearBlockCache()
  924. return
  925. }
  926. func CloseDatabase() {
  927. if err := closeDatabase(); nil != err {
  928. logging.LogErrorf("close database failed: %s", err)
  929. return
  930. }
  931. if err := historyDB.Close(); nil != err {
  932. logging.LogErrorf("close history database failed: %s", err)
  933. return
  934. }
  935. logging.LogInfof("closed database")
  936. }
  937. func queryRow(query string, args ...interface{}) *sql.Row {
  938. query = strings.TrimSpace(query)
  939. if "" == query {
  940. logging.LogErrorf("statement is empty")
  941. return nil
  942. }
  943. return db.QueryRow(query, args...)
  944. }
  945. func query(query string, args ...interface{}) (*sql.Rows, error) {
  946. query = strings.TrimSpace(query)
  947. if "" == query {
  948. return nil, errors.New("statement is empty")
  949. }
  950. return db.Query(query, args...)
  951. }
  952. func beginTx() (tx *sql.Tx, err error) {
  953. if tx, err = db.Begin(); nil != err {
  954. logging.LogErrorf("begin tx failed: %s\n %s", err, logging.ShortStack())
  955. if strings.Contains(err.Error(), "database is locked") {
  956. os.Exit(util.ExitCodeReadOnlyDatabase)
  957. }
  958. }
  959. return
  960. }
  961. func BeginHistoryTx() (tx *sql.Tx, err error) {
  962. if tx, err = historyDB.Begin(); nil != err {
  963. logging.LogErrorf("begin history tx failed: %s\n %s", err, logging.ShortStack())
  964. if strings.Contains(err.Error(), "database is locked") {
  965. os.Exit(util.ExitCodeReadOnlyDatabase)
  966. }
  967. }
  968. return
  969. }
  970. func CommitHistoryTx(tx *sql.Tx) (err error) {
  971. if nil == tx {
  972. logging.LogErrorf("tx is nil")
  973. return errors.New("tx is nil")
  974. }
  975. if err = tx.Commit(); nil != err {
  976. logging.LogErrorf("commit tx failed: %s\n %s", err, logging.ShortStack())
  977. }
  978. return
  979. }
  980. func commitTx(tx *sql.Tx) (err error) {
  981. if nil == tx {
  982. logging.LogErrorf("tx is nil")
  983. return errors.New("tx is nil")
  984. }
  985. if err = tx.Commit(); nil != err {
  986. logging.LogErrorf("commit tx failed: %s\n %s", err, logging.ShortStack())
  987. }
  988. return
  989. }
  990. func prepareExecInsertTx(tx *sql.Tx, stmtSQL string, args []interface{}) (err error) {
  991. stmt, err := tx.Prepare(stmtSQL)
  992. if nil != err {
  993. return
  994. }
  995. if _, err = stmt.Exec(args...); nil != err {
  996. logging.LogErrorf("exec database stmt [%s] failed: %s", stmtSQL, err)
  997. return
  998. }
  999. return
  1000. }
  1001. func execStmtTx(tx *sql.Tx, stmt string, args ...interface{}) (err error) {
  1002. if _, err = tx.Exec(stmt, args...); nil != err {
  1003. if strings.Contains(err.Error(), "database disk image is malformed") {
  1004. tx.Rollback()
  1005. closeDatabase()
  1006. removeDatabaseFile()
  1007. logging.LogFatalf("database disk image [%s] is malformed, please restart SiYuan kernel to rebuild it", util.DBPath)
  1008. }
  1009. logging.LogErrorf("exec database stmt [%s] failed: %s\n %s", stmt, err, logging.ShortStack())
  1010. return
  1011. }
  1012. return
  1013. }
  1014. func nSort(n *ast.Node) int {
  1015. switch n.Type {
  1016. // 以下为块级元素
  1017. case ast.NodeDocument:
  1018. return 0
  1019. case ast.NodeHeading:
  1020. return 5
  1021. case ast.NodeParagraph:
  1022. return 10
  1023. case ast.NodeCodeBlock:
  1024. return 10
  1025. case ast.NodeMathBlock:
  1026. return 10
  1027. case ast.NodeTable:
  1028. return 10
  1029. case ast.NodeHTMLBlock:
  1030. return 10
  1031. case ast.NodeList:
  1032. return 20
  1033. case ast.NodeListItem:
  1034. return 20
  1035. case ast.NodeBlockquote:
  1036. return 20
  1037. case ast.NodeSuperBlock:
  1038. return 30
  1039. case ast.NodeText, ast.NodeTextMark:
  1040. if n.IsTextMarkType("tag") {
  1041. return 205
  1042. }
  1043. return 200
  1044. }
  1045. return 100
  1046. }
  1047. func ialAttr(ial, name string) (ret string) {
  1048. idx := strings.Index(ial, name)
  1049. if 0 > idx {
  1050. return ""
  1051. }
  1052. ret = ial[idx+len(name)+2:]
  1053. ret = ret[:strings.Index(ret, "\"")]
  1054. return
  1055. }
  1056. func removeDatabaseFile() (err error) {
  1057. err = os.RemoveAll(util.DBPath)
  1058. if nil != err {
  1059. return
  1060. }
  1061. err = os.RemoveAll(util.DBPath + "-shm")
  1062. if nil != err {
  1063. return
  1064. }
  1065. err = os.RemoveAll(util.DBPath + "-wal")
  1066. if nil != err {
  1067. return
  1068. }
  1069. return
  1070. }
  1071. func closeDatabase() (err error) {
  1072. if nil == db {
  1073. return
  1074. }
  1075. err = db.Close()
  1076. runtime.GC() // 没有这句的话文件句柄不会释放,后面就无法删除文件
  1077. return
  1078. }