database.go 31 KB

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